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 { 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." }; }), }); }