diff --git a/packages/cli/src/commands/spawn.ts b/packages/cli/src/commands/spawn.ts index f912450b5..749480af4 100644 --- a/packages/cli/src/commands/spawn.ts +++ b/packages/cli/src/commands/spawn.ts @@ -5,6 +5,7 @@ import { loadConfig, type OrchestratorConfig } from "@composio/ao-core"; import { exec } from "../lib/shell.js"; import { banner } from "../lib/format.js"; import { getSessionManager } from "../lib/create-session-manager.js"; +import { preflight } from "../lib/preflight.js"; async function spawnSession( config: OrchestratorConfig, @@ -13,6 +14,15 @@ async function spawnSession( openTab?: boolean, agent?: string, ): Promise { + // Pre-flight: ensure tmux is available (default runtime) + await preflight.checkTmux(); + + // Pre-flight: ensure gh is authenticated if using github tracker + const project = config.projects[projectId]; + if (project?.tracker?.plugin === "github") { + await preflight.checkGhAuth(); + } + const spinner = ora("Creating session").start(); try { diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 3e7edc1c9..b37b6ec41 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -22,6 +22,7 @@ import { exec } from "../lib/shell.js"; import { getSessionManager } from "../lib/create-session-manager.js"; import { findWebDir, buildDashboardEnv } from "../lib/web-dir.js"; import { cleanNextCache } from "../lib/dashboard-rebuild.js"; +import { preflight } from "../lib/preflight.js"; /** * Resolve project from config. @@ -138,6 +139,10 @@ export function registerStart(program: Command): void { const sessionId = `${project.sessionPrefix}-orchestrator`; const port = config.port ?? 3000; + // Pre-flight checks + await preflight.checkPort(port); + await preflight.checkBuilt(); + console.log(chalk.bold(`\nStarting orchestrator for ${chalk.cyan(project.name)}\n`)); // Start dashboard (unless --no-dashboard) diff --git a/packages/cli/src/lib/preflight.ts b/packages/cli/src/lib/preflight.ts new file mode 100644 index 000000000..8a808a4ba --- /dev/null +++ b/packages/cli/src/lib/preflight.ts @@ -0,0 +1,73 @@ +/** + * Pre-flight checks for `ao start` and `ao spawn`. + * + * Validates runtime prerequisites before entering the main command flow, + * giving clear errors instead of cryptic failures. + */ + +import { existsSync } from "node:fs"; +import { resolve } from "node:path"; +import chalk from "chalk"; +import { isPortAvailable, findWebDir } from "./web-dir.js"; +import { exec } from "./shell.js"; + +/** + * Check that the dashboard port is free. + * Exits with a clear message if it's already in use. + */ +async function checkPort(port: number): Promise { + const free = await isPortAvailable(port); + if (!free) { + console.error( + chalk.red( + `Port ${port} is already in use. Free it or change 'port' in agent-orchestrator.yaml.`, + ), + ); + process.exit(1); + } +} + +/** + * Check that packages have been built (the web .next directory exists). + * Exits with a clear message if not. + */ +async function checkBuilt(): Promise { + const webDir = findWebDir(); + const buildId = resolve(webDir, ".next", "BUILD_ID"); + if (!existsSync(buildId)) { + console.error(chalk.red("Packages not built. Run: pnpm build")); + process.exit(1); + } +} + +/** + * Check that tmux is installed (required for the default runtime). + * Throws with a clear message if not. + */ +async function checkTmux(): Promise { + try { + await exec("tmux", ["-V"]); + } catch { + throw new Error("tmux is not installed. Install it: brew install tmux"); + } +} + +/** + * Check that the GitHub CLI is authenticated. + * Only relevant when the project uses the github tracker plugin. + * Throws with a clear message if not authenticated. + */ +async function checkGhAuth(): Promise { + try { + await exec("gh", ["auth", "status"]); + } catch { + throw new Error("GitHub CLI is not authenticated. Run: gh auth login"); + } +} + +export const preflight = { + checkPort, + checkBuilt, + checkTmux, + checkGhAuth, +}; diff --git a/scripts/setup.sh b/scripts/setup.sh index 6c5672650..e23e961ee 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -1,38 +1,149 @@ #!/bin/bash # Agent Orchestrator setup script -# Installs dependencies, builds packages, and links the CLI globally +# Validates prerequisites, installs dependencies, builds packages, and links the CLI globally set -e # Exit on error -echo "๐Ÿค– Agent Orchestrator Setup" +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +echo "Agent Orchestrator Setup" echo "" -# Check for pnpm -if ! command -v pnpm &> /dev/null; then - echo "โŒ pnpm not found. Installing pnpm..." - npm install -g pnpm +# โ”€โ”€โ”€ Hard requirements (exit 1 if missing) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +# Node.js >= 20 +if ! command -v node &> /dev/null; then + echo "ERROR: Node.js is not installed." + echo " Install Node.js 20+: https://nodejs.org/en/download" + exit 1 fi -echo "๐Ÿ“ฆ Installing dependencies..." +NODE_MAJOR=$(node -e "process.stdout.write(String(process.versions.node.split('.')[0]))") +if [ "$NODE_MAJOR" -lt 20 ]; then + echo "ERROR: Node.js $NODE_MAJOR.x detected, but 20+ is required." + echo " Install Node.js 20+: https://nodejs.org/en/download" + exit 1 +fi +echo "[ok] Node.js $(node --version)" + +# git >= 2.25 (required for worktree support) +if ! command -v git &> /dev/null; then + echo "ERROR: git is not installed." + echo " Install git 2.25+: https://git-scm.com/downloads" + exit 1 +fi + +GIT_VERSION=$(git --version | grep -oE '[0-9]+\.[0-9]+' | head -1) +GIT_MAJOR=$(echo "$GIT_VERSION" | cut -d. -f1) +GIT_MINOR=$(echo "$GIT_VERSION" | cut -d. -f2) +if [ "$GIT_MAJOR" -lt 2 ] || { [ "$GIT_MAJOR" -eq 2 ] && [ "$GIT_MINOR" -lt 25 ]; }; then + echo "ERROR: git $GIT_VERSION detected, but 2.25+ is required (worktree support)." + echo " Upgrade git: https://git-scm.com/downloads" + exit 1 +fi +echo "[ok] git $GIT_VERSION" + +# โ”€โ”€โ”€ Soft requirements (warn + offer interactive fix) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +# tmux +if ! command -v tmux &> /dev/null; then + echo "" + echo "WARNING: tmux is not installed (default runtime requires it)." + if command -v brew &> /dev/null; then + read -r -p " Install tmux via Homebrew? [Y/n] " response + response=${response:-Y} + if [[ "$response" =~ ^[Yy]$ ]]; then + brew install tmux + echo "[ok] tmux installed" + else + echo " Skipping. Install later: brew install tmux" + fi + else + echo " Install tmux: https://github.com/tmux/tmux/wiki/Installing" + fi +else + echo "[ok] tmux $(tmux -V | grep -oE '[0-9]+\.[0-9a-z]+')" +fi + +# gh CLI authentication +if ! command -v gh &> /dev/null; then + echo "" + echo "WARNING: GitHub CLI (gh) is not installed." + echo " Install: https://cli.github.com/" +else + if ! gh auth status &> /dev/null; then + echo "" + echo "WARNING: GitHub CLI is not authenticated." + read -r -p " Run 'gh auth login' now? [Y/n] " response + response=${response:-Y} + if [[ "$response" =~ ^[Yy]$ ]]; then + gh auth login + else + echo " Skipping. Authenticate later: gh auth login" + fi + else + echo "[ok] gh authenticated" + fi +fi + +# claude CLI +if ! command -v claude &> /dev/null; then + echo "" + echo "WARNING: Claude CLI is not installed (required for claude-code agent)." + echo " Install: npm install -g @anthropic-ai/claude-code" +fi + +echo "" + +# โ”€โ”€โ”€ Install pnpm via corepack โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +echo "Enabling corepack and preparing pnpm..." +corepack enable +corepack prepare --activate +echo "[ok] pnpm $(pnpm --version)" + +# โ”€โ”€โ”€ Install, build, link โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +echo "" +echo "Installing dependencies..." pnpm install -echo "๐Ÿงน Cleaning stale build artifacts..." +echo "" +echo "Cleaning stale build artifacts..." rm -rf packages/web/.next -echo "๐Ÿ”จ Building all packages..." +echo "" +echo "Building all packages..." pnpm build -echo "๐Ÿ”— Linking CLI globally..." +echo "" +echo "Linking CLI globally..." cd packages/cli npm link -cd ../.. +cd "$REPO_ROOT" + +# โ”€โ”€โ”€ Verify ao is in PATH โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ echo "" -echo "โœ… Setup complete! The 'ao' command is now available." +if command -v ao &> /dev/null; then + echo "[ok] 'ao' command is available in PATH" +else + NPM_BIN="$(npm bin -g 2>/dev/null || npm config get prefix)/bin" + echo "WARNING: 'ao' is not in your PATH." + echo " Add this to your shell profile (~/.zshrc or ~/.bashrc):" + echo "" + echo " export PATH=\"$NPM_BIN:\$PATH\"" + echo "" + echo " Then restart your terminal or run: source ~/.zshrc" +fi + +# โ”€โ”€โ”€ Done โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +echo "" +echo "Setup complete!" echo "" echo "Next steps:" -echo " 1. cd /path/to/your/project" +echo " 1. cd /path/to/your-project" echo " 2. ao init --auto" -echo " 3. gh auth login" -echo " 4. ao start" +echo " 3. ao start" echo ""