fix(cli): restore interactive installs for ao start prerequisites

This commit is contained in:
suraj-markup 2026-03-26 15:13:54 +05:30
parent b976c36c5d
commit d4ff10b5b8
1 changed files with 77 additions and 3 deletions

View File

@ -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<void> {
await new Promise<void>((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<boolean>,
@ -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<void> {
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<De
const selected = AGENT_INSTALL_OPTIONS[idx - 1];
console.log(chalk.dim(` Installing ${selected.label}...`));
try {
await exec(selected.cmd, selected.args);
await runInteractiveCommand(selected.cmd, selected.args);
const refreshed = await detectAvailableAgents();
if (refreshed.length > 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<void> {
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()) {