import { spawn, execSync } from "node:child_process"; import { existsSync } from "node:fs"; import { OLLAMA_PORT } from "../config.js"; import { loadConfig, saveConfig, getActiveTui, getActiveChatModel, } from "../runtime-config.js"; import { getModelById } from "../registry/models.js"; import { getTuiById } from "../registry/tuis.js"; import { log, err } from "../log.js"; import { ensureOllama, pullIfNeeded } from "./server.js"; import { commandExists, runPassthrough } from "../util.js"; import type { ModelDef } from "../registry/models.js"; import type { TuiDef } from "../registry/tuis.js"; export interface LaunchOverrides { model?: string; tui?: string; passthrough: string[]; } function ensureGit(): void { if (!existsSync(".git")) { log("Initializing git repo..."); execSync("git init", { stdio: "inherit" }); execSync("git add -A", { stdio: "inherit" }); execSync('git commit -m "Initial commit (before AI edits)" --allow-empty', { stdio: "inherit", }); } } function resolveOverrides(overrides: LaunchOverrides): { chatModel: ModelDef; tui: TuiDef; } { const config = loadConfig(); let changed = false; let chatModel: ModelDef; if (overrides.model) { const m = getModelById(overrides.model); if (!m) err(`Unknown model: ${overrides.model}`); if (m.role !== "chat") err(`${overrides.model} is not a chat model.`); chatModel = m; config.chatModel = m.id; changed = true; } else { chatModel = getActiveChatModel(); } let tui: TuiDef; if (overrides.tui) { const t = getTuiById(overrides.tui); if (!t) err(`Unknown TUI: ${overrides.tui}`); tui = t; config.tui = t.id; changed = true; } else { tui = getActiveTui(); } if (changed) saveConfig(config); return { chatModel, tui }; } function ensureTuiInstalled(tui: TuiDef): void { // ollama launch handles installation if (tui.ollamaLaunch) return; if (!commandExists(tui.checkCmd)) { log(`Installing ${tui.name}...`); runPassthrough(tui.installCmd); } } export async function runDefault(overrides: LaunchOverrides): Promise { await ensureOllama(); const { chatModel, tui } = resolveOverrides(overrides); await pullIfNeeded(chatModel.ollamaTag, chatModel.name); ensureTuiInstalled(tui); ensureGit(); const args = overrides.passthrough; let tuiArgs: string[]; let tuiCmd: string; const env = { ...process.env }; if (tui.ollamaLaunch) { // ollama launch handles all config automatically tuiCmd = "ollama"; tuiArgs = ["launch", tui.ollamaLaunch, "--model", chatModel.ollamaTag, ...args]; } else { tuiCmd = tui.checkCmd; switch (tui.id) { case "aider": env.OPENAI_API_KEY = "sk-not-needed"; env.OPENAI_API_BASE = `http://127.0.0.1:${OLLAMA_PORT}/v1`; tuiArgs = [ "--model", `openai/${chatModel.id}`, "--no-show-model-warnings", "--no-check-update", ...args, ]; break; case "goose": env.GOOSE_PROVIDER = "ollama"; env.GOOSE_MODEL = chatModel.ollamaTag; env.OLLAMA_HOST = `http://127.0.0.1:${OLLAMA_PORT}`; tuiArgs = [...args]; break; case "gptme": env.OPENAI_BASE_URL = `http://127.0.0.1:${OLLAMA_PORT}/v1`; tuiArgs = ["--model", `local/${chatModel.ollamaTag}`, ...args]; break; default: tuiArgs = [...args]; break; } if (tui.resumeArgs) { tuiArgs.push(...tui.resumeArgs); } } log(`Launching ${tui.name} with ${chatModel.name}...`); const child = spawn(tuiCmd, tuiArgs, { stdio: "inherit", env }); child.on("exit", (code) => process.exit(code ?? 0)); }