From d4ff10b5b8eddd964dc1db19ccf43ba06e567614 Mon Sep 17 00:00:00 2001 From: suraj-markup Date: Thu, 26 Mar 2026 15:13:54 +0530 Subject: [PATCH] fix(cli): restore interactive installs for ao start prerequisites --- packages/cli/src/commands/start.ts | 80 ++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index d1d62d692..93eda5679 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -168,6 +168,28 @@ async function askYesNo( } } +function gitInstallAttempts(): InstallAttempt[] { + if (process.platform === "darwin") { + return [{ cmd: "brew", args: ["install", "git"], label: "brew install git" }]; + } + if (process.platform === "linux") { + return [ + { cmd: "sudo", args: ["apt-get", "install", "-y", "git"], label: "sudo apt-get install -y git" }, + { cmd: "sudo", args: ["dnf", "install", "-y", "git"], label: "sudo dnf install -y git" }, + ]; + } + if (process.platform === "win32") { + return [ + { + cmd: "winget", + args: ["install", "--id", "Git.Git", "-e", "--source", "winget"], + label: "winget install --id Git.Git -e --source winget", + }, + ]; + } + return []; +} + function gitInstallHints(): string[] { if (process.platform === "darwin") return ["brew install git"]; if (process.platform === "win32") return ["winget install --id Git.Git -e --source winget"]; @@ -199,6 +221,20 @@ function ghInstallAttempts(): InstallAttempt[] { return []; } +async function runInteractiveCommand(cmd: string, args: string[]): Promise { + await new Promise((resolve, reject) => { + const child = spawn(cmd, args, { stdio: "inherit" }); + child.once("error", reject); + child.once("close", (code) => { + if (code === 0) { + resolve(); + return; + } + reject(new Error(`Command failed (${code ?? "unknown"}): ${cmd} ${args.join(" ")}`)); + }); + }); +} + async function tryInstallWithAttempts( attempts: InstallAttempt[], verify: () => Promise, @@ -206,7 +242,7 @@ async function tryInstallWithAttempts( for (const attempt of attempts) { try { console.log(chalk.dim(` Running: ${attempt.label}`)); - await exec(attempt.cmd, attempt.args); + await runInteractiveCommand(attempt.cmd, attempt.args); if (await verify()) return true; } catch { // Try next installer @@ -220,6 +256,18 @@ async function ensureGit(context: string): Promise { if (hasGit) return; console.log(chalk.yellow(`⚠ Git is required for ${context}.`)); + const shouldInstall = await askYesNo("Install Git now?", true, false); + if (shouldInstall) { + const installed = await tryInstallWithAttempts( + gitInstallAttempts(), + async () => (await execSilent("git", ["--version"])) !== null, + ); + if (installed) { + console.log(chalk.green(" ✓ Git installed successfully")); + return; + } + } + console.error(chalk.red("\n✗ Git is required but is not installed.\n")); console.log(chalk.bold(" Install Git manually, then re-run ao start:\n")); for (const hint of gitInstallHints()) { @@ -290,7 +338,7 @@ async function promptInstallAgentRuntime(available: DetectedAgent[]): Promise 0) { console.log(chalk.green(` ✓ ${selected.label} installed successfully`)); @@ -684,9 +732,23 @@ async function startDashboard( } /** - * Ensure tmux is available. Called from runStartup() so ALL ao start + * Ensure tmux is available — interactive install with user consent if missing. + * Called from runStartup() so ALL ao start * paths (normal, URL, retry with existing config) are covered. */ +function tmuxInstallAttempts(): InstallAttempt[] { + if (process.platform === "darwin") { + return [{ cmd: "brew", args: ["install", "tmux"], label: "brew install tmux" }]; + } + if (process.platform === "linux") { + return [ + { cmd: "sudo", args: ["apt-get", "install", "-y", "tmux"], label: "sudo apt-get install -y tmux" }, + { cmd: "sudo", args: ["dnf", "install", "-y", "tmux"], label: "sudo dnf install -y tmux" }, + ]; + } + return []; +} + function tmuxInstallHints(): string[] { if (process.platform === "darwin") return ["brew install tmux"]; if (process.platform === "win32") return [ @@ -704,6 +766,18 @@ async function ensureTmux(): Promise { if (hasTmux) return; console.log(chalk.yellow("⚠ tmux is required for runtime \"tmux\".")); + const shouldInstall = await askYesNo("Install tmux now?", true, false); + if (shouldInstall) { + const installed = await tryInstallWithAttempts( + tmuxInstallAttempts(), + async () => (await execSilent("tmux", ["-V"])) !== null, + ); + if (installed) { + console.log(chalk.green(" ✓ tmux installed successfully")); + return; + } + } + console.error(chalk.red("\n✗ tmux is required but is not installed.\n")); console.log(chalk.bold(" Install tmux manually, then re-run ao start:\n")); for (const hint of tmuxInstallHints()) {