Files
shockbot/mcp/dependencies.ts
T

92 lines
3.2 KiB
TypeScript

import { execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { type } from "arktype";
import { log } from "../utils/cli.ts";
import type { ToolContext } from "./server.ts";
import { execute, tool } from "./shared.ts";
const EmptyParams = type({});
async function runInstallation(): Promise<unknown[]> {
if (!existsSync("package.json")) {
return [];
}
try {
log.info("» installing Node.js dependencies...");
execSync("npm install --silent", { stdio: "pipe" });
log.info("» Node.js dependencies installed");
return [{ language: "node", installed: true }];
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
log.warning(`» dependency installation failed: ${msg}`);
return [{ language: "node", installed: false, error: msg }];
}
}
export function startInstallation(ctx: ToolContext): void {
if (ctx.toolState.dependencyInstallation) return;
const promise = runInstallation();
ctx.toolState.dependencyInstallation = {
status: "in_progress",
promise,
results: undefined,
};
promise.then(
(results) => {
if (ctx.toolState.dependencyInstallation) {
ctx.toolState.dependencyInstallation.status = "completed";
ctx.toolState.dependencyInstallation.results = results;
}
},
() => {
if (ctx.toolState.dependencyInstallation) {
ctx.toolState.dependencyInstallation.status = "failed";
}
}
);
}
export function StartDependencyInstallationTool(ctx: ToolContext) {
return tool({
name: "start_dependency_installation",
description:
"Start installing project dependencies in the background. Non-blocking, returns immediately. Call early after branch checkout.",
parameters: EmptyParams,
execute: execute(async () => {
const state = ctx.toolState.dependencyInstallation;
if (state?.status === "completed" || state?.status === "failed") {
return { status: state.status, message: "Dependency installation already completed." };
}
if (state?.status === "in_progress") {
return { status: "in_progress", message: "Dependency installation is already in progress." };
}
startInstallation(ctx);
return { status: "started", message: "Dependency installation started in background." };
}),
});
}
export function AwaitDependencyInstallationTool(ctx: ToolContext) {
return tool({
name: "await_dependency_installation",
description:
"Wait for dependency installation to complete. Auto-starts if not yet started.",
parameters: EmptyParams,
execute: execute(async () => {
if (!ctx.toolState.dependencyInstallation) {
startInstallation(ctx);
}
const state = ctx.toolState.dependencyInstallation;
if (!state) throw new Error("failed to initialize dependency installation state");
if (state.status === "completed" || state.status === "failed") {
return { status: state.status, message: "Dependency installation complete." };
}
if (!state.promise) throw new Error("dependency installation state corrupted");
await state.promise;
return { status: state.status, message: "Dependency installation complete." };
}),
});
}