From 99d74d7ee2f08d5f6f3ee39118926fc8ab88028a Mon Sep 17 00:00:00 2001 From: Jorge Vidaurre <3512039+kokevidaurre@users.noreply.github.com> Date: Sat, 21 Feb 2026 12:32:34 -0300 Subject: [PATCH 001/127] fix(security): update minimatch to fix ReDoS vulnerability (#350) Closes #342 Co-authored-by: Squads Cloud Worker Co-authored-by: Claude --- package-lock.json | 58 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 67d3b7d8..a8c07304 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "squads-cli", - "version": "0.6.0", + "version": "0.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "squads-cli", - "version": "0.6.0", + "version": "0.6.2", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.71.2", @@ -16,7 +16,7 @@ "dotenv": "^17.2.3", "gray-matter": "^4.0.3", "inquirer": "^9.2.12", - "minimatch": "^10.1.1", + "minimatch": "^10.2.1", "open": "^11.0.0", "ora": "^8.0.1" }, @@ -841,27 +841,6 @@ "node": ">=18" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -3335,20 +3314,41 @@ } }, "node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/mlly": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", diff --git a/package.json b/package.json index ca468c0a..32ecfd52 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "dotenv": "^17.2.3", "gray-matter": "^4.0.3", "inquirer": "^9.2.12", - "minimatch": "^10.1.1", + "minimatch": "^10.2.1", "open": "^11.0.0", "ora": "^8.0.1" }, From e900f6b50100e50bb5c3a5d7201a823b76fe025c Mon Sep 17 00:00:00 2001 From: Jorge Vidaurre <3512039+kokevidaurre@users.noreply.github.com> Date: Sat, 21 Feb 2026 12:32:37 -0300 Subject: [PATCH 002/127] fix(security): escape shell variables in background/watch mode spawn (#351) Prevents shell injection via crafted paths in background and watch execution modes. Applies same escaping used in foreground mode (PR #324). Adds shellEscape() helper that replaces single quotes with '\'' to safely interpolate variables into single-quoted shell strings. Applied to: - Watch mode: projectRoot, worktreeDir, branchName, logFile, pidFile - Background mode: projectRoot, worktreeDir, branchName, logFile, pidFile - Provider background mode: workDir, logFile, pidFile, provider args - execSync worktree calls in foreground and provider modes Closes #340 Co-authored-by: Squads Cloud Worker Co-authored-by: Claude --- src/commands/run.ts | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/commands/run.ts b/src/commands/run.ts index 768dc479..d96fea8b 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -56,6 +56,14 @@ const SOFT_DEADLINE_RATIO = 0.7; const LOG_FILE_INIT_DELAY_MS = 500; const VERBOSE_COMMAND_MAX_CHARS = 50; +/** + * Escape a string for safe interpolation inside single-quoted shell arguments. + * Replaces every ' with '\'' (end quote, escaped quote, start quote). + */ +function shellEscape(s: string): string { + return s.replace(/'/g, "'\\''"); +} + interface RunOptions { verbose?: boolean; dryRun?: boolean; @@ -2008,7 +2016,7 @@ async function executeWithClaude( let fgWorkDir = projectRoot; try { mkdirSync(join(projectRoot, '..', '.worktrees'), { recursive: true }); - execSync(`git worktree add '${fgWorktreePath}' -b '${fgBranchName}' HEAD`, { cwd: projectRoot, stdio: 'pipe' }); + execSync(`git worktree add '${shellEscape(fgWorktreePath)}' -b '${shellEscape(fgBranchName)}' HEAD`, { cwd: projectRoot, stdio: 'pipe' }); fgWorkDir = fgWorktreePath; } catch { // Worktree creation failed — fall back to project root @@ -2089,11 +2097,11 @@ async function executeWithClaude( BRIDGE_API: process.env.SQUADS_BRIDGE_URL || DEFAULT_BRIDGE_URL, }; - const modelFlag = claudeModelAlias ? `--model ${claudeModelAlias}` : ''; + const modelFlag = claudeModelAlias ? `--model '${shellEscape(claudeModelAlias)}'` : ''; const watchBranchName = `agent/${squadName}/${agentName}-${timestamp}`; - const watchWorktreeDir = `\${PROJECT_ROOT}/../.worktrees/${squadName}-${agentName}-${timestamp}`; - const shellScript = `PROJECT_ROOT='${projectRoot}'; mkdir -p "\${PROJECT_ROOT}/../.worktrees"; WORK_DIR="\${PROJECT_ROOT}"; if git -C "\${PROJECT_ROOT}" worktree add '${watchWorktreeDir}' -b '${watchBranchName}' HEAD 2>/dev/null; then WORK_DIR='${watchWorktreeDir}'; fi; cd "\${WORK_DIR}"; exec claude --print --dangerously-skip-permissions ${modelFlag} -- '${escapedPrompt}' > '${logFile}' 2>&1`; - const wrapperScript = `echo $$ > '${pidFile}'; ${shellScript}`; + const escapedWatchWorktreeDir = shellEscape(`\${PROJECT_ROOT}/../.worktrees/${squadName}-${agentName}-${timestamp}`); + const shellScript = `PROJECT_ROOT='${shellEscape(projectRoot)}'; mkdir -p "\${PROJECT_ROOT}/../.worktrees"; WORK_DIR="\${PROJECT_ROOT}"; if git -C "\${PROJECT_ROOT}" worktree add '${escapedWatchWorktreeDir}' -b '${shellEscape(watchBranchName)}' HEAD 2>/dev/null; then WORK_DIR='${escapedWatchWorktreeDir}'; fi; cd "\${WORK_DIR}"; exec claude --print --dangerously-skip-permissions ${modelFlag} -- '${escapedPrompt}' > '${shellEscape(logFile)}' 2>&1`; + const wrapperScript = `echo $$ > '${shellEscape(pidFile)}'; ${shellScript}`; // Spawn background process const child = spawn('sh', ['-c', wrapperScript], { @@ -2186,14 +2194,15 @@ async function executeWithClaude( // 2. cd to worktree (or project root as fallback) // 3. run claude (output to logfile) // Note: MCP config removed - causes blocking issues in background execution - const modelFlag = claudeModelAlias ? `--model ${claudeModelAlias}` : ''; + const modelFlag = claudeModelAlias ? `--model '${shellEscape(claudeModelAlias)}'` : ''; const bgBranchName = `agent/${squadName}/${agentName}-${timestamp}`; - const bgWorktreeDir = `${projectRoot}/../.worktrees/${squadName}-${agentName}-${timestamp}`; - const shellScript = `mkdir -p '${projectRoot}/../.worktrees'; WORK_DIR='${projectRoot}'; if git -C '${projectRoot}' worktree add '${bgWorktreeDir}' -b '${bgBranchName}' HEAD 2>/dev/null; then WORK_DIR='${bgWorktreeDir}'; fi; cd "\${WORK_DIR}"; claude --print --dangerously-skip-permissions ${modelFlag} -- '${escapedPrompt}' > '${logFile}' 2>&1`; + const escapedBgWorktreeDir = shellEscape(`${projectRoot}/../.worktrees/${squadName}-${agentName}-${timestamp}`); + const escapedProjectRoot = shellEscape(projectRoot); + const shellScript = `mkdir -p '${escapedProjectRoot}/../.worktrees'; WORK_DIR='${escapedProjectRoot}'; if git -C '${escapedProjectRoot}' worktree add '${escapedBgWorktreeDir}' -b '${shellEscape(bgBranchName)}' HEAD 2>/dev/null; then WORK_DIR='${escapedBgWorktreeDir}'; fi; cd "\${WORK_DIR}"; claude --print --dangerously-skip-permissions ${modelFlag} -- '${escapedPrompt}' > '${shellEscape(logFile)}' 2>&1`; // Get child PID by using a wrapper that writes PID then execs - const wrapperScript = `echo $$ > '${pidFile}'; ${shellScript}`; + const wrapperScript = `echo $$ > '${shellEscape(pidFile)}'; ${shellScript}`; const child = spawn('sh', ['-c', wrapperScript], { cwd: projectRoot, @@ -2261,7 +2270,7 @@ async function executeWithProvider( let workDir = projectRoot; try { mkdirSync(join(projectRoot, '..', '.worktrees'), { recursive: true }); - execSync(`git worktree add '${worktreePath}' -b '${branchName}' HEAD`, { cwd: projectRoot, stdio: 'pipe' }); + execSync(`git worktree add '${shellEscape(worktreePath)}' -b '${shellEscape(branchName)}' HEAD`, { cwd: projectRoot, stdio: 'pipe' }); workDir = worktreePath; } catch { // Worktree creation failed — fall back to project root @@ -2309,9 +2318,9 @@ async function executeWithProvider( } const escapedPrompt = prompt.replace(/'/g, "'\\''"); - const providerArgs = cliConfig.buildArgs(escapedPrompt).map(a => `'${a}'`).join(' '); - const shellScript = `cd '${workDir}' && ${cliConfig.command} ${providerArgs} > '${logFile}' 2>&1`; - const wrapperScript = `echo $$ > '${pidFile}'; ${shellScript}`; + const providerArgs = cliConfig.buildArgs(escapedPrompt).map(a => `'${shellEscape(a)}'`).join(' '); + const shellScript = `cd '${shellEscape(workDir)}' && ${cliConfig.command} ${providerArgs} > '${shellEscape(logFile)}' 2>&1`; + const wrapperScript = `echo $$ > '${shellEscape(pidFile)}'; ${shellScript}`; const child = spawn('sh', ['-c', wrapperScript], { cwd: workDir, From 49cfe953dafe3206b4d7f0264436d8dc6115e213 Mon Sep 17 00:00:00 2001 From: Jorge Vidaurre <3512039+kokevidaurre@users.noreply.github.com> Date: Sat, 21 Feb 2026 12:32:39 -0300 Subject: [PATCH 003/127] docs(brief): CLI daily brief for 2026-02-21 (#349) v0.6.2 released, 3 security P1 issue-solvers dispatched, 751 tests passing, Q1 goals 2/3 achieved. Co-authored-by: Squads Cloud Worker Co-authored-by: Claude --- briefs/daily-2026-02-21.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 briefs/daily-2026-02-21.md diff --git a/briefs/daily-2026-02-21.md b/briefs/daily-2026-02-21.md new file mode 100644 index 00000000..35231e3a --- /dev/null +++ b/briefs/daily-2026-02-21.md @@ -0,0 +1,29 @@ +*⌨️ CLI Daily Brief - 2026-02-21* + +*What happened:* +• *v0.6.2 released* — version bump with constants extraction + frontmatter cooldown (#347) +• *v0.6.1 shipped* — CI must pass before npm publish + test fixes (#345) +• *v0.6.0 released* — branch isolation via worktrees, memory injection, nested execution fix (#336) +• *3 security fixes merged* — shell injection (#324), HTML escaping (#323), auth file permissions (#325) +• *Tests improved*: 751 passing (up from 715), build time down to 634ms + +*Needs attention:* +• :rotating_light: *3 P1 security issues open — no PRs yet* (issue-solvers spawned): + - #342: minimatch ReDoS vulnerability (GHSA-3ppc-4f35-3m26) + - #341: Hardcoded telemetry API key in public repo (base64 is not encryption) + - #340: Shell injection in background/watch mode (foreground fixed, these missed) +• :warning: *P0 #335*: Multi-provider execution broken — non-Anthropic LLMs don't work end-to-end. _Needs architecture decision, not just code._ +• :warning: *P1 #343*: `squads autonomous start` doesn't persist daemon process +• 2 open PRs needing review: #339 (exit codes), #338 (otel-collector test) + +*Q1 Goals:* +• :white_check_mark: Zero P1 user-blocking bugs — maintained (security issues are code quality, not user-blocking) +• :white_check_mark: Dashboard ROI — achieved +• :hourglass: Test coverage 80% — 751 tests, ~70%+ coverage. PRs needed for remaining modules. +• :new: Security hardening — 3/6 issues fixed, 3 in progress (issue-solvers dispatched today) + +*Next:* +• Merge security PRs once CI passes (#340, #341, #342) +• Architecture decision needed for #335 (multi-provider support) +• Review #339 (exit codes) and #338 (otel test) for merge +• Plan #343 daemon persistence fix From 7b22fd22994c671da50715d0ebeea3dfcedaa362 Mon Sep 17 00:00:00 2001 From: Jorge Vidaurre <3512039+kokevidaurre@users.noreply.github.com> Date: Sat, 21 Feb 2026 14:33:58 -0300 Subject: [PATCH 004/127] fix(ux): consistent exit code 0 when parent commands show help text (#339) Closes #319 Added default .action(() => cmd.outputHelp()) to 7 parent commands (env, kpi, feedback, session, trigger, approval, autonomous) so they exit 0 instead of 1 when invoked without a subcommand. Matches the pattern already used by memory, goal, deploy, and exec commands. Co-Authored-By: engineering/issue-solver Agent: engineering/issue-solver Squad: engineering Model: claude-opus-4-6 Co-authored-by: Squads Cloud Worker --- src/cli.ts | 12 ++++++++---- src/commands/approval.ts | 3 ++- src/commands/autonomous.ts | 3 ++- src/commands/trigger.ts | 3 ++- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 4815e292..67a6dd05 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -313,7 +313,8 @@ registerOrchestrateCommand(program); // Env command - squad execution environment (MCP, skills, budget, model) const env = program .command('env') - .description('View squad execution environment (MCP, skills, model, budget)'); + .description('View squad execution environment (MCP, skills, model, budget)') + .action(() => { env.outputHelp(); }); env .command('show ') @@ -514,7 +515,8 @@ Examples: $ squads kpi record engineering leads_generated 15 $ squads kpi trend engineering leads_generated $ squads kpi insights Show insights across all squads -`); +`) + .action(() => { kpi.outputHelp(); }); kpi .command('list') @@ -569,7 +571,8 @@ progress // Feedback command group const feedback = program .command('feedback') - .description('Record and view execution feedback'); + .description('Record and view execution feedback') + .action(() => { feedback.outputHelp(); }); feedback .command('add ') @@ -791,7 +794,8 @@ sessions // Session command group - lifecycle management const session = program .command('session') - .description('Manage current session lifecycle'); + .description('Manage current session lifecycle') + .action(() => { session.outputHelp(); }); session .command('start') diff --git a/src/commands/approval.ts b/src/commands/approval.ts index 191f4f03..463278b8 100644 --- a/src/commands/approval.ts +++ b/src/commands/approval.ts @@ -288,7 +288,8 @@ async function cancelApproval(approvalId: string): Promise { export function registerApprovalCommand(program: Command): void { const approval = program .command("approval") - .description("Manage approval requests for agent actions"); + .description("Manage approval requests for agent actions") + .action(() => { approval.outputHelp(); }); approval .command("send ") diff --git a/src/commands/autonomous.ts b/src/commands/autonomous.ts index ab9108e3..db04f54b 100644 --- a/src/commands/autonomous.ts +++ b/src/commands/autonomous.ts @@ -627,7 +627,8 @@ export function registerAutonomousCommand(program: Command): void { const autonomous = program .command("autonomous") .alias("auto") - .description("Local scheduling daemon for autonomous agent execution"); + .description("Local scheduling daemon for autonomous agent execution") + .action(() => { autonomous.outputHelp(); }); autonomous .command("start") diff --git a/src/commands/trigger.ts b/src/commands/trigger.ts index a4705ab7..b15e2756 100644 --- a/src/commands/trigger.ts +++ b/src/commands/trigger.ts @@ -228,7 +228,8 @@ async function showStatus(): Promise { export function registerTriggerCommand(program: Command): void { const trigger = program .command("trigger") - .description("Manage smart triggers"); + .description("Manage smart triggers") + .action(() => { trigger.outputHelp(); }); trigger .command("list [squad]") From 472b968062a09d5b06e40a5f7f9b309d189b05f1 Mon Sep 17 00:00:00 2001 From: Jorge Vidaurre <3512039+kokevidaurre@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:26:22 -0300 Subject: [PATCH 005/127] fix: replace console.log with writeLine for structured output (#311) (#354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace scattered console.log calls with the project's writeLine() utility from src/lib/terminal.ts. This provides a single output layer for consistent formatting and future output control. - Convert 238 console.log calls to writeLine across 10 files - Remove 8 debug/placeholder log statements from anthropic.ts - Keep console.log only for JSON.stringify output (--json flags) and raw prompt piping — standard CLI patterns - Reduction: 269 → 31 occurrences (88% decrease) - Zero new TypeScript errors Files: init.ts, deploy.ts, autonomous.ts, trigger.ts, approval.ts, eval.ts, login.ts, cli.ts, anthropic.ts, update.ts Co-authored-by: kokevidaurre Co-authored-by: Claude --- src/cli.ts | 18 ++--- src/commands/approval.ts | 37 ++++++----- src/commands/autonomous.ts | 67 ++++++++++--------- src/commands/deploy.ts | 101 ++++++++++++++-------------- src/commands/eval.ts | 29 ++++---- src/commands/init.ts | 133 +++++++++++++++++++------------------ src/commands/login.ts | 25 +++---- src/commands/trigger.ts | 63 +++++++++--------- src/lib/anthropic.ts | 11 +-- src/lib/update.ts | 4 +- 10 files changed, 243 insertions(+), 245 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 67a6dd05..88c5eb24 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -6,7 +6,7 @@ import { Command } from 'commander'; import chalk from 'chalk'; import { version } from './version.js'; import { autoUpdateOnStartup } from './lib/update.js'; -import { colors as termColors, RESET as termReset, bold as termBold } from './lib/terminal.js'; +import { colors as termColors, RESET as termReset, bold as termBold, writeLine } from './lib/terminal.js'; // Disable colors when output is piped (not a TTY) // This ensures piped output is clean for parsing @@ -234,16 +234,16 @@ program const { gradient, colors, RESET } = await import('./lib/terminal.js'); const { checkForUpdate } = await import('./lib/update.js'); - console.log(); - console.log(` ${gradient('squads')} ${colors.dim}v${version}${RESET}`); - console.log(); + writeLine(); + writeLine(` ${gradient('squads')} ${colors.dim}v${version}${RESET}`); + writeLine(); // Check for updates const updateInfo = checkForUpdate(); if (updateInfo.updateAvailable) { - console.log(` ${colors.cyan}⬆${RESET} Update available: ${colors.dim}${updateInfo.currentVersion}${RESET} → ${colors.green}${updateInfo.latestVersion}${RESET}`); - console.log(` ${colors.dim}Run \`squads update\` to install${RESET}`); - console.log(); + writeLine(` ${colors.cyan}⬆${RESET} Update available: ${colors.dim}${updateInfo.currentVersion}${RESET} → ${colors.green}${updateInfo.latestVersion}${RESET}`); + writeLine(` ${colors.dim}Run \`squads update\` to install${RESET}`); + writeLine(); } // Run status command to show all squads (includes quick commands) @@ -403,7 +403,7 @@ program return; } // Fall through to default dashboard with a warning - console.log(` Dashboard "${name}" not found. Showing default dashboard.\n`); + writeLine(` Dashboard "${name}" not found. Showing default dashboard.\n`); } // Default: show the comprehensive dashboard @@ -864,7 +864,7 @@ program .command('version') .description('Show version information') .action(() => { - console.log(`squads-cli ${version}`); + writeLine(`squads-cli ${version}`); }); // ─── Removed commands (hidden from --help, show helpful message if invoked) ── diff --git a/src/commands/approval.ts b/src/commands/approval.ts index 463278b8..32adfa97 100644 --- a/src/commands/approval.ts +++ b/src/commands/approval.ts @@ -10,6 +10,7 @@ import { Command } from "commander"; import chalk from "chalk"; +import { writeLine } from "../lib/terminal.js"; const API_URL = process.env.SQUADS_API_URL || @@ -124,13 +125,13 @@ async function sendApproval( approval_id: string; }; - console.log(chalk.green(`\nApproval sent: ${result.approval_id}`)); - console.log(chalk.dim(` Expires: ${expiresAt.toISOString()}`)); - console.log(); + writeLine(chalk.green(`\nApproval sent: ${result.approval_id}`)); + writeLine(chalk.dim(` Expires: ${expiresAt.toISOString()}`)); + writeLine(); // Output for agent consumption if (process.env.SQUADS_AGENT) { - console.log(`APPROVAL_ID=${approvalId}`); + writeLine(`APPROVAL_ID=${approvalId}`); } } catch (error) { console.error(chalk.red(`Failed to send approval: ${error}`)); @@ -162,11 +163,11 @@ async function listApprovals(options: { } if (approvals.length === 0) { - console.log(chalk.gray("\nNo pending approvals\n")); + writeLine(chalk.gray("\nNo pending approvals\n")); return; } - console.log(chalk.bold("\nPending Approvals\n")); + writeLine(chalk.bold("\nPending Approvals\n")); for (const a of approvals) { const typeColors: Record = { @@ -178,15 +179,15 @@ async function listApprovals(options: { }; const color = typeColors[a.type] || chalk.white; - console.log( + writeLine( ` ${color(`[${a.type}]`)} ${chalk.bold(a.title)} ${chalk.dim(`(${a.approval_id})`)}` ); - console.log( + writeLine( chalk.dim( ` Squad: ${a.squad}${a.agent ? "/" + a.agent : ""} | Created: ${new Date(a.created_at).toLocaleString()}` ) ); - console.log(); + writeLine(); } } catch (error) { console.error(chalk.red(`Failed to list approvals: ${error}`)); @@ -233,13 +234,13 @@ async function checkApproval( }; const color = statusColors[approval.status] || chalk.white; - console.log(`\nApproval: ${approvalId}`); - console.log(` Status: ${color(approval.status)}`); + writeLine(`\nApproval: ${approvalId}`); + writeLine(` Status: ${color(approval.status)}`); if (approval.decided_by) { - console.log(` Decided by: ${approval.decided_by}`); - console.log(` Decided at: ${approval.decided_at}`); + writeLine(` Decided by: ${approval.decided_by}`); + writeLine(` Decided at: ${approval.decided_at}`); } - console.log(); + writeLine(); // Exit with appropriate code if (approval.status === "approved") { @@ -254,7 +255,7 @@ async function checkApproval( } // Wait mode - console.log(chalk.dim(`Waiting for decision on ${approvalId}...`)); + writeLine(chalk.dim(`Waiting for decision on ${approvalId}...`)); while (Date.now() - startTime < timeoutMs) { await new Promise((resolve) => setTimeout(resolve, 5000)); // Poll every 5s @@ -268,9 +269,9 @@ async function checkApproval( if (updated.status !== "pending") { const color = updated.status === "approved" ? chalk.green : chalk.red; - console.log(color(`\nDecision: ${updated.status}`)); + writeLine(color(`\nDecision: ${updated.status}`)); if (updated.decided_by) { - console.log(chalk.dim(` By: ${updated.decided_by}`)); + writeLine(chalk.dim(` By: ${updated.decided_by}`)); } process.exit(updated.status === "approved" ? 0 : 1); } @@ -281,7 +282,7 @@ async function checkApproval( } async function cancelApproval(approvalId: string): Promise { - console.log(chalk.yellow(`Cancel not yet implemented for: ${approvalId}`)); + writeLine(chalk.yellow(`Cancel not yet implemented for: ${approvalId}`)); // TODO: Implement cancel endpoint } diff --git a/src/commands/autonomous.ts b/src/commands/autonomous.ts index db04f54b..af63a867 100644 --- a/src/commands/autonomous.ts +++ b/src/commands/autonomous.ts @@ -14,6 +14,7 @@ import { Command } from "commander"; import chalk from "chalk"; +import { writeLine } from "../lib/terminal.js"; import { existsSync, readFileSync, @@ -420,10 +421,10 @@ function isRunning(): { running: boolean; pid?: number } { async function startScheduler(): Promise { const status = isRunning(); if (status.running) { - console.log( + writeLine( chalk.yellow(`Daemon already running (PID ${status.pid})`) ); - console.log(chalk.gray(` Log: ${DAEMON_LOG}`)); + writeLine(chalk.gray(` Log: ${DAEMON_LOG}`)); return; } @@ -434,8 +435,8 @@ async function startScheduler(): Promise { const routines = collectRoutines().filter((r) => r.enabled !== false); if (routines.length === 0) { - console.log(chalk.yellow("No enabled routines found.")); - console.log( + writeLine(chalk.yellow("No enabled routines found.")); + writeLine( chalk.gray("Add routines to SQUAD.md files under ### Routines section.") ); return; @@ -470,16 +471,16 @@ async function startScheduler(): Promise { const check = isRunning(); if (check.running) { - console.log(chalk.green(`\n Daemon started (PID ${check.pid})`)); + writeLine(chalk.green(`\n Daemon started (PID ${check.pid})`)); } else { - console.log(chalk.green("\n Daemon starting...")); + writeLine(chalk.green("\n Daemon starting...")); } - console.log(chalk.gray(` Log: ${DAEMON_LOG}`)); - console.log(chalk.gray(` Config: SQUAD.md routines\n`)); + writeLine(chalk.gray(` Log: ${DAEMON_LOG}`)); + writeLine(chalk.gray(` Config: SQUAD.md routines\n`)); // Show what's scheduled - console.log(chalk.cyan(" Routines")); + writeLine(chalk.cyan(" Routines")); const bySquad = new Map(); for (const r of routines) { if (!bySquad.has(r.squad)) bySquad.set(r.squad, []); @@ -493,24 +494,24 @@ async function startScheduler(): Promise { hour: "2-digit", minute: "2-digit", }); - console.log( + writeLine( ` ${chalk.green("●")} ${chalk.cyan(squad)}/${r.name} ${chalk.gray(r.schedule)} ${chalk.gray(`→ ${timeStr}`)}` ); } } - console.log( + writeLine( chalk.gray(`\n ${routines.length} routines, max ${MAX_CONCURRENT} concurrent`) ); - console.log(chalk.gray(" Stop: squads autonomous stop")); - console.log(chalk.gray(` Monitor: tail -f ${DAEMON_LOG}\n`)); + writeLine(chalk.gray(" Stop: squads autonomous stop")); + writeLine(chalk.gray(` Monitor: tail -f ${DAEMON_LOG}\n`)); } function stopScheduler(): void { const status = isRunning(); if (!status.running) { - console.log(chalk.gray("Daemon not running")); + writeLine(chalk.gray("Daemon not running")); return; } @@ -521,7 +522,7 @@ function stopScheduler(): void { } catch { /* ignore */ } - console.log(chalk.green(`Daemon stopped (PID ${status.pid})`)); + writeLine(chalk.green(`Daemon stopped (PID ${status.pid})`)); } catch (error) { console.error(chalk.red(`Failed to stop daemon: ${error}`)); } @@ -533,42 +534,42 @@ async function showStatus(): Promise { const enabled = routines.filter((r) => r.enabled !== false); const running = getRunningAgents(); - console.log(chalk.bold("\n Autonomous Scheduler\n")); + writeLine(chalk.bold("\n Autonomous Scheduler\n")); // Daemon status if (daemon.running) { - console.log( + writeLine( ` ${chalk.green("●")} Daemon running ${chalk.gray(`(PID ${daemon.pid})`)}` ); } else { - console.log(` ${chalk.red("●")} Daemon not running`); + writeLine(` ${chalk.red("●")} Daemon not running`); } - console.log(); + writeLine(); // Running agents if (running.length > 0) { - console.log(chalk.cyan(" Running Agents")); + writeLine(chalk.cyan(" Running Agents")); for (const agent of running) { const runtimeMin = Math.round((Date.now() - agent.startedAt) / 60000); const timeoutWarning = runtimeMin > AGENT_TIMEOUT_MIN * 0.8 ? chalk.yellow(" ⚠") : ""; - console.log( + writeLine( ` ${chalk.green("●")} ${chalk.cyan(agent.squad)}/${agent.agent} ${chalk.gray(`${runtimeMin}min`)}${timeoutWarning} ${chalk.gray(`PID ${agent.pid}`)}` ); } - console.log(); + writeLine(); } // Routine summary - console.log(chalk.cyan(" Routines")); - console.log( + writeLine(chalk.cyan(" Routines")); + writeLine( ` ${enabled.length} enabled / ${routines.length} total, ${running.length}/${MAX_CONCURRENT} running` ); - console.log(); + writeLine(); // Next 10 upcoming runs if (enabled.length > 0) { - console.log(chalk.cyan(" Next Runs")); + writeLine(chalk.cyan(" Next Runs")); const now = new Date(); const nextRuns: { @@ -605,18 +606,18 @@ async function showStatus(): Promise { month: "short", day: "numeric", }); - console.log( + writeLine( ` ${chalk.gray(timeStr)} ${chalk.gray(dateStr)} ${chalk.cyan(run.squad)}/${run.agent}` ); }); } - console.log(); - console.log(chalk.gray(" Commands:")); - console.log(chalk.gray(" $ squads autonomous start Start daemon")); - console.log(chalk.gray(" $ squads autonomous stop Stop daemon")); - console.log(chalk.gray(` $ tail -f ${DAEMON_LOG}`)); - console.log(); + writeLine(); + writeLine(chalk.gray(" Commands:")); + writeLine(chalk.gray(" $ squads autonomous start Start daemon")); + writeLine(chalk.gray(" $ squads autonomous stop Stop daemon")); + writeLine(chalk.gray(` $ tail -f ${DAEMON_LOG}`)); + writeLine(); } // ============================================================================= diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts index b1bb27ba..95b848c3 100644 --- a/src/commands/deploy.ts +++ b/src/commands/deploy.ts @@ -18,6 +18,7 @@ import { loadSquad, listAgents, } from '../lib/squad-parser.js'; +import { writeLine } from '../lib/terminal.js'; import matter from 'gray-matter'; import { loadSession } from '../lib/auth.js'; import { track } from '../lib/telemetry.js'; @@ -80,7 +81,7 @@ export async function deployCommand(options: { const session = loadSession(); if (!session || session.status !== 'active') { - console.log(` + writeLine(` ${chalk.yellow('Not logged in or account not active.')} ${chalk.bold('To deploy agents to the platform:')} @@ -98,7 +99,7 @@ ${chalk.dim('Need access?')} ${chalk.cyan('hello@agents-squads.com')} const squadsDir = findSquadsDir(); if (!squadsDir) { console.error(chalk.red('No .agents/squads/ directory found.')); - console.log(chalk.dim('Run: squads init')); + writeLine(chalk.dim('Run: squads init')); return; } @@ -116,46 +117,46 @@ ${chalk.dim('Need access?')} ${chalk.cyan('hello@agents-squads.com')} spinner.succeed(`Found ${manifest.squads.length} squad(s), ${manifest.triggers.length} trigger(s)`); // Show what will be deployed - console.log(''); - console.log(chalk.bold('Deployment Manifest')); - console.log(chalk.dim('─'.repeat(50))); + writeLine(''); + writeLine(chalk.bold('Deployment Manifest')); + writeLine(chalk.dim('─'.repeat(50))); for (const squad of manifest.squads) { - console.log(` ${chalk.cyan(squad.name)} — ${squad.agentCount} agent(s), ${squad.routineCount} routine(s)`); + writeLine(` ${chalk.cyan(squad.name)} — ${squad.agentCount} agent(s), ${squad.routineCount} routine(s)`); if (options.verbose) { for (const agent of squad.agents) { const status = agent.status === 'active' ? chalk.green('active') : chalk.yellow(agent.status); - console.log(` ${chalk.dim('→')} ${agent.name} (${agent.model}) [${status}]`); + writeLine(` ${chalk.dim('→')} ${agent.name} (${agent.model}) [${status}]`); if (agent.schedule) { - console.log(` ${chalk.dim('schedule:')} ${agent.schedule}`); + writeLine(` ${chalk.dim('schedule:')} ${agent.schedule}`); } } } } if (manifest.triggers.length > 0) { - console.log(''); - console.log(chalk.bold('Triggers to sync')); - console.log(chalk.dim('─'.repeat(50))); + writeLine(''); + writeLine(chalk.bold('Triggers to sync')); + writeLine(chalk.dim('─'.repeat(50))); for (const trigger of manifest.triggers) { - console.log(` ${chalk.magenta(trigger.name)} — ${trigger.squad}${trigger.agent ? '/' + trigger.agent : ''}`); + writeLine(` ${chalk.magenta(trigger.name)} — ${trigger.squad}${trigger.agent ? '/' + trigger.agent : ''}`); if (options.verbose) { - console.log(` ${chalk.dim('schedule:')} ${trigger.condition}`); - console.log(` ${chalk.dim('cooldown:')} ${trigger.cooldown}`); + writeLine(` ${chalk.dim('schedule:')} ${trigger.condition}`); + writeLine(` ${chalk.dim('cooldown:')} ${trigger.cooldown}`); } } } if (manifest.gitSha) { - console.log(''); - console.log(chalk.dim(`Git SHA: ${manifest.gitSha}`)); + writeLine(''); + writeLine(chalk.dim(`Git SHA: ${manifest.gitSha}`)); } // Dry run stops here if (options.dryRun) { - console.log(''); - console.log(chalk.yellow('Dry run — no changes pushed to platform.')); - console.log(chalk.dim('Remove --dry-run to deploy.')); + writeLine(''); + writeLine(chalk.yellow('Dry run — no changes pushed to platform.')); + writeLine(chalk.dim('Remove --dry-run to deploy.')); await track('cli.deploy.dry_run', { squads: manifest.squads.length, triggers: manifest.triggers.length, @@ -164,7 +165,7 @@ ${chalk.dim('Need access?')} ${chalk.cyan('hello@agents-squads.com')} } // Push to platform - console.log(''); + writeLine(''); const pushSpinner = ora('Pushing to platform...').start(); const result = await pushToplatform(manifest, session.accessToken || ''); @@ -172,21 +173,21 @@ ${chalk.dim('Need access?')} ${chalk.cyan('hello@agents-squads.com')} if (result.errors.length > 0) { pushSpinner.warn(`Deployed with ${result.errors.length} error(s)`); for (const err of result.errors) { - console.log(` ${chalk.red('✗')} ${err.name}: ${err.error}`); + writeLine(` ${chalk.red('✗')} ${err.name}: ${err.error}`); } } else { pushSpinner.succeed(`Deployed ${result.triggersCreated} trigger(s) to platform`); } if (result.triggersSynced.length > 0 && options.verbose) { - console.log(''); - console.log(chalk.dim('Synced triggers:')); + writeLine(''); + writeLine(chalk.dim('Synced triggers:')); for (const name of result.triggersSynced) { - console.log(` ${chalk.green('✓')} ${name}`); + writeLine(` ${chalk.green('✓')} ${name}`); } } - console.log(` + writeLine(` ${chalk.green('✓ Deployment complete.')} ${chalk.bold('Next steps:')} @@ -207,7 +208,7 @@ ${chalk.bold('Next steps:')} console.error(chalk.red(message)); if (message.includes('fetch failed') || message.includes('ECONNREFUSED')) { - console.log(chalk.dim('\nPlatform may be unreachable. Check your connection.')); + writeLine(chalk.dim('\nPlatform may be unreachable. Check your connection.')); } await track('cli.deploy.error', { error: message }); @@ -217,7 +218,7 @@ ${chalk.bold('Next steps:')} export async function deployStatusCommand(): Promise { const session = loadSession(); if (!session?.accessToken) { - console.log(chalk.yellow('Not logged in. Run: squads login')); + writeLine(chalk.yellow('Not logged in. Run: squads login')); return; } @@ -247,13 +248,13 @@ export async function deployStatusCommand(): Promise { spinner.succeed(`${data.length} trigger(s) on platform`); if (data.length === 0) { - console.log(chalk.dim('\nNo triggers deployed. Run: squads deploy')); + writeLine(chalk.dim('\nNo triggers deployed. Run: squads deploy')); return; } - console.log(''); - console.log(chalk.bold('Platform Triggers')); - console.log(chalk.dim('─'.repeat(60))); + writeLine(''); + writeLine(chalk.bold('Platform Triggers')); + writeLine(chalk.dim('─'.repeat(60))); for (const trigger of data) { const status = trigger.enabled ? chalk.green('enabled') : chalk.red('disabled'); @@ -261,8 +262,8 @@ export async function deployStatusCommand(): Promise { ? chalk.dim(new Date(trigger.last_fired_at).toLocaleString()) : chalk.dim('never'); - console.log(` ${status} ${chalk.cyan(trigger.name)} — ${trigger.squad}${trigger.agent ? '/' + trigger.agent : ''}`); - console.log(` ${chalk.dim('type:')} ${trigger.trigger_type} ${chalk.dim('last fired:')} ${lastFired}`); + writeLine(` ${status} ${chalk.cyan(trigger.name)} — ${trigger.squad}${trigger.agent ? '/' + trigger.agent : ''}`); + writeLine(` ${chalk.dim('type:')} ${trigger.trigger_type} ${chalk.dim('last fired:')} ${lastFired}`); } // Show execution stats @@ -274,17 +275,17 @@ export async function deployStatusCommand(): Promise { if (execResponse.ok) { const stats = await execResponse.json() as Record; - console.log(''); - console.log(chalk.bold('Platform Stats')); - console.log(chalk.dim('─'.repeat(60))); + writeLine(''); + writeLine(chalk.bold('Platform Stats')); + writeLine(chalk.dim('─'.repeat(60))); if (stats.running_agents !== undefined) { - console.log(` Running agents: ${chalk.cyan(String(stats.running_agents))}`); + writeLine(` Running agents: ${chalk.cyan(String(stats.running_agents))}`); } if (stats.executions_today !== undefined) { - console.log(` Executions today: ${chalk.cyan(String(stats.executions_today))}`); + writeLine(` Executions today: ${chalk.cyan(String(stats.executions_today))}`); } if (stats.total_cost_today !== undefined) { - console.log(` Cost today: ${chalk.cyan('$' + String(stats.total_cost_today))}`); + writeLine(` Cost today: ${chalk.cyan('$' + String(stats.total_cost_today))}`); } } @@ -297,7 +298,7 @@ export async function deployStatusCommand(): Promise { export async function deployPullCommand(options: { verbose?: boolean }): Promise { const session = loadSession(); if (!session?.accessToken) { - console.log(chalk.yellow('Not logged in. Run: squads login')); + writeLine(chalk.yellow('Not logged in. Run: squads login')); return; } @@ -330,13 +331,13 @@ export async function deployPullCommand(options: { verbose?: boolean }): Promise spinner.succeed(`Pulled ${executions.length} recent execution(s)`); if (executions.length === 0) { - console.log(chalk.dim('\nNo executions found on platform.')); + writeLine(chalk.dim('\nNo executions found on platform.')); return; } - console.log(''); - console.log(chalk.bold('Recent Platform Executions')); - console.log(chalk.dim('─'.repeat(70))); + writeLine(''); + writeLine(chalk.bold('Recent Platform Executions')); + writeLine(chalk.dim('─'.repeat(70))); for (const exec of executions) { const statusColor = exec.status === 'completed' ? chalk.green @@ -347,11 +348,11 @@ export async function deployPullCommand(options: { verbose?: boolean }): Promise const cost = exec.cost_usd !== null ? chalk.dim(`$${exec.cost_usd.toFixed(2)}`) : ''; const time = new Date(exec.started_at).toLocaleString(); - console.log(` ${statusColor(exec.status.padEnd(10))} ${chalk.cyan(exec.trigger_name)} ${chalk.dim(time)} ${cost}`); + writeLine(` ${statusColor(exec.status.padEnd(10))} ${chalk.cyan(exec.trigger_name)} ${chalk.dim(time)} ${cost}`); if (options.verbose && exec.completed_at) { const duration = (new Date(exec.completed_at).getTime() - new Date(exec.started_at).getTime()) / 1000; - console.log(` ${chalk.dim(`duration: ${duration.toFixed(0)}s`)}`); + writeLine(` ${chalk.dim(`duration: ${duration.toFixed(0)}s`)}`); } } @@ -371,11 +372,11 @@ export async function deployPullCommand(options: { verbose?: boolean }): Promise }>; if (learnings.length > 0) { - console.log(''); - console.log(chalk.bold('Recent Learnings')); - console.log(chalk.dim('─'.repeat(70))); + writeLine(''); + writeLine(chalk.bold('Recent Learnings')); + writeLine(chalk.dim('─'.repeat(70))); for (const l of learnings) { - console.log(` ${chalk.cyan(l.squad)}/${l.agent}: ${l.insight.substring(0, 80)}${l.insight.length > 80 ? '...' : ''}`); + writeLine(` ${chalk.cyan(l.squad)}/${l.agent}: ${l.insight.substring(0, 80)}${l.insight.length > 80 ? '...' : ''}`); } } } diff --git a/src/commands/eval.ts b/src/commands/eval.ts index f3cc0dbb..b0702292 100644 --- a/src/commands/eval.ts +++ b/src/commands/eval.ts @@ -14,6 +14,7 @@ import { Command } from 'commander'; import chalk from 'chalk'; +import { writeLine } from '../lib/terminal.js'; import { existsSync, readFileSync, readdirSync, statSync } from 'fs'; import { join } from 'path'; import matter from 'gray-matter'; @@ -468,7 +469,7 @@ function renderReadinessLevel(level: EvalResult['readinessLevel']): string { } function renderResult(result: EvalResult): void { - console.log(` + writeLine(` ${chalk.bold(`Agent Readiness: ${result.squad}/${result.agent}`)} ${chalk.dim('━'.repeat(50))} `); @@ -477,19 +478,19 @@ ${chalk.dim('━'.repeat(50))} const icon = dim.status === 'pass' ? chalk.green('✓') : dim.status === 'warn' ? chalk.yellow('⚠') : chalk.red('✗'); - console.log(` ${icon} ${dim.name.padEnd(22)} ${renderBar(dim.score, dim.maxScore)}`); + writeLine(` ${icon} ${dim.name.padEnd(22)} ${renderBar(dim.score, dim.maxScore)}`); } - console.log(` + writeLine(` Overall readiness: ${chalk.bold(String(result.overallScore) + '%')} — ${renderReadinessLevel(result.readinessLevel)} `); if (result.recommendations.length > 0) { - console.log(` ${chalk.bold('Recommendations:')}`); + writeLine(` ${chalk.bold('Recommendations:')}`); for (const rec of result.recommendations) { - console.log(` ${chalk.dim('→')} ${rec}`); + writeLine(` ${chalk.dim('→')} ${rec}`); } - console.log(''); + writeLine(''); } } @@ -502,7 +503,7 @@ export async function evalCommand(target: string, options: { const squadsDir = findSquadsDir(); if (!squadsDir) { console.error(chalk.red('No .agents/squads/ directory found.')); - console.log(chalk.dim('Run: squads init')); + writeLine(chalk.dim('Run: squads init')); return; } @@ -538,7 +539,7 @@ export async function evalCommand(target: string, options: { } if (options.json) { - console.log(JSON.stringify(results, null, 2)); + writeLine(JSON.stringify(results, null, 2)); return; } @@ -555,14 +556,14 @@ export async function evalCommand(target: string, options: { return acc; }, {} as Record); - console.log(chalk.bold('Squad Summary')); - console.log(chalk.dim('━'.repeat(50))); - console.log(` Agents evaluated: ${results.length}`); - console.log(` Average score: ${avgScore}%`); + writeLine(chalk.bold('Squad Summary')); + writeLine(chalk.dim('━'.repeat(50))); + writeLine(` Agents evaluated: ${results.length}`); + writeLine(` Average score: ${avgScore}%`); for (const [level, count] of Object.entries(levels)) { - console.log(` ${renderReadinessLevel(level as EvalResult['readinessLevel'])}: ${count}`); + writeLine(` ${renderReadinessLevel(level as EvalResult['readinessLevel'])}: ${count}`); } - console.log(''); + writeLine(''); } await track('cli.eval', { diff --git a/src/commands/init.ts b/src/commands/init.ts index b4baf265..e881e878 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -28,6 +28,7 @@ import { runAuthChecks, displayCheckResults, } from '../lib/setup-checks.js'; +import { writeLine } from '../lib/terminal.js'; export interface InitOptions { provider?: string; @@ -190,15 +191,15 @@ async function promptProvider(forceProvider?: string): Promise { } if (!isInteractive()) return 'claude'; - console.log(); - console.log(chalk.bold(' Select your AI assistant:')); - console.log(); - console.log(` ${chalk.cyan('1)')} Claude Code ${chalk.dim('(recommended)')}`); - console.log(` ${chalk.cyan('2)')} Gemini`); - console.log(` ${chalk.cyan('3)')} OpenAI GPT`); - console.log(` ${chalk.cyan('4)')} Ollama ${chalk.dim('(local)')}`); - console.log(` ${chalk.cyan('5)')} Other/None`); - console.log(); + writeLine(); + writeLine(chalk.bold(' Select your AI assistant:')); + writeLine(); + writeLine(` ${chalk.cyan('1)')} Claude Code ${chalk.dim('(recommended)')}`); + writeLine(` ${chalk.cyan('2)')} Gemini`); + writeLine(` ${chalk.cyan('3)')} OpenAI GPT`); + writeLine(` ${chalk.cyan('4)')} Ollama ${chalk.dim('(local)')}`); + writeLine(` ${chalk.cyan('5)')} Other/None`); + writeLine(); const rl = createInterface({ input: process.stdin, @@ -224,15 +225,15 @@ async function promptProvider(forceProvider?: string): Promise { async function promptUseCase(): Promise { if (!isInteractive()) return 'full-company'; - console.log(); - console.log(chalk.bold(' What does your AI workforce need to do?')); - console.log(); - console.log(` ${chalk.cyan('1)')} Engineering ${chalk.dim('— ships code (issue-solver, code-reviewer, test-writer)')}`); - console.log(` ${chalk.cyan('2)')} Marketing ${chalk.dim('— grows audience (content-drafter, social-poster, growth-analyst)')}`); - console.log(` ${chalk.cyan('3)')} Operations ${chalk.dim('— runs the business (ops-lead, finance-tracker, goal-tracker)')}`); - console.log(` ${chalk.cyan('4)')} Full Company ${chalk.dim('— all of the above')} ${chalk.green('(recommended)')}`); - console.log(` ${chalk.cyan('5)')} Custom ${chalk.dim('— empty scaffold, you build from scratch')}`); - console.log(); + writeLine(); + writeLine(chalk.bold(' What does your AI workforce need to do?')); + writeLine(); + writeLine(` ${chalk.cyan('1)')} Engineering ${chalk.dim('— ships code (issue-solver, code-reviewer, test-writer)')}`); + writeLine(` ${chalk.cyan('2)')} Marketing ${chalk.dim('— grows audience (content-drafter, social-poster, growth-analyst)')}`); + writeLine(` ${chalk.cyan('3)')} Operations ${chalk.dim('— runs the business (ops-lead, finance-tracker, goal-tracker)')}`); + writeLine(` ${chalk.cyan('4)')} Full Company ${chalk.dim('— all of the above')} ${chalk.green('(recommended)')}`); + writeLine(` ${chalk.cyan('5)')} Custom ${chalk.dim('— empty scaffold, you build from scratch')}`); + writeLine(); const rl = createInterface({ input: process.stdin, @@ -298,19 +299,19 @@ export async function initCommand(options: InitOptions): Promise { const cwd = process.cwd(); // 1. Welcome - console.log(); - console.log(chalk.bold(' Plant the seed for your AI workforce')); - console.log(chalk.dim(' https://agents-squads.com/docs/getting-started')); - console.log(); + writeLine(); + writeLine(chalk.bold(' Plant the seed for your AI workforce')); + writeLine(chalk.dim(' https://agents-squads.com/docs/getting-started')); + writeLine(); // 2. Select provider const selectedProvider = await promptProvider(options.provider); const provider = PROVIDERS[selectedProvider]; // 3. Prerequisite checks - console.log(); - console.log(chalk.bold(' Checking prerequisites...')); - console.log(); + writeLine(); + writeLine(chalk.bold(' Checking prerequisites...')); + writeLine(); const checks = [ ...runAuthChecks(selectedProvider), @@ -338,14 +339,14 @@ export async function initCommand(options: InitOptions): Promise { const { hasErrors } = displayCheckResults(checks); if (hasErrors && !options.force) { - console.log(); - console.log(chalk.red(' Fix the errors above before continuing.')); - console.log(chalk.dim(' Or run with --force to skip checks.')); - console.log(); + writeLine(); + writeLine(chalk.red(' Fix the errors above before continuing.')); + writeLine(chalk.dim(' Or run with --force to skip checks.')); + writeLine(); process.exit(1); } - console.log(); + writeLine(); // 4. Ask about the business let businessName: string; @@ -361,8 +362,8 @@ export async function initCommand(options: InitOptions): Promise { } else { const dirName = path.basename(cwd); - console.log(chalk.bold(' Tell us about your business:')); - console.log(); + writeLine(chalk.bold(' Tell us about your business:')); + writeLine(); businessName = await prompt( 'Company or project name?', @@ -374,7 +375,7 @@ export async function initCommand(options: InitOptions): Promise { '' ); - console.log(); + writeLine(); businessFocus = await prompt( 'What should your first research squad investigate?', @@ -394,12 +395,12 @@ export async function initCommand(options: InitOptions): Promise { const totalAgentCount = coreAgentCount + useCaseAgentCount; const totalSquadCount = coreSquadCount + useCaseConfig.squads.length; - console.log(); - console.log(` ${chalk.green('✓')} Business: ${chalk.cyan(businessName)}${businessDescription ? chalk.dim(` — ${businessDescription}`) : ''}`); - console.log(` ${chalk.green('✓')} Provider: ${chalk.cyan(provider?.name || selectedProvider)}`); - console.log(` ${chalk.green('✓')} Research focus: ${chalk.cyan(businessFocus)}`); - console.log(` ${chalk.green('✓')} Use case: ${chalk.cyan(useCaseConfig.label)} ${chalk.dim(`— ${useCaseConfig.description}`)}`); - console.log(); + writeLine(); + writeLine(` ${chalk.green('✓')} Business: ${chalk.cyan(businessName)}${businessDescription ? chalk.dim(` — ${businessDescription}`) : ''}`); + writeLine(` ${chalk.green('✓')} Provider: ${chalk.cyan(provider?.name || selectedProvider)}`); + writeLine(` ${chalk.green('✓')} Research focus: ${chalk.cyan(businessFocus)}`); + writeLine(` ${chalk.green('✓')} Use case: ${chalk.cyan(useCaseConfig.label)} ${chalk.dim(`— ${useCaseConfig.description}`)}`); + writeLine(); // 5. Create the seed const spinner = ora('Planting the seed...').start(); @@ -555,46 +556,46 @@ export async function initCommand(options: InitOptions): Promise { } // 6. Success message - console.log(); - console.log(chalk.green.bold(` ${businessName}'s AI workforce is ready.`)); - console.log(); - console.log(chalk.dim(' Created:')); + writeLine(); + writeLine(chalk.green.bold(` ${businessName}'s AI workforce is ready.`)); + writeLine(); + writeLine(chalk.dim(' Created:')); // Core squads (always present) - console.log(chalk.dim(' • .agents/squads/company/ 5 agents (manager, dispatcher, tracker, eval, critic)')); - console.log(chalk.dim(' • .agents/squads/research/ 4 agents (researcher, analyst, eval, critic)')); - console.log(chalk.dim(' • .agents/squads/intelligence/ 3 agents (intel-lead, eval, critic)')); + writeLine(chalk.dim(' • .agents/squads/company/ 5 agents (manager, dispatcher, tracker, eval, critic)')); + writeLine(chalk.dim(' • .agents/squads/research/ 4 agents (researcher, analyst, eval, critic)')); + writeLine(chalk.dim(' • .agents/squads/intelligence/ 3 agents (intel-lead, eval, critic)')); // Use-case specific squads for (const squad of useCaseConfig.squads) { const padding = ' '.repeat(Math.max(0, 22 - squad.name.length)); - console.log(chalk.dim(` • .agents/squads/${squad.name}/${padding}${squad.agentCount} agents (${squad.agentSummary})`)); + writeLine(chalk.dim(` • .agents/squads/${squad.name}/${padding}${squad.agentCount} agents (${squad.agentSummary})`)); } - console.log(chalk.dim(' • .agents/skills/ CLI + GitHub workflow skills')); - console.log(chalk.dim(' • .agents/memory/ Persistent state')); - console.log(chalk.dim(' • .agents/BUSINESS_BRIEF.md')); + writeLine(chalk.dim(' • .agents/skills/ CLI + GitHub workflow skills')); + writeLine(chalk.dim(' • .agents/memory/ Persistent state')); + writeLine(chalk.dim(' • .agents/BUSINESS_BRIEF.md')); if (selectedProvider === 'claude') { - console.log(chalk.dim(' • CLAUDE.md Operating manual')); - console.log(chalk.dim(' • .claude/settings.json Session hooks')); + writeLine(chalk.dim(' • CLAUDE.md Operating manual')); + writeLine(chalk.dim(' • .claude/settings.json Session hooks')); } - console.log(); - console.log(chalk.bold(' Getting started:')); - console.log(); - console.log(` ${chalk.cyan('1.')} ${chalk.yellow('git add -A && git commit -m "feat: init AI workforce"')}`); - console.log(chalk.dim(' Git is the coordination layer — commit first')); - console.log(); + writeLine(); + writeLine(chalk.bold(' Getting started:')); + writeLine(); + writeLine(` ${chalk.cyan('1.')} ${chalk.yellow('git add -A && git commit -m "feat: init AI workforce"')}`); + writeLine(chalk.dim(' Git is the coordination layer — commit first')); + writeLine(); // Dynamic "first run" suggestion based on use case const firstRunCommand = getFirstRunCommand(selectedUseCase); - console.log(` ${chalk.cyan('2.')} ${chalk.yellow(firstRunCommand.command)}`); - console.log(chalk.dim(` ${firstRunCommand.description}`)); - console.log(); - console.log(` ${chalk.cyan('3.')} ${chalk.yellow(`squads dash`)}`); - console.log(chalk.dim(' See all your squads and agents at a glance')); - console.log(); - console.log(chalk.dim(' Docs: https://agents-squads.com/docs/getting-started')); - console.log(); + writeLine(` ${chalk.cyan('2.')} ${chalk.yellow(firstRunCommand.command)}`); + writeLine(chalk.dim(` ${firstRunCommand.description}`)); + writeLine(); + writeLine(` ${chalk.cyan('3.')} ${chalk.yellow(`squads dash`)}`); + writeLine(chalk.dim(' See all your squads and agents at a glance')); + writeLine(); + writeLine(chalk.dim(' Docs: https://agents-squads.com/docs/getting-started')); + writeLine(); } /** diff --git a/src/commands/login.ts b/src/commands/login.ts index 8314f1f1..170bba43 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -11,6 +11,7 @@ import { AuthSession } from '../lib/auth.js'; import { track } from '../lib/telemetry.js'; +import { writeLine } from '../lib/terminal.js'; const AUTH_URL = process.env.SQUADS_AUTH_URL || ''; const CALLBACK_PORT = 54321; @@ -34,9 +35,9 @@ export async function loginCommand(): Promise { const existingSession = loadSession(); if (existingSession && existingSession.status === 'active') { - console.log(chalk.green(`✓ Already logged in as ${existingSession.email}`)); - console.log(chalk.dim(` Domain: ${existingSession.domain}`)); - console.log(chalk.dim(` Run 'squads logout' to sign out.`)); + writeLine(chalk.green(`✓ Already logged in as ${existingSession.email}`)); + writeLine(chalk.dim(` Domain: ${existingSession.domain}`)); + writeLine(chalk.dim(` Run 'squads logout' to sign out.`)); return; } @@ -47,7 +48,7 @@ export async function loginCommand(): Promise { if (!isAvailable) { spinner.stop(); spinner.clear(); - console.log(` + writeLine(` ${chalk.bold.cyan('Pro & Enterprise Login')} ${chalk.yellow('(Coming Soon)')} ${chalk.dim('─'.repeat(40))} @@ -67,7 +68,7 @@ ${chalk.dim('Questions?')} ${chalk.cyan('hello@agents-squads.com')} spinner.text = 'Opening browser to authenticate...'; spinner.succeed(); - console.log(` + writeLine(` ${chalk.bold.magenta('Squads CLI Login')} ${chalk.dim('─'.repeat(40))} `); @@ -88,7 +89,7 @@ ${chalk.dim('─'.repeat(40))} // Check if personal email if (isPersonalEmail(email)) { authSpinner.fail('Personal emails not supported'); - console.log(` + writeLine(` ${chalk.yellow('⚠ Squads CLI is for Pro & Enterprise teams only.')} Personal email domains (Gmail, Yahoo, etc.) are not supported. @@ -116,7 +117,7 @@ ${chalk.dim('Want to stay updated?')} await track('cli.login.success', { domain: session.domain }); - console.log(` + writeLine(` ${chalk.green('✓ Thanks for signing up!')} ${chalk.bold('What happens next:')} @@ -142,12 +143,12 @@ export async function logoutCommand(): Promise { const session = loadSession(); if (!session) { - console.log(chalk.yellow('Not logged in.')); + writeLine(chalk.yellow('Not logged in.')); return; } clearSession(); - console.log(chalk.green(`✓ Logged out from ${session.email}`)); + writeLine(chalk.green(`✓ Logged out from ${session.email}`)); await track('cli.logout'); } @@ -155,12 +156,12 @@ export async function whoamiCommand(): Promise { const session = loadSession(); if (!session) { - console.log(chalk.yellow('Not logged in.')); - console.log(chalk.dim('Run: squads login')); + writeLine(chalk.yellow('Not logged in.')); + writeLine(chalk.dim('Run: squads login')); return; } - console.log(` + writeLine(` ${chalk.bold('Current Session')} ${chalk.dim('─'.repeat(30))} Email: ${chalk.cyan(session.email)} diff --git a/src/commands/trigger.ts b/src/commands/trigger.ts index b15e2756..738661ff 100644 --- a/src/commands/trigger.ts +++ b/src/commands/trigger.ts @@ -12,6 +12,7 @@ import { Command } from "commander"; import chalk from "chalk"; +import { writeLine } from "../lib/terminal.js"; const API_URL = process.env.SQUADS_API_URL || process.env.SCHEDULER_URL || "http://localhost:8090"; @@ -76,9 +77,9 @@ async function listTriggers(squad?: string): Promise { if (isConnectionError) { console.error(chalk.red("\n Scheduler not running\n")); - console.log(chalk.gray(" The trigger system requires the local stack to be running.\n")); - console.log(` ${chalk.cyan("$ squads stack start")} Start the local stack`); - console.log(` ${chalk.cyan("$ squads stack status")} Check stack status\n`); + writeLine(chalk.gray(" The trigger system requires the local stack to be running.\n")); + writeLine(` ${chalk.cyan("$ squads stack start")} Start the local stack`); + writeLine(` ${chalk.cyan("$ squads stack status")} Check stack status\n`); return; } @@ -87,11 +88,11 @@ async function listTriggers(squad?: string): Promise { } if (triggers.length === 0) { - console.log(chalk.gray("No triggers found")); + writeLine(chalk.gray("No triggers found")); return; } - console.log(chalk.bold("\nSmart Triggers\n")); + writeLine(chalk.bold("\nSmart Triggers\n")); const grouped = triggers.reduce( (acc, t) => { @@ -102,23 +103,23 @@ async function listTriggers(squad?: string): Promise { ); for (const [squadName, squadTriggers] of Object.entries(grouped)) { - console.log(chalk.cyan(` ${squadName}`)); + writeLine(chalk.cyan(` ${squadName}`)); for (const t of squadTriggers) { const status = t.enabled ? chalk.green("●") : chalk.gray("○"); const agent = t.agent ? `/${t.agent}` : ""; const fires = t.fire_count > 0 ? chalk.gray(` (${t.fire_count}x)`) : ""; - console.log( + writeLine( ` ${status} ${t.name}${chalk.gray(agent)} P${t.priority}${fires}` ); } - console.log(); + writeLine(); } } async function syncTriggers(): Promise { - console.log(chalk.gray("Syncing triggers from SQUAD.md files...\n")); + writeLine(chalk.gray("Syncing triggers from SQUAD.md files...\n")); try { const result = await fetchScheduler<{ synced: number; triggers: string[]; errors: Array<{ name: string; error: string }> }>( @@ -127,16 +128,16 @@ async function syncTriggers(): Promise { ); if (result.errors && result.errors.length > 0) { - console.log(chalk.yellow(`Synced with ${result.errors.length} error(s):`)); + writeLine(chalk.yellow(`Synced with ${result.errors.length} error(s):`)); for (const err of result.errors) { - console.log(chalk.red(` - ${err.name}: ${err.error}`)); + writeLine(chalk.red(` - ${err.name}: ${err.error}`)); } } - console.log(chalk.green(`Synced ${result.synced} trigger(s)`)); + writeLine(chalk.green(`Synced ${result.synced} trigger(s)`)); if (result.triggers && result.triggers.length > 0) { for (const name of result.triggers) { - console.log(chalk.gray(` - ${name}`)); + writeLine(chalk.gray(` - ${name}`)); } } } catch (error: unknown) { @@ -146,9 +147,9 @@ async function syncTriggers(): Promise { if (isConnectionError) { console.error(chalk.red("\n API not running\n")); - console.log(chalk.gray(" The sync command requires the API to be running.\n")); - console.log(` ${chalk.cyan("$ squads stack start")} Start the local stack`); - console.log(` ${chalk.cyan("$ squads health")} Check service status\n`); + writeLine(chalk.gray(" The sync command requires the API to be running.\n")); + writeLine(` ${chalk.cyan("$ squads stack start")} Start the local stack`); + writeLine(` ${chalk.cyan("$ squads health")} Check service status\n`); return; } @@ -167,7 +168,7 @@ async function fireTrigger(name: string): Promise { return; } - console.log( + writeLine( chalk.gray(`Firing ${trigger.squad}/${trigger.agent || "*"}...`) ); @@ -181,7 +182,7 @@ async function fireTrigger(name: string): Promise { { method: "POST" } ); - console.log(chalk.green(`✓ Queued execution ${execution.id.slice(0, 8)}`)); + writeLine(chalk.green(`✓ Queued execution ${execution.id.slice(0, 8)}`)); } async function toggleTrigger(name: string, enable: boolean): Promise { @@ -199,29 +200,29 @@ async function toggleTrigger(name: string, enable: boolean): Promise { }); const status = enable ? chalk.green("enabled") : chalk.gray("disabled"); - console.log(`${trigger.name} ${status}`); + writeLine(`${trigger.name} ${status}`); } async function showStatus(): Promise { try { const stats = await fetchScheduler("/stats"); - console.log(chalk.bold("\nScheduler Status\n")); + writeLine(chalk.bold("\nScheduler Status\n")); - console.log(chalk.cyan(" Triggers")); - console.log(` Total: ${stats.triggers.total}`); - console.log(` Enabled: ${chalk.green(stats.triggers.enabled)}`); - console.log(` Fired 24h: ${stats.triggers.fired_24h}`); + writeLine(chalk.cyan(" Triggers")); + writeLine(` Total: ${stats.triggers.total}`); + writeLine(` Enabled: ${chalk.green(stats.triggers.enabled)}`); + writeLine(` Fired 24h: ${stats.triggers.fired_24h}`); - console.log(chalk.cyan("\n Executions (24h)")); - console.log(` Completed: ${chalk.green(stats.executions_24h.completed)}`); - console.log(` Failed: ${chalk.red(stats.executions_24h.failed)}`); - console.log(` Running: ${chalk.yellow(stats.executions_24h.running)}`); - console.log(` Queued: ${stats.executions_24h.queued}`); - console.log(); + writeLine(chalk.cyan("\n Executions (24h)")); + writeLine(` Completed: ${chalk.green(stats.executions_24h.completed)}`); + writeLine(` Failed: ${chalk.red(stats.executions_24h.failed)}`); + writeLine(` Running: ${chalk.yellow(stats.executions_24h.running)}`); + writeLine(` Queued: ${stats.executions_24h.queued}`); + writeLine(); } catch { console.error(chalk.red("Scheduler not running or unreachable")); - console.log(chalk.gray(` Expected at: ${API_URL}`)); + writeLine(chalk.gray(` Expected at: ${API_URL}`)); } } diff --git a/src/lib/anthropic.ts b/src/lib/anthropic.ts index 6af2fc55..2d7bc34a 100644 --- a/src/lib/anthropic.ts +++ b/src/lib/anthropic.ts @@ -57,7 +57,6 @@ export async function listSkills(): Promise { // Note: The actual Skills API may have a different endpoint // This is a placeholder until we have the exact API spec // Real implementation will use: anthropic.beta.skills.list({ betas: [SKILLS_BETA] }) - console.warn('Skills API: Using placeholder implementation. Actual API may differ.'); return []; } catch (error) { if (error instanceof Error && error.message.includes('404')) { @@ -181,10 +180,6 @@ export async function uploadSkill(skillPath: string): Promise { try { // Note: This is a placeholder for the actual Skills API endpoint // The real implementation will use the correct beta endpoint - console.log(`Uploading skill: ${displayTitle}`); - console.log(`Files: ${files.map(f => f.name).join(', ')}`); - console.log(`Total size: ${(totalSize / 1024).toFixed(2)}KB`); - // Placeholder response until we have actual API spec const skill: Skill = { id: `skill_${Date.now()}`, @@ -195,7 +190,6 @@ export async function uploadSkill(skillPath: string): Promise { files }; - console.warn('Skills API: Using placeholder implementation. Skill not actually uploaded.'); return skill; } catch (error) { throw new Error(`Failed to upload skill: ${error instanceof Error ? error.message : String(error)}`); @@ -211,8 +205,7 @@ export async function deleteSkill(skillId: string): Promise { try { // Placeholder until we have actual API spec - console.log(`Deleting skill: ${skillId}`); - console.warn('Skills API: Using placeholder implementation. Skill not actually deleted.'); + // No-op: Skills API not yet available } catch (error) { throw new Error(`Failed to delete skill: ${error instanceof Error ? error.message : String(error)}`); } @@ -227,8 +220,6 @@ export async function getSkill(skillId: string): Promise { try { // Placeholder until we have actual API spec - console.log(`Getting skill: ${skillId}`); - console.warn('Skills API: Using placeholder implementation.'); return null; } catch (error) { if (error instanceof Error && error.message.includes('404')) { diff --git a/src/lib/update.ts b/src/lib/update.ts index 916aebda..a7a289a5 100644 --- a/src/lib/update.ts +++ b/src/lib/update.ts @@ -8,7 +8,7 @@ import { join, dirname } from 'path'; import { homedir } from 'os'; import { execSync } from 'child_process'; import { fileURLToPath } from 'url'; -import { colors as termColors, RESET as termReset } from './terminal.js'; +import { colors as termColors, RESET as termReset, writeLine } from './terminal.js'; // Get current version from package.json function getPackageVersion(): string { @@ -306,7 +306,7 @@ export async function autoUpdateOnStartup(silent = false): Promise { child.on('close', (code) => { if (code === 0) { // Success - show Gemini-style message - console.log(`\n ${termColors.green}✓${termReset} Update successful! v${info.latestVersion} will be used on your next run.\n`); + writeLine(`\n ${termColors.green}✓${termReset} Update successful! v${info.latestVersion} will be used on your next run.\n`); writeAutoUpdateCache({ lastAttempt: now, lastSuccess: now }); // Clear version cache so next startup detects new version try { unlinkSync(CACHE_FILE); } catch { /* ignore */ } From c3b370cd0c8e318efeb68c0e919bb5cef61716bd Mon Sep 17 00:00:00 2001 From: Jorge Vidaurre <3512039+kokevidaurre@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:27:08 -0300 Subject: [PATCH 006/127] docs: professional README for v0.7.0 (#356) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace minimal README with comprehensive 331-line version covering: - Quick start with real output examples - Why Squads (4 differentiators) - Provider table (7 LLM providers) - Feature showcase (dashboard, memory, sessions, autonomous, hooks) - Command reference (21 active commands, no removed ones) - Project structure and configuration examples - Development guide and tech stack - Contributing and community links References only current commands (memory write/read instead of learn, env show instead of context, exec list instead of history). 🤖 Generated with [Agents Squads](https://agents-squads.com) Co-authored-by: kokevidaurre Co-authored-by: Claude --- README.md | 1348 ++++++++--------------------------------------------- 1 file changed, 196 insertions(+), 1152 deletions(-) diff --git a/README.md b/README.md index 05e4fc0e..86a172f0 100644 --- a/README.md +++ b/README.md @@ -1,1096 +1,168 @@ -# squads-cli +
-[![npm version](https://img.shields.io/npm/v/squads-cli)](https://www.npmjs.com/package/squads-cli) -[![npm downloads](https://img.shields.io/npm/dw/squads-cli)](https://www.npmjs.com/package/squads-cli) -[![GitHub stars](https://img.shields.io/github/stars/agents-squads/squads-cli?style=social)](https://github.com/agents-squads/squads-cli) -[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) -[![Docs](https://img.shields.io/badge/docs-agents--squads.com-purple)](https://agents-squads.com/docs) - -**Build your AI workforce.** Finance, marketing, engineering, operations — AI agents organized into domain-aligned squads that actually run your business. - -📖 **[Documentation](https://agents-squads.com/docs)** · 🚀 **[Getting Started](https://agents-squads.com/onboarding)** · 💡 **[Architecture Guide](https://agents-squads.com/engineering/squads-architecture)** - -```bash -npm install -g squads-cli && squads init -``` - -> **Why squads?** A full business team costs $1M+/year. An AI workforce costs API calls. Squads organizes AI agents by business domain — giving them persistent memory, goals, and coordinated execution. Your agents, your data, no lock-in. Works with Claude, GPT-4, Gemini, and more. - -![squads dashboard](./assets/dashboard.png) - -⭐ **If you find this useful, [star the repo](https://github.com/agents-squads/squads-cli)** — it helps others discover it! - -``` -$ squads status - - squads status - ● 7 active sessions across 1 squad (claude 7) - - 10/10 squads │ memory: enabled - - ┌────────────────────────────────────────────────────────┐ - │ SQUAD AGENTS MEMORY ACTIVITY │ - ├────────────────────────────────────────────────────────┤ - │ cli 7 1 entry today │ - │ engineering 6 1 entry today │ - │ intelligence 17 1 entry 4d ago │ - │ marketing 4 2 entries today │ - │ website 10 1 entry 5d ago │ - └────────────────────────────────────────────────────────┘ -``` - -## Traditional Hiring vs AI Workforce - -| | Traditional Team | AI Workforce (squads-cli) | -|---|---|---| -| **Cost** | $1M+/year for a small team | API calls ($50-500/month) | -| **Availability** | 8 hours/day, 5 days/week | 24/7, including weekends | -| **Scale** | Hire, train, onboard (months) | Add a markdown file (minutes) | -| **Ownership** | Vendor contracts, platforms | Your agents, your data, your system | -| **Memory** | Tribal knowledge, Notion docs | Persistent state across sessions | +# squads -## Why squads-cli? +**Your AI workforce** -| Other Frameworks | squads-cli | -|------------------|------------| -| Framework lock-in | Markdown files you own | -| Complex setup | `npm install -g` and go | -| No memory | Persistent state across sessions | -| Single agent focus | Domain-aligned teams | -| Code-heavy | CLI-first, zero code to start | +One person + AI teammates = a real business. -**Works with:** Claude Code, Cursor, Aider, Gemini, GitHub Copilot, and more. +[![npm version](https://img.shields.io/npm/v/squads-cli.svg)](https://www.npmjs.com/package/squads-cli) +[![npm downloads](https://img.shields.io/npm/dw/squads-cli.svg)](https://www.npmjs.com/package/squads-cli) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org) +[![GitHub stars](https://img.shields.io/github/stars/agents-squads/squads-cli?style=social)](https://github.com/agents-squads/squads-cli) -## Key Features +[Documentation](https://agents-squads.com/docs) · [Getting Started](https://agents-squads.com/onboarding) · [Architecture](https://agents-squads.com/engineering/squads-architecture) -- **Squads** — Group agents by domain (engineering, research, marketing) -- **Memory** — Persistent state that survives across sessions -- **Goals** — Track objectives and measure progress -- **Sessions** — Real-time detection of running AI assistants -- **Hooks** — Inject context at session start, sync memory at session end -- **Stack** — Optional local infrastructure for telemetry and cost tracking +
-No complex infrastructure. Just markdown files and a CLI. +--- -## Installation +Squads organizes AI agents into domain-aligned teams -- marketing, engineering, finance, operations -- that coordinate work, remember what they learn, and track goals over time. Agents are plain markdown files. No framework lock-in, no proprietary formats. Works with any LLM provider. -```bash -npm install -g squads-cli -``` +![squads dashboard](./assets/dashboard.png) ## Quick Start ```bash -# Initialize in your project +npm install -g squads-cli squads init - -# See what you have -squads status - -# Full dashboard with goals and metrics -squads dash - -# Run a squad -squads run engineering - -# Search memory -squads memory query "authentication" - -# Set a goal -squads goal set engineering "Ship v2.0 by Friday" -``` - -**Expected output after `squads init`:** - ``` - squads init - - ✓ Created .agents/squads/ directory - ✓ Created example squad: engineering - ✓ Created .agents/memory/ directory - - Next steps: - $ squads status See your new squad - $ squads dash Full dashboard view -``` - -## Features - -### Dashboard - -View comprehensive metrics across all squads: - -``` -$ squads dash - - squads dashboard - ● 7 active sessions across 1 squad (claude 7) - - 8/10 squads │ 404 commits │ use -f for PRs/issues - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 35% goal progress - - ┌──────────────────────────────────────────────────────────┐ - │ SQUAD COMMITS PRs ISSUES GOALS PROGRESS │ - ├──────────────────────────────────────────────────────────┤ - │ marketing 203 0 0/0 9/12 ━━━━━━━━ │ - │ website 203 0 0/0 0/1 ━━━━━━━━ │ - │ engineering 139 0 0/0 0/1 ━━━━━━━━ │ - │ cli 48 0 0/0 2/3 ━━━━━━━━ │ - └──────────────────────────────────────────────────────────┘ - - Git Activity (30d) - Last 14d: ▁▁▁▁▁▁▁▄▆▄▆▅█▂ - 404 commits │ 13.5/day │ 21 active days -``` - -**CEO Mode** provides an executive summary: - -``` -$ squads dash --ceo - - squads CEO Report - 2026-01-05 - - ┌─────────────────────────────────────┐ - │ METRIC VALUE │ - ├─────────────────────────────────────┤ - │ Active Squads 8/10 │ - │ P0 Goals 3 │ - │ P1 Goals 5 │ - │ Blockers 2 │ - │ Daily Spend $12.50 / $50 │ - └─────────────────────────────────────┘ - - P0 Priorities (revenue/launch critical) - ✗ customer Generate first consulting revenue - ✗ website Launch public website -``` - -### Memory Search - -``` -$ squads memory query "telemetry" - - squads memory query "telemetry" - - 5 results found - - ┌──────────────────────────────────────────────────┐ - │ LOCATION TYPE SCORE │ - ├──────────────────────────────────────────────────┤ - │ cli/cli-lead state 7.2 │ - │ engineering/eng-lead state 7.2 │ - │ marketing/marketing-lead state 7.2 │ - └──────────────────────────────────────────────────┘ - - Matches - ◇ Telemetry pipeline COMPLETE. Dashboard showing real-time... - └ cli/cli-lead -``` - -### Session Detection - -Real-time detection of running AI coding assistants: ``` $ squads status - ● 7 active sessions across 1 squad (claude 7) -``` - -Supports multiple tools: -- Claude Code -- Cursor -- Aider -- Gemini -- GitHub Copilot -- Sourcegraph Cody -- Continue - -### Stack Management - -Local Docker infrastructure for telemetry and memory: - -``` -$ squads stack health - - squads stack health - - ✓ postgres healthy - ✓ redis healthy - ✓ neo4j healthy - ✓ bridge healthy - ✓ langfuse healthy - ✓ mem0 healthy - ✓ engram healthy - - ● 8/8 services healthy -``` - -**Valid services for `squads stack logs`:** -- `postgres` - PostgreSQL database -- `redis` - Redis cache -- `neo4j` - Graph database -- `bridge` - Conversation capture API -- `langfuse` - Telemetry dashboard -- `mem0` - Memory extraction -- `engram` - Memory MCP server -- `otel` - OpenTelemetry collector - -### Auto-Update - -``` -$ squads status - - ⬆ Update available: 0.1.2 → 0.2.0 (run `squads update`) - -$ squads update - Checking npm registry... - ⬆ Update available: 0.1.2 → 0.2.0 - Update now? [y/N]: y - Installing update... - ● Updated to 0.2.0 -``` - -### Agent History - -Track and review agent execution history with costs and performance metrics: - -``` -$ squads history - - squads history - - Last 7 days of agent executions - - ┌────────────────────────────────────────────────────────────┐ - │ TIMESTAMP SQUAD AGENT COST STATUS │ - ├────────────────────────────────────────────────────────────┤ - │ 2026-01-06 10:30 engineering ci-lead $0.45 ✓ │ - │ 2026-01-06 09:15 website seo-critic $0.23 ✓ │ - │ 2026-01-05 16:20 customer lead $0.89 ✓ │ - └────────────────────────────────────────────────────────────┘ - - Total cost: $1.57 │ 3 executions -``` - -Filter by squad and get detailed metrics: - -```bash -squads history -s engineering -v # Verbose with token details -squads history -d 30 -j # Last 30 days, JSON output -``` - -### Context Feed - -Get curated context for agents based on goals, memory, and recent activity: - -```bash -# Human-readable summary -squads context -s engineering - -# JSON for agent consumption -squads context -s engineering --json - -# Search memory for specific topic -squads context -t "authentication" -s website -``` - -Perfect for injecting relevant context into agent prompts or hooks. - -### Live Monitoring - -**Watch Mode** — Refresh any command in real-time: - -```bash -squads watch status # Refresh every 2 seconds -squads watch "dash --ceo" -n 5 # Custom interval -``` - -**Live Dashboard** — Interactive TUI like htop: - -```bash -squads live # Full dashboard -``` - -**Process Table** — Real-time process monitoring: - -```bash -squads top # Live process table -``` - -### Infrastructure Health - -Quick health check across all services without verbose Docker output: - -``` -$ squads health - - squads health - - ✓ postgres healthy - ✓ redis healthy - ✓ bridge healthy - ✓ langfuse healthy - - ● 4/4 core services healthy -``` - -Use `-v` to see logs for failing services. - -## Core Concepts - -### Squads = Domain-Aligned Teams - -``` -.agents/squads/ -├── engineering/ -│ ├── SQUAD.md # Squad config + goals -│ └── ci-optimizer.md # Agent definition -├── research/ -│ ├── SQUAD.md -│ └── market-analyst.md -└── intelligence/ - └── ... -``` - -### Agents = Markdown Prompts - -```markdown -# CI Optimizer - -## Purpose -Reduce build times and optimize CI/CD pipelines. - -## Model -claude-sonnet-4 - -## Tools -- Bash(gh:*, git:*) -- Read -- Edit - -## Instructions -1. Analyze current build configuration -2. Identify slow steps -3. Implement caching strategies -4. Verify improvements -``` - -### Memory = Cross-Session State - -Memory files are stored at `.agents/memory///.md`: - -``` -.agents/memory/ -├── engineering/ -│ └── eng-lead/ -│ ├── state.md # Current state -│ ├── learnings.md # Accumulated insights -│ └── feedback.md # Execution feedback -└── research/ - └── analyst/ - └── state.md -``` - -```bash -# Agents accumulate knowledge -squads memory show engineering -# → "Switched to pnpm for faster installs" -# → "Build cache reduced CI time by 40%" - -# Search across all squads -squads memory query "performance" -``` - -### Learning Loop - -Capture insights that persist across sessions: - -```bash -# After fixing a bug -squads learn "PostgreSQL connection pool exhaustion was caused by unclosed transactions" - -# With metadata -squads learn "Always check memory before researching" --squad engineering --category pattern - -# View learnings -squads learnings show engineering -squads learnings search "postgres" -``` - -Learnings are stored in `.agents/memory//shared/learnings.md` and sync with `squads memory sync`. - -**Categories:** -- `success` — What worked well -- `failure` — What didn't work (learn from mistakes) -- `pattern` — Reusable approach -- `tip` — General advice - -**The learning loop:** -1. Session starts → hooks inject squad status + memory -2. Work happens → you solve problems, discover things -3. Session ends → Stop hook prompts "Capture learnings: squads learn..." -4. Next session → Previous learnings compound via memory queries - -### Goals with Metrics - -Goals can include optional metric annotations for tracking KPIs: - -```bash -# Set a goal with metrics -squads goal set finance "Reduce monthly costs by 20%" --metric "cost_usd" --metric "savings_pct" - -# View goals with progress -squads goal list - - finance - ● [1] Reduce monthly costs by 20% - └ Current: $450/mo, target: $400/mo -``` - -### Learning Extraction - -Extract learnings from recent conversations and store in Engram: - -```bash -# Extract memories from last 24 hours -squads memory extract - -# Preview without storing -squads memory extract --dry-run - -# Extract from specific time window -squads memory extract --hours 48 -``` - -## Commands - -### Initialization - -```bash -squads init # Initialize project with default template -squads init -t minimal # Use minimal template -``` - -### Status & Dashboard - -```bash -squads status # All squads overview -squads status engineering # Single squad details -squads status -v # Verbose with agent list - -squads dash # Full dashboard with goals (fast mode) -squads dash -f # Include GitHub PR/issue stats (~30s slower) -squads dash --ceo # Executive summary with priorities and blockers -squads dash -v # Additional details -``` - -### Running Agents - -```bash -squads run engineering # Run the whole squad -squads run engineering/ci-optimizer # Run specific agent (slash notation) -squads run engineering -a ci-optimizer # Run specific agent (flag notation) -squads run engineering --dry-run # Preview what would run -squads run engineering --execute # Execute via Claude CLI -squads run engineering --parallel # Run all agents in parallel -squads run engineering --lead # Lead mode: single orchestrator with Task agents -squads run engineering --foreground # Run in foreground (no tmux) -squads run engineering --timeout 60 # Set timeout in minutes (default: 30) -``` - -### Memory Management - -```bash -# Query (semantic search across markdown files) -squads memory query "deployment" # Search all memory -squads memory query "auth" -s website # Filter by squad -squads memory query "cache" -a lead # Filter by agent - -# View -squads memory show research # View squad memory -squads memory list # List all entries - -# Update -squads memory update cli "Fixed telemetry bug" # Add to learnings -squads memory update cli "State: ready" -t state # Update state -squads memory update cli "Good" -a cli-lead -t feedback # Add feedback - -# Sync (git-based) -squads memory sync # Pull remote, process commits -squads memory sync -v # Verbose output -squads memory sync -p # Push local changes after sync -squads memory sync --no-pull # Skip pulling from remote - -# Search (postgres via squads-bridge) -squads memory search "authentication" # Search stored conversations -squads memory search "error" -l 20 # Limit results -squads memory search "api" -r user # Filter by role (user/assistant/thinking) -squads memory search "bug" -i high # Filter by importance (low/normal/high) - -# Extract (conversations → Engram memories) -squads memory extract # Extract from last 24h -squads memory extract -h 48 # Extract from last 48h -squads memory extract -s abc123 # Extract specific session -squads memory extract -d # Dry run (preview only) -``` - -### Execution History - -View and analyze past agent executions: - -```bash -$ squads exec list + squads status + ● 3 active sessions across 2 squads (claude 2, gemini 1) - squads exec list + 4/4 squads | memory: enabled - ┌────────────────────────────────────────────────────────────────────────────────────┐ - │ AGENT STATUS DURATION TIME ID │ - ├────────────────────────────────────────────────────────────────────────────────────┤ - │ cli/code-eval ● completed 5m 30s 2h ago exec_abc123_xyz │ - │ website/web-lead ◆ running — 3h ago exec_def456_abc │ - └────────────────────────────────────────────────────────────────────────────────────┘ + SQUAD AGENTS MEMORY ACTIVITY + engineering 3 4 entries today + marketing 2 2 entries today + research 5 1 entry yesterday + operations 2 -- 3d ago ``` ```bash -squads exec list # Recent executions -squads exec list --squad cli # Filter by squad -squads exec list --status completed # Filter by status -squads exec show # Execution details -squads exec stats # Statistics across all executions -squads exec --json # JSON output for programmatic access -``` +# Run a specific agent +squads run engineering/code-review -### Goal Tracking +# Run an entire squad in parallel +squads run engineering --parallel -```bash -squads goal set finance "Cut costs 20%" # Set goal -squads goal set finance "Track API usage" -m "api_calls" -m "cost_usd" # With metrics -squads goal list # View all active goals -squads goal list -a # Include completed goals -squads goal list finance # View goals for one squad -squads goal progress finance 1 "Reduced to $400/mo" # Update progress (index, text) -squads goal complete finance 1 # Mark done (by index) -``` +# Search across all agent memory +squads memory query "authentication patterns" -### Feedback Loop - -```bash -squads feedback add research 4 "Good analysis" # Rate 1-5 with comment -squads feedback add cli 5 "Excellent" -l "Cache key insight" # With learning -squads feedback show research # View history -squads feedback show research -n 10 # Show more entries -squads feedback stats # Summary across all squads +# Set and track goals +squads goal set engineering "Ship v2.0 by Friday" +squads dash ``` -### Learnings +## Why Squads -```bash -squads learn "Insight here" # Capture a learning -squads learn "Pattern" -s engineering -c pattern # With squad and category -squads learn "Tip" -t "cli,perf" # With tags -squads learnings show engineering # View squad learnings -squads learnings show engineering -n 5 # Limit results -squads learnings show engineering --category pattern # Filter by category -squads learnings show engineering --tag perf # Filter by tag -squads learnings search "postgres" # Search all learnings -``` +**Agents are markdown files.** No DSLs, no YAML pipelines, no SDKs. A squad is a directory. An agent is a `.md` file with a role, model preference, and instructions. You own everything -- version it, edit it, fork it. -### Session Management +**Multi-provider by default.** Route agents to the right model for the job. Claude for deep reasoning, Gemini for speed, GPT-4o for breadth, Ollama for local execution. Switch providers per agent or per run with a single flag. -```bash -# List sessions -squads sessions # Show active sessions -squads sessions -v # With session details -squads sessions -j # JSON output - -# History -squads sessions history # Last 7 days -squads sessions history -d 30 # Last 30 days -squads sessions history -s website # Filter by squad -squads sessions history -j # JSON output - -# Summary -squads sessions summary # Auto-detect current session -squads sessions summary -f data.json # From JSON file -squads sessions summary -j # Output as JSON - -# Lifecycle (for hooks/automation) -squads session start # Register new session -squads session start -s engineering # Override squad detection -squads session start -q # Quiet mode -squads session stop # End current session -squads session stop -q # Quiet mode -squads session heartbeat # Update session activity -squads session heartbeat -q # Quiet mode - -# Detection -squads detect-squad # Detect current squad from cwd -``` +**Memory that persists.** Agents accumulate knowledge across sessions. Learnings survive restarts, context carries forward, and any agent can search the collective memory of the organization. -### Progress Tracking +**Goals, not just tasks.** Set objectives at the squad level, track progress through KPIs, and get executive summaries. Squads is a business operating system, not a script runner. -```bash -squads progress # Show active/completed tasks -squads progress -v # More activity details -squads progress start engineering "Fixing CI" # Register task -squads progress complete abc123 # Mark task completed -squads progress complete abc123 -f # Mark as failed -``` +## Supported Providers -### Results & KPIs +| Provider | CLI | Models | +|----------|-----|--------| +| Anthropic | `claude` | Opus, Sonnet, Haiku | +| Google | `gemini` | Gemini 2.5 Flash, Pro | +| OpenAI | `codex` | GPT-4o, o1, o3 | +| Mistral | `vibe` | Large, Medium | +| xAI | `grok` | Grok | +| Aider | `aider` | Multi-model | +| Ollama | `ollama` | Any local model | ```bash -squads results # All squads, last 7 days -squads results engineering # Single squad -squads results -d 30 # Last 30 days -squads results -v # Detailed KPIs per goal -``` +# Use a specific provider for a run +squads run research --provider=google --model=gemini-2.5-flash -### Workers - -```bash -squads workers # Show Claude sessions, tasks, dev servers -squads workers -v # More details -squads workers -k 12345 # Kill process by PID +# Check which providers are available +squads providers ``` -### Issues Management +## Features -```bash -# View issues -squads issues # Show GitHub issues -squads issues -o my-org # Different organization -squads issues -r repo1,repo2 # Specific repos - -# Solve issues (create PRs) -squads solve-issues # Solve ready-to-fix issues -squads solve-issues -r hq # Target specific repo -squads solve-issues -i 123 # Solve specific issue -squads solve-issues -d # Dry run -squads solve-issues -e # Execute with Claude CLI - -# Open issues (run evaluators) -squads open-issues # Find new issues via evaluators -squads open-issues -s website # Target squad -squads open-issues -a seo-critic # Specific evaluator -squads open-issues -d # Dry run -squads open-issues -e # Execute with Claude CLI -``` +### Dashboard -### Stack Management +Full visibility into squad activity, goal progress, and git contribution metrics. -```bash -# Setup -squads stack init # Interactive setup wizard - -# Status -squads stack status # Container health overview -squads stack health # Comprehensive diagnostics -squads stack health -v # Show logs for unhealthy services -squads stack env # Print export commands - -# Control -squads stack up # Start Docker containers -squads stack down # Stop Docker containers -squads stack logs bridge # View container logs -squads stack logs postgres -n 100 # Last 100 lines ``` +$ squads dash -### Tonight Mode (Autonomous Execution) - -Run agents autonomously overnight with safety guardrails: - -```bash -# Run intelligence squad overnight -squads tonight intelligence - -# Multiple targets with cost cap -squads tonight intelligence customer/outreach --cost-cap 25 - -# Preview what would run -squads tonight engineering --dry-run - -# Check status while running -squads tonight status + squads dashboard + ● 7 active sessions across 3 squads -# Stop all overnight agents -squads tonight stop + 8/10 squads | 404 commits | use -f for PRs/issues -# View the morning report -squads tonight report -``` + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 35% goal progress -**Example output:** + SQUAD COMMITS PRs ISSUES GOALS PROGRESS + marketing 203 0 0/0 9/12 ━━━━━━━━ + engineering 139 0 0/0 0/1 ━━━━━━━━ + cli 48 0 0/0 2/3 ━━━━━━━━ + Git Activity (30d) + Last 14d: ▁▁▁▁▁▁▁▄▆▄▆▅█▂ + 404 commits | 13.5/day | 21 active days ``` -$ squads tonight intelligence customer/outreach - - squads tonight - - Config: - Cost cap: $50 - Stop at: 07:00 - Max retries: 3 - Targets: intelligence, customer/outreach - ✓ Launched 2 agent(s) +The `--ceo` flag produces an executive summary with P0/P1 priorities and spend tracking. - ✓ Tonight mode active +### Memory System - Monitor: - squads tonight status - Check progress - squads tonight stop - Kill all agents - tmux ls | grep tonight - List sessions - - Logs: .agents/outputs/tonight/ -``` - -**Safety features:** -- **Cost cap** — Automatically stops when spending limit reached (default: $50) -- **Time limit** — Stops at specified time (default: 7:00 AM) -- **Max retries** — Limits restart attempts for crashed agents (default: 3) -- **Session isolation** — Each agent runs in isolated tmux session -- **Logging** — All output captured to `.agents/outputs/tonight/` -- **Morning report** — Summary generated on completion - -**Monitoring commands:** +Agents write learnings as they work. The memory system makes that knowledge searchable and shareable across the organization. ```bash -# Check current status -squads tonight status - -# Attach to a running session -tmux attach -t squads-tonight-intelligence-... +# Capture a learning from the command line +squads memory write engineering "Redis connection pooling requires min 5 connections for our load" -# View live logs -tail -f .agents/outputs/tonight/*.log +# Search all agent memory +squads memory query "deployment" -# Stop everything immediately -squads tonight stop +# View a specific squad's accumulated knowledge +squads memory read engineering ``` -> **Warning**: Tonight mode uses `--dangerously-skip-permissions` which bypasses -> Claude's safety confirmations. Only use with trusted agent definitions. Review -> your agent prompts and ensure they have appropriate scope limits before running -> autonomously. - -### Smart Triggers - -Triggers execute agents based on conditions in PostgreSQL: +### Session Detection -```bash -squads trigger list # View all triggers -squads trigger list engineering # Filter by squad -squads trigger sync # Sync SQUAD.md → Postgres -squads trigger fire cost-alert # Manually fire a trigger -squads trigger enable cost-alert # Enable a trigger -squads trigger disable cost-alert # Disable a trigger -squads trigger status # Scheduler health & stats -``` +Automatically detects running AI coding sessions across your machine and maps them to squads based on working directory. -**Trigger definition in SQUAD.md:** - -```yaml -triggers: - - name: cost-alert - agent: cost-tracker - condition: | - SELECT value > 100 - FROM latest_metrics - WHERE name = 'daily_cost_usd' - cooldown: 6 hours - priority: 3 ``` +$ squads sessions -### List & Discovery - -```bash -squads list # List all squads and agents -squads list -s # Squads only -squads list -a # Agents only + ● 4 active sessions + claude engineering/backend ~/projects/api 12m + claude engineering/frontend ~/projects/web 3m + gemini research/analyst ~/projects/research 45m + cursor marketing/content ~/projects/site 8m ``` -### Authentication (Pro & Enterprise) +Supports: Claude Code, Cursor, Aider, Gemini, GitHub Copilot, Sourcegraph Cody, Continue. -```bash -squads login # Log in to Squads cloud -squads logout # Log out -squads whoami # Show current user -``` +### Autonomous Execution -### Updates +Schedule agents to run on their own with the local daemon. ```bash -squads update # Interactive update -squads update -y # Auto-confirm -squads update -c # Check only, don't install -``` - -## Command Reference +# Start the autonomous scheduler +squads autonomous start +# Check what's running +squads autonomous status ``` -squads init Initialize project - -t, --template