squads-cli 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8,9 +8,9 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
 
9
9
  // src/cli.ts
10
10
  import { config } from "dotenv";
11
- import { existsSync as existsSync14 } from "fs";
12
- import { join as join14 } from "path";
13
- import { homedir as homedir4 } from "os";
11
+ import { existsSync as existsSync17 } from "fs";
12
+ import { join as join17 } from "path";
13
+ import { homedir as homedir6 } from "os";
14
14
  import { Command } from "commander";
15
15
  import chalk3 from "chalk";
16
16
 
@@ -100,7 +100,7 @@ var SQUAD_LABELS = {
100
100
  company: ["company", "strategy"],
101
101
  marketing: ["marketing", "content", "seo"]
102
102
  };
103
- function getGitHubStats(basePath, days = 30) {
103
+ function getGitHubStatsOptimized(basePath, days = 30) {
104
104
  const stats = {
105
105
  prsOpened: 0,
106
106
  prsMerged: 0,
@@ -121,74 +121,81 @@ function getGitHubStats(basePath, days = 30) {
121
121
  }
122
122
  const repos = ["hq", "agents-squads-web"];
123
123
  const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
124
+ const results = [];
124
125
  for (const repo of repos) {
125
126
  const repoPath = join(basePath, repo);
126
127
  if (!existsSync(repoPath)) continue;
127
128
  try {
128
- const prsOutput = execSync(
129
- `gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit 100 2>/dev/null`,
130
- { cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
129
+ const output = execSync(
130
+ `echo '{"prs":' && gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit 50 2>/dev/null && echo ',"issues":' && gh issue list --state all --json number,title,state,closedAt,labels --limit 50 2>/dev/null && echo '}'`,
131
+ { cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
131
132
  );
132
- const prs = JSON.parse(prsOutput || "[]");
133
- for (const pr of prs) {
134
- const created = new Date(pr.createdAt);
135
- if (created < new Date(since)) continue;
136
- stats.prsOpened++;
137
- if (pr.mergedAt) stats.prsMerged++;
138
- const squad = detectSquadFromPR(pr, repo);
139
- const squadStats = stats.bySquad.get(squad);
140
- if (squadStats) {
141
- squadStats.prsOpened++;
142
- if (pr.mergedAt) squadStats.prsMerged++;
143
- if (squadStats.recentPRs.length < 3) {
144
- squadStats.recentPRs.push({
145
- title: pr.title,
146
- number: pr.number,
147
- merged: !!pr.mergedAt
148
- });
149
- }
133
+ const prsMatch = output.match(/"prs":(\[.*?\]),"issues":/s);
134
+ const issuesMatch = output.match(/"issues":(\[.*?\])\s*\}/s);
135
+ const prs = prsMatch ? JSON.parse(prsMatch[1]) : [];
136
+ const issues = issuesMatch ? JSON.parse(issuesMatch[1]) : [];
137
+ results.push({ repo, prs, issues });
138
+ } catch {
139
+ try {
140
+ const prsOutput = execSync(
141
+ `gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit 50 2>/dev/null`,
142
+ { cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
143
+ );
144
+ const issuesOutput = execSync(
145
+ `gh issue list --state all --json number,title,state,closedAt,labels --limit 50 2>/dev/null`,
146
+ { cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
147
+ );
148
+ results.push({
149
+ repo,
150
+ prs: JSON.parse(prsOutput || "[]"),
151
+ issues: JSON.parse(issuesOutput || "[]")
152
+ });
153
+ } catch {
154
+ }
155
+ }
156
+ }
157
+ for (const { repo, prs, issues } of results) {
158
+ for (const pr of prs) {
159
+ const created = new Date(pr.createdAt);
160
+ if (created < new Date(since)) continue;
161
+ stats.prsOpened++;
162
+ if (pr.mergedAt) stats.prsMerged++;
163
+ const squad = detectSquadFromPR(pr, repo);
164
+ const squadStats = stats.bySquad.get(squad);
165
+ if (squadStats) {
166
+ squadStats.prsOpened++;
167
+ if (pr.mergedAt) squadStats.prsMerged++;
168
+ if (squadStats.recentPRs.length < 3) {
169
+ squadStats.recentPRs.push({
170
+ title: pr.title,
171
+ number: pr.number,
172
+ merged: !!pr.mergedAt
173
+ });
150
174
  }
151
175
  }
152
- const issuesOutput = execSync(
153
- `gh issue list --state all --json number,title,state,closedAt,labels --limit 100 2>/dev/null`,
154
- { cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
155
- );
156
- const issues = JSON.parse(issuesOutput || "[]");
157
- for (const issue of issues) {
158
- const squad = detectSquadFromIssue(issue, repo);
159
- const squadStats = stats.bySquad.get(squad);
160
- if (issue.state === "CLOSED") {
161
- const closed = new Date(issue.closedAt);
162
- if (closed >= new Date(since)) {
163
- stats.issuesClosed++;
164
- if (squadStats) {
165
- squadStats.issuesClosed++;
166
- }
167
- }
168
- } else {
169
- stats.issuesOpen++;
176
+ }
177
+ for (const issue of issues) {
178
+ const squad = detectSquadFromIssue(issue, repo);
179
+ const squadStats = stats.bySquad.get(squad);
180
+ if (issue.state === "CLOSED") {
181
+ const closed = new Date(issue.closedAt || 0);
182
+ if (closed >= new Date(since)) {
183
+ stats.issuesClosed++;
170
184
  if (squadStats) {
171
- squadStats.issuesOpen++;
172
- if (squadStats.recentIssues.length < 3) {
173
- squadStats.recentIssues.push({
174
- title: issue.title,
175
- number: issue.number,
176
- state: issue.state
177
- });
178
- }
185
+ squadStats.issuesClosed++;
179
186
  }
180
187
  }
181
- }
182
- } catch {
183
- }
184
- }
185
- const gitStats = getMultiRepoGitStats(basePath, days);
186
- for (const [repo, commits] of gitStats.commitsByRepo) {
187
- for (const [squad, repos2] of Object.entries(SQUAD_REPO_MAP)) {
188
- if (repos2.includes(repo)) {
189
- const squadStats = stats.bySquad.get(squad);
188
+ } else {
189
+ stats.issuesOpen++;
190
190
  if (squadStats) {
191
- squadStats.commits += commits;
191
+ squadStats.issuesOpen++;
192
+ if (squadStats.recentIssues.length < 3) {
193
+ squadStats.recentIssues.push({
194
+ title: issue.title,
195
+ number: issue.number,
196
+ state: issue.state
197
+ });
198
+ }
192
199
  }
193
200
  }
194
201
  }
@@ -341,7 +348,8 @@ import { randomUUID } from "crypto";
341
348
  var TELEMETRY_DIR = join2(homedir(), ".squads-cli");
342
349
  var CONFIG_PATH = join2(TELEMETRY_DIR, "telemetry.json");
343
350
  var EVENTS_PATH = join2(TELEMETRY_DIR, "events.json");
344
- var TELEMETRY_ENDPOINT = process.env.SQUADS_TELEMETRY_URL || (process.env.SQUADS_BRIDGE_URL ? `${process.env.SQUADS_BRIDGE_URL}/api/telemetry` : null);
351
+ var DEFAULT_TELEMETRY_ENDPOINT = "https://qzayrjwxiznkqfvmjxyy.supabase.co/functions/v1/telemetry-ingest";
352
+ var TELEMETRY_ENDPOINT = process.env.SQUADS_TELEMETRY_URL || (process.env.SQUADS_BRIDGE_URL ? `${process.env.SQUADS_BRIDGE_URL}/api/telemetry` : DEFAULT_TELEMETRY_ENDPOINT);
345
353
  var eventQueue = [];
346
354
  var flushScheduled = false;
347
355
  function ensureDir() {
@@ -1428,22 +1436,32 @@ async function runSquad(squad, squadsDir, options) {
1428
1436
  }
1429
1437
  }
1430
1438
  } else {
1431
- const orchestrator = squad.agents.find(
1432
- (a) => a.name.includes("lead") || a.trigger === "Manual"
1433
- );
1434
- if (orchestrator) {
1435
- const agentPath = join5(squadsDir, squad.name, `${orchestrator.name}.md`);
1439
+ if (options.agent) {
1440
+ const agentPath = join5(squadsDir, squad.name, `${options.agent}.md`);
1436
1441
  if (existsSync5(agentPath)) {
1437
- await runAgent(orchestrator.name, agentPath, squad.name, options);
1442
+ await runAgent(options.agent, agentPath, squad.name, options);
1443
+ } else {
1444
+ writeLine(` ${icons.error} ${colors.red}Agent ${options.agent} not found${RESET}`);
1445
+ return;
1438
1446
  }
1439
1447
  } else {
1440
- writeLine(` ${colors.dim}No pipeline defined. Available agents:${RESET}`);
1441
- for (const agent of squad.agents) {
1442
- writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET} ${colors.dim}${agent.role}${RESET}`);
1448
+ const orchestrator = squad.agents.find(
1449
+ (a) => a.name.includes("lead") || a.trigger === "Manual"
1450
+ );
1451
+ if (orchestrator) {
1452
+ const agentPath = join5(squadsDir, squad.name, `${orchestrator.name}.md`);
1453
+ if (existsSync5(agentPath)) {
1454
+ await runAgent(orchestrator.name, agentPath, squad.name, options);
1455
+ }
1456
+ } else {
1457
+ writeLine(` ${colors.dim}No pipeline defined. Available agents:${RESET}`);
1458
+ for (const agent of squad.agents) {
1459
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET} ${colors.dim}${agent.role}${RESET}`);
1460
+ }
1461
+ writeLine();
1462
+ writeLine(` ${colors.dim}Run a specific agent:${RESET}`);
1463
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --agent ${colors.cyan}<name>${RESET}`);
1443
1464
  }
1444
- writeLine();
1445
- writeLine(` ${colors.dim}Run a specific agent:${RESET}`);
1446
- writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --agent ${colors.cyan}<name>${RESET}`);
1447
1465
  }
1448
1466
  }
1449
1467
  writeLine();
@@ -1485,18 +1503,16 @@ After completion:
1485
1503
  3. Report what was accomplished`;
1486
1504
  const claudeAvailable = await checkClaudeCliAvailable();
1487
1505
  if (options.execute && claudeAvailable) {
1488
- spinner.text = `Executing ${agentName} with Claude Code...`;
1506
+ spinner.text = `Launching ${agentName} as background task...`;
1489
1507
  try {
1490
- const result = await executeWithClaude(prompt, options.verbose);
1491
- spinner.succeed(`Agent ${agentName} completed`);
1492
- updateExecutionStatus(squadName, agentName, "completed", "Executed via Claude CLI");
1493
- if (result) {
1494
- writeLine(` ${colors.dim}Output:${RESET}`);
1495
- writeLine(` ${result.slice(0, 500)}`);
1496
- if (result.length > 500) writeLine(` ${colors.dim}... (truncated)${RESET}`);
1497
- }
1508
+ const result = await executeWithClaude(prompt, options.verbose, options.timeout || 30);
1509
+ spinner.succeed(`Agent ${agentName} launched`);
1510
+ writeLine(` ${colors.dim}${result}${RESET}`);
1511
+ writeLine();
1512
+ writeLine(` ${colors.dim}Monitor:${RESET} squads workers`);
1513
+ writeLine(` ${colors.dim}Memory:${RESET} squads memory show ${squadName}`);
1498
1514
  } catch (error) {
1499
- spinner.fail(`Agent ${agentName} failed`);
1515
+ spinner.fail(`Agent ${agentName} failed to launch`);
1500
1516
  updateExecutionStatus(squadName, agentName, "failed", String(error));
1501
1517
  writeLine(` ${colors.red}${String(error)}${RESET}`);
1502
1518
  }
@@ -1509,13 +1525,10 @@ After completion:
1509
1525
  writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
1510
1526
  }
1511
1527
  writeLine();
1512
- writeLine(` ${colors.dim}To execute with Claude Code:${RESET}`);
1513
- writeLine(` ${colors.dim}$${RESET} claude --print "${prompt.replace(/"/g, '\\"').replace(/\n/g, " ").slice(0, 80)}..."`);
1514
- writeLine();
1515
- writeLine(` ${colors.dim}Or run with --execute flag:${RESET}`);
1516
- writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} --execute`);
1528
+ writeLine(` ${colors.dim}To launch as background task:${RESET}`);
1529
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} -a ${colors.cyan}${agentName}${RESET} --execute`);
1517
1530
  writeLine();
1518
- writeLine(` ${colors.dim}Or in Claude Code session:${RESET}`);
1531
+ writeLine(` ${colors.dim}Or run interactively:${RESET}`);
1519
1532
  writeLine(` ${colors.dim}$${RESET} Run the ${colors.cyan}${agentName}${RESET} agent from ${agentPath}`);
1520
1533
  }
1521
1534
  }
@@ -1526,48 +1539,51 @@ async function checkClaudeCliAvailable() {
1526
1539
  check.on("error", () => resolve(false));
1527
1540
  });
1528
1541
  }
1529
- async function executeWithClaude(prompt, verbose) {
1530
- return new Promise((resolve, reject) => {
1531
- const args = ["--print", prompt];
1532
- if (verbose) {
1533
- writeLine(` ${colors.dim}Spawning: claude ${args.slice(0, 1).join(" ")} ...${RESET}`);
1534
- }
1535
- const squadMatch = prompt.match(/squad (\w+)/);
1536
- const agentMatch = prompt.match(/(\w+) agent/);
1537
- const claude = spawn("claude", args, {
1538
- stdio: ["pipe", "pipe", "pipe"],
1539
- env: {
1540
- ...process.env,
1541
- SQUADS_SQUAD: squadMatch?.[1] || "unknown",
1542
- SQUADS_AGENT: agentMatch?.[1] || "unknown"
1543
- }
1544
- });
1545
- let output = "";
1546
- let error = "";
1547
- claude.stdout?.on("data", (data) => {
1548
- output += data.toString();
1549
- if (verbose) {
1550
- process.stdout.write(data.toString());
1551
- }
1552
- });
1553
- claude.stderr?.on("data", (data) => {
1554
- error += data.toString();
1555
- });
1556
- claude.on("close", (code) => {
1557
- if (code === 0) {
1558
- resolve(output);
1559
- } else {
1560
- reject(new Error(error || `Claude exited with code ${code}`));
1561
- }
1562
- });
1563
- claude.on("error", (err) => {
1564
- reject(err);
1565
- });
1566
- setTimeout(() => {
1567
- claude.kill();
1568
- reject(new Error("Execution timed out after 5 minutes"));
1569
- }, 5 * 60 * 1e3);
1542
+ async function executeWithClaude(prompt, verbose, timeoutMinutes = 30) {
1543
+ const userConfigPath = join5(process.env.HOME || "", ".claude.json");
1544
+ const squadMatch = prompt.match(/squad (\w+)/);
1545
+ const agentMatch = prompt.match(/(\w+) agent/);
1546
+ const squadName = squadMatch?.[1] || "unknown";
1547
+ const agentName = agentMatch?.[1] || "unknown";
1548
+ const timestamp = Date.now();
1549
+ const sessionName = `squads-${squadName}-${agentName}-${timestamp}`;
1550
+ if (verbose) {
1551
+ writeLine(` ${colors.dim}Spawning tmux session: ${sessionName}${RESET}`);
1552
+ }
1553
+ const escapedPrompt = prompt.replace(/'/g, "'\\''");
1554
+ const claudeCmd = `claude --dangerously-skip-permissions --mcp-config '${userConfigPath}' -- '${escapedPrompt}'`;
1555
+ const tmux = spawn("tmux", [
1556
+ "new-session",
1557
+ "-d",
1558
+ // Detached
1559
+ "-s",
1560
+ sessionName,
1561
+ "-x",
1562
+ "200",
1563
+ // Wide terminal for better output
1564
+ "-y",
1565
+ "50",
1566
+ "/bin/sh",
1567
+ "-c",
1568
+ claudeCmd
1569
+ ], {
1570
+ stdio: "ignore",
1571
+ detached: true,
1572
+ env: {
1573
+ ...process.env,
1574
+ SQUADS_SQUAD: squadName,
1575
+ SQUADS_AGENT: agentName
1576
+ }
1570
1577
  });
1578
+ tmux.unref();
1579
+ spawn("/bin/sh", ["-c", `sleep 2 && tmux send-keys -t '${sessionName}' Down Enter`], {
1580
+ stdio: "ignore",
1581
+ detached: true
1582
+ }).unref();
1583
+ if (verbose) {
1584
+ writeLine(` ${colors.dim}Attach: tmux attach -t ${sessionName}${RESET}`);
1585
+ }
1586
+ return `tmux session: ${sessionName}. Attach: tmux attach -t ${sessionName}`;
1571
1587
  }
1572
1588
 
1573
1589
  // src/commands/list.ts
@@ -1620,139 +1636,704 @@ async function listCommand(options) {
1620
1636
  }
1621
1637
 
1622
1638
  // src/commands/status.ts
1623
- import { existsSync as existsSync6, statSync } from "fs";
1624
- import { join as join6 } from "path";
1625
- async function statusCommand(squadName, options = {}) {
1626
- const squadsDir = findSquadsDir();
1627
- if (!squadsDir) {
1628
- writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
1629
- writeLine(`${colors.dim}Run \`squads init\` to create one.${RESET}`);
1630
- process.exit(1);
1639
+ import { existsSync as existsSync8, statSync } from "fs";
1640
+ import { join as join8 } from "path";
1641
+
1642
+ // src/lib/sessions.ts
1643
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync5, unlinkSync, appendFileSync, createReadStream } from "fs";
1644
+ import { join as join6, dirname as dirname3 } from "path";
1645
+ import { randomBytes } from "crypto";
1646
+ import { createInterface } from "readline";
1647
+ import { execSync as execSync2 } from "child_process";
1648
+ var AI_TOOL_PATTERNS = [
1649
+ { pattern: /^claude$/, name: "claude" },
1650
+ { pattern: /^cursor$/i, name: "cursor" },
1651
+ { pattern: /^aider$/, name: "aider" },
1652
+ { pattern: /^gemini$/i, name: "gemini" },
1653
+ { pattern: /^copilot$/i, name: "copilot" },
1654
+ { pattern: /^cody$/i, name: "cody" },
1655
+ { pattern: /^continue$/i, name: "continue" }
1656
+ ];
1657
+ var STALE_THRESHOLD_MS = 5 * 60 * 1e3;
1658
+ var HISTORY_FILE = "history.jsonl";
1659
+ var ACTIVE_DIR = "active";
1660
+ var SQUAD_DIR_MAP = {
1661
+ "hq": "company",
1662
+ "agents-squads-web": "website",
1663
+ "company": "company",
1664
+ "product": "product",
1665
+ "engineering": "engineering",
1666
+ "research": "research",
1667
+ "intelligence": "intelligence",
1668
+ "customer": "customer",
1669
+ "finance": "finance",
1670
+ "marketing": "marketing"
1671
+ };
1672
+ function findAgentsDir() {
1673
+ let dir = process.cwd();
1674
+ for (let i = 0; i < 5; i++) {
1675
+ const agentsPath = join6(dir, ".agents");
1676
+ if (existsSync6(agentsPath)) {
1677
+ return agentsPath;
1678
+ }
1679
+ const parent = dirname3(dir);
1680
+ if (parent === dir) break;
1681
+ dir = parent;
1631
1682
  }
1632
- if (squadName) {
1633
- await showSquadStatus(squadName, squadsDir, options);
1634
- } else {
1635
- await showOverallStatus(squadsDir, options);
1683
+ return null;
1684
+ }
1685
+ function getSessionsBaseDir() {
1686
+ const agentsDir = findAgentsDir();
1687
+ if (!agentsDir) return null;
1688
+ const sessionsDir = join6(agentsDir, "sessions");
1689
+ if (!existsSync6(sessionsDir)) {
1690
+ mkdirSync4(sessionsDir, { recursive: true });
1636
1691
  }
1692
+ return sessionsDir;
1637
1693
  }
1638
- async function showOverallStatus(squadsDir, _options) {
1639
- const squads = listSquads(squadsDir);
1640
- const memoryDir = findMemoryDir();
1641
- writeLine();
1642
- writeLine(` ${gradient("squads")} ${colors.dim}status${RESET}`);
1643
- writeLine();
1644
- const totalSquads = squads.length;
1645
- const activeCount = squads.length;
1646
- writeLine(` ${colors.cyan}${activeCount}${RESET}/${totalSquads} squads ${colors.dim}\u2502${RESET} ${colors.dim}memory: ${memoryDir ? "enabled" : "none"}${RESET}`);
1647
- writeLine();
1648
- const w = { name: 16, agents: 8, memory: 14, activity: 12 };
1649
- const tableWidth = w.name + w.agents + w.memory + w.activity + 6;
1650
- writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
1651
- const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.name)}${RESET}${bold}${padEnd("AGENTS", w.agents)}${RESET}${bold}${padEnd("MEMORY", w.memory)}${RESET}${bold}ACTIVITY${RESET} ${colors.purple}${box.vertical}${RESET}`;
1652
- writeLine(header);
1653
- writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
1654
- for (const squadName of squads) {
1655
- const agents = listAgents(squadsDir, squadName);
1656
- let memoryStatus = `${colors.dim}none${RESET}`;
1657
- let lastActivity = `${colors.dim}\u2014${RESET}`;
1658
- let activityColor = colors.dim;
1659
- if (memoryDir) {
1660
- const squadMemoryPath = join6(memoryDir, squadName);
1661
- if (existsSync6(squadMemoryPath)) {
1662
- const states = getSquadState(squadName);
1663
- memoryStatus = `${colors.green}${states.length} entries${RESET}`;
1664
- let mostRecent = 0;
1665
- for (const state of states) {
1666
- const stat = statSync(state.path);
1667
- if (stat.mtimeMs > mostRecent) {
1668
- mostRecent = stat.mtimeMs;
1669
- }
1670
- }
1671
- if (mostRecent > 0) {
1672
- const daysAgo = Math.floor((Date.now() - mostRecent) / (1e3 * 60 * 60 * 24));
1673
- if (daysAgo === 0) {
1674
- lastActivity = "today";
1675
- activityColor = colors.green;
1676
- } else if (daysAgo === 1) {
1677
- lastActivity = "yesterday";
1678
- activityColor = colors.green;
1679
- } else if (daysAgo < 7) {
1680
- lastActivity = `${daysAgo}d ago`;
1681
- activityColor = colors.yellow;
1682
- } else {
1683
- lastActivity = `${daysAgo}d ago`;
1684
- activityColor = colors.dim;
1685
- }
1694
+ function getSessionsDir() {
1695
+ const baseDir = getSessionsBaseDir();
1696
+ if (!baseDir) return null;
1697
+ const activeDir = join6(baseDir, ACTIVE_DIR);
1698
+ if (!existsSync6(activeDir)) {
1699
+ mkdirSync4(activeDir, { recursive: true });
1700
+ }
1701
+ return activeDir;
1702
+ }
1703
+ function getHistoryFilePath() {
1704
+ const baseDir = getSessionsBaseDir();
1705
+ if (!baseDir) return null;
1706
+ return join6(baseDir, HISTORY_FILE);
1707
+ }
1708
+ function appendEvent(event) {
1709
+ const historyPath = getHistoryFilePath();
1710
+ if (!historyPath) return;
1711
+ try {
1712
+ const line = JSON.stringify(event) + "\n";
1713
+ appendFileSync(historyPath, line);
1714
+ } catch {
1715
+ }
1716
+ }
1717
+ function detectSquad(cwd = process.cwd()) {
1718
+ const match = cwd.match(/agents-squads\/([^/]+)/);
1719
+ if (match) {
1720
+ const dir = match[1];
1721
+ return SQUAD_DIR_MAP[dir] || dir;
1722
+ }
1723
+ return null;
1724
+ }
1725
+ function detectAIProcesses() {
1726
+ const processes = [];
1727
+ try {
1728
+ const psOutput = execSync2("ps -eo pid,tty,comm 2>/dev/null", {
1729
+ encoding: "utf-8",
1730
+ timeout: 5e3
1731
+ }).trim();
1732
+ if (!psOutput) return [];
1733
+ const lines = psOutput.split("\n").filter((line) => line.trim());
1734
+ for (const line of lines) {
1735
+ const parts = line.trim().split(/\s+/);
1736
+ if (parts.length < 3) continue;
1737
+ const pid = parseInt(parts[0], 10);
1738
+ const tty = parts[1];
1739
+ const comm = parts.slice(2).join(" ");
1740
+ if (isNaN(pid)) continue;
1741
+ let toolName = null;
1742
+ for (const { pattern, name } of AI_TOOL_PATTERNS) {
1743
+ if (pattern.test(comm)) {
1744
+ toolName = name;
1745
+ break;
1686
1746
  }
1687
1747
  }
1748
+ if (!toolName) continue;
1749
+ let cwd = "";
1750
+ try {
1751
+ const lsofOutput = execSync2(`lsof -p ${pid} 2>/dev/null | grep cwd | awk '{print $NF}'`, {
1752
+ encoding: "utf-8",
1753
+ timeout: 3e3
1754
+ }).trim();
1755
+ cwd = lsofOutput || "";
1756
+ } catch {
1757
+ cwd = "";
1758
+ }
1759
+ const squad = detectSquad(cwd);
1760
+ processes.push({
1761
+ pid,
1762
+ tty,
1763
+ cwd,
1764
+ squad,
1765
+ tool: toolName
1766
+ });
1688
1767
  }
1689
- const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squadName, w.name)}${RESET}${padEnd(String(agents.length), w.agents)}${padEnd(memoryStatus, w.memory)}${padEnd(`${activityColor}${lastActivity}${RESET}`, w.activity)}${colors.purple}${box.vertical}${RESET}`;
1690
- writeLine(row);
1768
+ } catch {
1691
1769
  }
1692
- writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
1693
- writeLine();
1694
- writeLine(` ${colors.dim}$${RESET} squads status ${colors.cyan}<squad>${RESET} ${colors.dim}Squad details${RESET}`);
1695
- writeLine(` ${colors.dim}$${RESET} squads dash ${colors.dim}Full dashboard${RESET}`);
1696
- writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}<squad>${RESET} ${colors.dim}Execute a squad${RESET}`);
1697
- writeLine();
1770
+ return processes;
1698
1771
  }
1699
- async function showSquadStatus(squadName, squadsDir, options) {
1700
- const squad = loadSquad(squadName);
1701
- if (!squad) {
1702
- writeLine(`${colors.red}Squad "${squadName}" not found.${RESET}`);
1703
- process.exit(1);
1772
+ function getLiveSessionSummary() {
1773
+ const processes = detectAIProcesses();
1774
+ const bySquad = {};
1775
+ const byTool = {};
1776
+ for (const proc of processes) {
1777
+ const squad = proc.squad || "unknown";
1778
+ bySquad[squad] = (bySquad[squad] || 0) + 1;
1779
+ byTool[proc.tool] = (byTool[proc.tool] || 0) + 1;
1704
1780
  }
1705
- writeLine();
1706
- writeLine(` ${gradient("squads")} ${colors.dim}status${RESET} ${colors.cyan}${squad.name}${RESET}`);
1707
- writeLine();
1708
- if (squad.mission) {
1709
- writeLine(` ${colors.dim}${squad.mission}${RESET}`);
1710
- writeLine();
1781
+ return {
1782
+ totalSessions: processes.length,
1783
+ bySquad,
1784
+ squadCount: Object.keys(bySquad).length,
1785
+ byTool
1786
+ };
1787
+ }
1788
+ function generateSessionId() {
1789
+ return randomBytes(8).toString("hex");
1790
+ }
1791
+ var currentSessionId = null;
1792
+ function getSessionId() {
1793
+ if (currentSessionId) return currentSessionId;
1794
+ const sessionsDir = getSessionsDir();
1795
+ if (sessionsDir) {
1796
+ const pid = process.pid;
1797
+ const files = readdirSync3(sessionsDir).filter((f) => f.endsWith(".json"));
1798
+ for (const file of files) {
1799
+ try {
1800
+ const content = readFileSync5(join6(sessionsDir, file), "utf-8");
1801
+ const session2 = JSON.parse(content);
1802
+ if (session2.pid === pid) {
1803
+ currentSessionId = session2.sessionId;
1804
+ return currentSessionId;
1805
+ }
1806
+ } catch {
1807
+ }
1808
+ }
1711
1809
  }
1712
- const agents = listAgents(squadsDir, squadName);
1713
- const w = { name: 24, role: 36 };
1714
- const tableWidth = w.name + w.role + 4;
1715
- writeLine(` ${bold}Agents${RESET} ${colors.dim}(${agents.length})${RESET}`);
1716
- writeLine();
1717
- writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
1718
- for (const agent of agents) {
1719
- const status = agent.status?.toLowerCase() === "active" ? icons.active : icons.pending;
1720
- const role = options.verbose && agent.role ? `${colors.dim}${agent.role.substring(0, w.role - 2)}${RESET}` : "";
1721
- const row = ` ${colors.purple}${box.vertical}${RESET} ${status} ${padEnd(agent.name, w.name - 2)}${padEnd(role, w.role)}${colors.purple}${box.vertical}${RESET}`;
1722
- writeLine(row);
1810
+ currentSessionId = generateSessionId();
1811
+ return currentSessionId;
1812
+ }
1813
+ function startSession(squad) {
1814
+ const sessionsDir = getSessionsDir();
1815
+ if (!sessionsDir) return null;
1816
+ const sessionId = getSessionId();
1817
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1818
+ const cwd = process.cwd();
1819
+ const detectedSquad = squad || detectSquad(cwd);
1820
+ const session2 = {
1821
+ sessionId,
1822
+ squad: detectedSquad,
1823
+ startedAt: now,
1824
+ lastHeartbeat: now,
1825
+ cwd,
1826
+ pid: process.pid
1827
+ };
1828
+ const sessionPath = join6(sessionsDir, `${sessionId}.json`);
1829
+ writeFileSync5(sessionPath, JSON.stringify(session2, null, 2));
1830
+ appendEvent({
1831
+ type: "start",
1832
+ sessionId,
1833
+ squad: detectedSquad,
1834
+ ts: now,
1835
+ cwd,
1836
+ pid: process.pid
1837
+ });
1838
+ return session2;
1839
+ }
1840
+ function updateHeartbeat() {
1841
+ const sessionsDir = getSessionsDir();
1842
+ if (!sessionsDir) return false;
1843
+ const sessionId = getSessionId();
1844
+ const sessionPath = join6(sessionsDir, `${sessionId}.json`);
1845
+ if (!existsSync6(sessionPath)) {
1846
+ startSession();
1847
+ return true;
1723
1848
  }
1724
- writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
1725
- if (squad.pipelines.length > 0) {
1726
- writeLine();
1727
- writeLine(` ${bold}Pipelines${RESET}`);
1728
- for (const pipeline of squad.pipelines) {
1729
- writeLine(` ${colors.dim}${pipeline.agents.join(" \u2192 ")}${RESET}`);
1849
+ try {
1850
+ const content = readFileSync5(sessionPath, "utf-8");
1851
+ const session2 = JSON.parse(content);
1852
+ session2.lastHeartbeat = (/* @__PURE__ */ new Date()).toISOString();
1853
+ writeFileSync5(sessionPath, JSON.stringify(session2, null, 2));
1854
+ return true;
1855
+ } catch {
1856
+ return false;
1857
+ }
1858
+ }
1859
+ function stopSession() {
1860
+ const sessionsDir = getSessionsDir();
1861
+ if (!sessionsDir) return false;
1862
+ const sessionId = getSessionId();
1863
+ const sessionPath = join6(sessionsDir, `${sessionId}.json`);
1864
+ if (existsSync6(sessionPath)) {
1865
+ let squad = null;
1866
+ let durationMs;
1867
+ try {
1868
+ const content = readFileSync5(sessionPath, "utf-8");
1869
+ const session2 = JSON.parse(content);
1870
+ squad = session2.squad;
1871
+ durationMs = Date.now() - new Date(session2.startedAt).getTime();
1872
+ } catch {
1730
1873
  }
1874
+ unlinkSync(sessionPath);
1875
+ currentSessionId = null;
1876
+ appendEvent({
1877
+ type: "stop",
1878
+ sessionId,
1879
+ squad,
1880
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1881
+ durationMs
1882
+ });
1883
+ return true;
1731
1884
  }
1732
- const memoryDir = findMemoryDir();
1733
- if (memoryDir) {
1734
- const states = getSquadState(squadName);
1735
- if (states.length > 0) {
1736
- writeLine();
1737
- writeLine(` ${bold}Memory${RESET} ${colors.dim}(${states.length} entries)${RESET}`);
1738
- writeLine();
1739
- for (const state of states) {
1740
- const updated = state.content.match(/Updated:\s*(\S+)/)?.[1] || "unknown";
1741
- writeLine(` ${icons.progress} ${colors.white}${state.agent}${RESET}`);
1742
- writeLine(` ${colors.dim}\u2514 updated: ${updated}${RESET}`);
1743
- if (options.verbose) {
1744
- const signalsMatch = state.content.match(/## Active Signals([\s\S]*?)(?=##|$)/);
1745
- if (signalsMatch) {
1746
- const signalLines = signalsMatch[1].split("\n").filter((l) => l.match(/^\d+\./)).slice(0, 3);
1747
- for (const sig of signalLines) {
1748
- writeLine(` ${colors.dim} ${sig.trim()}${RESET}`);
1749
- }
1750
- }
1885
+ return false;
1886
+ }
1887
+ function getActiveSessions() {
1888
+ const sessionsDir = getSessionsDir();
1889
+ if (!sessionsDir) return [];
1890
+ const now = Date.now();
1891
+ const sessions2 = [];
1892
+ try {
1893
+ const files = readdirSync3(sessionsDir).filter((f) => f.endsWith(".json"));
1894
+ for (const file of files) {
1895
+ try {
1896
+ const filePath = join6(sessionsDir, file);
1897
+ const content = readFileSync5(filePath, "utf-8");
1898
+ const session2 = JSON.parse(content);
1899
+ const lastHeartbeat = new Date(session2.lastHeartbeat).getTime();
1900
+ if (now - lastHeartbeat < STALE_THRESHOLD_MS) {
1901
+ sessions2.push(session2);
1751
1902
  }
1903
+ } catch {
1752
1904
  }
1753
1905
  }
1906
+ } catch {
1754
1907
  }
1755
- writeLine();
1908
+ return sessions2;
1909
+ }
1910
+ function getSessionSummary() {
1911
+ const sessions2 = getActiveSessions();
1912
+ const bySquad = {};
1913
+ for (const session2 of sessions2) {
1914
+ const squad = session2.squad || "unknown";
1915
+ bySquad[squad] = (bySquad[squad] || 0) + 1;
1916
+ }
1917
+ return {
1918
+ totalSessions: sessions2.length,
1919
+ bySquad,
1920
+ squadCount: Object.keys(bySquad).length
1921
+ };
1922
+ }
1923
+ function cleanupStaleSessions() {
1924
+ const sessionsDir = getSessionsDir();
1925
+ if (!sessionsDir) return 0;
1926
+ const now = Date.now();
1927
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
1928
+ let cleaned = 0;
1929
+ try {
1930
+ const files = readdirSync3(sessionsDir).filter((f) => f.endsWith(".json"));
1931
+ for (const file of files) {
1932
+ try {
1933
+ const filePath = join6(sessionsDir, file);
1934
+ const content = readFileSync5(filePath, "utf-8");
1935
+ const session2 = JSON.parse(content);
1936
+ const lastHeartbeat = new Date(session2.lastHeartbeat).getTime();
1937
+ if (now - lastHeartbeat >= STALE_THRESHOLD_MS) {
1938
+ const durationMs = now - new Date(session2.startedAt).getTime();
1939
+ unlinkSync(filePath);
1940
+ cleaned++;
1941
+ appendEvent({
1942
+ type: "stale_cleanup",
1943
+ sessionId: session2.sessionId,
1944
+ squad: session2.squad,
1945
+ ts: nowIso,
1946
+ durationMs
1947
+ });
1948
+ }
1949
+ } catch {
1950
+ try {
1951
+ unlinkSync(join6(sessionsDir, file));
1952
+ cleaned++;
1953
+ } catch {
1954
+ }
1955
+ }
1956
+ }
1957
+ } catch {
1958
+ }
1959
+ return cleaned;
1960
+ }
1961
+ async function readSessionHistory(options = {}) {
1962
+ const historyPath = getHistoryFilePath();
1963
+ if (!historyPath || !existsSync6(historyPath)) return [];
1964
+ const events = [];
1965
+ const sinceMs = options.since?.getTime() || 0;
1966
+ const untilMs = options.until?.getTime() || Date.now();
1967
+ return new Promise((resolve) => {
1968
+ const rl = createInterface({
1969
+ input: createReadStream(historyPath),
1970
+ crlfDelay: Infinity
1971
+ });
1972
+ rl.on("line", (line) => {
1973
+ if (!line.trim()) return;
1974
+ try {
1975
+ const event = JSON.parse(line);
1976
+ const eventMs = new Date(event.ts).getTime();
1977
+ if (eventMs < sinceMs || eventMs > untilMs) return;
1978
+ if (options.squad && event.squad !== options.squad) return;
1979
+ if (options.type && event.type !== options.type) return;
1980
+ events.push(event);
1981
+ } catch {
1982
+ }
1983
+ });
1984
+ rl.on("close", () => {
1985
+ if (options.limit && events.length > options.limit) {
1986
+ resolve(events.slice(-options.limit));
1987
+ } else {
1988
+ resolve(events);
1989
+ }
1990
+ });
1991
+ rl.on("error", () => {
1992
+ resolve([]);
1993
+ });
1994
+ });
1995
+ }
1996
+ async function getSessionHistoryStats(options = {}) {
1997
+ const events = await readSessionHistory(options);
1998
+ const stats = {
1999
+ totalSessions: 0,
2000
+ totalDurationMs: 0,
2001
+ avgDurationMs: 0,
2002
+ bySquad: {},
2003
+ byDate: {},
2004
+ peakConcurrent: 0
2005
+ };
2006
+ const activeSessions = /* @__PURE__ */ new Map();
2007
+ let currentConcurrent = 0;
2008
+ for (const event of events) {
2009
+ const squad = event.squad || "unknown";
2010
+ const date = event.ts.split("T")[0];
2011
+ if (event.type === "start") {
2012
+ stats.totalSessions++;
2013
+ stats.byDate[date] = (stats.byDate[date] || 0) + 1;
2014
+ if (!stats.bySquad[squad]) {
2015
+ stats.bySquad[squad] = { count: 0, durationMs: 0 };
2016
+ }
2017
+ stats.bySquad[squad].count++;
2018
+ activeSessions.set(event.sessionId, {
2019
+ squad: event.squad,
2020
+ startTs: new Date(event.ts).getTime()
2021
+ });
2022
+ currentConcurrent++;
2023
+ stats.peakConcurrent = Math.max(stats.peakConcurrent, currentConcurrent);
2024
+ }
2025
+ if (event.type === "stop" || event.type === "stale_cleanup") {
2026
+ const duration = event.durationMs || 0;
2027
+ stats.totalDurationMs += duration;
2028
+ if (stats.bySquad[squad]) {
2029
+ stats.bySquad[squad].durationMs += duration;
2030
+ }
2031
+ if (activeSessions.has(event.sessionId)) {
2032
+ activeSessions.delete(event.sessionId);
2033
+ currentConcurrent = Math.max(0, currentConcurrent - 1);
2034
+ }
2035
+ }
2036
+ }
2037
+ const completedSessions = events.filter((e) => e.type === "stop" || e.type === "stale_cleanup").length;
2038
+ if (completedSessions > 0) {
2039
+ stats.avgDurationMs = Math.round(stats.totalDurationMs / completedSessions);
2040
+ }
2041
+ return stats;
2042
+ }
2043
+ async function getRecentSessions(limit = 20) {
2044
+ const events = await readSessionHistory({ limit: limit * 3 });
2045
+ const sessionEvents = /* @__PURE__ */ new Map();
2046
+ for (const event of events) {
2047
+ if (!sessionEvents.has(event.sessionId)) {
2048
+ sessionEvents.set(event.sessionId, []);
2049
+ }
2050
+ sessionEvents.get(event.sessionId).push(event);
2051
+ }
2052
+ const startEvents = events.filter((e) => e.type === "start").slice(-limit);
2053
+ return startEvents.reverse();
2054
+ }
2055
+
2056
+ // src/lib/update.ts
2057
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync2 } from "fs";
2058
+ import { join as join7, dirname as dirname4 } from "path";
2059
+ import { homedir as homedir2 } from "os";
2060
+ import { execSync as execSync3 } from "child_process";
2061
+ import { fileURLToPath } from "url";
2062
+ function getPackageVersion() {
2063
+ try {
2064
+ const __filename2 = fileURLToPath(import.meta.url);
2065
+ const __dirname2 = dirname4(__filename2);
2066
+ const possiblePaths = [
2067
+ join7(__dirname2, "..", "..", "package.json"),
2068
+ // From dist/lib/
2069
+ join7(__dirname2, "..", "package.json"),
2070
+ // From dist/
2071
+ join7(__dirname2, "package.json")
2072
+ // Same dir
2073
+ ];
2074
+ for (const pkgPath of possiblePaths) {
2075
+ if (existsSync7(pkgPath)) {
2076
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
2077
+ return pkg.version || "0.0.0";
2078
+ }
2079
+ }
2080
+ } catch {
2081
+ }
2082
+ return "0.0.0";
2083
+ }
2084
+ var CURRENT_VERSION = getPackageVersion();
2085
+ var CACHE_DIR = join7(homedir2(), ".squads");
2086
+ var CACHE_FILE = join7(CACHE_DIR, "update-check.json");
2087
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
2088
+ function isNewerVersion(v1, v2) {
2089
+ const parts1 = v1.replace(/^v/, "").split(".").map(Number);
2090
+ const parts2 = v2.replace(/^v/, "").split(".").map(Number);
2091
+ for (let i = 0; i < 3; i++) {
2092
+ const p1 = parts1[i] || 0;
2093
+ const p2 = parts2[i] || 0;
2094
+ if (p2 > p1) return true;
2095
+ if (p2 < p1) return false;
2096
+ }
2097
+ return false;
2098
+ }
2099
+ function readCache() {
2100
+ try {
2101
+ if (!existsSync7(CACHE_FILE)) return null;
2102
+ const data = JSON.parse(readFileSync6(CACHE_FILE, "utf-8"));
2103
+ return data;
2104
+ } catch {
2105
+ return null;
2106
+ }
2107
+ }
2108
+ function writeCache(latestVersion) {
2109
+ try {
2110
+ if (!existsSync7(CACHE_DIR)) {
2111
+ mkdirSync5(CACHE_DIR, { recursive: true });
2112
+ }
2113
+ const cache = {
2114
+ latestVersion,
2115
+ checkedAt: Date.now()
2116
+ };
2117
+ writeFileSync6(CACHE_FILE, JSON.stringify(cache, null, 2));
2118
+ } catch {
2119
+ }
2120
+ }
2121
+ function fetchLatestVersion() {
2122
+ try {
2123
+ const result = execSync3("npm view squads-cli version 2>/dev/null", {
2124
+ encoding: "utf-8",
2125
+ timeout: 5e3
2126
+ }).trim();
2127
+ return result || null;
2128
+ } catch {
2129
+ return null;
2130
+ }
2131
+ }
2132
+ function checkForUpdate() {
2133
+ const result = {
2134
+ currentVersion: CURRENT_VERSION,
2135
+ latestVersion: CURRENT_VERSION,
2136
+ updateAvailable: false
2137
+ };
2138
+ const cache = readCache();
2139
+ const now = Date.now();
2140
+ if (cache && now - cache.checkedAt < CACHE_TTL_MS) {
2141
+ result.latestVersion = cache.latestVersion;
2142
+ result.updateAvailable = isNewerVersion(CURRENT_VERSION, cache.latestVersion);
2143
+ return result;
2144
+ }
2145
+ const latestVersion = fetchLatestVersion();
2146
+ if (latestVersion) {
2147
+ writeCache(latestVersion);
2148
+ result.latestVersion = latestVersion;
2149
+ result.updateAvailable = isNewerVersion(CURRENT_VERSION, latestVersion);
2150
+ } else if (cache) {
2151
+ result.latestVersion = cache.latestVersion;
2152
+ result.updateAvailable = isNewerVersion(CURRENT_VERSION, cache.latestVersion);
2153
+ }
2154
+ return result;
2155
+ }
2156
+ function performUpdate() {
2157
+ try {
2158
+ execSync3("npm update -g squads-cli", {
2159
+ encoding: "utf-8",
2160
+ stdio: "inherit",
2161
+ timeout: 12e4
2162
+ // 2 minutes
2163
+ });
2164
+ try {
2165
+ unlinkSync2(CACHE_FILE);
2166
+ } catch {
2167
+ }
2168
+ return { success: true };
2169
+ } catch (err) {
2170
+ return {
2171
+ success: false,
2172
+ error: err instanceof Error ? err.message : "Unknown error"
2173
+ };
2174
+ }
2175
+ }
2176
+ function refreshVersionCache() {
2177
+ const latestVersion = fetchLatestVersion();
2178
+ if (latestVersion) {
2179
+ writeCache(latestVersion);
2180
+ return {
2181
+ currentVersion: CURRENT_VERSION,
2182
+ latestVersion,
2183
+ updateAvailable: isNewerVersion(CURRENT_VERSION, latestVersion)
2184
+ };
2185
+ }
2186
+ return checkForUpdate();
2187
+ }
2188
+
2189
+ // src/commands/status.ts
2190
+ async function statusCommand(squadName, options = {}) {
2191
+ const squadsDir = findSquadsDir();
2192
+ if (!squadsDir) {
2193
+ writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
2194
+ writeLine(`${colors.dim}Run \`squads init\` to create one.${RESET}`);
2195
+ process.exit(1);
2196
+ }
2197
+ if (squadName) {
2198
+ await showSquadStatus(squadName, squadsDir, options);
2199
+ } else {
2200
+ await showOverallStatus(squadsDir, options);
2201
+ }
2202
+ }
2203
+ async function showOverallStatus(squadsDir, _options) {
2204
+ const squads = listSquads(squadsDir);
2205
+ const memoryDir = findMemoryDir();
2206
+ cleanupStaleSessions();
2207
+ const sessionSummary = getLiveSessionSummary();
2208
+ writeLine();
2209
+ writeLine(` ${gradient("squads")} ${colors.dim}status${RESET}`);
2210
+ const updateInfo = checkForUpdate();
2211
+ if (updateInfo.updateAvailable) {
2212
+ writeLine(` ${colors.cyan}\u2B06${RESET} Update available: ${colors.dim}${updateInfo.currentVersion}${RESET} \u2192 ${colors.green}${updateInfo.latestVersion}${RESET} ${colors.dim}(run \`squads update\`)${RESET}`);
2213
+ }
2214
+ if (sessionSummary.totalSessions > 0) {
2215
+ const sessionText = sessionSummary.totalSessions === 1 ? "session" : "sessions";
2216
+ const squadText = sessionSummary.squadCount === 1 ? "squad" : "squads";
2217
+ let toolInfo = "";
2218
+ if (sessionSummary.byTool && Object.keys(sessionSummary.byTool).length > 0) {
2219
+ const toolParts = Object.entries(sessionSummary.byTool).sort((a, b) => b[1] - a[1]).map(([tool, count]) => `${colors.dim}${tool}${RESET} ${colors.cyan}${count}${RESET}`);
2220
+ toolInfo = ` ${colors.dim}(${RESET}${toolParts.join(` ${colors.dim}\xB7${RESET} `)}${colors.dim})${RESET}`;
2221
+ }
2222
+ writeLine(` ${colors.green}${icons.active}${RESET} ${colors.white}${sessionSummary.totalSessions}${RESET} active ${sessionText} ${colors.dim}across${RESET} ${colors.cyan}${sessionSummary.squadCount}${RESET} ${squadText}${toolInfo}`);
2223
+ }
2224
+ writeLine();
2225
+ const totalSquads = squads.length;
2226
+ const activeCount = squads.length;
2227
+ writeLine(` ${colors.cyan}${activeCount}${RESET}/${totalSquads} squads ${colors.dim}\u2502${RESET} ${colors.dim}memory: ${memoryDir ? "enabled" : "none"}${RESET}`);
2228
+ writeLine();
2229
+ const w = { name: 16, agents: 8, memory: 14, activity: 12 };
2230
+ const tableWidth = w.name + w.agents + w.memory + w.activity + 6;
2231
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
2232
+ const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.name)}${RESET}${bold}${padEnd("AGENTS", w.agents)}${RESET}${bold}${padEnd("MEMORY", w.memory)}${RESET}${bold}ACTIVITY${RESET} ${colors.purple}${box.vertical}${RESET}`;
2233
+ writeLine(header);
2234
+ writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
2235
+ for (const squadName of squads) {
2236
+ const agents = listAgents(squadsDir, squadName);
2237
+ let memoryStatus = `${colors.dim}none${RESET}`;
2238
+ let lastActivity = `${colors.dim}\u2014${RESET}`;
2239
+ let activityColor = colors.dim;
2240
+ if (memoryDir) {
2241
+ const squadMemoryPath = join8(memoryDir, squadName);
2242
+ if (existsSync8(squadMemoryPath)) {
2243
+ const states = getSquadState(squadName);
2244
+ memoryStatus = `${colors.green}${states.length} ${states.length === 1 ? "entry" : "entries"}${RESET}`;
2245
+ let mostRecent = 0;
2246
+ for (const state of states) {
2247
+ const stat = statSync(state.path);
2248
+ if (stat.mtimeMs > mostRecent) {
2249
+ mostRecent = stat.mtimeMs;
2250
+ }
2251
+ }
2252
+ if (mostRecent > 0) {
2253
+ const daysAgo = Math.floor((Date.now() - mostRecent) / (1e3 * 60 * 60 * 24));
2254
+ if (daysAgo === 0) {
2255
+ lastActivity = "today";
2256
+ activityColor = colors.green;
2257
+ } else if (daysAgo === 1) {
2258
+ lastActivity = "yesterday";
2259
+ activityColor = colors.green;
2260
+ } else if (daysAgo < 7) {
2261
+ lastActivity = `${daysAgo}d ago`;
2262
+ activityColor = colors.yellow;
2263
+ } else {
2264
+ lastActivity = `${daysAgo}d ago`;
2265
+ activityColor = colors.dim;
2266
+ }
2267
+ }
2268
+ }
2269
+ }
2270
+ const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squadName, w.name)}${RESET}${padEnd(String(agents.length), w.agents)}${padEnd(memoryStatus, w.memory)}${padEnd(`${activityColor}${lastActivity}${RESET}`, w.activity)}${colors.purple}${box.vertical}${RESET}`;
2271
+ writeLine(row);
2272
+ }
2273
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
2274
+ writeLine();
2275
+ writeLine(` ${colors.dim}$${RESET} squads status ${colors.cyan}<squad>${RESET} ${colors.dim}Squad details${RESET}`);
2276
+ writeLine(` ${colors.dim}$${RESET} squads dash ${colors.dim}Full dashboard${RESET}`);
2277
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}<squad>${RESET} ${colors.dim}Execute a squad${RESET}`);
2278
+ writeLine();
2279
+ }
2280
+ async function showSquadStatus(squadName, squadsDir, options) {
2281
+ const squad = loadSquad(squadName);
2282
+ if (!squad) {
2283
+ writeLine(`${colors.red}Squad "${squadName}" not found.${RESET}`);
2284
+ process.exit(1);
2285
+ }
2286
+ writeLine();
2287
+ writeLine(` ${gradient("squads")} ${colors.dim}status${RESET} ${colors.cyan}${squad.name}${RESET}`);
2288
+ writeLine();
2289
+ if (squad.mission) {
2290
+ writeLine(` ${colors.dim}${squad.mission}${RESET}`);
2291
+ writeLine();
2292
+ }
2293
+ const agents = listAgents(squadsDir, squadName);
2294
+ const w = { name: 24, role: 36 };
2295
+ const tableWidth = w.name + w.role + 4;
2296
+ writeLine(` ${bold}Agents${RESET} ${colors.dim}(${agents.length})${RESET}`);
2297
+ writeLine();
2298
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
2299
+ for (const agent of agents) {
2300
+ const status = agent.status?.toLowerCase() === "active" ? icons.active : icons.pending;
2301
+ const role = options.verbose && agent.role ? `${colors.dim}${agent.role.substring(0, w.role - 2)}${RESET}` : "";
2302
+ const row = ` ${colors.purple}${box.vertical}${RESET} ${status} ${padEnd(agent.name, w.name - 2)}${padEnd(role, w.role)}${colors.purple}${box.vertical}${RESET}`;
2303
+ writeLine(row);
2304
+ }
2305
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
2306
+ if (squad.pipelines.length > 0) {
2307
+ writeLine();
2308
+ writeLine(` ${bold}Pipelines${RESET}`);
2309
+ for (const pipeline of squad.pipelines) {
2310
+ writeLine(` ${colors.dim}${pipeline.agents.join(" \u2192 ")}${RESET}`);
2311
+ }
2312
+ }
2313
+ const memoryDir = findMemoryDir();
2314
+ if (memoryDir) {
2315
+ const states = getSquadState(squadName);
2316
+ if (states.length > 0) {
2317
+ writeLine();
2318
+ writeLine(` ${bold}Memory${RESET} ${colors.dim}(${states.length} ${states.length === 1 ? "entry" : "entries"})${RESET}`);
2319
+ writeLine();
2320
+ for (const state of states) {
2321
+ const updated = state.content.match(/Updated:\s*(\S+)/)?.[1] || "unknown";
2322
+ writeLine(` ${icons.progress} ${colors.white}${state.agent}${RESET}`);
2323
+ writeLine(` ${colors.dim}\u2514 updated: ${updated}${RESET}`);
2324
+ if (options.verbose) {
2325
+ const signalsMatch = state.content.match(/## Active Signals([\s\S]*?)(?=##|$)/);
2326
+ if (signalsMatch) {
2327
+ const signalLines = signalsMatch[1].split("\n").filter((l) => l.match(/^\d+\./)).slice(0, 3);
2328
+ for (const sig of signalLines) {
2329
+ writeLine(` ${colors.dim} ${sig.trim()}${RESET}`);
2330
+ }
2331
+ }
2332
+ }
2333
+ }
2334
+ }
2335
+ }
2336
+ writeLine();
1756
2337
  writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} ${colors.dim}Run the squad${RESET}`);
1757
2338
  writeLine(` ${colors.dim}$${RESET} squads memory show ${colors.cyan}${squadName}${RESET} ${colors.dim}View full memory${RESET}`);
1758
2339
  writeLine(` ${colors.dim}$${RESET} squads status ${colors.cyan}${squadName}${RESET} -v ${colors.dim}Verbose status${RESET}`);
@@ -1997,9 +2578,9 @@ async function memorySearchCommand(query, options = {}) {
1997
2578
  }
1998
2579
 
1999
2580
  // src/commands/sync.ts
2000
- import { execSync as execSync2 } from "child_process";
2001
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
2002
- import { join as join7 } from "path";
2581
+ import { execSync as execSync4 } from "child_process";
2582
+ import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, readdirSync as readdirSync4 } from "fs";
2583
+ import { join as join9 } from "path";
2003
2584
  var PATH_TO_SQUAD = {
2004
2585
  "squads-cli": "product",
2005
2586
  "agents-squads-web": "website",
@@ -2027,21 +2608,21 @@ var MESSAGE_TO_SQUAD = {
2027
2608
  "infra": "engineering"
2028
2609
  };
2029
2610
  function getLastSyncTime(memoryDir) {
2030
- const syncFile = join7(memoryDir, ".last-sync");
2031
- if (existsSync7(syncFile)) {
2032
- return readFileSync5(syncFile, "utf-8").trim();
2611
+ const syncFile = join9(memoryDir, ".last-sync");
2612
+ if (existsSync9(syncFile)) {
2613
+ return readFileSync7(syncFile, "utf-8").trim();
2033
2614
  }
2034
2615
  return null;
2035
2616
  }
2036
2617
  function updateLastSyncTime(memoryDir) {
2037
- const syncFile = join7(memoryDir, ".last-sync");
2038
- writeFileSync5(syncFile, (/* @__PURE__ */ new Date()).toISOString());
2618
+ const syncFile = join9(memoryDir, ".last-sync");
2619
+ writeFileSync7(syncFile, (/* @__PURE__ */ new Date()).toISOString());
2039
2620
  }
2040
2621
  function getRecentCommits(since) {
2041
2622
  const commits = [];
2042
2623
  try {
2043
2624
  const sinceArg = since ? `--since="${since}"` : "-n 20";
2044
- const logOutput = execSync2(
2625
+ const logOutput = execSync4(
2045
2626
  `git log ${sinceArg} --format="%H|%aI|%s" --name-only`,
2046
2627
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
2047
2628
  ).trim();
@@ -2107,22 +2688,22 @@ ${messages}
2107
2688
  `;
2108
2689
  }
2109
2690
  function appendToSquadMemory(memoryDir, squad, summary) {
2110
- const squadMemoryDir = join7(memoryDir, squad);
2111
- if (!existsSync7(squadMemoryDir)) {
2112
- mkdirSync4(squadMemoryDir, { recursive: true });
2691
+ const squadMemoryDir = join9(memoryDir, squad);
2692
+ if (!existsSync9(squadMemoryDir)) {
2693
+ mkdirSync6(squadMemoryDir, { recursive: true });
2113
2694
  }
2114
2695
  let agentDir;
2115
- const existingDirs = existsSync7(squadMemoryDir) ? readdirSync3(squadMemoryDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) : [];
2696
+ const existingDirs = existsSync9(squadMemoryDir) ? readdirSync4(squadMemoryDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) : [];
2116
2697
  if (existingDirs.length > 0) {
2117
- agentDir = join7(squadMemoryDir, existingDirs[0]);
2698
+ agentDir = join9(squadMemoryDir, existingDirs[0]);
2118
2699
  } else {
2119
- agentDir = join7(squadMemoryDir, `${squad}-lead`);
2120
- mkdirSync4(agentDir, { recursive: true });
2700
+ agentDir = join9(squadMemoryDir, `${squad}-lead`);
2701
+ mkdirSync6(agentDir, { recursive: true });
2121
2702
  }
2122
- const statePath = join7(agentDir, "state.md");
2703
+ const statePath = join9(agentDir, "state.md");
2123
2704
  let content = "";
2124
- if (existsSync7(statePath)) {
2125
- content = readFileSync5(statePath, "utf-8");
2705
+ if (existsSync9(statePath)) {
2706
+ content = readFileSync7(statePath, "utf-8");
2126
2707
  } else {
2127
2708
  content = `# ${squad} Squad - State
2128
2709
 
@@ -2134,9 +2715,53 @@ Updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
2134
2715
  `Updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
2135
2716
  );
2136
2717
  content += summary;
2137
- writeFileSync5(statePath, content);
2718
+ writeFileSync7(statePath, content);
2138
2719
  return true;
2139
2720
  }
2721
+ function gitPullMemory() {
2722
+ try {
2723
+ execSync4("git fetch origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
2724
+ const status = execSync4("git status -sb", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
2725
+ const behindMatch = status.match(/behind (\d+)/);
2726
+ const aheadMatch = status.match(/ahead (\d+)/);
2727
+ const behind = behindMatch ? parseInt(behindMatch[1]) : 0;
2728
+ const ahead = aheadMatch ? parseInt(aheadMatch[1]) : 0;
2729
+ if (behind === 0) {
2730
+ return { success: true, output: "Already up to date", behind: 0, ahead };
2731
+ }
2732
+ const output = execSync4("git pull --rebase origin main", {
2733
+ encoding: "utf-8",
2734
+ stdio: ["pipe", "pipe", "pipe"]
2735
+ });
2736
+ return { success: true, output: output.trim(), behind, ahead };
2737
+ } catch (error) {
2738
+ const err = error;
2739
+ return { success: false, output: err.message || "Pull failed", behind: 0, ahead: 0 };
2740
+ }
2741
+ }
2742
+ function gitPushMemory() {
2743
+ try {
2744
+ const status = execSync4("git status --porcelain .agents/memory/", {
2745
+ encoding: "utf-8",
2746
+ stdio: ["pipe", "pipe", "pipe"]
2747
+ }).trim();
2748
+ if (status) {
2749
+ execSync4("git add .agents/memory/", { stdio: ["pipe", "pipe", "pipe"] });
2750
+ execSync4('git commit -m "chore: sync squad memory"', {
2751
+ encoding: "utf-8",
2752
+ stdio: ["pipe", "pipe", "pipe"]
2753
+ });
2754
+ }
2755
+ const output = execSync4("git push origin main", {
2756
+ encoding: "utf-8",
2757
+ stdio: ["pipe", "pipe", "pipe"]
2758
+ });
2759
+ return { success: true, output: output.trim() || "Pushed successfully" };
2760
+ } catch (error) {
2761
+ const err = error;
2762
+ return { success: false, output: err.message || "Push failed" };
2763
+ }
2764
+ }
2140
2765
  async function syncCommand(options = {}) {
2141
2766
  const memoryDir = findMemoryDir();
2142
2767
  const squadsDir = findSquadsDir();
@@ -2148,6 +2773,25 @@ async function syncCommand(options = {}) {
2148
2773
  writeLine();
2149
2774
  writeLine(` ${gradient("squads")} ${colors.dim}memory sync${RESET}`);
2150
2775
  writeLine();
2776
+ const doPull = options.pull !== false;
2777
+ const doPush = options.push === true;
2778
+ if (doPull) {
2779
+ writeLine(` ${icons.progress} Pulling from remote...`);
2780
+ const pullResult = gitPullMemory();
2781
+ if (pullResult.success) {
2782
+ if (pullResult.behind > 0) {
2783
+ writeLine(` ${icons.success} Pulled ${colors.cyan}${pullResult.behind}${RESET} commits from remote`);
2784
+ } else {
2785
+ writeLine(` ${icons.success} ${colors.dim}Already up to date${RESET}`);
2786
+ }
2787
+ if (pullResult.ahead > 0) {
2788
+ writeLine(` ${colors.dim} ${pullResult.ahead} local commits to push${RESET}`);
2789
+ }
2790
+ } else {
2791
+ writeLine(` ${icons.error} ${colors.red}Pull failed: ${pullResult.output}${RESET}`);
2792
+ }
2793
+ writeLine();
2794
+ }
2151
2795
  const lastSync = getLastSyncTime(memoryDir);
2152
2796
  if (lastSync) {
2153
2797
  writeLine(` ${colors.dim}Last sync: ${lastSync.split("T")[0]}${RESET}`);
@@ -2189,8 +2833,21 @@ async function syncCommand(options = {}) {
2189
2833
  writeLine(` ${colors.green}${updated}${RESET} squad memories updated`);
2190
2834
  writeLine();
2191
2835
  updateLastSyncTime(memoryDir);
2836
+ if (doPush) {
2837
+ writeLine(` ${icons.progress} Pushing to remote...`);
2838
+ const pushResult = gitPushMemory();
2839
+ if (pushResult.success) {
2840
+ writeLine(` ${icons.success} ${colors.green}Pushed memory updates to remote${RESET}`);
2841
+ } else {
2842
+ writeLine(` ${icons.error} ${colors.red}Push failed: ${pushResult.output}${RESET}`);
2843
+ }
2844
+ writeLine();
2845
+ }
2192
2846
  writeLine(` ${colors.dim}$${RESET} squads memory show ${colors.cyan}<squad>${RESET} ${colors.dim}View updated memory${RESET}`);
2193
2847
  writeLine(` ${colors.dim}$${RESET} squads status ${colors.dim}See all squads${RESET}`);
2848
+ if (!doPush && updated > 0) {
2849
+ writeLine(` ${colors.dim}$${RESET} squads memory sync --push ${colors.dim}Push changes to remote${RESET}`);
2850
+ }
2194
2851
  writeLine();
2195
2852
  }
2196
2853
 
@@ -2318,28 +2975,28 @@ async function goalProgressCommand(squadName, goalIndex, progress2) {
2318
2975
  }
2319
2976
 
2320
2977
  // src/commands/feedback.ts
2321
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync4 } from "fs";
2322
- import { join as join8, dirname as dirname4 } from "path";
2978
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, existsSync as existsSync10, mkdirSync as mkdirSync7, readdirSync as readdirSync5 } from "fs";
2979
+ import { join as join10, dirname as dirname6 } from "path";
2323
2980
  function getFeedbackPath(squadName) {
2324
2981
  const memoryDir = findMemoryDir();
2325
2982
  if (!memoryDir) return null;
2326
2983
  const squad = loadSquad(squadName);
2327
2984
  const agentName = squad?.agents[0]?.name || `${squadName}-lead`;
2328
- return join8(memoryDir, squadName, agentName, "feedback.md");
2985
+ return join10(memoryDir, squadName, agentName, "feedback.md");
2329
2986
  }
2330
2987
  function getOutputPath(squadName) {
2331
2988
  const memoryDir = findMemoryDir();
2332
2989
  if (!memoryDir) return null;
2333
2990
  const squad = loadSquad(squadName);
2334
2991
  const agentName = squad?.agents[0]?.name || `${squadName}-lead`;
2335
- return join8(memoryDir, squadName, agentName, "output.md");
2992
+ return join10(memoryDir, squadName, agentName, "output.md");
2336
2993
  }
2337
2994
  function getLastExecution(squadName) {
2338
2995
  const outputPath = getOutputPath(squadName);
2339
- if (!outputPath || !existsSync8(outputPath)) {
2996
+ if (!outputPath || !existsSync10(outputPath)) {
2340
2997
  return null;
2341
2998
  }
2342
- const content = readFileSync6(outputPath, "utf-8");
2999
+ const content = readFileSync8(outputPath, "utf-8");
2343
3000
  const lines = content.split("\n");
2344
3001
  let date = "unknown";
2345
3002
  let summary = lines.slice(0, 5).join("\n");
@@ -2390,9 +3047,9 @@ async function feedbackAddCommand(squadName, rating, feedback2, options) {
2390
3047
  return;
2391
3048
  }
2392
3049
  const lastExec = getLastExecution(squadName);
2393
- const dir = dirname4(feedbackPath);
2394
- if (!existsSync8(dir)) {
2395
- mkdirSync5(dir, { recursive: true });
3050
+ const dir = dirname6(feedbackPath);
3051
+ if (!existsSync10(dir)) {
3052
+ mkdirSync7(dir, { recursive: true });
2396
3053
  }
2397
3054
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2398
3055
  let entry = `
@@ -2420,15 +3077,15 @@ _Date: ${date}_
2420
3077
  }
2421
3078
  }
2422
3079
  let existing = "";
2423
- if (existsSync8(feedbackPath)) {
2424
- existing = readFileSync6(feedbackPath, "utf-8");
3080
+ if (existsSync10(feedbackPath)) {
3081
+ existing = readFileSync8(feedbackPath, "utf-8");
2425
3082
  } else {
2426
3083
  existing = `# ${squadName} - Feedback Log
2427
3084
 
2428
3085
  > Execution feedback and learnings
2429
3086
  `;
2430
3087
  }
2431
- writeFileSync6(feedbackPath, existing + entry);
3088
+ writeFileSync8(feedbackPath, existing + entry);
2432
3089
  const stars = `${colors.yellow}${"\u2605".repeat(ratingNum)}${"\u2606".repeat(5 - ratingNum)}${RESET}`;
2433
3090
  writeLine();
2434
3091
  writeLine(` ${icons.success} Feedback recorded for ${colors.cyan}${squadName}${RESET}`);
@@ -2441,11 +3098,11 @@ _Date: ${date}_
2441
3098
  }
2442
3099
  async function feedbackShowCommand(squadName, options) {
2443
3100
  const feedbackPath = getFeedbackPath(squadName);
2444
- if (!feedbackPath || !existsSync8(feedbackPath)) {
3101
+ if (!feedbackPath || !existsSync10(feedbackPath)) {
2445
3102
  writeLine(` ${colors.yellow}No feedback recorded for ${squadName}${RESET}`);
2446
3103
  return;
2447
3104
  }
2448
- const content = readFileSync6(feedbackPath, "utf-8");
3105
+ const content = readFileSync8(feedbackPath, "utf-8");
2449
3106
  const entries = parseFeedbackHistory(content);
2450
3107
  const limit = options.limit ? parseInt(options.limit) : 5;
2451
3108
  const recent = entries.slice(-limit).reverse();
@@ -2480,7 +3137,7 @@ async function feedbackStatsCommand() {
2480
3137
  writeLine();
2481
3138
  writeLine(` ${gradient("squads")} ${colors.dim}feedback stats${RESET}`);
2482
3139
  writeLine();
2483
- const squads = readdirSync4(memoryDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3140
+ const squads = readdirSync5(memoryDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
2484
3141
  const w = { squad: 18, avg: 12, count: 8, trend: 6 };
2485
3142
  const tableWidth = w.squad + w.avg + w.count + w.trend + 4;
2486
3143
  writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
@@ -2489,10 +3146,10 @@ async function feedbackStatsCommand() {
2489
3146
  writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
2490
3147
  for (const squad of squads) {
2491
3148
  const feedbackPath = getFeedbackPath(squad);
2492
- if (!feedbackPath || !existsSync8(feedbackPath)) {
3149
+ if (!feedbackPath || !existsSync10(feedbackPath)) {
2493
3150
  continue;
2494
3151
  }
2495
- const content = readFileSync6(feedbackPath, "utf-8");
3152
+ const content = readFileSync8(feedbackPath, "utf-8");
2496
3153
  const entries = parseFeedbackHistory(content);
2497
3154
  if (entries.length === 0) continue;
2498
3155
  const avgRating = entries.reduce((sum, e) => sum + e.rating, 0) / entries.length;
@@ -2515,9 +3172,9 @@ async function feedbackStatsCommand() {
2515
3172
  }
2516
3173
 
2517
3174
  // src/commands/dashboard.ts
2518
- import { readdirSync as readdirSync5, existsSync as existsSync9, statSync as statSync2 } from "fs";
2519
- import { join as join9 } from "path";
2520
- import { homedir as homedir2 } from "os";
3175
+ import { readdirSync as readdirSync6, existsSync as existsSync11, statSync as statSync2 } from "fs";
3176
+ import { join as join11 } from "path";
3177
+ import { homedir as homedir3 } from "os";
2521
3178
 
2522
3179
  // src/lib/costs.ts
2523
3180
  var MODEL_PRICING = {
@@ -2528,16 +3185,29 @@ var MODEL_PRICING = {
2528
3185
  "claude-3-5-haiku-20241022": { input: 0.8, output: 4 },
2529
3186
  default: { input: 3, output: 15 }
2530
3187
  };
2531
- var DEFAULT_DAILY_BUDGET = 50;
3188
+ var DEFAULT_DAILY_BUDGET = 200;
2532
3189
  var DEFAULT_DAILY_CALL_LIMIT = 1e3;
2533
3190
  var BRIDGE_URL = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
3191
+ var FETCH_TIMEOUT_MS = 2e3;
3192
+ async function fetchWithTimeout(url, options = {}, timeoutMs = FETCH_TIMEOUT_MS) {
3193
+ const controller = new AbortController();
3194
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
3195
+ try {
3196
+ const response = await fetch(url, { ...options, signal: controller.signal });
3197
+ clearTimeout(timeoutId);
3198
+ return response;
3199
+ } catch (error) {
3200
+ clearTimeout(timeoutId);
3201
+ throw error;
3202
+ }
3203
+ }
2534
3204
  function calcCost(model, inputTokens, outputTokens) {
2535
3205
  const pricing = MODEL_PRICING[model] || MODEL_PRICING.default;
2536
3206
  return inputTokens / 1e6 * pricing.input + outputTokens / 1e6 * pricing.output;
2537
3207
  }
2538
3208
  async function fetchFromBridge(period = "day") {
2539
3209
  try {
2540
- const response = await fetch(`${BRIDGE_URL}/api/cost/summary?period=${period}`, {
3210
+ const response = await fetchWithTimeout(`${BRIDGE_URL}/api/cost/summary?period=${period}`, {
2541
3211
  headers: { "Content-Type": "application/json" }
2542
3212
  });
2543
3213
  if (!response.ok) {
@@ -2589,7 +3259,7 @@ async function fetchFromLangfuse(limit = 100) {
2589
3259
  try {
2590
3260
  const auth = Buffer.from(`${publicKey}:${secretKey}`).toString("base64");
2591
3261
  const url = `${host}/api/public/observations?limit=${limit}`;
2592
- const response = await fetch(url, {
3262
+ const response = await fetchWithTimeout(url, {
2593
3263
  headers: {
2594
3264
  Authorization: `Basic ${auth}`,
2595
3265
  "Content-Type": "application/json"
@@ -2684,41 +3354,78 @@ function formatCostBar(usedPercent, width = 20) {
2684
3354
  const empty = width - filled;
2685
3355
  return "\u2588".repeat(filled) + "\u2591".repeat(empty);
2686
3356
  }
2687
- async function fetchRateLimits() {
3357
+ async function fetchBridgeStats() {
2688
3358
  try {
2689
- const response = await fetch(`${BRIDGE_URL}/api/rate-limits`, {
3359
+ const statsResponse = await fetchWithTimeout(`${BRIDGE_URL}/stats`, {
2690
3360
  headers: { "Content-Type": "application/json" }
2691
3361
  });
2692
- if (!response.ok) {
2693
- return { limits: {}, source: "none" };
2694
- }
2695
- const data = await response.json();
2696
- const rateLimits = data.rate_limits || {};
2697
- const limits = {};
2698
- for (const [key, value] of Object.entries(rateLimits)) {
2699
- limits[key] = {
2700
- model: value.model || key,
2701
- requestsLimit: value.requests_limit || 0,
2702
- requestsRemaining: value.requests_remaining || 0,
2703
- requestsReset: value.requests_reset,
2704
- tokensLimit: value.tokens_limit || 0,
2705
- tokensRemaining: value.tokens_remaining || 0,
2706
- tokensReset: value.tokens_reset,
2707
- inputTokensLimit: value.input_tokens_limit,
2708
- inputTokensRemaining: value.input_tokens_remaining,
2709
- outputTokensLimit: value.output_tokens_limit,
2710
- outputTokensRemaining: value.output_tokens_remaining,
2711
- capturedAt: value.captured_at || (/* @__PURE__ */ new Date()).toISOString()
2712
- };
3362
+ if (!statsResponse.ok) {
3363
+ return null;
2713
3364
  }
2714
- return { limits, source: "proxy" };
3365
+ const stats = await statsResponse.json();
3366
+ const healthResponse = await fetchWithTimeout(`${BRIDGE_URL}/health`, {
3367
+ headers: { "Content-Type": "application/json" }
3368
+ });
3369
+ const health = healthResponse.ok ? await healthResponse.json() : {};
3370
+ const [costResponse, weekResponse] = await Promise.all([
3371
+ fetchWithTimeout(`${BRIDGE_URL}/api/cost/summary?period=day`, {
3372
+ headers: { "Content-Type": "application/json" }
3373
+ }),
3374
+ fetchWithTimeout(`${BRIDGE_URL}/api/cost/summary?period=week`, {
3375
+ headers: { "Content-Type": "application/json" }
3376
+ })
3377
+ ]);
3378
+ const costData = costResponse.ok ? await costResponse.json() : {};
3379
+ const weekData = weekResponse.ok ? await weekResponse.json() : {};
3380
+ return {
3381
+ status: stats.status || "unknown",
3382
+ source: stats.source || "none",
3383
+ today: {
3384
+ generations: stats.today?.generations || 0,
3385
+ inputTokens: stats.today?.input_tokens || 0,
3386
+ outputTokens: stats.today?.output_tokens || 0,
3387
+ costUsd: stats.today?.cost_usd || 0
3388
+ },
3389
+ week: weekData.totals ? {
3390
+ generations: weekData.totals.generations || 0,
3391
+ inputTokens: weekData.totals.input_tokens || 0,
3392
+ outputTokens: weekData.totals.output_tokens || 0,
3393
+ costUsd: weekData.totals.cost_usd || 0,
3394
+ byModel: (weekData.by_model || []).map((m) => ({
3395
+ model: m.model || "unknown",
3396
+ generations: m.generations || 0,
3397
+ costUsd: m.cost_usd || 0
3398
+ }))
3399
+ } : void 0,
3400
+ budget: {
3401
+ daily: stats.budget?.daily || DEFAULT_DAILY_BUDGET,
3402
+ used: stats.budget?.used || 0,
3403
+ remaining: stats.budget?.remaining || DEFAULT_DAILY_BUDGET,
3404
+ usedPct: stats.budget?.used_pct || 0
3405
+ },
3406
+ bySquad: (stats.by_squad || []).map((s) => ({
3407
+ squad: s.squad || "unknown",
3408
+ costUsd: s.cost_usd || 0,
3409
+ generations: s.generations || 0
3410
+ })),
3411
+ byModel: (costData.by_model || []).map((m) => ({
3412
+ model: m.model || "unknown",
3413
+ generations: m.generations || 0,
3414
+ costUsd: m.cost_usd || 0
3415
+ })),
3416
+ health: {
3417
+ postgres: health.postgres || "unknown",
3418
+ redis: health.redis || "unknown",
3419
+ langfuse: health.langfuse || "unknown"
3420
+ }
3421
+ };
2715
3422
  } catch {
2716
- return { limits: {}, source: "none" };
3423
+ return null;
2717
3424
  }
2718
3425
  }
2719
3426
  async function fetchInsights(period = "week") {
2720
3427
  try {
2721
- const response = await fetch(`${BRIDGE_URL}/api/insights?period=${period}`, {
3428
+ const response = await fetchWithTimeout(`${BRIDGE_URL}/api/insights?period=${period}`, {
2722
3429
  headers: { "Content-Type": "application/json" }
2723
3430
  });
2724
3431
  if (!response.ok) {
@@ -2793,7 +3500,8 @@ function getPool() {
2793
3500
  connectionString: DATABASE_URL,
2794
3501
  max: 5,
2795
3502
  idleTimeoutMillis: 3e4,
2796
- connectionTimeoutMillis: 5e3
3503
+ connectionTimeoutMillis: 1500
3504
+ // Fast timeout for CLI responsiveness
2797
3505
  });
2798
3506
  pool.on("error", (err) => {
2799
3507
  console.error("Unexpected database pool error:", err);
@@ -2895,21 +3603,27 @@ async function getDashboardHistory(limit = 30) {
2895
3603
  return [];
2896
3604
  }
2897
3605
  }
3606
+ async function closeDatabase() {
3607
+ if (pool) {
3608
+ await pool.end();
3609
+ pool = null;
3610
+ }
3611
+ }
2898
3612
 
2899
3613
  // src/commands/dashboard.ts
2900
3614
  function getLastActivityDate(squadName) {
2901
3615
  const memoryDir = findMemoryDir();
2902
3616
  if (!memoryDir) return "unknown";
2903
- const squadMemory = join9(memoryDir, squadName);
2904
- if (!existsSync9(squadMemory)) return "\u2014";
3617
+ const squadMemory = join11(memoryDir, squadName);
3618
+ if (!existsSync11(squadMemory)) return "\u2014";
2905
3619
  let latestTime = 0;
2906
3620
  try {
2907
- const agents = readdirSync5(squadMemory, { withFileTypes: true }).filter((e) => e.isDirectory());
3621
+ const agents = readdirSync6(squadMemory, { withFileTypes: true }).filter((e) => e.isDirectory());
2908
3622
  for (const agent of agents) {
2909
- const agentPath = join9(squadMemory, agent.name);
2910
- const files = readdirSync5(agentPath).filter((f) => f.endsWith(".md"));
3623
+ const agentPath = join11(squadMemory, agent.name);
3624
+ const files = readdirSync6(agentPath).filter((f) => f.endsWith(".md"));
2911
3625
  for (const file of files) {
2912
- const filePath = join9(agentPath, file);
3626
+ const filePath = join11(agentPath, file);
2913
3627
  const stats = statSync2(filePath);
2914
3628
  if (stats.mtimeMs > latestTime) {
2915
3629
  latestTime = stats.mtimeMs;
@@ -2937,54 +3651,119 @@ async function dashboardCommand(options = {}) {
2937
3651
  await renderCeoReport(squadsDir);
2938
3652
  return;
2939
3653
  }
3654
+ const baseDir = findAgentsSquadsDir();
2940
3655
  const squadNames = listSquads(squadsDir);
3656
+ const skipGitHub = options.fast !== false;
3657
+ const timeout = (promise, ms, fallback) => Promise.race([promise, new Promise((resolve) => setTimeout(() => resolve(fallback), ms))]);
3658
+ const [gitStats, ghStats, costs, bridgeStats, activity, dbAvailable, history, insights] = await Promise.all([
3659
+ // Git stats (local, ~1s)
3660
+ Promise.resolve(baseDir ? getMultiRepoGitStats(baseDir, 30) : null),
3661
+ // GitHub stats (network, ~20-30s) - skip by default for fast mode
3662
+ skipGitHub ? Promise.resolve(null) : Promise.resolve(baseDir ? getGitHubStatsOptimized(baseDir, 30) : null),
3663
+ // Langfuse costs (network, 2s timeout)
3664
+ timeout(fetchCostSummary(100), 2e3, null),
3665
+ // Bridge stats (local network, 2s timeout)
3666
+ timeout(fetchBridgeStats(), 2e3, null),
3667
+ // Activity sparkline (local, <1s)
3668
+ Promise.resolve(baseDir ? getActivitySparkline(baseDir, 14) : []),
3669
+ // Database availability check (1.5s timeout)
3670
+ timeout(isDatabaseAvailable(), 1500, false),
3671
+ // Dashboard history (1.5s timeout)
3672
+ timeout(getDashboardHistory(14).catch(() => []), 1500, []),
3673
+ // Insights (2s timeout)
3674
+ timeout(fetchInsights("week").catch(() => null), 2e3, null)
3675
+ ]);
3676
+ const cache = { gitStats, ghStats, costs, bridgeStats, activity, dbAvailable, history, insights };
2941
3677
  const squadData = [];
2942
- const baseDir = findAgentsSquadsDir();
2943
- const ghStats = baseDir ? getGitHubStats(baseDir, 30) : null;
2944
3678
  for (const name of squadNames) {
2945
3679
  const squad = loadSquad(name);
2946
3680
  if (!squad) continue;
2947
3681
  const lastActivity = getLastActivityDate(name);
2948
3682
  const github = ghStats?.bySquad.get(name) || null;
2949
3683
  let status = "active";
2950
- const activeGoals2 = squad.goals.filter((g) => !g.completed);
2951
- if (activeGoals2.length === 0) {
3684
+ const activeGoals = squad.goals.filter((g) => !g.completed);
3685
+ if (activeGoals.length === 0) {
2952
3686
  status = "needs-goal";
2953
3687
  } else if (lastActivity.includes("w") || lastActivity === "\u2014") {
2954
3688
  status = "stale";
2955
3689
  }
2956
- const totalGoals2 = squad.goals.length;
2957
- const completedGoals2 = squad.goals.filter((g) => g.completed).length;
3690
+ const totalGoals = squad.goals.length;
3691
+ const completedGoals = squad.goals.filter((g) => g.completed).length;
2958
3692
  const hasProgress = squad.goals.filter((g) => g.progress).length;
2959
- const goalProgress = totalGoals2 > 0 ? Math.round((completedGoals2 + hasProgress * 0.3) / totalGoals2 * 100) : 0;
3693
+ const goalProgress = totalGoals > 0 ? Math.round((completedGoals + hasProgress * 0.3) / totalGoals * 100) : 0;
3694
+ const repoSquadMap = {
3695
+ website: ["agents-squads-web"],
3696
+ product: ["squads-cli"],
3697
+ engineering: ["hq", "squads-cli"],
3698
+ research: ["research"],
3699
+ intelligence: ["intelligence"],
3700
+ customer: ["customer"],
3701
+ finance: ["finance"],
3702
+ company: ["company", "hq"],
3703
+ marketing: ["marketing", "agents-squads-web"],
3704
+ cli: ["squads-cli"]
3705
+ };
3706
+ let squadCommits = 0;
3707
+ if (gitStats) {
3708
+ for (const [repo, commits] of gitStats.commitsByRepo) {
3709
+ if (repoSquadMap[name]?.includes(repo)) {
3710
+ squadCommits += commits;
3711
+ }
3712
+ }
3713
+ }
3714
+ const githubStats = github || {
3715
+ prsOpened: 0,
3716
+ prsMerged: 0,
3717
+ issuesClosed: 0,
3718
+ issuesOpen: 0,
3719
+ commits: 0,
3720
+ recentIssues: [],
3721
+ recentPRs: []
3722
+ };
3723
+ githubStats.commits = squadCommits;
2960
3724
  squadData.push({
2961
3725
  name,
2962
3726
  mission: squad.mission,
2963
3727
  goals: squad.goals,
2964
3728
  lastActivity,
2965
3729
  status,
2966
- github,
3730
+ github: githubStats,
2967
3731
  goalProgress
2968
3732
  });
2969
3733
  }
2970
- const totalGoals = squadData.reduce((sum, s) => sum + s.goals.length, 0);
2971
- const activeGoals = squadData.reduce((sum, s) => sum + s.goals.filter((g) => !g.completed).length, 0);
2972
- const completedGoals = totalGoals - activeGoals;
2973
- const completionRate = totalGoals > 0 ? Math.round(completedGoals / totalGoals * 100) : 0;
2974
3734
  const activeSquads = squadData.filter((s) => s.status === "active").length;
2975
3735
  const totalPRs = ghStats ? ghStats.prsMerged : 0;
2976
3736
  const totalIssuesClosed = ghStats ? ghStats.issuesClosed : 0;
2977
3737
  const totalIssuesOpen = ghStats ? ghStats.issuesOpen : 0;
3738
+ cleanupStaleSessions();
3739
+ const sessionSummary = getLiveSessionSummary();
2978
3740
  writeLine();
2979
3741
  writeLine(` ${gradient("squads")} ${colors.dim}dashboard${RESET}`);
2980
- writeLine();
2981
- const stats = [
2982
- `${colors.cyan}${activeSquads}${RESET}/${squadData.length} squads`,
2983
- `${colors.green}${totalPRs}${RESET} PRs merged`,
2984
- `${colors.purple}${totalIssuesClosed}${RESET} closed`,
2985
- `${colors.yellow}${totalIssuesOpen}${RESET} open`
2986
- ].join(` ${colors.dim}\u2502${RESET} `);
2987
- writeLine(` ${stats}`);
3742
+ const updateInfo = checkForUpdate();
3743
+ if (updateInfo.updateAvailable) {
3744
+ writeLine(` ${colors.cyan}\u2B06${RESET} Update available: ${colors.dim}${updateInfo.currentVersion}${RESET} \u2192 ${colors.green}${updateInfo.latestVersion}${RESET} ${colors.dim}(run \`squads update\`)${RESET}`);
3745
+ }
3746
+ if (sessionSummary.totalSessions > 0) {
3747
+ const sessionText = sessionSummary.totalSessions === 1 ? "session" : "sessions";
3748
+ const squadText = sessionSummary.squadCount === 1 ? "squad" : "squads";
3749
+ let toolInfo = "";
3750
+ if (sessionSummary.byTool && Object.keys(sessionSummary.byTool).length > 0) {
3751
+ const toolParts = Object.entries(sessionSummary.byTool).sort((a, b) => b[1] - a[1]).map(([tool, count]) => `${colors.dim}${tool}${RESET} ${colors.cyan}${count}${RESET}`);
3752
+ toolInfo = ` ${colors.dim}(${RESET}${toolParts.join(` ${colors.dim}\xB7${RESET} `)}${colors.dim})${RESET}`;
3753
+ }
3754
+ writeLine(` ${colors.green}${icons.active}${RESET} ${colors.white}${sessionSummary.totalSessions}${RESET} active ${sessionText} ${colors.dim}across${RESET} ${colors.cyan}${sessionSummary.squadCount}${RESET} ${squadText}${toolInfo}`);
3755
+ }
3756
+ writeLine();
3757
+ const statsParts = [`${colors.cyan}${activeSquads}${RESET}/${squadData.length} squads`];
3758
+ if (ghStats) {
3759
+ statsParts.push(`${colors.green}${totalPRs}${RESET} PRs merged`);
3760
+ statsParts.push(`${colors.purple}${totalIssuesClosed}${RESET} closed`);
3761
+ statsParts.push(`${colors.yellow}${totalIssuesOpen}${RESET} open`);
3762
+ } else {
3763
+ statsParts.push(`${colors.cyan}${gitStats?.totalCommits || 0}${RESET} commits`);
3764
+ statsParts.push(`${colors.dim}use -f for PRs/issues${RESET}`);
3765
+ }
3766
+ writeLine(` ${statsParts.join(` ${colors.dim}\u2502${RESET} `)}`);
2988
3767
  writeLine();
2989
3768
  const overallProgress = squadData.length > 0 ? Math.round(squadData.reduce((sum, s) => sum + s.goalProgress, 0) / squadData.length) : 0;
2990
3769
  writeLine(` ${progressBar(overallProgress, 32)} ${colors.dim}${overallProgress}% goal progress${RESET}`);
@@ -2992,8 +3771,7 @@ async function dashboardCommand(options = {}) {
2992
3771
  const w = { name: 13, commits: 7, prs: 4, issues: 6, goals: 6, bar: 10 };
2993
3772
  const tableWidth = w.name + w.commits + w.prs + w.issues + w.goals + w.bar + 12;
2994
3773
  writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
2995
- const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.name)}${RESET}${bold}${padEnd("COMMITS", w.commits)}${RESET}${bold}${padEnd("PRs", w.prs)}${RESET}${bold}${padEnd("ISSUES", w.issues)}${RESET}${bold}${padEnd("GOALS", w.goals)}${RESET}${bold}PROGRESS${RESET} ${colors.purple}${box.vertical}${RESET}`;
2996
- writeLine(header);
3774
+ writeLine(` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.name)}${RESET}${bold}${padEnd("COMMITS", w.commits)}${RESET}${bold}${padEnd("PRs", w.prs)}${RESET}${bold}${padEnd("ISSUES", w.issues)}${RESET}${bold}${padEnd("GOALS", w.goals)}${RESET}${bold}PROGRESS${RESET} ${colors.purple}${box.vertical}${RESET}`);
2997
3775
  writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
2998
3776
  const sortedSquads = [...squadData].sort((a, b) => {
2999
3777
  const aActivity = (a.github?.commits || 0) + (a.github?.prsMerged || 0) * 5;
@@ -3011,16 +3789,15 @@ async function dashboardCommand(options = {}) {
3011
3789
  const commitColor = commits > 10 ? colors.green : commits > 0 ? colors.cyan : colors.dim;
3012
3790
  const prColor = prs > 0 ? colors.green : colors.dim;
3013
3791
  const issueColor = issuesClosed > 0 ? colors.green : colors.dim;
3014
- const issuesDisplay = `${issuesClosed}/${issuesOpen}`;
3015
- const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squad.name, w.name)}${RESET}${commitColor}${padEnd(String(commits), w.commits)}${RESET}${prColor}${padEnd(String(prs), w.prs)}${RESET}${issueColor}${padEnd(issuesDisplay, w.issues)}${RESET}${padEnd(`${activeCount}/${totalCount}`, w.goals)}${progressBar(squad.goalProgress, 8)} ${colors.purple}${box.vertical}${RESET}`;
3016
- writeLine(row);
3792
+ writeLine(` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squad.name, w.name)}${RESET}${commitColor}${padEnd(String(commits), w.commits)}${RESET}${prColor}${padEnd(String(prs), w.prs)}${RESET}${issueColor}${padEnd(`${issuesClosed}/${issuesOpen}`, w.issues)}${RESET}${padEnd(`${activeCount}/${totalCount}`, w.goals)}${progressBar(squad.goalProgress, 8)} ${colors.purple}${box.vertical}${RESET}`);
3017
3793
  }
3018
3794
  writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
3019
3795
  writeLine();
3020
- await renderGitPerformance();
3021
- await renderTokenEconomics(squadData.map((s) => s.name));
3022
- await renderHistoricalTrends();
3023
- await renderInsights();
3796
+ renderGitPerformanceCached(cache);
3797
+ renderTokenEconomicsCached(cache);
3798
+ renderInfrastructureCached(cache);
3799
+ renderHistoricalTrendsCached(cache);
3800
+ renderInsightsCached(cache);
3024
3801
  const allActiveGoals = squadData.flatMap(
3025
3802
  (s) => s.goals.filter((g) => !g.completed).map((g) => ({ squad: s.name, goal: g }))
3026
3803
  );
@@ -3031,12 +3808,9 @@ async function dashboardCommand(options = {}) {
3031
3808
  for (const { squad, goal: goal2 } of allActiveGoals.slice(0, maxGoals)) {
3032
3809
  const hasProgress = goal2.progress && goal2.progress.length > 0;
3033
3810
  const icon = hasProgress ? icons.progress : icons.empty;
3034
- const squadLabel = `${colors.dim}${squad}${RESET}`;
3035
- const goalText = truncate(goal2.description, 48);
3036
- writeLine(` ${icon} ${squadLabel} ${goalText}`);
3811
+ writeLine(` ${icon} ${colors.dim}${squad}${RESET} ${truncate(goal2.description, 48)}`);
3037
3812
  if (hasProgress) {
3038
- const progressText = truncate(goal2.progress, 52);
3039
- writeLine(` ${colors.dim}\u2514${RESET} ${colors.green}${progressText}${RESET}`);
3813
+ writeLine(` ${colors.dim}\u2514${RESET} ${colors.green}${truncate(goal2.progress, 52)}${RESET}`);
3040
3814
  }
3041
3815
  }
3042
3816
  if (allActiveGoals.length > maxGoals) {
@@ -3047,82 +3821,41 @@ async function dashboardCommand(options = {}) {
3047
3821
  writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}<squad>${RESET} ${colors.dim}Execute a squad${RESET}`);
3048
3822
  writeLine(` ${colors.dim}$${RESET} squads goal set ${colors.dim}Add a goal${RESET}`);
3049
3823
  writeLine();
3050
- await saveSnapshot(squadData, ghStats, baseDir);
3051
- }
3052
- async function saveSnapshot(squadData, ghStats, baseDir) {
3053
- const dbAvailable = await isDatabaseAvailable();
3054
- if (!dbAvailable) return;
3055
- const gitStats = baseDir ? getMultiRepoGitStats(baseDir, 30) : null;
3056
- const costs = await fetchCostSummary(100);
3057
- const squadsData = squadData.map((s) => ({
3058
- name: s.name,
3059
- commits: s.github?.commits || 0,
3060
- prsOpened: s.github?.prsOpened || 0,
3061
- prsMerged: s.github?.prsMerged || 0,
3062
- issuesClosed: s.github?.issuesClosed || 0,
3063
- issuesOpen: s.github?.issuesOpen || 0,
3064
- goalsActive: s.goals.filter((g) => !g.completed).length,
3065
- goalsTotal: s.goals.length,
3066
- progress: s.goalProgress
3067
- }));
3068
- const authorsData = gitStats ? Array.from(gitStats.commitsByAuthor.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, commits]) => ({ name, commits })) : [];
3069
- const reposData = gitStats ? Array.from(gitStats.commitsByRepo.entries()).sort((a, b) => b[1] - a[1]).map(([name, commits]) => ({ name, commits })) : [];
3070
- const totalInputTokens = costs?.bySquad.reduce((sum, s) => sum + s.inputTokens, 0) || 0;
3071
- const totalOutputTokens = costs?.bySquad.reduce((sum, s) => sum + s.outputTokens, 0) || 0;
3072
- const overallProgress = squadData.length > 0 ? Math.round(squadData.reduce((sum, s) => sum + s.goalProgress, 0) / squadData.length) : 0;
3073
- const snapshot = {
3074
- totalSquads: squadData.length,
3075
- totalCommits: gitStats?.totalCommits || 0,
3076
- totalPrsMerged: ghStats?.prsMerged || 0,
3077
- totalIssuesClosed: ghStats?.issuesClosed || 0,
3078
- totalIssuesOpen: ghStats?.issuesOpen || 0,
3079
- goalProgressPct: overallProgress,
3080
- costUsd: costs?.totalCost || 0,
3081
- dailyBudgetUsd: costs?.dailyBudget || 50,
3082
- inputTokens: totalInputTokens,
3083
- outputTokens: totalOutputTokens,
3084
- commits30d: gitStats?.totalCommits || 0,
3085
- avgCommitsPerDay: gitStats?.avgCommitsPerDay || 0,
3086
- activeDays: gitStats?.activeDays || 0,
3087
- peakCommits: gitStats?.peakDay?.count || 0,
3088
- peakDate: gitStats?.peakDay?.date || null,
3089
- squadsData,
3090
- authorsData,
3091
- reposData
3092
- };
3093
- await saveDashboardSnapshot(snapshot);
3824
+ saveSnapshotCached(squadData, cache, baseDir).catch(() => {
3825
+ });
3826
+ await closeDatabase();
3094
3827
  }
3095
3828
  function findAgentsSquadsDir() {
3096
3829
  const candidates = [
3097
- join9(process.cwd(), ".."),
3098
- join9(homedir2(), "agents-squads")
3830
+ join11(process.cwd(), ".."),
3831
+ join11(homedir3(), "agents-squads")
3099
3832
  ];
3100
3833
  for (const dir of candidates) {
3101
- if (existsSync9(join9(dir, "hq"))) {
3834
+ if (existsSync11(join11(dir, "hq"))) {
3102
3835
  return dir;
3103
3836
  }
3104
3837
  }
3105
3838
  return null;
3106
3839
  }
3107
- async function renderGitPerformance() {
3108
- const baseDir = findAgentsSquadsDir();
3109
- if (!baseDir) {
3110
- writeLine(` ${bold}Git Activity${RESET} ${colors.dim}(no repos found)${RESET}`);
3111
- writeLine();
3112
- return;
3113
- }
3114
- const stats = getMultiRepoGitStats(baseDir, 30);
3115
- const activity = getActivitySparkline(baseDir, 14);
3116
- if (stats.totalCommits === 0) {
3840
+ function formatK(n) {
3841
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
3842
+ if (n >= 1e3) return (n / 1e3).toFixed(0) + "k";
3843
+ return String(n);
3844
+ }
3845
+ function renderGitPerformanceCached(cache) {
3846
+ const { gitStats: stats, activity } = cache;
3847
+ if (!stats || stats.totalCommits === 0) {
3117
3848
  writeLine(` ${bold}Git Activity${RESET} ${colors.dim}(no commits in 30d)${RESET}`);
3118
3849
  writeLine();
3119
3850
  return;
3120
3851
  }
3121
3852
  writeLine(` ${bold}Git Activity${RESET} ${colors.dim}(30d)${RESET}`);
3122
3853
  writeLine();
3123
- const spark = sparkline(activity);
3124
- writeLine(` ${colors.dim}Last 14d:${RESET} ${spark}`);
3125
- writeLine();
3854
+ if (activity.length > 0) {
3855
+ const spark = sparkline(activity);
3856
+ writeLine(` ${colors.dim}Last 14d:${RESET} ${spark}`);
3857
+ writeLine();
3858
+ }
3126
3859
  const metrics = [
3127
3860
  `${colors.cyan}${stats.totalCommits}${RESET} commits`,
3128
3861
  `${colors.green}${stats.avgCommitsPerDay}${RESET}/day`,
@@ -3149,8 +3882,8 @@ async function renderGitPerformance() {
3149
3882
  writeLine();
3150
3883
  }
3151
3884
  }
3152
- async function renderTokenEconomics(squadNames) {
3153
- const costs = await fetchCostSummary(100);
3885
+ function renderTokenEconomicsCached(cache) {
3886
+ const costs = cache.costs;
3154
3887
  if (!costs) {
3155
3888
  writeLine(` ${bold}Token Economics${RESET} ${colors.dim}(no data)${RESET}`);
3156
3889
  writeLine(` ${colors.dim}Set LANGFUSE_PUBLIC_KEY & LANGFUSE_SECRET_KEY for cost tracking${RESET}`);
@@ -3165,81 +3898,13 @@ async function renderTokenEconomics(squadNames) {
3165
3898
  writeLine(` ${colors.green}$${costs.totalCost.toFixed(2)}${RESET} used ${colors.dim}\u2502${RESET} ${colors.cyan}$${costs.idleBudget.toFixed(2)}${RESET} idle`);
3166
3899
  writeLine();
3167
3900
  const tier = parseInt(process.env.ANTHROPIC_TIER || "4", 10);
3168
- const rpmByTier = { 1: 50, 2: 1e3, 3: 2e3, 4: 4e3 };
3169
- const rpmLimit = rpmByTier[tier] || 4e3;
3170
- const tokenLimits = {
3171
- 1: { opus: { itpm: 3e4, otpm: 8e3 }, sonnet: { itpm: 3e4, otpm: 8e3 }, haiku: { itpm: 5e4, otpm: 1e4 } },
3172
- 2: { opus: { itpm: 45e4, otpm: 9e4 }, sonnet: { itpm: 45e4, otpm: 9e4 }, haiku: { itpm: 45e4, otpm: 9e4 } },
3173
- 3: { opus: { itpm: 8e5, otpm: 16e4 }, sonnet: { itpm: 8e5, otpm: 16e4 }, haiku: { itpm: 1e6, otpm: 2e5 } },
3174
- 4: { opus: { itpm: 2e6, otpm: 4e5 }, sonnet: { itpm: 2e6, otpm: 4e5 }, haiku: { itpm: 4e6, otpm: 8e5 } }
3175
- };
3176
- const modelShortNames = {
3177
- "claude-opus-4-5-20251101": "opus-4.5",
3178
- "claude-sonnet-4-20250514": "sonnet-4",
3179
- "claude-haiku-4-5-20251001": "haiku-4.5",
3180
- "claude-3-5-sonnet-20241022": "sonnet-3.5",
3181
- "claude-3-5-haiku-20241022": "haiku-3.5"
3182
- };
3183
- const modelToFamily = {
3184
- "claude-opus-4-5-20251101": "opus",
3185
- "claude-sonnet-4-20250514": "sonnet",
3186
- "claude-haiku-4-5-20251001": "haiku",
3187
- "claude-3-5-sonnet-20241022": "sonnet",
3188
- "claude-3-5-haiku-20241022": "haiku"
3189
- };
3190
- const modelStats = {};
3191
- for (const squad of costs.bySquad) {
3192
- for (const [model, count] of Object.entries(squad.models)) {
3193
- if (!modelStats[model]) {
3194
- modelStats[model] = { calls: 0, input: 0, output: 0, cached: 0 };
3195
- }
3196
- modelStats[model].calls += count;
3197
- }
3198
- const totalCalls2 = Object.values(squad.models).reduce((a, b) => a + b, 0);
3199
- for (const [model, count] of Object.entries(squad.models)) {
3200
- const ratio = totalCalls2 > 0 ? count / totalCalls2 : 0;
3201
- modelStats[model].input += Math.round(squad.inputTokens * ratio);
3202
- modelStats[model].output += Math.round(squad.outputTokens * ratio);
3203
- }
3204
- }
3205
- const totalInput = costs.bySquad.reduce((sum, s) => sum + s.inputTokens, 0);
3206
- const totalOutput = costs.bySquad.reduce((sum, s) => sum + s.outputTokens, 0);
3207
- const totalCalls = costs.bySquad.reduce((sum, s) => sum + s.calls, 0);
3208
- const hourlyRate = costs.totalCost;
3901
+ writeLine(` ${colors.dim}Rate Limits (Tier ${tier})${RESET}`);
3902
+ writeLine();
3903
+ const now = /* @__PURE__ */ new Date();
3904
+ const hoursElapsed = Math.max(now.getHours() + now.getMinutes() / 60, 1);
3905
+ const hourlyRate = costs.totalCost / hoursElapsed;
3209
3906
  const dailyProjection = hourlyRate * 24;
3210
3907
  const monthlyProjection = dailyProjection * 30;
3211
- const rateLimits = await fetchRateLimits();
3212
- const hasRealLimits = rateLimits.source === "proxy" && Object.keys(rateLimits.limits).length > 0;
3213
- if (hasRealLimits) {
3214
- writeLine(` ${colors.dim}Rate Limits${RESET} ${colors.green}(live)${RESET}`);
3215
- for (const [family, limits] of Object.entries(rateLimits.limits)) {
3216
- const name = family === "opus" ? "opus" : family === "sonnet" ? "sonnet" : family === "haiku" ? "haiku" : family;
3217
- const reqUsed = limits.requestsLimit - limits.requestsRemaining;
3218
- const reqPct = limits.requestsLimit > 0 ? reqUsed / limits.requestsLimit * 100 : 0;
3219
- const reqColor = reqPct > 80 ? colors.red : reqPct > 50 ? colors.yellow : colors.green;
3220
- const tokUsed = limits.tokensLimit - limits.tokensRemaining;
3221
- const tokPct = limits.tokensLimit > 0 ? tokUsed / limits.tokensLimit * 100 : 0;
3222
- const tokColor = tokPct > 80 ? colors.red : tokPct > 50 ? colors.yellow : colors.green;
3223
- writeLine(` ${colors.cyan}${padEnd(name, 8)}${RESET} ${reqColor}${String(reqUsed).padStart(4)}${RESET}${colors.dim}/${limits.requestsLimit}req${RESET} ${tokColor}${formatK(tokUsed)}${RESET}${colors.dim}/${formatK(limits.tokensLimit)}tok${RESET}`);
3224
- }
3225
- } else {
3226
- writeLine(` ${colors.dim}Rate Limits (Tier ${tier})${RESET}`);
3227
- const sortedModels = Object.entries(modelStats).sort((a, b) => b[1].calls - a[1].calls);
3228
- for (const [model, stats] of sortedModels.slice(0, 3)) {
3229
- const name = modelShortNames[model] || model.split("-").slice(1, 3).join("-");
3230
- const family = modelToFamily[model] || "sonnet";
3231
- const limits = tokenLimits[tier]?.[family] || { itpm: 1e6, otpm: 2e5 };
3232
- const rpmPct = stats.calls / rpmLimit * 100;
3233
- const rpmColor = rpmPct > 80 ? colors.red : rpmPct > 50 ? colors.yellow : colors.green;
3234
- writeLine(` ${colors.cyan}${padEnd(name, 11)}${RESET} ${rpmColor}${String(stats.calls).padStart(4)}${RESET}${colors.dim}rpm${RESET} ${colors.dim}${formatK(stats.input)}${RESET}${colors.dim}/${formatK(limits.itpm)}i${RESET} ${colors.dim}${formatK(stats.output)}${RESET}${colors.dim}/${formatK(limits.otpm)}o${RESET}`);
3235
- }
3236
- }
3237
- writeLine();
3238
- if (costs.totalCachedTokens > 0 || costs.cacheHitRate > 0) {
3239
- const cacheColor = costs.cacheHitRate > 50 ? colors.green : costs.cacheHitRate > 20 ? colors.yellow : colors.red;
3240
- writeLine(` ${colors.dim}Cache:${RESET} ${cacheColor}${costs.cacheHitRate.toFixed(1)}%${RESET} hit rate ${colors.dim}(${formatK(costs.totalCachedTokens)} cached / ${formatK(costs.totalInputTokens + costs.totalCachedTokens)} total)${RESET}`);
3241
- writeLine();
3242
- }
3243
3908
  writeLine(` ${colors.dim}Projections${RESET}`);
3244
3909
  const projColor = dailyProjection > costs.dailyBudget ? colors.red : colors.green;
3245
3910
  writeLine(` ${colors.dim}Daily:${RESET} ${projColor}~$${dailyProjection.toFixed(2)}${RESET}${colors.dim}/${costs.dailyBudget}${RESET} ${colors.dim}Monthly:${RESET} ${colors.cyan}~$${monthlyProjection.toFixed(0)}${RESET}`);
@@ -3251,100 +3916,83 @@ async function renderTokenEconomics(squadNames) {
3251
3916
  }
3252
3917
  writeLine();
3253
3918
  }
3254
- function formatK(n) {
3255
- if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
3256
- if (n >= 1e3) return (n / 1e3).toFixed(0) + "k";
3257
- return String(n);
3258
- }
3259
- async function renderHistoricalTrends() {
3260
- const dbAvailable = await isDatabaseAvailable();
3261
- if (!dbAvailable) return;
3262
- const history = await getDashboardHistory(14);
3263
- if (history.length < 2) return;
3264
- writeLine(` ${bold}Usage Trends${RESET} ${colors.dim}(${history.length}d history)${RESET}`);
3265
- writeLine();
3266
- const dailyCosts = history.map((h) => h.costUsd).reverse();
3267
- const costSparkStr = sparkline(dailyCosts);
3268
- const totalSpend = dailyCosts.reduce((sum, c) => sum + c, 0);
3269
- const avgDaily = totalSpend / dailyCosts.length;
3270
- writeLine(` ${colors.dim}Cost:${RESET} ${costSparkStr} ${colors.green}$${totalSpend.toFixed(2)}${RESET} total ${colors.dim}($${avgDaily.toFixed(2)}/day avg)${RESET}`);
3271
- const inputTokens = history.map((h) => h.inputTokens).reverse();
3272
- const totalInput = inputTokens.reduce((sum, t) => sum + t, 0);
3273
- const tokenSparkStr = sparkline(inputTokens);
3274
- writeLine(` ${colors.dim}Tokens:${RESET} ${tokenSparkStr} ${colors.cyan}${formatK(totalInput)}${RESET} input ${colors.dim}(${formatK(Math.round(totalInput / inputTokens.length))}/day)${RESET}`);
3275
- const goalProgress = history.map((h) => h.goalProgressPct).reverse();
3276
- const latestProgress = goalProgress[goalProgress.length - 1] || 0;
3277
- const earliestProgress = goalProgress[0] || 0;
3278
- const progressDelta = latestProgress - earliestProgress;
3279
- const progressColor = progressDelta > 0 ? colors.green : progressDelta < 0 ? colors.red : colors.dim;
3280
- const progressSign = progressDelta > 0 ? "+" : "";
3281
- writeLine(` ${colors.dim}Goals:${RESET} ${sparkline(goalProgress)} ${colors.purple}${latestProgress}%${RESET} ${progressColor}${progressSign}${progressDelta.toFixed(0)}%${RESET}${colors.dim} vs start${RESET}`);
3282
- writeLine();
3283
- }
3284
- async function renderInsights() {
3285
- const insights = await fetchInsights("week");
3286
- if (insights.source === "none" || insights.taskMetrics.length === 0) {
3919
+ function renderInfrastructureCached(cache) {
3920
+ const stats = cache.bridgeStats;
3921
+ if (!stats) {
3922
+ writeLine(` ${bold}Infrastructure${RESET} ${colors.dim}(bridge offline)${RESET}`);
3923
+ writeLine(` ${colors.dim}Start with: cd docker && docker-compose up -d${RESET}`);
3924
+ writeLine();
3287
3925
  return;
3288
3926
  }
3289
- writeLine(` ${bold}Agent Insights${RESET} ${colors.dim}(${insights.days}d)${RESET}`);
3927
+ writeLine(` ${bold}Infrastructure${RESET} ${colors.dim}(${stats.source})${RESET}`);
3290
3928
  writeLine();
3291
- const totals = insights.taskMetrics.reduce(
3292
- (acc, t) => ({
3293
- tasks: acc.tasks + t.tasksTotal,
3294
- completed: acc.completed + t.tasksCompleted,
3295
- failed: acc.failed + t.tasksFailed,
3296
- retries: acc.retries + t.totalRetries,
3297
- withRetries: acc.withRetries + t.tasksWithRetries
3298
- }),
3299
- { tasks: 0, completed: 0, failed: 0, retries: 0, withRetries: 0 }
3300
- );
3301
- if (totals.tasks > 0) {
3302
- const successRate = totals.tasks > 0 ? (totals.completed / totals.tasks * 100).toFixed(0) : "0";
3303
- const successColor = parseInt(successRate) >= 80 ? colors.green : parseInt(successRate) >= 60 ? colors.yellow : colors.red;
3304
- writeLine(` ${colors.dim}Tasks:${RESET} ${colors.green}${totals.completed}${RESET}${colors.dim}/${totals.tasks} completed${RESET} ${successColor}${successRate}%${RESET}${colors.dim} success${RESET} ${colors.red}${totals.failed}${RESET}${colors.dim} failed${RESET}`);
3305
- if (totals.retries > 0) {
3306
- const retryRate = totals.tasks > 0 ? (totals.withRetries / totals.tasks * 100).toFixed(0) : "0";
3307
- const retryColor = parseInt(retryRate) > 30 ? colors.red : parseInt(retryRate) > 15 ? colors.yellow : colors.green;
3308
- writeLine(` ${colors.dim}Retries:${RESET} ${retryColor}${totals.retries}${RESET}${colors.dim} total${RESET} ${retryColor}${retryRate}%${RESET}${colors.dim} of tasks needed retry${RESET}`);
3929
+ const pgStatus = stats.health.postgres === "connected" ? `${colors.green}\u25CF${RESET}` : `${colors.red}\u25CF${RESET}`;
3930
+ const redisStatus = stats.health.redis === "connected" ? `${colors.green}\u25CF${RESET}` : stats.health.redis === "disabled" ? `${colors.dim}\u25CB${RESET}` : `${colors.red}\u25CF${RESET}`;
3931
+ const otelWorking = stats.health.postgres === "connected" && stats.today.generations > 0;
3932
+ const otelStatus = otelWorking ? `${colors.green}\u25CF${RESET}` : `${colors.dim}\u25CB${RESET}`;
3933
+ writeLine(` ${pgStatus} postgres ${redisStatus} redis ${otelStatus} otel`);
3934
+ writeLine();
3935
+ if (stats.today.generations > 0 || stats.today.costUsd > 0) {
3936
+ const costColor = stats.budget.usedPct > 80 ? colors.red : stats.budget.usedPct > 50 ? colors.yellow : colors.green;
3937
+ writeLine(` ${colors.dim}Today:${RESET} ${colors.cyan}${stats.today.generations}${RESET}${colors.dim} calls${RESET} ${costColor}$${stats.today.costUsd.toFixed(2)}${RESET}${colors.dim}/$${stats.budget.daily}${RESET} ${colors.dim}${formatK(stats.today.inputTokens)}+${formatK(stats.today.outputTokens)} tokens${RESET}`);
3938
+ if (stats.byModel && stats.byModel.length > 0) {
3939
+ const modelLine = stats.byModel.map((m) => {
3940
+ const shortName = m.model.includes("opus") ? "opus" : m.model.includes("sonnet") ? "sonnet" : m.model.includes("haiku") ? "haiku" : m.model.slice(0, 10);
3941
+ return `${colors.dim}${shortName}${RESET} ${colors.cyan}${m.generations}${RESET}`;
3942
+ }).join(" ");
3943
+ writeLine(` ${colors.dim}Models:${RESET} ${modelLine}`);
3309
3944
  }
3310
3945
  }
3311
- const qualityTotals = insights.qualityMetrics.reduce(
3312
- (acc, q) => ({
3313
- feedback: acc.feedback + q.feedbackCount,
3314
- qualitySum: acc.qualitySum + q.avgQuality * q.feedbackCount,
3315
- helpfulSum: acc.helpfulSum + q.helpfulPct * q.feedbackCount / 100,
3316
- fixSum: acc.fixSum + q.fixRequiredPct * q.feedbackCount / 100
3317
- }),
3318
- { feedback: 0, qualitySum: 0, helpfulSum: 0, fixSum: 0 }
3319
- );
3320
- if (qualityTotals.feedback > 0) {
3321
- const avgQuality = qualityTotals.qualitySum / qualityTotals.feedback;
3322
- const helpfulPct = qualityTotals.helpfulSum / qualityTotals.feedback * 100;
3323
- const fixPct = qualityTotals.fixSum / qualityTotals.feedback * 100;
3324
- const qualityColor = avgQuality >= 4 ? colors.green : avgQuality >= 3 ? colors.yellow : colors.red;
3325
- const stars = "\u2605".repeat(Math.round(avgQuality)) + "\u2606".repeat(5 - Math.round(avgQuality));
3326
- writeLine(` ${colors.dim}Quality:${RESET} ${qualityColor}${stars}${RESET} ${colors.dim}(${avgQuality.toFixed(1)}/5)${RESET} ${colors.green}${helpfulPct.toFixed(0)}%${RESET}${colors.dim} helpful${RESET} ${fixPct > 20 ? colors.red : colors.dim}${fixPct.toFixed(0)}% needed fixes${RESET}`);
3327
- }
3328
- const contextMetrics = insights.taskMetrics.filter((t) => t.avgContextPct > 0);
3329
- if (contextMetrics.length > 0) {
3330
- const avgContext = contextMetrics.reduce((sum, t) => sum + t.avgContextPct, 0) / contextMetrics.length;
3331
- const maxContext = Math.max(...contextMetrics.map((t) => t.maxContextTokens));
3332
- const contextColor = avgContext < 40 ? colors.green : avgContext < 70 ? colors.yellow : colors.red;
3333
- const contextStatus = avgContext < 40 ? "lean" : avgContext < 70 ? "moderate" : "heavy";
3334
- writeLine(` ${colors.dim}Context:${RESET} ${contextColor}${avgContext.toFixed(0)}%${RESET}${colors.dim} avg utilization (${contextStatus})${RESET} ${colors.dim}peak ${formatK(maxContext)} tokens${RESET}`);
3335
- }
3336
- writeLine();
3337
- if (insights.topTools.length > 0) {
3338
- const toolLine = insights.topTools.slice(0, 5).map((t) => {
3339
- const successColor = t.successRate >= 95 ? colors.green : t.successRate >= 80 ? colors.yellow : colors.red;
3340
- return `${colors.dim}${t.toolName.replace("mcp__", "").slice(0, 12)}${RESET} ${successColor}${t.successRate.toFixed(0)}%${RESET}`;
3341
- }).join(" ");
3342
- writeLine(` ${colors.dim}Tools:${RESET} ${toolLine}`);
3343
- if (insights.toolFailureRate > 5) {
3344
- writeLine(` ${colors.yellow}\u26A0${RESET} ${colors.yellow}${insights.toolFailureRate.toFixed(1)}% tool failure rate${RESET}`);
3345
- }
3346
- writeLine();
3946
+ if (stats.week && stats.week.generations > 0) {
3947
+ const weekModelLine = stats.week.byModel?.map((m) => {
3948
+ const shortName = m.model.includes("opus") ? "opus" : m.model.includes("sonnet") ? "sonnet" : m.model.includes("haiku") ? "haiku" : m.model.slice(0, 10);
3949
+ return `${colors.dim}${shortName}${RESET} ${colors.purple}$${m.costUsd.toFixed(0)}${RESET}`;
3950
+ }).join(" ") || "";
3951
+ writeLine(` ${colors.dim}Week:${RESET} ${colors.cyan}${stats.week.generations}${RESET}${colors.dim} calls${RESET} ${colors.purple}$${stats.week.costUsd.toFixed(2)}${RESET} ${weekModelLine}`);
3347
3952
  }
3953
+ writeLine();
3954
+ }
3955
+ async function saveSnapshotCached(squadData, cache, baseDir) {
3956
+ if (!cache.dbAvailable) return;
3957
+ const { gitStats, ghStats, costs } = cache;
3958
+ const squadsData = squadData.map((s) => ({
3959
+ name: s.name,
3960
+ commits: s.github?.commits || 0,
3961
+ prsOpened: s.github?.prsOpened || 0,
3962
+ prsMerged: s.github?.prsMerged || 0,
3963
+ issuesClosed: s.github?.issuesClosed || 0,
3964
+ issuesOpen: s.github?.issuesOpen || 0,
3965
+ goalsActive: s.goals.filter((g) => !g.completed).length,
3966
+ goalsTotal: s.goals.length,
3967
+ progress: s.goalProgress
3968
+ }));
3969
+ const authorsData = gitStats ? Array.from(gitStats.commitsByAuthor.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, commits]) => ({ name, commits })) : [];
3970
+ const reposData = gitStats ? Array.from(gitStats.commitsByRepo.entries()).sort((a, b) => b[1] - a[1]).map(([name, commits]) => ({ name, commits })) : [];
3971
+ const totalInputTokens = costs?.bySquad.reduce((sum, s) => sum + s.inputTokens, 0) || 0;
3972
+ const totalOutputTokens = costs?.bySquad.reduce((sum, s) => sum + s.outputTokens, 0) || 0;
3973
+ const overallProgress = squadData.length > 0 ? Math.round(squadData.reduce((sum, s) => sum + s.goalProgress, 0) / squadData.length) : 0;
3974
+ const snapshot = {
3975
+ totalSquads: squadData.length,
3976
+ totalCommits: gitStats?.totalCommits || 0,
3977
+ totalPrsMerged: ghStats?.prsMerged || 0,
3978
+ totalIssuesClosed: ghStats?.issuesClosed || 0,
3979
+ totalIssuesOpen: ghStats?.issuesOpen || 0,
3980
+ goalProgressPct: overallProgress,
3981
+ costUsd: costs?.totalCost || 0,
3982
+ dailyBudgetUsd: costs?.dailyBudget || 50,
3983
+ inputTokens: totalInputTokens,
3984
+ outputTokens: totalOutputTokens,
3985
+ commits30d: gitStats?.totalCommits || 0,
3986
+ avgCommitsPerDay: gitStats?.avgCommitsPerDay || 0,
3987
+ activeDays: gitStats?.activeDays || 0,
3988
+ peakCommits: gitStats?.peakDay?.count || 0,
3989
+ peakDate: gitStats?.peakDay?.date || null,
3990
+ squadsData,
3991
+ authorsData,
3992
+ reposData
3993
+ };
3994
+ const saveTimeout = new Promise((resolve) => setTimeout(resolve, 2e3));
3995
+ await Promise.race([saveDashboardSnapshot(snapshot), saveTimeout]);
3348
3996
  }
3349
3997
  var P0_KEYWORDS = ["revenue", "first", "launch", "publish", "ship", "critical", "urgent"];
3350
3998
  var P1_KEYWORDS = ["track", "establish", "identify", "define", "fix"];
@@ -3453,9 +4101,62 @@ async function renderCeoReport(squadsDir) {
3453
4101
  writeLine(` ${colors.dim}$${RESET} squads goal list ${colors.dim}All active goals${RESET}`);
3454
4102
  writeLine();
3455
4103
  }
4104
+ function renderHistoricalTrendsCached(cache) {
4105
+ if (!cache.dbAvailable) return;
4106
+ const history = cache.history;
4107
+ if (history.length < 2) return;
4108
+ writeLine(` ${bold}Usage Trends${RESET} ${colors.dim}(${history.length}d history)${RESET}`);
4109
+ writeLine();
4110
+ const dailyCosts = history.map((h) => h.costUsd).reverse();
4111
+ const costSparkStr = sparkline(dailyCosts);
4112
+ const totalSpend = dailyCosts.reduce((sum, c) => sum + c, 0);
4113
+ const avgDaily = totalSpend / dailyCosts.length;
4114
+ writeLine(` ${colors.dim}Cost:${RESET} ${costSparkStr} ${colors.green}$${totalSpend.toFixed(2)}${RESET} total ${colors.dim}($${avgDaily.toFixed(2)}/day avg)${RESET}`);
4115
+ const inputTokens = history.map((h) => h.inputTokens).reverse();
4116
+ const totalInput = inputTokens.reduce((sum, t) => sum + t, 0);
4117
+ const tokenSparkStr = sparkline(inputTokens);
4118
+ writeLine(` ${colors.dim}Tokens:${RESET} ${tokenSparkStr} ${colors.cyan}${formatK(totalInput)}${RESET} input ${colors.dim}(${formatK(Math.round(totalInput / inputTokens.length))}/day)${RESET}`);
4119
+ const goalProgress = history.map((h) => h.goalProgressPct).reverse();
4120
+ const latestProgress = goalProgress[goalProgress.length - 1] || 0;
4121
+ const earliestProgress = goalProgress[0] || 0;
4122
+ const progressDelta = latestProgress - earliestProgress;
4123
+ const progressColor = progressDelta > 0 ? colors.green : progressDelta < 0 ? colors.red : colors.dim;
4124
+ const progressSign = progressDelta > 0 ? "+" : "";
4125
+ writeLine(` ${colors.dim}Goals:${RESET} ${sparkline(goalProgress)} ${colors.purple}${latestProgress}%${RESET} ${progressColor}${progressSign}${progressDelta.toFixed(0)}%${RESET}${colors.dim} vs start${RESET}`);
4126
+ writeLine();
4127
+ }
4128
+ function renderInsightsCached(cache) {
4129
+ const insights = cache.insights;
4130
+ if (!insights || insights.source === "none" || insights.taskMetrics.length === 0) {
4131
+ return;
4132
+ }
4133
+ writeLine(` ${bold}Agent Insights${RESET} ${colors.dim}(${insights.days}d)${RESET}`);
4134
+ writeLine();
4135
+ const totals = insights.taskMetrics.reduce(
4136
+ (acc, t) => ({
4137
+ tasks: acc.tasks + t.tasksTotal,
4138
+ completed: acc.completed + t.tasksCompleted,
4139
+ failed: acc.failed + t.tasksFailed,
4140
+ retries: acc.retries + t.totalRetries,
4141
+ withRetries: acc.withRetries + t.tasksWithRetries
4142
+ }),
4143
+ { tasks: 0, completed: 0, failed: 0, retries: 0, withRetries: 0 }
4144
+ );
4145
+ if (totals.tasks > 0) {
4146
+ const successRate = totals.tasks > 0 ? (totals.completed / totals.tasks * 100).toFixed(0) : "0";
4147
+ const successColor = parseInt(successRate) >= 80 ? colors.green : parseInt(successRate) >= 60 ? colors.yellow : colors.red;
4148
+ writeLine(` ${colors.dim}Tasks:${RESET} ${colors.green}${totals.completed}${RESET}${colors.dim}/${totals.tasks} completed${RESET} ${successColor}${successRate}%${RESET}${colors.dim} success${RESET} ${colors.red}${totals.failed}${RESET}${colors.dim} failed${RESET}`);
4149
+ if (totals.retries > 0) {
4150
+ const retryRate = totals.tasks > 0 ? (totals.withRetries / totals.tasks * 100).toFixed(0) : "0";
4151
+ const retryColor = parseInt(retryRate) > 30 ? colors.red : parseInt(retryRate) > 15 ? colors.yellow : colors.green;
4152
+ writeLine(` ${colors.dim}Retries:${RESET} ${retryColor}${totals.retries}${RESET}${colors.dim} total${RESET} ${retryColor}${retryRate}%${RESET}${colors.dim} of tasks needed retry${RESET}`);
4153
+ }
4154
+ }
4155
+ writeLine();
4156
+ }
3456
4157
 
3457
4158
  // src/commands/issues.ts
3458
- import { execSync as execSync3 } from "child_process";
4159
+ import { execSync as execSync5 } from "child_process";
3459
4160
  var DEFAULT_ORG = "agents-squads";
3460
4161
  var DEFAULT_REPOS = ["hq", "agents-squads-web", "squads-cli"];
3461
4162
  async function issuesCommand(options = {}) {
@@ -3465,7 +4166,7 @@ async function issuesCommand(options = {}) {
3465
4166
  writeLine(` ${gradient("squads")} ${colors.dim}issues${RESET}`);
3466
4167
  writeLine();
3467
4168
  try {
3468
- execSync3("gh --version", { stdio: "pipe" });
4169
+ execSync5("gh --version", { stdio: "pipe" });
3469
4170
  } catch {
3470
4171
  writeLine(` ${colors.red}GitHub CLI (gh) not found${RESET}`);
3471
4172
  writeLine(` ${colors.dim}Install: brew install gh${RESET}`);
@@ -3476,7 +4177,7 @@ async function issuesCommand(options = {}) {
3476
4177
  let totalOpen = 0;
3477
4178
  for (const repo of repos) {
3478
4179
  try {
3479
- const result = execSync3(
4180
+ const result = execSync5(
3480
4181
  `gh issue list -R ${org}/${repo} --state open --json number,title,state,labels,createdAt --limit 50`,
3481
4182
  { stdio: "pipe", encoding: "utf-8" }
3482
4183
  );
@@ -3527,7 +4228,7 @@ async function issuesCommand(options = {}) {
3527
4228
  }
3528
4229
 
3529
4230
  // src/commands/solve-issues.ts
3530
- import { execSync as execSync4, spawn as spawn2 } from "child_process";
4231
+ import { execSync as execSync6, spawn as spawn2 } from "child_process";
3531
4232
  import ora3 from "ora";
3532
4233
  var DEFAULT_ORG2 = "agents-squads";
3533
4234
  var DEFAULT_REPOS2 = ["hq", "agents-squads-web", "squads-cli", "agents-squads"];
@@ -3537,7 +4238,7 @@ async function solveIssuesCommand(options = {}) {
3537
4238
  writeLine(` ${gradient("squads")} ${colors.dim}solve-issues${RESET}`);
3538
4239
  writeLine();
3539
4240
  try {
3540
- execSync4("gh --version", { stdio: "pipe" });
4241
+ execSync6("gh --version", { stdio: "pipe" });
3541
4242
  } catch {
3542
4243
  writeLine(` ${colors.red}GitHub CLI (gh) not found${RESET}`);
3543
4244
  writeLine(` ${colors.dim}Install: brew install gh${RESET}`);
@@ -3547,7 +4248,7 @@ async function solveIssuesCommand(options = {}) {
3547
4248
  if (options.issue) {
3548
4249
  const repo = options.repo || "hq";
3549
4250
  try {
3550
- const result = execSync4(
4251
+ const result = execSync6(
3551
4252
  `gh issue view ${options.issue} -R ${DEFAULT_ORG2}/${repo} --json number,title,labels,body`,
3552
4253
  { stdio: "pipe", encoding: "utf-8" }
3553
4254
  );
@@ -3560,7 +4261,7 @@ async function solveIssuesCommand(options = {}) {
3560
4261
  } else {
3561
4262
  for (const repo of repos) {
3562
4263
  try {
3563
- const result = execSync4(
4264
+ const result = execSync6(
3564
4265
  `gh issue list -R ${DEFAULT_ORG2}/${repo} --label "ready-to-fix" --state open --json number,title,labels --limit 20`,
3565
4266
  { stdio: "pipe", encoding: "utf-8" }
3566
4267
  );
@@ -3614,7 +4315,7 @@ function showSolveInstructions(issues) {
3614
4315
  async function solveWithClaude(issues) {
3615
4316
  const spinner = ora3("Starting issue solver...").start();
3616
4317
  try {
3617
- execSync4("which claude", { stdio: "pipe" });
4318
+ execSync6("which claude", { stdio: "pipe" });
3618
4319
  } catch {
3619
4320
  spinner.fail("Claude CLI not found");
3620
4321
  writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
@@ -3700,9 +4401,9 @@ function executeClaudePrompt(prompt) {
3700
4401
  }
3701
4402
 
3702
4403
  // src/commands/open-issues.ts
3703
- import { execSync as execSync5, spawn as spawn3 } from "child_process";
3704
- import { readdirSync as readdirSync6 } from "fs";
3705
- import { join as join10 } from "path";
4404
+ import { execSync as execSync7, spawn as spawn3 } from "child_process";
4405
+ import { readdirSync as readdirSync7 } from "fs";
4406
+ import { join as join12 } from "path";
3706
4407
  import ora4 from "ora";
3707
4408
  var ISSUE_FINDER_PATTERNS = [
3708
4409
  "*-eval.md",
@@ -3757,10 +4458,10 @@ async function openIssuesCommand(options = {}) {
3757
4458
  }
3758
4459
  function findEvalAgents(squadsDir, filterSquad) {
3759
4460
  const agents = [];
3760
- const squads = readdirSync6(squadsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).filter((d) => !filterSquad || d.name === filterSquad).map((d) => d.name);
4461
+ const squads = readdirSync7(squadsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).filter((d) => !filterSquad || d.name === filterSquad).map((d) => d.name);
3761
4462
  for (const squad of squads) {
3762
- const squadPath = join10(squadsDir, squad);
3763
- const files = readdirSync6(squadPath).filter((f) => f.endsWith(".md"));
4463
+ const squadPath = join12(squadsDir, squad);
4464
+ const files = readdirSync7(squadPath).filter((f) => f.endsWith(".md"));
3764
4465
  for (const file of files) {
3765
4466
  const isEval = ISSUE_FINDER_PATTERNS.some((pattern) => {
3766
4467
  const regex = new RegExp("^" + pattern.replace("*", ".*") + "$");
@@ -3770,7 +4471,7 @@ function findEvalAgents(squadsDir, filterSquad) {
3770
4471
  agents.push({
3771
4472
  name: file,
3772
4473
  squad,
3773
- path: join10(squadPath, file)
4474
+ path: join12(squadPath, file)
3774
4475
  });
3775
4476
  }
3776
4477
  }
@@ -3796,7 +4497,7 @@ function showRunInstructions(agents) {
3796
4497
  async function runEvaluators(agents) {
3797
4498
  const spinner = ora4("Starting evaluators...").start();
3798
4499
  try {
3799
- execSync5("which claude", { stdio: "pipe" });
4500
+ execSync7("which claude", { stdio: "pipe" });
3800
4501
  } catch {
3801
4502
  spinner.fail("Claude CLI not found");
3802
4503
  writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
@@ -3880,9 +4581,9 @@ import open from "open";
3880
4581
 
3881
4582
  // src/lib/auth.ts
3882
4583
  import { createClient } from "@supabase/supabase-js";
3883
- import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6 } from "fs";
3884
- import { join as join11 } from "path";
3885
- import { homedir as homedir3 } from "os";
4584
+ import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8 } from "fs";
4585
+ import { join as join13 } from "path";
4586
+ import { homedir as homedir4 } from "os";
3886
4587
  import http from "http";
3887
4588
  var PERSONAL_DOMAINS = [
3888
4589
  "gmail.com",
@@ -3910,8 +4611,8 @@ var PERSONAL_DOMAINS = [
3910
4611
  "tutanota.com",
3911
4612
  "hey.com"
3912
4613
  ];
3913
- var AUTH_DIR = join11(homedir3(), ".squads-cli");
3914
- var AUTH_PATH = join11(AUTH_DIR, "auth.json");
4614
+ var AUTH_DIR = join13(homedir4(), ".squads-cli");
4615
+ var AUTH_PATH = join13(AUTH_DIR, "auth.json");
3915
4616
  function isPersonalEmail(email) {
3916
4617
  const domain = email.split("@")[1]?.toLowerCase();
3917
4618
  return PERSONAL_DOMAINS.includes(domain);
@@ -3919,23 +4620,23 @@ function isPersonalEmail(email) {
3919
4620
  function getEmailDomain(email) {
3920
4621
  return email.split("@")[1]?.toLowerCase() || "";
3921
4622
  }
3922
- function saveSession(session) {
3923
- if (!existsSync11(AUTH_DIR)) {
3924
- mkdirSync6(AUTH_DIR, { recursive: true });
4623
+ function saveSession(session2) {
4624
+ if (!existsSync13(AUTH_DIR)) {
4625
+ mkdirSync8(AUTH_DIR, { recursive: true });
3925
4626
  }
3926
- writeFileSync7(AUTH_PATH, JSON.stringify(session, null, 2));
4627
+ writeFileSync9(AUTH_PATH, JSON.stringify(session2, null, 2));
3927
4628
  }
3928
4629
  function loadSession() {
3929
- if (!existsSync11(AUTH_PATH)) return null;
4630
+ if (!existsSync13(AUTH_PATH)) return null;
3930
4631
  try {
3931
- return JSON.parse(readFileSync8(AUTH_PATH, "utf-8"));
4632
+ return JSON.parse(readFileSync10(AUTH_PATH, "utf-8"));
3932
4633
  } catch {
3933
4634
  return null;
3934
4635
  }
3935
4636
  }
3936
4637
  function clearSession() {
3937
- if (existsSync11(AUTH_PATH)) {
3938
- writeFileSync7(AUTH_PATH, "");
4638
+ if (existsSync13(AUTH_PATH)) {
4639
+ writeFileSync9(AUTH_PATH, "");
3939
4640
  }
3940
4641
  }
3941
4642
  function startAuthCallbackServer(port = 54321) {
@@ -4032,7 +4733,7 @@ ${chalk2.dim("Want to stay updated?")}
4032
4733
  await track("cli.login.personal_email", { domain: getEmailDomain(email) });
4033
4734
  return;
4034
4735
  }
4035
- const session = {
4736
+ const session2 = {
4036
4737
  email,
4037
4738
  domain: getEmailDomain(email),
4038
4739
  status: "pending",
@@ -4040,9 +4741,9 @@ ${chalk2.dim("Want to stay updated?")}
4040
4741
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4041
4742
  accessToken: token
4042
4743
  };
4043
- saveSession(session);
4744
+ saveSession(session2);
4044
4745
  spinner.succeed(`Logged in as ${chalk2.cyan(email)}`);
4045
- await track("cli.login.success", { domain: session.domain });
4746
+ await track("cli.login.success", { domain: session2.domain });
4046
4747
  console.log(`
4047
4748
  ${chalk2.green("\u2713 Thanks for signing up!")}
4048
4749
 
@@ -4064,18 +4765,18 @@ ${chalk2.dim("Questions? Email us at")} ${chalk2.cyan("hello@agents-squads.com")
4064
4765
  }
4065
4766
  }
4066
4767
  async function logoutCommand() {
4067
- const session = loadSession();
4068
- if (!session) {
4768
+ const session2 = loadSession();
4769
+ if (!session2) {
4069
4770
  console.log(chalk2.yellow("Not logged in."));
4070
4771
  return;
4071
4772
  }
4072
4773
  clearSession();
4073
- console.log(chalk2.green(`\u2713 Logged out from ${session.email}`));
4774
+ console.log(chalk2.green(`\u2713 Logged out from ${session2.email}`));
4074
4775
  await track("cli.logout");
4075
4776
  }
4076
4777
  async function whoamiCommand() {
4077
- const session = loadSession();
4078
- if (!session) {
4778
+ const session2 = loadSession();
4779
+ if (!session2) {
4079
4780
  console.log(chalk2.yellow("Not logged in."));
4080
4781
  console.log(chalk2.dim("Run: squads login"));
4081
4782
  return;
@@ -4083,34 +4784,100 @@ async function whoamiCommand() {
4083
4784
  console.log(`
4084
4785
  ${chalk2.bold("Current Session")}
4085
4786
  ${chalk2.dim("\u2500".repeat(30))}
4086
- Email: ${chalk2.cyan(session.email)}
4087
- Domain: ${session.domain}
4088
- Status: ${session.status === "active" ? chalk2.green("Active") : chalk2.yellow("Pending")}
4089
- Since: ${new Date(session.createdAt).toLocaleDateString()}
4787
+ Email: ${chalk2.cyan(session2.email)}
4788
+ Domain: ${session2.domain}
4789
+ Status: ${session2.status === "active" ? chalk2.green("Active") : chalk2.yellow("Pending")}
4790
+ Since: ${new Date(session2.createdAt).toLocaleDateString()}
4090
4791
  `);
4091
4792
  }
4092
4793
 
4794
+ // src/commands/update.ts
4795
+ import { createInterface as createInterface2 } from "readline";
4796
+ async function confirm(message) {
4797
+ const rl = createInterface2({
4798
+ input: process.stdin,
4799
+ output: process.stdout
4800
+ });
4801
+ return new Promise((resolve) => {
4802
+ rl.question(` ${message} [y/N]: `, (answer) => {
4803
+ rl.close();
4804
+ resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
4805
+ });
4806
+ });
4807
+ }
4808
+ async function updateCommand(options = {}) {
4809
+ writeLine();
4810
+ writeLine(` ${gradient("squads")} ${colors.dim}update${RESET}`);
4811
+ writeLine();
4812
+ if (options.check) {
4813
+ writeLine(` ${colors.dim}Checking for updates...${RESET}`);
4814
+ const info2 = refreshVersionCache();
4815
+ writeLine();
4816
+ if (info2.updateAvailable) {
4817
+ writeLine(` ${colors.cyan}\u2B06${RESET} Update available: ${colors.dim}${info2.currentVersion}${RESET} \u2192 ${colors.green}${info2.latestVersion}${RESET}`);
4818
+ writeLine();
4819
+ writeLine(` ${colors.dim}Run \`squads update\` to install${RESET}`);
4820
+ } else {
4821
+ writeLine(` ${colors.green}${icons.success}${RESET} Already on latest version ${colors.cyan}${info2.currentVersion}${RESET}`);
4822
+ }
4823
+ writeLine();
4824
+ return;
4825
+ }
4826
+ writeLine(` ${colors.dim}Checking npm registry...${RESET}`);
4827
+ const info = refreshVersionCache();
4828
+ if (!info.updateAvailable) {
4829
+ writeLine();
4830
+ writeLine(` ${colors.green}${icons.success}${RESET} Already on latest version ${colors.cyan}${info.currentVersion}${RESET}`);
4831
+ writeLine();
4832
+ return;
4833
+ }
4834
+ writeLine();
4835
+ writeLine(` ${colors.cyan}\u2B06${RESET} Update available: ${colors.dim}${info.currentVersion}${RESET} \u2192 ${colors.green}${info.latestVersion}${RESET}`);
4836
+ writeLine();
4837
+ const shouldUpdate = options.yes || await confirm("Update now?");
4838
+ if (!shouldUpdate) {
4839
+ writeLine();
4840
+ writeLine(` ${colors.dim}Update skipped${RESET}`);
4841
+ writeLine();
4842
+ return;
4843
+ }
4844
+ writeLine();
4845
+ writeLine(` ${colors.dim}Installing update...${RESET}`);
4846
+ writeLine();
4847
+ const result = performUpdate();
4848
+ writeLine();
4849
+ if (result.success) {
4850
+ writeLine(` ${colors.green}${icons.success}${RESET} Updated to ${colors.green}${info.latestVersion}${RESET}`);
4851
+ writeLine(` ${colors.dim}Restart your terminal to use the new version${RESET}`);
4852
+ } else {
4853
+ writeLine(` ${colors.red}${icons.error}${RESET} Update failed: ${result.error}`);
4854
+ writeLine(` ${colors.dim}Try manually: npm update -g squads-cli${RESET}`);
4855
+ process.exitCode = 1;
4856
+ }
4857
+ writeLine();
4858
+ }
4859
+
4093
4860
  // src/commands/progress.ts
4094
- import { execSync as execSync6 } from "child_process";
4095
- import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
4096
- import { join as join12 } from "path";
4861
+ import { execSync as execSync8 } from "child_process";
4862
+ import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9 } from "fs";
4863
+ import { join as join14 } from "path";
4097
4864
  function getTasksFilePath() {
4098
4865
  const memoryDir = findMemoryDir();
4099
4866
  if (!memoryDir) {
4100
4867
  const cwd = process.cwd();
4101
- const agentsDir = join12(cwd, ".agents");
4102
- if (!existsSync12(agentsDir)) {
4103
- mkdirSync7(agentsDir, { recursive: true });
4868
+ const agentsDir = join14(cwd, ".agents");
4869
+ if (!existsSync14(agentsDir)) {
4870
+ mkdirSync9(agentsDir, { recursive: true });
4104
4871
  }
4105
- return join12(agentsDir, "tasks.json");
4872
+ return join14(agentsDir, "tasks.json");
4106
4873
  }
4107
- return join12(memoryDir, "..", "tasks.json");
4874
+ return join14(memoryDir, "..", "tasks.json");
4108
4875
  }
4109
4876
  function loadTasks() {
4110
4877
  const tasksPath = getTasksFilePath();
4111
- if (existsSync12(tasksPath)) {
4878
+ if (existsSync14(tasksPath)) {
4112
4879
  try {
4113
- return JSON.parse(readFileSync9(tasksPath, "utf-8"));
4880
+ return JSON.parse(readFileSync11(tasksPath, "utf-8"));
4114
4881
  } catch {
4115
4882
  return { tasks: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
4116
4883
  }
@@ -4120,7 +4887,7 @@ function loadTasks() {
4120
4887
  function saveTasks(data) {
4121
4888
  const tasksPath = getTasksFilePath();
4122
4889
  data.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
4123
- writeFileSync8(tasksPath, JSON.stringify(data, null, 2));
4890
+ writeFileSync10(tasksPath, JSON.stringify(data, null, 2));
4124
4891
  }
4125
4892
  function getRecentActivity() {
4126
4893
  const activity = [];
@@ -4136,7 +4903,7 @@ function getRecentActivity() {
4136
4903
  marketing: ["marketing", "content", "social"]
4137
4904
  };
4138
4905
  try {
4139
- const logOutput = execSync6(
4906
+ const logOutput = execSync8(
4140
4907
  'git log --since="24 hours ago" --format="%h|%aI|%s" 2>/dev/null',
4141
4908
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4142
4909
  ).trim();
@@ -4270,7 +5037,7 @@ function getElapsedTime(startTime) {
4270
5037
  }
4271
5038
 
4272
5039
  // src/commands/results.ts
4273
- import { execSync as execSync7 } from "child_process";
5040
+ import { execSync as execSync9 } from "child_process";
4274
5041
  function getGitStats(days = 7) {
4275
5042
  const stats = /* @__PURE__ */ new Map();
4276
5043
  const squadKeywords = {
@@ -4285,7 +5052,7 @@ function getGitStats(days = 7) {
4285
5052
  marketing: ["marketing"]
4286
5053
  };
4287
5054
  try {
4288
- const logOutput = execSync7(
5055
+ const logOutput = execSync9(
4289
5056
  `git log --since="${days} days ago" --format="%s" --name-only 2>/dev/null`,
4290
5057
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4291
5058
  ).trim();
@@ -4324,7 +5091,7 @@ function getGitHubStats2(days = 7) {
4324
5091
  const prsMerged = /* @__PURE__ */ new Map();
4325
5092
  const issuesClosed = /* @__PURE__ */ new Map();
4326
5093
  try {
4327
- const prsOutput = execSync7(
5094
+ const prsOutput = execSync9(
4328
5095
  `gh pr list --state all --json title,createdAt,mergedAt --limit 50 2>/dev/null`,
4329
5096
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4330
5097
  );
@@ -4339,7 +5106,7 @@ function getGitHubStats2(days = 7) {
4339
5106
  prsMerged.set(squad, (prsMerged.get(squad) || 0) + 1);
4340
5107
  }
4341
5108
  }
4342
- const issuesOutput = execSync7(
5109
+ const issuesOutput = execSync9(
4343
5110
  `gh issue list --state closed --json title,closedAt --limit 50 2>/dev/null`,
4344
5111
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4345
5112
  );
@@ -4471,19 +5238,19 @@ async function resultsCommand(options = {}) {
4471
5238
  }
4472
5239
 
4473
5240
  // src/commands/workers.ts
4474
- import { execSync as execSync8 } from "child_process";
4475
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
4476
- import { join as join13 } from "path";
5241
+ import { execSync as execSync10 } from "child_process";
5242
+ import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
5243
+ import { join as join15 } from "path";
4477
5244
  function getTasksFilePath2() {
4478
5245
  const memoryDir = findMemoryDir();
4479
5246
  if (!memoryDir) return null;
4480
- return join13(memoryDir, "..", "tasks.json");
5247
+ return join15(memoryDir, "..", "tasks.json");
4481
5248
  }
4482
5249
  function loadActiveTasks() {
4483
5250
  const tasksPath = getTasksFilePath2();
4484
- if (!tasksPath || !existsSync13(tasksPath)) return [];
5251
+ if (!tasksPath || !existsSync15(tasksPath)) return [];
4485
5252
  try {
4486
- const data = JSON.parse(readFileSync10(tasksPath, "utf-8"));
5253
+ const data = JSON.parse(readFileSync12(tasksPath, "utf-8"));
4487
5254
  return data.tasks?.filter((t) => t.status === "active") || [];
4488
5255
  } catch {
4489
5256
  return [];
@@ -4492,7 +5259,7 @@ function loadActiveTasks() {
4492
5259
  function getRunningProcesses() {
4493
5260
  const processes = [];
4494
5261
  try {
4495
- const psOutput = execSync8(
5262
+ const psOutput = execSync10(
4496
5263
  'ps aux | grep -E "claude|squads|astro|node.*agent" | grep -v grep',
4497
5264
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4498
5265
  ).trim();
@@ -4543,7 +5310,7 @@ async function workersCommand(options = {}) {
4543
5310
  writeLine();
4544
5311
  if (options.kill) {
4545
5312
  try {
4546
- execSync8(`kill ${options.kill}`, { stdio: "pipe" });
5313
+ execSync10(`kill ${options.kill}`, { stdio: "pipe" });
4547
5314
  writeLine(` ${icons.success} Killed process ${colors.cyan}${options.kill}${RESET}`);
4548
5315
  writeLine();
4549
5316
  return;
@@ -4629,26 +5396,689 @@ function getElapsedTime2(startTime) {
4629
5396
  return "<1m";
4630
5397
  }
4631
5398
 
5399
+ // src/commands/sessions.ts
5400
+ async function sessionsCommand(options = {}) {
5401
+ cleanupStaleSessions();
5402
+ const sessions2 = getActiveSessions();
5403
+ const summary = getSessionSummary();
5404
+ if (options.json) {
5405
+ console.log(JSON.stringify({ sessions: sessions2, summary }, null, 2));
5406
+ return;
5407
+ }
5408
+ writeLine();
5409
+ writeLine(` ${gradient("squads")} ${colors.dim}sessions${RESET}`);
5410
+ writeLine();
5411
+ if (sessions2.length === 0) {
5412
+ writeLine(` ${colors.dim}No active sessions${RESET}`);
5413
+ writeLine();
5414
+ writeLine(` ${colors.dim}Sessions are tracked automatically when Claude Code runs.${RESET}`);
5415
+ writeLine(` ${colors.dim}Each session updates its heartbeat via squads CLI commands.${RESET}`);
5416
+ writeLine();
5417
+ return;
5418
+ }
5419
+ const squadText = summary.squadCount === 1 ? "squad" : "squads";
5420
+ const sessionText = summary.totalSessions === 1 ? "session" : "sessions";
5421
+ writeLine(` ${colors.green}${summary.totalSessions}${RESET} active ${sessionText} ${colors.dim}across${RESET} ${colors.cyan}${summary.squadCount}${RESET} ${squadText}`);
5422
+ writeLine();
5423
+ const bySquad = {};
5424
+ for (const session2 of sessions2) {
5425
+ const squad = session2.squad || "unknown";
5426
+ if (!bySquad[squad]) bySquad[squad] = [];
5427
+ bySquad[squad].push(session2);
5428
+ }
5429
+ const w = { squad: 16, sessions: 10, activity: 14 };
5430
+ const tableWidth = w.squad + w.sessions + w.activity + 4;
5431
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
5432
+ const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.squad)}${RESET}${bold}${padEnd("SESSIONS", w.sessions)}${RESET}${bold}LAST ACTIVITY${RESET} ${colors.purple}${box.vertical}${RESET}`;
5433
+ writeLine(header);
5434
+ writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
5435
+ for (const [squad, squadSessions] of Object.entries(bySquad).sort()) {
5436
+ let mostRecent = 0;
5437
+ for (const session2 of squadSessions) {
5438
+ const ts = new Date(session2.lastHeartbeat).getTime();
5439
+ if (ts > mostRecent) mostRecent = ts;
5440
+ }
5441
+ const lastActivity = formatTimeAgo(mostRecent);
5442
+ const activityColor = getActivityColor(mostRecent);
5443
+ const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squad, w.squad)}${RESET}${padEnd(String(squadSessions.length), w.sessions)}${padEnd(`${activityColor}${lastActivity}${RESET}`, w.activity)}${colors.purple}${box.vertical}${RESET}`;
5444
+ writeLine(row);
5445
+ }
5446
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
5447
+ if (options.verbose) {
5448
+ writeLine();
5449
+ writeLine(` ${bold}Session Details${RESET}`);
5450
+ writeLine();
5451
+ for (const session2 of sessions2) {
5452
+ const squad = session2.squad || "unknown";
5453
+ const ago = formatTimeAgo(new Date(session2.lastHeartbeat).getTime());
5454
+ writeLine(` ${icons.active} ${colors.white}${session2.sessionId}${RESET}`);
5455
+ writeLine(` ${colors.dim}squad: ${squad} | pid: ${session2.pid} | heartbeat: ${ago}${RESET}`);
5456
+ writeLine(` ${colors.dim}cwd: ${session2.cwd}${RESET}`);
5457
+ }
5458
+ }
5459
+ writeLine();
5460
+ writeLine(` ${colors.dim}$${RESET} squads sessions -v ${colors.dim}Show session details${RESET}`);
5461
+ writeLine();
5462
+ }
5463
+ function formatTimeAgo(timestamp) {
5464
+ const now = Date.now();
5465
+ const diff = now - timestamp;
5466
+ const seconds = Math.floor(diff / 1e3);
5467
+ const minutes = Math.floor(seconds / 60);
5468
+ if (minutes >= 1) {
5469
+ return `${minutes}m ago`;
5470
+ }
5471
+ return `${seconds}s ago`;
5472
+ }
5473
+ function getActivityColor(timestamp) {
5474
+ const now = Date.now();
5475
+ const diff = now - timestamp;
5476
+ const minutes = Math.floor(diff / (1e3 * 60));
5477
+ if (minutes < 1) return colors.green;
5478
+ if (minutes < 3) return colors.yellow;
5479
+ return colors.dim;
5480
+ }
5481
+ async function sessionsHistoryCommand(options = {}) {
5482
+ const days = options.days || 7;
5483
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
5484
+ const stats = await getSessionHistoryStats({
5485
+ since,
5486
+ squad: options.squad
5487
+ });
5488
+ const recentSessions = await getRecentSessions(10);
5489
+ if (options.json) {
5490
+ console.log(JSON.stringify({ stats, recentSessions }, null, 2));
5491
+ return;
5492
+ }
5493
+ writeLine();
5494
+ writeLine(` ${gradient("squads")} ${colors.dim}sessions history${RESET} ${colors.dim}(${days}d)${RESET}`);
5495
+ writeLine();
5496
+ if (stats.totalSessions === 0) {
5497
+ writeLine(` ${colors.dim}No session history found${RESET}`);
5498
+ writeLine();
5499
+ writeLine(` ${colors.dim}Session events are logged to .agents/sessions/history.jsonl${RESET}`);
5500
+ writeLine();
5501
+ return;
5502
+ }
5503
+ const avgMinutes = Math.round(stats.avgDurationMs / 6e4);
5504
+ const totalHours = Math.round(stats.totalDurationMs / 36e5 * 10) / 10;
5505
+ writeLine(` ${bold}Summary${RESET}`);
5506
+ writeLine(` ${colors.cyan}${stats.totalSessions}${RESET} sessions ${colors.dim}\u2502${RESET} ${colors.green}${totalHours}h${RESET} total ${colors.dim}\u2502${RESET} ${colors.yellow}${avgMinutes}m${RESET} avg ${colors.dim}\u2502${RESET} ${colors.purple}${stats.peakConcurrent}${RESET} peak`);
5507
+ writeLine();
5508
+ const squads = Object.entries(stats.bySquad).sort((a, b) => b[1].count - a[1].count);
5509
+ if (squads.length > 0) {
5510
+ const w = { squad: 16, sessions: 10, duration: 12 };
5511
+ const tableWidth = w.squad + w.sessions + w.duration + 4;
5512
+ writeLine(` ${bold}By Squad${RESET}`);
5513
+ writeLine();
5514
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
5515
+ const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.squad)}${RESET}${bold}${padEnd("SESSIONS", w.sessions)}${RESET}${bold}DURATION${RESET} ${colors.purple}${box.vertical}${RESET}`;
5516
+ writeLine(header);
5517
+ writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
5518
+ for (const [squad, data] of squads) {
5519
+ const hours = Math.round(data.durationMs / 36e5 * 10) / 10;
5520
+ const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(squad, w.squad)}${RESET}${padEnd(String(data.count), w.sessions)}${padEnd(`${hours}h`, w.duration)}${colors.purple}${box.vertical}${RESET}`;
5521
+ writeLine(row);
5522
+ }
5523
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
5524
+ }
5525
+ if (recentSessions.length > 0) {
5526
+ writeLine();
5527
+ writeLine(` ${bold}Recent Sessions${RESET}`);
5528
+ writeLine();
5529
+ for (const event of recentSessions.slice(0, 5)) {
5530
+ const squad = event.squad || "unknown";
5531
+ const date = new Date(event.ts);
5532
+ const timeStr = date.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
5533
+ const dateStr = date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
5534
+ writeLine(` ${colors.dim}${dateStr} ${timeStr}${RESET} ${colors.cyan}${squad}${RESET} ${colors.dim}${event.sessionId.slice(0, 8)}${RESET}`);
5535
+ }
5536
+ }
5537
+ const dates = Object.entries(stats.byDate).sort((a, b) => b[0].localeCompare(a[0])).slice(0, 7);
5538
+ if (dates.length > 1) {
5539
+ writeLine();
5540
+ writeLine(` ${bold}Daily Activity${RESET}`);
5541
+ writeLine();
5542
+ for (const [date, count] of dates) {
5543
+ const bar = "\u2588".repeat(Math.min(count, 20));
5544
+ writeLine(` ${colors.dim}${date}${RESET} ${colors.green}${bar}${RESET} ${count}`);
5545
+ }
5546
+ }
5547
+ writeLine();
5548
+ writeLine(` ${colors.dim}$${RESET} squads sessions history --days 30 ${colors.dim}Longer history${RESET}`);
5549
+ writeLine(` ${colors.dim}$${RESET} squads sessions history -s website ${colors.dim}Filter by squad${RESET}`);
5550
+ writeLine();
5551
+ }
5552
+
5553
+ // src/commands/session.ts
5554
+ async function sessionStartCommand(options = {}) {
5555
+ cleanupStaleSessions();
5556
+ const session2 = startSession(options.squad);
5557
+ if (!options.quiet) {
5558
+ if (session2) {
5559
+ writeLine(`${icons.active} Session started: ${colors.cyan}${session2.sessionId}${RESET}`);
5560
+ if (session2.squad) {
5561
+ writeLine(` ${colors.dim}Squad: ${session2.squad}${RESET}`);
5562
+ }
5563
+ } else {
5564
+ writeLine(`${colors.yellow}Could not start session (no .agents directory)${RESET}`);
5565
+ }
5566
+ }
5567
+ }
5568
+ async function sessionStopCommand(options = {}) {
5569
+ const stopped = stopSession();
5570
+ if (!options.quiet) {
5571
+ if (stopped) {
5572
+ writeLine(`${icons.progress} Session stopped`);
5573
+ } else {
5574
+ writeLine(`${colors.dim}No active session to stop${RESET}`);
5575
+ }
5576
+ }
5577
+ }
5578
+ async function sessionHeartbeatCommand(options = {}) {
5579
+ const updated = updateHeartbeat();
5580
+ if (!options.quiet) {
5581
+ if (updated) {
5582
+ writeLine(`${icons.active} Heartbeat updated`);
5583
+ } else {
5584
+ writeLine(`${colors.dim}No session to update${RESET}`);
5585
+ }
5586
+ }
5587
+ }
5588
+ async function detectSquadCommand() {
5589
+ const squad = detectSquad();
5590
+ if (squad) {
5591
+ process.stdout.write(squad);
5592
+ }
5593
+ }
5594
+
5595
+ // src/commands/stack.ts
5596
+ import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
5597
+ import { join as join16 } from "path";
5598
+ import { homedir as homedir5 } from "os";
5599
+ import { execSync as execSync11, spawn as spawn4 } from "child_process";
5600
+ var DEFAULT_CONFIG = {
5601
+ SQUADS_DATABASE_URL: "postgresql://squads:squads@localhost:5433/squads",
5602
+ SQUADS_BRIDGE_URL: "http://localhost:8088",
5603
+ LANGFUSE_HOST: "http://localhost:3100",
5604
+ LANGFUSE_PUBLIC_KEY: "",
5605
+ LANGFUSE_SECRET_KEY: "",
5606
+ REDIS_URL: "redis://localhost:6379"
5607
+ };
5608
+ var CONFIG_PATH2 = join16(homedir5(), ".squadsrc");
5609
+ function loadStackConfig() {
5610
+ if (!existsSync16(CONFIG_PATH2)) {
5611
+ return null;
5612
+ }
5613
+ try {
5614
+ const content = readFileSync13(CONFIG_PATH2, "utf-8");
5615
+ const config2 = {};
5616
+ for (const line of content.split("\n")) {
5617
+ const trimmed = line.trim();
5618
+ if (!trimmed || trimmed.startsWith("#")) continue;
5619
+ const match = trimmed.match(/^export\s+(\w+)=["']?([^"'\n]*)["']?$/);
5620
+ if (match) {
5621
+ const [, key, value] = match;
5622
+ if (key in DEFAULT_CONFIG) {
5623
+ config2[key] = value;
5624
+ }
5625
+ }
5626
+ }
5627
+ return config2;
5628
+ } catch {
5629
+ return null;
5630
+ }
5631
+ }
5632
+ function saveStackConfig(config2) {
5633
+ const lines = [
5634
+ "# Squads CLI Local Stack Configuration",
5635
+ "# Generated by: squads stack init",
5636
+ `# Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
5637
+ "",
5638
+ "# Database",
5639
+ `export SQUADS_DATABASE_URL="${config2.SQUADS_DATABASE_URL}"`,
5640
+ "",
5641
+ "# Bridge API",
5642
+ `export SQUADS_BRIDGE_URL="${config2.SQUADS_BRIDGE_URL}"`,
5643
+ "",
5644
+ "# Langfuse",
5645
+ `export LANGFUSE_HOST="${config2.LANGFUSE_HOST}"`,
5646
+ `export LANGFUSE_PUBLIC_KEY="${config2.LANGFUSE_PUBLIC_KEY}"`,
5647
+ `export LANGFUSE_SECRET_KEY="${config2.LANGFUSE_SECRET_KEY}"`,
5648
+ "",
5649
+ "# Redis",
5650
+ `export REDIS_URL="${config2.REDIS_URL}"`,
5651
+ "",
5652
+ "# To activate: source ~/.squadsrc",
5653
+ ""
5654
+ ];
5655
+ writeFileSync11(CONFIG_PATH2, lines.join("\n"));
5656
+ }
5657
+ function applyStackConfig() {
5658
+ const config2 = loadStackConfig();
5659
+ if (!config2) return;
5660
+ for (const [key, value] of Object.entries(config2)) {
5661
+ if (value && !process.env[key]) {
5662
+ process.env[key] = value;
5663
+ }
5664
+ }
5665
+ }
5666
+ function isDockerRunning() {
5667
+ try {
5668
+ execSync11("docker info", { stdio: "ignore" });
5669
+ return true;
5670
+ } catch {
5671
+ return false;
5672
+ }
5673
+ }
5674
+ function getContainerStatus(name) {
5675
+ try {
5676
+ const runningOutput = execSync11(
5677
+ `docker inspect ${name} --format '{{.State.Running}}'`,
5678
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
5679
+ ).trim();
5680
+ const running = runningOutput === "true";
5681
+ if (!running) {
5682
+ return { name, running: false, healthy: false };
5683
+ }
5684
+ let port;
5685
+ try {
5686
+ const portOutput = execSync11(
5687
+ `docker inspect ${name} --format '{{range .NetworkSettings.Ports}}{{range .}}{{.HostPort}}{{end}}{{end}}'`,
5688
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
5689
+ ).trim();
5690
+ port = portOutput || void 0;
5691
+ } catch {
5692
+ }
5693
+ let healthy = true;
5694
+ try {
5695
+ const healthOutput = execSync11(
5696
+ `docker inspect ${name} --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}'`,
5697
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
5698
+ ).trim();
5699
+ if (healthOutput === "healthy" || healthOutput === "none") {
5700
+ healthy = true;
5701
+ } else if (healthOutput === "starting") {
5702
+ healthy = false;
5703
+ } else {
5704
+ healthy = false;
5705
+ }
5706
+ } catch {
5707
+ healthy = true;
5708
+ }
5709
+ return { name, running, healthy, port };
5710
+ } catch {
5711
+ return { name, running: false, healthy: false };
5712
+ }
5713
+ }
5714
+ async function checkService(url, timeout = 2e3) {
5715
+ try {
5716
+ const controller = new AbortController();
5717
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
5718
+ const response = await fetch(url, { signal: controller.signal });
5719
+ clearTimeout(timeoutId);
5720
+ return response.ok;
5721
+ } catch {
5722
+ return false;
5723
+ }
5724
+ }
5725
+ function getLangfuseKeysFromDockerEnv() {
5726
+ const envPaths2 = [
5727
+ join16(process.cwd(), "docker", ".env"),
5728
+ join16(process.cwd(), "..", "squads-cli", "docker", ".env"),
5729
+ join16(homedir5(), "agents-squads", "squads-cli", "docker", ".env")
5730
+ ];
5731
+ for (const envPath of envPaths2) {
5732
+ if (existsSync16(envPath)) {
5733
+ const content = readFileSync13(envPath, "utf-8");
5734
+ const publicMatch = content.match(/LANGFUSE_PUBLIC_KEY=(\S+)/);
5735
+ const secretMatch = content.match(/LANGFUSE_SECRET_KEY=(\S+)/);
5736
+ if (publicMatch && secretMatch) {
5737
+ return {
5738
+ publicKey: publicMatch[1],
5739
+ secretKey: secretMatch[1]
5740
+ };
5741
+ }
5742
+ }
5743
+ }
5744
+ return null;
5745
+ }
5746
+ function findDockerComposeDir() {
5747
+ const candidates = [
5748
+ join16(process.cwd(), "docker"),
5749
+ join16(process.cwd(), "..", "squads-cli", "docker"),
5750
+ join16(homedir5(), "agents-squads", "squads-cli", "docker")
5751
+ ];
5752
+ for (const dir of candidates) {
5753
+ if (existsSync16(join16(dir, "docker-compose.yml"))) {
5754
+ return dir;
5755
+ }
5756
+ }
5757
+ return null;
5758
+ }
5759
+ async function stackInitCommand() {
5760
+ writeLine();
5761
+ writeLine(` ${gradient("squads")} ${colors.dim}stack init${RESET}`);
5762
+ writeLine();
5763
+ if (!isDockerRunning()) {
5764
+ writeLine(` ${colors.red}${icons.error}${RESET} Docker is not running`);
5765
+ writeLine(` ${colors.dim}Start Docker Desktop and try again${RESET}`);
5766
+ writeLine();
5767
+ return;
5768
+ }
5769
+ writeLine(` ${colors.green}${icons.success}${RESET} Docker is running`);
5770
+ const containers = [
5771
+ "squads-postgres",
5772
+ "squads-redis",
5773
+ "squads-bridge",
5774
+ "squads-langfuse",
5775
+ "squads-otel-collector"
5776
+ ];
5777
+ const statuses = containers.map(getContainerStatus);
5778
+ const allRunning = statuses.every((s) => s.running);
5779
+ writeLine();
5780
+ writeLine(` ${bold}Containers${RESET}`);
5781
+ for (const status of statuses) {
5782
+ const icon = status.running ? status.healthy ? `${colors.green}\u25CF${RESET}` : `${colors.yellow}\u25CF${RESET}` : `${colors.red}\u25CB${RESET}`;
5783
+ const portInfo = status.port ? `${colors.dim}:${status.port}${RESET}` : "";
5784
+ writeLine(` ${icon} ${status.name}${portInfo}`);
5785
+ }
5786
+ if (!allRunning) {
5787
+ writeLine();
5788
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} Some containers not running`);
5789
+ writeLine(` ${colors.dim}Run: squads stack up${RESET}`);
5790
+ writeLine();
5791
+ return;
5792
+ }
5793
+ const langfuseKeys = getLangfuseKeysFromDockerEnv();
5794
+ const config2 = {
5795
+ ...DEFAULT_CONFIG,
5796
+ LANGFUSE_PUBLIC_KEY: langfuseKeys?.publicKey || "",
5797
+ LANGFUSE_SECRET_KEY: langfuseKeys?.secretKey || ""
5798
+ };
5799
+ writeLine();
5800
+ writeLine(` ${bold}Testing connections${RESET}`);
5801
+ const bridgeOk = await checkService(`${config2.SQUADS_BRIDGE_URL}/health`);
5802
+ const langfuseOk = await checkService(`${config2.LANGFUSE_HOST}/api/public/health`);
5803
+ writeLine(
5804
+ ` ${bridgeOk ? colors.green + icons.success : colors.red + icons.error}${RESET} Bridge ${colors.dim}${config2.SQUADS_BRIDGE_URL}${RESET}`
5805
+ );
5806
+ writeLine(
5807
+ ` ${langfuseOk ? colors.green + icons.success : colors.red + icons.error}${RESET} Langfuse ${colors.dim}${config2.LANGFUSE_HOST}${RESET}`
5808
+ );
5809
+ saveStackConfig(config2);
5810
+ writeLine();
5811
+ writeLine(` ${colors.green}${icons.success}${RESET} Config saved to ${colors.cyan}~/.squadsrc${RESET}`);
5812
+ writeLine();
5813
+ writeLine(` ${colors.dim}To activate in current shell:${RESET}`);
5814
+ writeLine(` ${colors.cyan}source ~/.squadsrc${RESET}`);
5815
+ writeLine();
5816
+ writeLine(` ${colors.dim}Or add to your ~/.zshrc:${RESET}`);
5817
+ writeLine(` ${colors.cyan}[ -f ~/.squadsrc ] && source ~/.squadsrc${RESET}`);
5818
+ writeLine();
5819
+ }
5820
+ async function stackStatusCommand() {
5821
+ writeLine();
5822
+ writeLine(` ${gradient("squads")} ${colors.dim}stack status${RESET}`);
5823
+ writeLine();
5824
+ if (!isDockerRunning()) {
5825
+ writeLine(` ${colors.red}${icons.error}${RESET} Docker is not running`);
5826
+ writeLine();
5827
+ return;
5828
+ }
5829
+ const containers = [
5830
+ { name: "squads-postgres", service: "Database" },
5831
+ { name: "squads-redis", service: "Cache" },
5832
+ { name: "squads-bridge", service: "Bridge API" },
5833
+ { name: "squads-langfuse", service: "Langfuse" },
5834
+ { name: "squads-otel-collector", service: "Telemetry" }
5835
+ ];
5836
+ const w = { service: 12, container: 22, status: 12 };
5837
+ const tableWidth = w.service + w.container + w.status + 6;
5838
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
5839
+ writeLine(
5840
+ ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SERVICE", w.service)}${RESET}${bold}${padEnd("CONTAINER", w.container)}${RESET}${bold}STATUS${RESET} ${colors.purple}${box.vertical}${RESET}`
5841
+ );
5842
+ writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
5843
+ for (const { name, service } of containers) {
5844
+ const status = getContainerStatus(name);
5845
+ const statusIcon = status.running ? status.healthy ? `${colors.green}\u25CF healthy${RESET}` : `${colors.yellow}\u25CF starting${RESET}` : `${colors.red}\u25CB stopped${RESET}`;
5846
+ writeLine(
5847
+ ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(service, w.service)}${RESET}${colors.dim}${padEnd(name, w.container)}${RESET}${statusIcon} ${colors.purple}${box.vertical}${RESET}`
5848
+ );
5849
+ }
5850
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
5851
+ writeLine();
5852
+ const config2 = loadStackConfig();
5853
+ if (config2) {
5854
+ writeLine(` ${colors.green}${icons.success}${RESET} Config loaded from ${colors.cyan}~/.squadsrc${RESET}`);
5855
+ const envVars = ["SQUADS_DATABASE_URL", "SQUADS_BRIDGE_URL", "LANGFUSE_HOST"];
5856
+ const missing = envVars.filter((v) => !process.env[v]);
5857
+ if (missing.length > 0) {
5858
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} Env vars not in shell: ${colors.dim}${missing.join(", ")}${RESET}`);
5859
+ writeLine(` ${colors.dim}Run: source ~/.squadsrc${RESET}`);
5860
+ } else {
5861
+ writeLine(` ${colors.green}${icons.success}${RESET} All env vars loaded`);
5862
+ }
5863
+ } else {
5864
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} No config found`);
5865
+ writeLine(` ${colors.dim}Run: squads stack init${RESET}`);
5866
+ }
5867
+ writeLine();
5868
+ }
5869
+ function stackEnvCommand() {
5870
+ const config2 = loadStackConfig();
5871
+ if (!config2) {
5872
+ console.log(`# Squads Local Stack - Default Config`);
5873
+ console.log(`# Run 'squads stack init' to auto-detect and save`);
5874
+ console.log();
5875
+ for (const [key, value] of Object.entries(DEFAULT_CONFIG)) {
5876
+ console.log(`export ${key}="${value}"`);
5877
+ }
5878
+ return;
5879
+ }
5880
+ console.log(`# Squads Local Stack Config`);
5881
+ console.log(`# From: ~/.squadsrc`);
5882
+ console.log();
5883
+ for (const [key, value] of Object.entries(config2)) {
5884
+ console.log(`export ${key}="${value}"`);
5885
+ }
5886
+ }
5887
+ async function stackUpCommand() {
5888
+ writeLine();
5889
+ writeLine(` ${gradient("squads")} ${colors.dim}stack up${RESET}`);
5890
+ writeLine();
5891
+ const composeDir = findDockerComposeDir();
5892
+ if (!composeDir) {
5893
+ writeLine(` ${colors.red}${icons.error}${RESET} docker-compose.yml not found`);
5894
+ writeLine();
5895
+ writeLine(` ${colors.dim}Expected locations:${RESET}`);
5896
+ writeLine(` ${colors.dim} ./docker/docker-compose.yml${RESET}`);
5897
+ writeLine(` ${colors.dim} ~/agents-squads/squads-cli/docker/docker-compose.yml${RESET}`);
5898
+ writeLine();
5899
+ return;
5900
+ }
5901
+ writeLine(` ${colors.cyan}${icons.progress}${RESET} Starting containers from ${colors.dim}${composeDir}${RESET}`);
5902
+ writeLine();
5903
+ try {
5904
+ const child = spawn4("docker-compose", ["up", "-d"], {
5905
+ cwd: composeDir,
5906
+ stdio: "inherit"
5907
+ });
5908
+ await new Promise((resolve, reject) => {
5909
+ child.on("close", (code) => {
5910
+ if (code === 0) resolve();
5911
+ else reject(new Error(`docker-compose exited with code ${code}`));
5912
+ });
5913
+ child.on("error", reject);
5914
+ });
5915
+ writeLine();
5916
+ writeLine(` ${colors.green}${icons.success}${RESET} Stack started`);
5917
+ writeLine(` ${colors.dim}Run: squads stack init${RESET} to configure CLI`);
5918
+ writeLine();
5919
+ } catch (error) {
5920
+ writeLine(` ${colors.red}${icons.error}${RESET} Failed to start stack`);
5921
+ writeLine(` ${colors.dim}${error}${RESET}`);
5922
+ writeLine();
5923
+ }
5924
+ }
5925
+ async function stackHealthCommand(verbose = false) {
5926
+ writeLine();
5927
+ writeLine(` ${gradient("squads")} ${colors.dim}stack health${RESET}`);
5928
+ writeLine();
5929
+ if (!isDockerRunning()) {
5930
+ writeLine(` ${colors.red}${icons.error}${RESET} Docker is not running`);
5931
+ writeLine();
5932
+ process.exitCode = 1;
5933
+ return;
5934
+ }
5935
+ const containers = [
5936
+ { name: "squads-postgres", service: "postgres", healthUrl: "", critical: true },
5937
+ { name: "squads-redis", service: "redis", healthUrl: "", critical: true },
5938
+ { name: "squads-neo4j", service: "neo4j", healthUrl: "http://localhost:7474", critical: false },
5939
+ { name: "squads-bridge", service: "bridge", healthUrl: "http://localhost:8088/health", critical: true },
5940
+ { name: "squads-otel-collector", service: "otel", healthUrl: "", critical: false },
5941
+ { name: "squads-langfuse", service: "langfuse", healthUrl: "http://localhost:3100/api/public/health", critical: false },
5942
+ { name: "squads-mem0", service: "mem0", healthUrl: "http://localhost:8000/health", critical: false },
5943
+ { name: "squads-engram-mcp", service: "engram", healthUrl: "http://localhost:8080/", critical: false }
5944
+ ];
5945
+ let hasFailures = false;
5946
+ const results = [];
5947
+ for (const container of containers) {
5948
+ const status = getContainerStatus(container.name);
5949
+ let ok = status.running && status.healthy;
5950
+ let statusText = "";
5951
+ if (!status.running) {
5952
+ statusText = "stopped";
5953
+ ok = false;
5954
+ } else if (!status.healthy) {
5955
+ statusText = "unhealthy";
5956
+ ok = false;
5957
+ } else if (container.healthUrl) {
5958
+ const httpOk = await checkService(container.healthUrl);
5959
+ if (!httpOk) {
5960
+ statusText = "not responding";
5961
+ ok = false;
5962
+ } else {
5963
+ statusText = "healthy";
5964
+ }
5965
+ } else {
5966
+ statusText = "healthy";
5967
+ }
5968
+ let logs;
5969
+ if (!ok && verbose) {
5970
+ try {
5971
+ logs = execSync11(`docker logs ${container.name} --tail 10 2>&1`, {
5972
+ encoding: "utf-8",
5973
+ stdio: ["pipe", "pipe", "pipe"]
5974
+ });
5975
+ } catch {
5976
+ logs = "(no logs available)";
5977
+ }
5978
+ }
5979
+ if (!ok && container.critical) {
5980
+ hasFailures = true;
5981
+ }
5982
+ results.push({ name: container.name, service: container.service, ok, status: statusText, logs });
5983
+ }
5984
+ for (const r of results) {
5985
+ const icon = r.ok ? `${colors.green}\u2713${RESET}` : `${colors.red}\u2717${RESET}`;
5986
+ const statusColor = r.ok ? colors.green : colors.red;
5987
+ writeLine(` ${icon} ${padEnd(r.service, 10)} ${statusColor}${r.status}${RESET}`);
5988
+ if (r.logs && !r.ok) {
5989
+ writeLine(` ${colors.dim}${"-".repeat(40)}${RESET}`);
5990
+ for (const line of r.logs.split("\n").slice(0, 5)) {
5991
+ writeLine(` ${colors.dim}${line.substring(0, 70)}${RESET}`);
5992
+ }
5993
+ }
5994
+ }
5995
+ writeLine();
5996
+ const healthy = results.filter((r) => r.ok).length;
5997
+ const total = results.length;
5998
+ if (hasFailures) {
5999
+ writeLine(` ${colors.red}${icons.error}${RESET} ${healthy}/${total} services healthy - critical failures`);
6000
+ writeLine(` ${colors.dim}Run with -v for logs: squads stack health -v${RESET}`);
6001
+ process.exitCode = 1;
6002
+ } else if (healthy < total) {
6003
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} ${healthy}/${total} services healthy - non-critical issues`);
6004
+ } else {
6005
+ writeLine(` ${colors.green}${icons.success}${RESET} ${healthy}/${total} services healthy`);
6006
+ }
6007
+ writeLine();
6008
+ }
6009
+ function stackLogsCommand(service, tail = 50) {
6010
+ const containerMap = {
6011
+ postgres: "squads-postgres",
6012
+ redis: "squads-redis",
6013
+ neo4j: "squads-neo4j",
6014
+ bridge: "squads-bridge",
6015
+ otel: "squads-otel-collector",
6016
+ langfuse: "squads-langfuse",
6017
+ mem0: "squads-mem0",
6018
+ engram: "squads-engram-mcp"
6019
+ };
6020
+ const container = containerMap[service] || `squads-${service}`;
6021
+ try {
6022
+ execSync11(`docker logs ${container} --tail ${tail}`, { stdio: "inherit" });
6023
+ } catch {
6024
+ writeLine(` ${colors.red}${icons.error}${RESET} Container ${container} not found`);
6025
+ }
6026
+ }
6027
+ async function stackDownCommand() {
6028
+ writeLine();
6029
+ writeLine(` ${gradient("squads")} ${colors.dim}stack down${RESET}`);
6030
+ writeLine();
6031
+ const composeDir = findDockerComposeDir();
6032
+ if (!composeDir) {
6033
+ writeLine(` ${colors.red}${icons.error}${RESET} docker-compose.yml not found`);
6034
+ writeLine();
6035
+ return;
6036
+ }
6037
+ writeLine(` ${colors.cyan}${icons.progress}${RESET} Stopping containers...`);
6038
+ writeLine();
6039
+ try {
6040
+ const child = spawn4("docker-compose", ["down"], {
6041
+ cwd: composeDir,
6042
+ stdio: "inherit"
6043
+ });
6044
+ await new Promise((resolve, reject) => {
6045
+ child.on("close", (code) => {
6046
+ if (code === 0) resolve();
6047
+ else reject(new Error(`docker-compose exited with code ${code}`));
6048
+ });
6049
+ child.on("error", reject);
6050
+ });
6051
+ writeLine();
6052
+ writeLine(` ${colors.green}${icons.success}${RESET} Stack stopped`);
6053
+ writeLine();
6054
+ } catch (error) {
6055
+ writeLine(` ${colors.red}${icons.error}${RESET} Failed to stop stack`);
6056
+ writeLine(` ${colors.dim}${error}${RESET}`);
6057
+ writeLine();
6058
+ }
6059
+ }
6060
+
4632
6061
  // src/cli.ts
4633
6062
  var envPaths = [
4634
- join14(process.cwd(), ".env"),
4635
- join14(process.cwd(), "..", "hq", ".env"),
4636
- join14(homedir4(), "agents-squads", "hq", ".env")
6063
+ join17(process.cwd(), ".env"),
6064
+ join17(process.cwd(), "..", "hq", ".env"),
6065
+ join17(homedir6(), "agents-squads", "hq", ".env")
4637
6066
  ];
4638
6067
  for (const envPath of envPaths) {
4639
- if (existsSync14(envPath)) {
6068
+ if (existsSync17(envPath)) {
4640
6069
  config({ path: envPath, quiet: true });
4641
6070
  break;
4642
6071
  }
4643
6072
  }
6073
+ applyStackConfig();
4644
6074
  registerExitHandler();
4645
6075
  var program = new Command();
4646
6076
  program.name("squads").description("A CLI for humans and agents").version(version);
4647
6077
  program.command("init").description("Initialize a new squad project").option("-t, --template <template>", "Project template", "default").action(initCommand);
4648
- program.command("run <target>").description("Run a squad or agent").option("-v, --verbose", "Verbose output").option("-d, --dry-run", "Show what would be run without executing").option("-e, --execute", "Execute agent via Claude CLI (requires claude installed)").option("-a, --agent <agent>", "Run specific agent within squad").action(runCommand);
6078
+ program.command("run <target>").description("Run a squad or agent").option("-v, --verbose", "Verbose output").option("-d, --dry-run", "Show what would be run without executing").option("-e, --execute", "Execute agent via Claude CLI (requires claude installed)").option("-a, --agent <agent>", "Run specific agent within squad").option("-t, --timeout <minutes>", "Execution timeout in minutes (default: 30)", "30").action((target, options) => runCommand(target, { ...options, timeout: parseInt(options.timeout, 10) }));
4649
6079
  program.command("list").description("List agents and squads").option("-s, --squads", "List squads only").option("-a, --agents", "List agents only").action(listCommand);
4650
6080
  program.command("status [squad]").description("Show squad status and state").option("-v, --verbose", "Show detailed status").action(statusCommand);
4651
- program.command("dashboard").alias("dash").description("Show comprehensive goals and metrics dashboard").option("-v, --verbose", "Show additional details").option("-c, --ceo", "Executive summary with priorities and blockers").action(dashboardCommand);
6081
+ program.command("dashboard").alias("dash").description("Show comprehensive goals and metrics dashboard").option("-v, --verbose", "Show additional details").option("-c, --ceo", "Executive summary with priorities and blockers").option("-f, --full", "Include GitHub PR/issue stats (slower, ~30s)").action((options) => dashboardCommand({ ...options, fast: !options.full }));
4652
6082
  program.command("issues").description("Show GitHub issues across repos").option("-o, --org <org>", "GitHub organization", "agents-squads").option("-r, --repos <repos>", "Comma-separated repo names").action(issuesCommand);
4653
6083
  program.command("solve-issues").description("Solve ready-to-fix issues by creating PRs").option("-r, --repo <repo>", "Target repo (hq, agents-squads-web)").option("-i, --issue <number>", "Specific issue number", parseInt).option("-d, --dry-run", "Show what would be solved").option("-e, --execute", "Execute with Claude CLI").action(solveIssuesCommand);
4654
6084
  program.command("open-issues").description("Run evaluators/critics to find and create issues").option("-s, --squad <squad>", "Target squad (website, engineering, etc.)").option("-a, --agent <agent>", "Specific evaluator agent").option("-d, --dry-run", "Show what would run").option("-e, --execute", "Execute with Claude CLI").action(openIssuesCommand);
@@ -4662,7 +6092,7 @@ memory.command("query <query>").description("Search across all squad memory").op
4662
6092
  memory.command("show <squad>").description("Show memory for a squad").action(memoryShowCommand);
4663
6093
  memory.command("update <squad> <content>").description("Add to squad memory").option("-a, --agent <agent>", "Specific agent (default: squad-lead)").option("-t, --type <type>", "Memory type: state, learnings, feedback", "learnings").action(memoryUpdateCommand);
4664
6094
  memory.command("list").description("List all memory entries").action(memoryListCommand);
4665
- memory.command("sync").description("Sync memory from recent git commits (auto-update)").option("-v, --verbose", "Show detailed commit info").action(syncCommand);
6095
+ memory.command("sync").description("Sync memory from git: pull remote changes, process commits, optionally push").option("-v, --verbose", "Show detailed commit info").option("-p, --push", "Push local memory changes to remote after sync").option("--no-pull", "Skip pulling from remote").action((options) => syncCommand({ verbose: options.verbose, push: options.push, pull: options.pull }));
4666
6096
  memory.command("search <query>").description("Search conversations stored in postgres (via squads-bridge)").option("-l, --limit <limit>", "Number of results", "10").option("-r, --role <role>", "Filter by role: user, assistant, thinking").option("-i, --importance <importance>", "Filter by importance: low, normal, high").action((query, opts) => memorySearchCommand(query, {
4667
6097
  limit: parseInt(opts.limit, 10),
4668
6098
  role: opts.role,
@@ -4677,9 +6107,29 @@ var feedback = program.command("feedback").description("Record and view executio
4677
6107
  feedback.command("add <squad> <rating> <feedback>").description("Add feedback for last execution (rating 1-5)").option("-l, --learning <learnings...>", "Learnings to extract").action(feedbackAddCommand);
4678
6108
  feedback.command("show <squad>").description("Show feedback history").option("-n, --limit <n>", "Number of entries to show", "5").action(feedbackShowCommand);
4679
6109
  feedback.command("stats").description("Show feedback summary across all squads").action(feedbackStatsCommand);
6110
+ var sessions = program.command("sessions").description("Show active Claude Code sessions across squads").option("-v, --verbose", "Show session details").option("-j, --json", "Output as JSON").action(sessionsCommand);
6111
+ sessions.command("history").description("Show session history and statistics").option("-d, --days <days>", "Days of history to show", "7").option("-s, --squad <squad>", "Filter by squad").option("-j, --json", "Output as JSON").action((options) => sessionsHistoryCommand({
6112
+ days: parseInt(options.days, 10),
6113
+ squad: options.squad,
6114
+ json: options.json
6115
+ }));
6116
+ var session = program.command("session").description("Manage current session lifecycle");
6117
+ session.command("start").description("Register a new session").option("-s, --squad <squad>", "Override squad detection").option("-q, --quiet", "Suppress output").action((options) => sessionStartCommand({ squad: options.squad, quiet: options.quiet }));
6118
+ session.command("stop").description("End current session").option("-q, --quiet", "Suppress output").action((options) => sessionStopCommand({ quiet: options.quiet }));
6119
+ session.command("heartbeat").description("Update session heartbeat").option("-q, --quiet", "Suppress output").action((options) => sessionHeartbeatCommand({ quiet: options.quiet }));
6120
+ program.command("detect-squad").description("Detect current squad based on cwd (for use in hooks)").action(detectSquadCommand);
6121
+ var stack = program.command("stack").description("Manage local Docker stack (postgres, redis, langfuse, bridge)");
6122
+ stack.command("init").description("Auto-detect Docker containers and configure CLI connection").action(stackInitCommand);
6123
+ stack.command("status").description("Show container health and connection status").action(stackStatusCommand);
6124
+ stack.command("env").description("Print environment variables for shell export").action(stackEnvCommand);
6125
+ stack.command("up").description("Start Docker containers via docker-compose").action(stackUpCommand);
6126
+ stack.command("down").description("Stop Docker containers").action(stackDownCommand);
6127
+ stack.command("health").description("Comprehensive health check with diagnostics").option("-v, --verbose", "Show logs for unhealthy services").action((options) => stackHealthCommand(options.verbose));
6128
+ stack.command("logs <service>").description("Show logs for a service (postgres, redis, neo4j, bridge, langfuse, mem0, engram)").option("-n, --tail <lines>", "Number of lines to show", "50").action((service, options) => stackLogsCommand(service, parseInt(options.tail, 10)));
4680
6129
  program.command("login").description("Log in to Squads (Pro & Enterprise)").action(loginCommand);
4681
6130
  program.command("logout").description("Log out from Squads").action(logoutCommand);
4682
6131
  program.command("whoami").description("Show current logged in user").action(whoamiCommand);
6132
+ program.command("update").description("Check for and install updates").option("-y, --yes", "Auto-confirm update without prompting").option("-c, --check", "Check for updates without installing").action((options) => updateCommand(options));
4683
6133
  await program.parseAsync();
4684
6134
  if (!process.argv.slice(2).length) {
4685
6135
  console.log(`