squads-cli 0.1.2 → 0.3.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
@@ -1,18 +1,43 @@
1
1
  #!/usr/bin/env node
2
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
- }) : x)(function(x) {
5
- if (typeof require !== "undefined") return require.apply(this, arguments);
6
- throw Error('Dynamic require of "' + x + '" is not supported');
7
- });
2
+ import {
3
+ appendToMemory,
4
+ findMemoryDir,
5
+ getSquadState,
6
+ listMemoryEntries,
7
+ searchMemory
8
+ } from "./chunk-FUHBEL3L.js";
9
+ import {
10
+ RESET,
11
+ barChart,
12
+ bold,
13
+ box,
14
+ cleanupStaleSessions,
15
+ colors,
16
+ detectSquad,
17
+ getLiveSessionSummary,
18
+ gradient,
19
+ icons,
20
+ padEnd,
21
+ progressBar,
22
+ sessionsCommand,
23
+ sessionsHistoryCommand,
24
+ sessionsSummaryCommand,
25
+ sparkline,
26
+ startSession,
27
+ stopSession,
28
+ truncate,
29
+ updateHeartbeat,
30
+ writeLine
31
+ } from "./chunk-266URT5W.js";
32
+ import "./chunk-7OCVIDC7.js";
8
33
 
9
34
  // src/cli.ts
10
35
  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";
36
+ import { existsSync as existsSync15 } from "fs";
37
+ import { join as join15 } from "path";
38
+ import { homedir as homedir6 } from "os";
14
39
  import { Command } from "commander";
15
- import chalk3 from "chalk";
40
+ import chalk4 from "chalk";
16
41
 
17
42
  // src/version.ts
18
43
  var version = "0.1.0";
@@ -100,7 +125,7 @@ var SQUAD_LABELS = {
100
125
  company: ["company", "strategy"],
101
126
  marketing: ["marketing", "content", "seo"]
102
127
  };
103
- function getGitHubStats(basePath, days = 30) {
128
+ function getGitHubStatsOptimized(basePath, days = 30) {
104
129
  const stats = {
105
130
  prsOpened: 0,
106
131
  prsMerged: 0,
@@ -121,74 +146,81 @@ function getGitHubStats(basePath, days = 30) {
121
146
  }
122
147
  const repos = ["hq", "agents-squads-web"];
123
148
  const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
149
+ const results = [];
124
150
  for (const repo of repos) {
125
151
  const repoPath = join(basePath, repo);
126
152
  if (!existsSync(repoPath)) continue;
127
153
  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"] }
154
+ const output = execSync(
155
+ `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 '}'`,
156
+ { cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
131
157
  );
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
- }
158
+ const prsMatch = output.match(/"prs":(\[.*?\]),"issues":/s);
159
+ const issuesMatch = output.match(/"issues":(\[.*?\])\s*\}/s);
160
+ const prs = prsMatch ? JSON.parse(prsMatch[1]) : [];
161
+ const issues = issuesMatch ? JSON.parse(issuesMatch[1]) : [];
162
+ results.push({ repo, prs, issues });
163
+ } catch {
164
+ try {
165
+ const prsOutput = execSync(
166
+ `gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit 50 2>/dev/null`,
167
+ { cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
168
+ );
169
+ const issuesOutput = execSync(
170
+ `gh issue list --state all --json number,title,state,closedAt,labels --limit 50 2>/dev/null`,
171
+ { cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
172
+ );
173
+ results.push({
174
+ repo,
175
+ prs: JSON.parse(prsOutput || "[]"),
176
+ issues: JSON.parse(issuesOutput || "[]")
177
+ });
178
+ } catch {
179
+ }
180
+ }
181
+ }
182
+ for (const { repo, prs, issues } of results) {
183
+ for (const pr of prs) {
184
+ const created = new Date(pr.createdAt);
185
+ if (created < new Date(since)) continue;
186
+ stats.prsOpened++;
187
+ if (pr.mergedAt) stats.prsMerged++;
188
+ const squad = detectSquadFromPR(pr, repo);
189
+ const squadStats = stats.bySquad.get(squad);
190
+ if (squadStats) {
191
+ squadStats.prsOpened++;
192
+ if (pr.mergedAt) squadStats.prsMerged++;
193
+ if (squadStats.recentPRs.length < 3) {
194
+ squadStats.recentPRs.push({
195
+ title: pr.title,
196
+ number: pr.number,
197
+ merged: !!pr.mergedAt
198
+ });
150
199
  }
151
200
  }
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++;
201
+ }
202
+ for (const issue of issues) {
203
+ const squad = detectSquadFromIssue(issue, repo);
204
+ const squadStats = stats.bySquad.get(squad);
205
+ if (issue.state === "CLOSED") {
206
+ const closed = new Date(issue.closedAt || 0);
207
+ if (closed >= new Date(since)) {
208
+ stats.issuesClosed++;
170
209
  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
- }
210
+ squadStats.issuesClosed++;
179
211
  }
180
212
  }
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);
213
+ } else {
214
+ stats.issuesOpen++;
190
215
  if (squadStats) {
191
- squadStats.commits += commits;
216
+ squadStats.issuesOpen++;
217
+ if (squadStats.recentIssues.length < 3) {
218
+ squadStats.recentIssues.push({
219
+ title: issue.title,
220
+ number: issue.number,
221
+ state: issue.state
222
+ });
223
+ }
192
224
  }
193
225
  }
194
226
  }
@@ -341,7 +373,11 @@ import { randomUUID } from "crypto";
341
373
  var TELEMETRY_DIR = join2(homedir(), ".squads-cli");
342
374
  var CONFIG_PATH = join2(TELEMETRY_DIR, "telemetry.json");
343
375
  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);
376
+ var TELEMETRY_ENDPOINT = Buffer.from(
377
+ "aHR0cHM6Ly9zcXVhZHMtdGVsZW1ldHJ5LTk3ODg3MTgxNzYxMC51cy1jZW50cmFsMS5ydW4uYXBwL3Bpbmc=",
378
+ "base64"
379
+ ).toString();
380
+ var TELEMETRY_KEY = Buffer.from("c3FfdGVsX3YxXzdmOGE5YjJjM2Q0ZTVmNmE=", "base64").toString();
345
381
  var eventQueue = [];
346
382
  var flushScheduled = false;
347
383
  function ensureDir() {
@@ -409,7 +445,10 @@ async function flushEvents() {
409
445
  try {
410
446
  await fetch(TELEMETRY_ENDPOINT, {
411
447
  method: "POST",
412
- headers: { "Content-Type": "application/json" },
448
+ headers: {
449
+ "Content-Type": "application/json",
450
+ "X-Squads-Key": TELEMETRY_KEY
451
+ },
413
452
  body: JSON.stringify({ events: batch })
414
453
  });
415
454
  } catch {
@@ -464,24 +503,19 @@ var exitHandlerRegistered = false;
464
503
  function registerExitHandler() {
465
504
  if (exitHandlerRegistered) return;
466
505
  exitHandlerRegistered = true;
467
- const cleanup = () => {
468
- if (eventQueue.length > 0 && TELEMETRY_ENDPOINT) {
469
- storeEventLocally({
470
- event: "cli.exit",
471
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
472
- properties: { pendingEvents: eventQueue.length }
473
- });
506
+ process.on("beforeExit", async () => {
507
+ if (eventQueue.length > 0) {
508
+ await flushEvents();
474
509
  }
475
- };
476
- process.on("exit", cleanup);
477
- process.on("SIGINT", () => {
478
- cleanup();
479
- process.exit(0);
480
510
  });
481
- process.on("SIGTERM", () => {
482
- cleanup();
511
+ const signalHandler = async (signal) => {
512
+ if (eventQueue.length > 0) {
513
+ await flushEvents();
514
+ }
483
515
  process.exit(0);
484
- });
516
+ };
517
+ process.on("SIGINT", () => signalHandler("SIGINT"));
518
+ process.on("SIGTERM", () => signalHandler("SIGTERM"));
485
519
  }
486
520
 
487
521
  // src/commands/init.ts
@@ -727,8 +761,8 @@ ${chalk.dim("Put your first squad to work solving a problem in minutes.")}
727
761
  // src/commands/run.ts
728
762
  import ora2 from "ora";
729
763
  import { spawn } from "child_process";
730
- import { join as join5, dirname as dirname2 } from "path";
731
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
764
+ import { join as join4, dirname } from "path";
765
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
732
766
 
733
767
  // src/lib/squad-parser.ts
734
768
  import { readFileSync as readFileSync2, existsSync as existsSync3, readdirSync, writeFileSync as writeFileSync2 } from "fs";
@@ -975,378 +1009,22 @@ function updateGoalInSquad(squadName, goalIndex, updates) {
975
1009
  return false;
976
1010
  }
977
1011
 
978
- // src/lib/memory.ts
979
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, readdirSync as readdirSync2, mkdirSync as mkdirSync2 } from "fs";
980
- import { join as join4, dirname } from "path";
981
- function findMemoryDir() {
982
- let dir = process.cwd();
983
- for (let i = 0; i < 5; i++) {
984
- const memoryPath = join4(dir, ".agents", "memory");
985
- if (existsSync4(memoryPath)) {
986
- return memoryPath;
987
- }
988
- const parent = join4(dir, "..");
989
- if (parent === dir) break;
990
- dir = parent;
991
- }
992
- return null;
993
- }
994
- function listMemoryEntries(memoryDir) {
995
- const entries = [];
996
- const squads = readdirSync2(memoryDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
997
- for (const squad of squads) {
998
- const squadPath = join4(memoryDir, squad);
999
- const agents = readdirSync2(squadPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
1000
- for (const agent of agents) {
1001
- const agentPath = join4(squadPath, agent);
1002
- const files = readdirSync2(agentPath).filter((f) => f.endsWith(".md"));
1003
- for (const file of files) {
1004
- const filePath = join4(agentPath, file);
1005
- const type = file.replace(".md", "");
1006
- entries.push({
1007
- squad,
1008
- agent,
1009
- type,
1010
- content: readFileSync3(filePath, "utf-8"),
1011
- path: filePath
1012
- });
1013
- }
1014
- }
1015
- }
1016
- return entries;
1017
- }
1018
- var SEMANTIC_EXPANSIONS = {
1019
- "pricing": ["price", "cost", "$", "revenue", "fee", "rate"],
1020
- "price": ["pricing", "cost", "$", "fee"],
1021
- "revenue": ["income", "sales", "mrr", "arr", "$"],
1022
- "cost": ["expense", "spend", "budget", "$", "price"],
1023
- "customer": ["client", "lead", "prospect", "user"],
1024
- "client": ["customer", "lead", "prospect"],
1025
- "lead": ["prospect", "customer", "client", "pipeline"],
1026
- "agent": ["squad", "bot", "ai"],
1027
- "squad": ["team", "agent", "group"],
1028
- "status": ["state", "progress", "health"],
1029
- "bug": ["issue", "error", "problem", "fix"],
1030
- "feature": ["capability", "function", "ability"]
1031
- };
1032
- function expandQuery(query) {
1033
- const words = query.toLowerCase().split(/\s+/);
1034
- const expanded = new Set(words);
1035
- for (const word of words) {
1036
- if (SEMANTIC_EXPANSIONS[word]) {
1037
- SEMANTIC_EXPANSIONS[word].forEach((syn) => expanded.add(syn));
1038
- }
1039
- }
1040
- return Array.from(expanded);
1041
- }
1042
- function getFileAge(filePath) {
1043
- try {
1044
- const { statSync: statSync3 } = __require("fs");
1045
- const stats = statSync3(filePath);
1046
- const ageMs = Date.now() - stats.mtimeMs;
1047
- const ageDays = ageMs / (1e3 * 60 * 60 * 24);
1048
- return ageDays;
1049
- } catch {
1050
- return 999;
1051
- }
1052
- }
1053
- function searchMemory(query, memoryDir) {
1054
- const dir = memoryDir || findMemoryDir();
1055
- if (!dir) return [];
1056
- const entries = listMemoryEntries(dir);
1057
- const results = [];
1058
- const queryLower = query.toLowerCase();
1059
- const expandedTerms = expandQuery(queryLower);
1060
- for (const entry of entries) {
1061
- const contentLower = entry.content.toLowerCase();
1062
- const lines = entry.content.split("\n");
1063
- const matches = [];
1064
- let score = 0;
1065
- let directHits = 0;
1066
- let expandedHits = 0;
1067
- const directWords = queryLower.split(/\s+/);
1068
- for (const word of directWords) {
1069
- if (contentLower.includes(word)) {
1070
- directHits += 1;
1071
- score += 2;
1072
- }
1073
- }
1074
- for (const term of expandedTerms) {
1075
- if (contentLower.includes(term)) {
1076
- expandedHits += 1;
1077
- score += 0.5;
1078
- for (let i = 0; i < lines.length; i++) {
1079
- const line = lines[i];
1080
- if (line.toLowerCase().includes(term) && line.trim() && !matches.includes(line.trim())) {
1081
- matches.push(line.trim());
1082
- }
1083
- }
1084
- }
1085
- }
1086
- if (contentLower.includes(queryLower)) {
1087
- score += 5;
1088
- }
1089
- const ageDays = getFileAge(entry.path);
1090
- if (ageDays < 1) {
1091
- score *= 1.5;
1092
- } else if (ageDays < 7) {
1093
- score *= 1.2;
1094
- } else if (ageDays > 30) {
1095
- score *= 0.8;
1096
- }
1097
- const typeWeights = {
1098
- "state": 1.2,
1099
- // Current state slightly preferred
1100
- "learnings": 1.1,
1101
- // Learnings are valuable
1102
- "output": 1,
1103
- // Recent outputs
1104
- "feedback": 0.9
1105
- // Feedback less commonly needed
1106
- };
1107
- score *= typeWeights[entry.type] || 1;
1108
- if (score > 0 && (directHits > 0 || expandedHits > 1)) {
1109
- results.push({ entry, matches: matches.slice(0, 7), score });
1110
- }
1111
- }
1112
- return results.sort((a, b) => b.score - a.score);
1113
- }
1114
- function getSquadState(squadName) {
1115
- const memoryDir = findMemoryDir();
1116
- if (!memoryDir) return [];
1117
- const squadPath = join4(memoryDir, squadName);
1118
- if (!existsSync4(squadPath)) return [];
1119
- const entries = [];
1120
- const agents = readdirSync2(squadPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
1121
- for (const agent of agents) {
1122
- const statePath = join4(squadPath, agent, "state.md");
1123
- if (existsSync4(statePath)) {
1124
- entries.push({
1125
- squad: squadName,
1126
- agent,
1127
- type: "state",
1128
- content: readFileSync3(statePath, "utf-8"),
1129
- path: statePath
1130
- });
1131
- }
1132
- }
1133
- return entries;
1134
- }
1135
- function updateMemory(squadName, agentName, type, content) {
1136
- const memoryDir = findMemoryDir();
1137
- if (!memoryDir) {
1138
- throw new Error("No .agents/memory directory found");
1139
- }
1140
- const filePath = join4(memoryDir, squadName, agentName, `${type}.md`);
1141
- const dir = dirname(filePath);
1142
- if (!existsSync4(dir)) {
1143
- mkdirSync2(dir, { recursive: true });
1144
- }
1145
- writeFileSync3(filePath, content);
1146
- }
1147
- function appendToMemory(squadName, agentName, type, addition) {
1148
- const memoryDir = findMemoryDir();
1149
- if (!memoryDir) {
1150
- throw new Error("No .agents/memory directory found");
1151
- }
1152
- const filePath = join4(memoryDir, squadName, agentName, `${type}.md`);
1153
- let existing = "";
1154
- if (existsSync4(filePath)) {
1155
- existing = readFileSync3(filePath, "utf-8");
1156
- }
1157
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1158
- const newContent = existing + `
1159
-
1160
- ---
1161
- _Added: ${timestamp}_
1162
-
1163
- ${addition}`;
1164
- updateMemory(squadName, agentName, type, newContent.trim());
1165
- }
1166
-
1167
- // src/lib/terminal.ts
1168
- var ESC = "\x1B[";
1169
- var RESET = `${ESC}0m`;
1170
- var rgb = (r, g, b) => `${ESC}38;2;${r};${g};${b}m`;
1171
- var colors = {
1172
- purple: rgb(168, 85, 247),
1173
- // #a855f7
1174
- pink: rgb(236, 72, 153),
1175
- // #ec4899
1176
- cyan: rgb(6, 182, 212),
1177
- // #06b6d4
1178
- green: rgb(16, 185, 129),
1179
- // #10b981
1180
- yellow: rgb(234, 179, 8),
1181
- // #eab308
1182
- red: rgb(239, 68, 68),
1183
- // #ef4444
1184
- gray: rgb(107, 114, 128),
1185
- // #6b7280
1186
- dim: rgb(75, 85, 99),
1187
- // #4b5563
1188
- white: rgb(255, 255, 255)
1189
- };
1190
- var bold = `${ESC}1m`;
1191
- var dim = `${ESC}2m`;
1192
- var cursor = {
1193
- hide: `${ESC}?25l`,
1194
- show: `${ESC}?25h`,
1195
- up: (n = 1) => `${ESC}${n}A`,
1196
- down: (n = 1) => `${ESC}${n}B`,
1197
- left: (n = 1) => `${ESC}${n}D`,
1198
- right: (n = 1) => `${ESC}${n}C`,
1199
- to: (x, y) => `${ESC}${y};${x}H`,
1200
- save: `${ESC}s`,
1201
- restore: `${ESC}u`
1202
- };
1203
- var clear = {
1204
- line: `${ESC}2K`,
1205
- toEnd: `${ESC}0K`,
1206
- screen: `${ESC}2J${ESC}0;0H`
1207
- };
1208
- function gradient(text) {
1209
- const stops = [
1210
- [168, 85, 247],
1211
- // purple
1212
- [192, 132, 252],
1213
- // purple-light
1214
- [232, 121, 249],
1215
- // pink
1216
- [244, 114, 182],
1217
- // pink-light
1218
- [251, 113, 133]
1219
- // rose
1220
- ];
1221
- let result = "";
1222
- for (let i = 0; i < text.length; i++) {
1223
- const t = i / Math.max(text.length - 1, 1);
1224
- const stopIndex = t * (stops.length - 1);
1225
- const lower = Math.floor(stopIndex);
1226
- const upper = Math.min(lower + 1, stops.length - 1);
1227
- const blend = stopIndex - lower;
1228
- const r = Math.round(stops[lower][0] + (stops[upper][0] - stops[lower][0]) * blend);
1229
- const g = Math.round(stops[lower][1] + (stops[upper][1] - stops[lower][1]) * blend);
1230
- const b = Math.round(stops[lower][2] + (stops[upper][2] - stops[lower][2]) * blend);
1231
- result += rgb(r, g, b) + text[i];
1232
- }
1233
- return result + RESET;
1234
- }
1235
- function progressBar(percent, width = 20) {
1236
- const filled = Math.round(percent / 100 * width);
1237
- const empty = width - filled;
1238
- let bar = "";
1239
- for (let i = 0; i < filled; i++) {
1240
- const t = i / Math.max(filled - 1, 1);
1241
- const r = Math.round(16 + (168 - 16) * t);
1242
- const g = Math.round(185 + (85 - 185) * t);
1243
- const b = Math.round(129 + (247 - 129) * t);
1244
- bar += rgb(r, g, b) + "\u2501";
1245
- }
1246
- bar += colors.dim + "\u2501".repeat(empty) + RESET;
1247
- return bar;
1248
- }
1249
- var box = {
1250
- topLeft: "\u250C",
1251
- topRight: "\u2510",
1252
- bottomLeft: "\u2514",
1253
- bottomRight: "\u2518",
1254
- horizontal: "\u2500",
1255
- vertical: "\u2502",
1256
- teeRight: "\u251C",
1257
- teeLeft: "\u2524"
1258
- };
1259
- function padEnd(str, len) {
1260
- const visible = str.replace(/\x1b\[[0-9;]*m/g, "");
1261
- const pad = Math.max(0, len - visible.length);
1262
- return str + " ".repeat(pad);
1263
- }
1264
- function truncate(str, len) {
1265
- const visible = str.replace(/\x1b\[[0-9;]*m/g, "");
1266
- if (visible.length <= len) return str;
1267
- let result = "";
1268
- let count = 0;
1269
- let i = 0;
1270
- while (i < str.length && count < len - 1) {
1271
- if (str[i] === "\x1B") {
1272
- const end = str.indexOf("m", i);
1273
- if (end !== -1) {
1274
- result += str.slice(i, end + 1);
1275
- i = end + 1;
1276
- continue;
1277
- }
1278
- }
1279
- result += str[i];
1280
- count++;
1281
- i++;
1282
- }
1283
- return result + colors.dim + "\u2026" + RESET;
1284
- }
1285
- var icons = {
1286
- success: `${colors.green}\u25CF${RESET}`,
1287
- warning: `${colors.yellow}\u25CB${RESET}`,
1288
- error: `${colors.red}\u25CF${RESET}`,
1289
- pending: `${colors.dim}\u25CB${RESET}`,
1290
- active: `${colors.green}\u25CF${RESET}`,
1291
- progress: `${colors.cyan}\u25C6${RESET}`,
1292
- empty: `${colors.dim}\u25C7${RESET}`
1293
- };
1294
- function writeLine(str = "") {
1295
- process.stdout.write(str + "\n");
1296
- }
1297
- function sparkline(values, width) {
1298
- if (values.length === 0) return "";
1299
- const blocks = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
1300
- const max = Math.max(...values, 1);
1301
- let result = "";
1302
- for (const val of values) {
1303
- const normalized = val / max;
1304
- const blockIndex = Math.min(Math.floor(normalized * blocks.length), blocks.length - 1);
1305
- const intensity = normalized;
1306
- if (normalized === 0) {
1307
- result += colors.dim + blocks[0];
1308
- } else if (normalized < 0.5) {
1309
- result += colors.cyan + blocks[blockIndex];
1310
- } else {
1311
- result += colors.green + blocks[blockIndex];
1312
- }
1313
- }
1314
- return result + RESET;
1315
- }
1316
- function barChart(value, max, width = 20, label) {
1317
- const filled = Math.round(value / max * width);
1318
- const empty = width - filled;
1319
- let bar = "";
1320
- for (let i = 0; i < filled; i++) {
1321
- const t = i / Math.max(filled - 1, 1);
1322
- const r = Math.round(16 + (6 - 16) * t);
1323
- const g = Math.round(185 + (182 - 185) * t);
1324
- const b = Math.round(129 + (212 - 129) * t);
1325
- bar += rgb(r, g, b) + "\u2501";
1326
- }
1327
- bar += colors.dim + "\u2501".repeat(empty) + RESET;
1328
- if (label) {
1329
- return `${bar} ${label}`;
1330
- }
1331
- return bar;
1332
- }
1333
-
1334
1012
  // src/commands/run.ts
1335
1013
  function getExecutionLogPath(squadName, agentName) {
1336
1014
  const memoryDir = findMemoryDir();
1337
1015
  if (!memoryDir) return null;
1338
- return join5(memoryDir, squadName, agentName, "executions.md");
1016
+ return join4(memoryDir, squadName, agentName, "executions.md");
1339
1017
  }
1340
1018
  function logExecution(record) {
1341
1019
  const logPath = getExecutionLogPath(record.squadName, record.agentName);
1342
1020
  if (!logPath) return;
1343
- const dir = dirname2(logPath);
1344
- if (!existsSync5(dir)) {
1345
- mkdirSync3(dir, { recursive: true });
1021
+ const dir = dirname(logPath);
1022
+ if (!existsSync4(dir)) {
1023
+ mkdirSync2(dir, { recursive: true });
1346
1024
  }
1347
1025
  let content = "";
1348
- if (existsSync5(logPath)) {
1349
- content = readFileSync4(logPath, "utf-8");
1026
+ if (existsSync4(logPath)) {
1027
+ content = readFileSync3(logPath, "utf-8");
1350
1028
  } else {
1351
1029
  content = `# ${record.squadName}/${record.agentName} - Execution Log
1352
1030
 
@@ -1358,12 +1036,12 @@ function logExecution(record) {
1358
1036
  ${record.endTime ? `Completed: ${record.endTime}` : ""}
1359
1037
  ${record.outcome ? `Outcome: ${record.outcome}` : ""}
1360
1038
  `;
1361
- writeFileSync4(logPath, content + entry);
1039
+ writeFileSync3(logPath, content + entry);
1362
1040
  }
1363
1041
  function updateExecutionStatus(squadName, agentName, status, outcome) {
1364
1042
  const logPath = getExecutionLogPath(squadName, agentName);
1365
- if (!logPath || !existsSync5(logPath)) return;
1366
- let content = readFileSync4(logPath, "utf-8");
1043
+ if (!logPath || !existsSync4(logPath)) return;
1044
+ let content = readFileSync3(logPath, "utf-8");
1367
1045
  const endTime = (/* @__PURE__ */ new Date()).toISOString();
1368
1046
  content = content.replace(
1369
1047
  /Status: running\n$/,
@@ -1372,7 +1050,7 @@ Completed: ${endTime}
1372
1050
  ${outcome ? `Outcome: ${outcome}
1373
1051
  ` : ""}`
1374
1052
  );
1375
- writeFileSync4(logPath, content);
1053
+ writeFileSync3(logPath, content);
1376
1054
  }
1377
1055
  async function runCommand(target, options) {
1378
1056
  const squadsDir = findSquadsDir();
@@ -1384,6 +1062,7 @@ async function runCommand(target, options) {
1384
1062
  const squad = loadSquad(target);
1385
1063
  if (squad) {
1386
1064
  await track(Events.CLI_RUN, { type: "squad", target: squad.name });
1065
+ await flushEvents();
1387
1066
  await runSquad(squad, squadsDir, options);
1388
1067
  } else {
1389
1068
  const agents = listAgents(squadsDir);
@@ -1418,8 +1097,8 @@ async function runSquad(squad, squadsDir, options) {
1418
1097
  writeLine();
1419
1098
  for (let i = 0; i < pipeline.agents.length; i++) {
1420
1099
  const agentName = pipeline.agents[i];
1421
- const agentPath = join5(squadsDir, squad.name, `${agentName}.md`);
1422
- if (existsSync5(agentPath)) {
1100
+ const agentPath = join4(squadsDir, squad.name, `${agentName}.md`);
1101
+ if (existsSync4(agentPath)) {
1423
1102
  writeLine(` ${colors.dim}[${i + 1}/${pipeline.agents.length}]${RESET}`);
1424
1103
  await runAgent(agentName, agentPath, squad.name, options);
1425
1104
  writeLine();
@@ -1428,22 +1107,32 @@ async function runSquad(squad, squadsDir, options) {
1428
1107
  }
1429
1108
  }
1430
1109
  } 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`);
1436
- if (existsSync5(agentPath)) {
1437
- await runAgent(orchestrator.name, agentPath, squad.name, options);
1110
+ if (options.agent) {
1111
+ const agentPath = join4(squadsDir, squad.name, `${options.agent}.md`);
1112
+ if (existsSync4(agentPath)) {
1113
+ await runAgent(options.agent, agentPath, squad.name, options);
1114
+ } else {
1115
+ writeLine(` ${icons.error} ${colors.red}Agent ${options.agent} not found${RESET}`);
1116
+ return;
1438
1117
  }
1439
1118
  } 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}`);
1119
+ const orchestrator = squad.agents.find(
1120
+ (a) => a.name.includes("lead") || a.trigger === "Manual"
1121
+ );
1122
+ if (orchestrator) {
1123
+ const agentPath = join4(squadsDir, squad.name, `${orchestrator.name}.md`);
1124
+ if (existsSync4(agentPath)) {
1125
+ await runAgent(orchestrator.name, agentPath, squad.name, options);
1126
+ }
1127
+ } else {
1128
+ writeLine(` ${colors.dim}No pipeline defined. Available agents:${RESET}`);
1129
+ for (const agent of squad.agents) {
1130
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET} ${colors.dim}${agent.role}${RESET}`);
1131
+ }
1132
+ writeLine();
1133
+ writeLine(` ${colors.dim}Run a specific agent:${RESET}`);
1134
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --agent ${colors.cyan}<name>${RESET}`);
1443
1135
  }
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
1136
  }
1448
1137
  }
1449
1138
  writeLine();
@@ -1469,7 +1158,7 @@ async function runAgent(agentName, agentPath, squadName, options) {
1469
1158
  startTime,
1470
1159
  status: "running"
1471
1160
  });
1472
- const prompt = `Execute the ${agentName} agent from squad ${squadName}.
1161
+ const prompt2 = `Execute the ${agentName} agent from squad ${squadName}.
1473
1162
 
1474
1163
  Read the agent definition at ${agentPath} and follow its instructions exactly.
1475
1164
 
@@ -1485,18 +1174,16 @@ After completion:
1485
1174
  3. Report what was accomplished`;
1486
1175
  const claudeAvailable = await checkClaudeCliAvailable();
1487
1176
  if (options.execute && claudeAvailable) {
1488
- spinner.text = `Executing ${agentName} with Claude Code...`;
1177
+ spinner.text = `Launching ${agentName} as background task...`;
1489
1178
  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
- }
1179
+ const result = await executeWithClaude(prompt2, options.verbose, options.timeout || 30);
1180
+ spinner.succeed(`Agent ${agentName} launched`);
1181
+ writeLine(` ${colors.dim}${result}${RESET}`);
1182
+ writeLine();
1183
+ writeLine(` ${colors.dim}Monitor:${RESET} squads workers`);
1184
+ writeLine(` ${colors.dim}Memory:${RESET} squads memory show ${squadName}`);
1498
1185
  } catch (error) {
1499
- spinner.fail(`Agent ${agentName} failed`);
1186
+ spinner.fail(`Agent ${agentName} failed to launch`);
1500
1187
  updateExecutionStatus(squadName, agentName, "failed", String(error));
1501
1188
  writeLine(` ${colors.red}${String(error)}${RESET}`);
1502
1189
  }
@@ -1509,13 +1196,10 @@ After completion:
1509
1196
  writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
1510
1197
  }
1511
1198
  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)}..."`);
1199
+ writeLine(` ${colors.dim}To launch as background task:${RESET}`);
1200
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} -a ${colors.cyan}${agentName}${RESET} --execute`);
1514
1201
  writeLine();
1515
- writeLine(` ${colors.dim}Or run with --execute flag:${RESET}`);
1516
- writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} --execute`);
1517
- writeLine();
1518
- writeLine(` ${colors.dim}Or in Claude Code session:${RESET}`);
1202
+ writeLine(` ${colors.dim}Or run interactively:${RESET}`);
1519
1203
  writeLine(` ${colors.dim}$${RESET} Run the ${colors.cyan}${agentName}${RESET} agent from ${agentPath}`);
1520
1204
  }
1521
1205
  }
@@ -1526,48 +1210,51 @@ async function checkClaudeCliAvailable() {
1526
1210
  check.on("error", () => resolve(false));
1527
1211
  });
1528
1212
  }
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);
1213
+ async function executeWithClaude(prompt2, verbose, timeoutMinutes = 30) {
1214
+ const userConfigPath = join4(process.env.HOME || "", ".claude.json");
1215
+ const squadMatch = prompt2.match(/squad (\w+)/);
1216
+ const agentMatch = prompt2.match(/(\w+) agent/);
1217
+ const squadName = squadMatch?.[1] || "unknown";
1218
+ const agentName = agentMatch?.[1] || "unknown";
1219
+ const timestamp = Date.now();
1220
+ const sessionName = `squads-${squadName}-${agentName}-${timestamp}`;
1221
+ if (verbose) {
1222
+ writeLine(` ${colors.dim}Spawning tmux session: ${sessionName}${RESET}`);
1223
+ }
1224
+ const escapedPrompt = prompt2.replace(/'/g, "'\\''");
1225
+ const claudeCmd = `claude --dangerously-skip-permissions --mcp-config '${userConfigPath}' -- '${escapedPrompt}'`;
1226
+ const tmux = spawn("tmux", [
1227
+ "new-session",
1228
+ "-d",
1229
+ // Detached
1230
+ "-s",
1231
+ sessionName,
1232
+ "-x",
1233
+ "200",
1234
+ // Wide terminal for better output
1235
+ "-y",
1236
+ "50",
1237
+ "/bin/sh",
1238
+ "-c",
1239
+ claudeCmd
1240
+ ], {
1241
+ stdio: "ignore",
1242
+ detached: true,
1243
+ env: {
1244
+ ...process.env,
1245
+ SQUADS_SQUAD: squadName,
1246
+ SQUADS_AGENT: agentName
1247
+ }
1570
1248
  });
1249
+ tmux.unref();
1250
+ spawn("/bin/sh", ["-c", `sleep 2 && tmux send-keys -t '${sessionName}' Down Enter`], {
1251
+ stdio: "ignore",
1252
+ detached: true
1253
+ }).unref();
1254
+ if (verbose) {
1255
+ writeLine(` ${colors.dim}Attach: tmux attach -t ${sessionName}${RESET}`);
1256
+ }
1257
+ return `tmux session: ${sessionName}. Attach: tmux attach -t ${sessionName}`;
1571
1258
  }
1572
1259
 
1573
1260
  // src/commands/list.ts
@@ -1622,6 +1309,141 @@ async function listCommand(options) {
1622
1309
  // src/commands/status.ts
1623
1310
  import { existsSync as existsSync6, statSync } from "fs";
1624
1311
  import { join as join6 } from "path";
1312
+
1313
+ // src/lib/update.ts
1314
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, unlinkSync } from "fs";
1315
+ import { join as join5, dirname as dirname2 } from "path";
1316
+ import { homedir as homedir2 } from "os";
1317
+ import { execSync as execSync2 } from "child_process";
1318
+ import { fileURLToPath } from "url";
1319
+ function getPackageVersion() {
1320
+ try {
1321
+ const __filename3 = fileURLToPath(import.meta.url);
1322
+ const __dirname3 = dirname2(__filename3);
1323
+ const possiblePaths = [
1324
+ join5(__dirname3, "..", "..", "package.json"),
1325
+ // From dist/lib/
1326
+ join5(__dirname3, "..", "package.json"),
1327
+ // From dist/
1328
+ join5(__dirname3, "package.json")
1329
+ // Same dir
1330
+ ];
1331
+ for (const pkgPath of possiblePaths) {
1332
+ if (existsSync5(pkgPath)) {
1333
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1334
+ return pkg.version || "0.0.0";
1335
+ }
1336
+ }
1337
+ } catch {
1338
+ }
1339
+ return "0.0.0";
1340
+ }
1341
+ var CURRENT_VERSION = getPackageVersion();
1342
+ var CACHE_DIR = join5(homedir2(), ".squads");
1343
+ var CACHE_FILE = join5(CACHE_DIR, "update-check.json");
1344
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
1345
+ function isNewerVersion(v1, v2) {
1346
+ const parts1 = v1.replace(/^v/, "").split(".").map(Number);
1347
+ const parts2 = v2.replace(/^v/, "").split(".").map(Number);
1348
+ for (let i = 0; i < 3; i++) {
1349
+ const p1 = parts1[i] || 0;
1350
+ const p2 = parts2[i] || 0;
1351
+ if (p2 > p1) return true;
1352
+ if (p2 < p1) return false;
1353
+ }
1354
+ return false;
1355
+ }
1356
+ function readCache() {
1357
+ try {
1358
+ if (!existsSync5(CACHE_FILE)) return null;
1359
+ const data = JSON.parse(readFileSync4(CACHE_FILE, "utf-8"));
1360
+ return data;
1361
+ } catch {
1362
+ return null;
1363
+ }
1364
+ }
1365
+ function writeCache(latestVersion) {
1366
+ try {
1367
+ if (!existsSync5(CACHE_DIR)) {
1368
+ mkdirSync3(CACHE_DIR, { recursive: true });
1369
+ }
1370
+ const cache = {
1371
+ latestVersion,
1372
+ checkedAt: Date.now()
1373
+ };
1374
+ writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2));
1375
+ } catch {
1376
+ }
1377
+ }
1378
+ function fetchLatestVersion() {
1379
+ try {
1380
+ const result = execSync2("npm view squads-cli version 2>/dev/null", {
1381
+ encoding: "utf-8",
1382
+ timeout: 5e3
1383
+ }).trim();
1384
+ return result || null;
1385
+ } catch {
1386
+ return null;
1387
+ }
1388
+ }
1389
+ function checkForUpdate() {
1390
+ const result = {
1391
+ currentVersion: CURRENT_VERSION,
1392
+ latestVersion: CURRENT_VERSION,
1393
+ updateAvailable: false
1394
+ };
1395
+ const cache = readCache();
1396
+ const now = Date.now();
1397
+ if (cache && now - cache.checkedAt < CACHE_TTL_MS) {
1398
+ result.latestVersion = cache.latestVersion;
1399
+ result.updateAvailable = isNewerVersion(CURRENT_VERSION, cache.latestVersion);
1400
+ return result;
1401
+ }
1402
+ const latestVersion = fetchLatestVersion();
1403
+ if (latestVersion) {
1404
+ writeCache(latestVersion);
1405
+ result.latestVersion = latestVersion;
1406
+ result.updateAvailable = isNewerVersion(CURRENT_VERSION, latestVersion);
1407
+ } else if (cache) {
1408
+ result.latestVersion = cache.latestVersion;
1409
+ result.updateAvailable = isNewerVersion(CURRENT_VERSION, cache.latestVersion);
1410
+ }
1411
+ return result;
1412
+ }
1413
+ function performUpdate() {
1414
+ try {
1415
+ execSync2("npm update -g squads-cli", {
1416
+ encoding: "utf-8",
1417
+ stdio: "inherit",
1418
+ timeout: 12e4
1419
+ // 2 minutes
1420
+ });
1421
+ try {
1422
+ unlinkSync(CACHE_FILE);
1423
+ } catch {
1424
+ }
1425
+ return { success: true };
1426
+ } catch (err) {
1427
+ return {
1428
+ success: false,
1429
+ error: err instanceof Error ? err.message : "Unknown error"
1430
+ };
1431
+ }
1432
+ }
1433
+ function refreshVersionCache() {
1434
+ const latestVersion = fetchLatestVersion();
1435
+ if (latestVersion) {
1436
+ writeCache(latestVersion);
1437
+ return {
1438
+ currentVersion: CURRENT_VERSION,
1439
+ latestVersion,
1440
+ updateAvailable: isNewerVersion(CURRENT_VERSION, latestVersion)
1441
+ };
1442
+ }
1443
+ return checkForUpdate();
1444
+ }
1445
+
1446
+ // src/commands/status.ts
1625
1447
  async function statusCommand(squadName, options = {}) {
1626
1448
  const squadsDir = findSquadsDir();
1627
1449
  if (!squadsDir) {
@@ -1638,8 +1460,24 @@ async function statusCommand(squadName, options = {}) {
1638
1460
  async function showOverallStatus(squadsDir, _options) {
1639
1461
  const squads = listSquads(squadsDir);
1640
1462
  const memoryDir = findMemoryDir();
1463
+ cleanupStaleSessions();
1464
+ const sessionSummary = getLiveSessionSummary();
1641
1465
  writeLine();
1642
1466
  writeLine(` ${gradient("squads")} ${colors.dim}status${RESET}`);
1467
+ const updateInfo = checkForUpdate();
1468
+ if (updateInfo.updateAvailable) {
1469
+ writeLine(` ${colors.cyan}\u2B06${RESET} Update available: ${colors.dim}${updateInfo.currentVersion}${RESET} \u2192 ${colors.green}${updateInfo.latestVersion}${RESET} ${colors.dim}(run \`squads update\`)${RESET}`);
1470
+ }
1471
+ if (sessionSummary.totalSessions > 0) {
1472
+ const sessionText = sessionSummary.totalSessions === 1 ? "session" : "sessions";
1473
+ const squadText = sessionSummary.squadCount === 1 ? "squad" : "squads";
1474
+ let toolInfo = "";
1475
+ if (sessionSummary.byTool && Object.keys(sessionSummary.byTool).length > 0) {
1476
+ const toolParts = Object.entries(sessionSummary.byTool).sort((a, b) => b[1] - a[1]).map(([tool, count]) => `${colors.dim}${tool}${RESET} ${colors.cyan}${count}${RESET}`);
1477
+ toolInfo = ` ${colors.dim}(${RESET}${toolParts.join(` ${colors.dim}\xB7${RESET} `)}${colors.dim})${RESET}`;
1478
+ }
1479
+ writeLine(` ${colors.green}${icons.active}${RESET} ${colors.white}${sessionSummary.totalSessions}${RESET} active ${sessionText} ${colors.dim}across${RESET} ${colors.cyan}${sessionSummary.squadCount}${RESET} ${squadText}${toolInfo}`);
1480
+ }
1643
1481
  writeLine();
1644
1482
  const totalSquads = squads.length;
1645
1483
  const activeCount = squads.length;
@@ -1660,7 +1498,7 @@ async function showOverallStatus(squadsDir, _options) {
1660
1498
  const squadMemoryPath = join6(memoryDir, squadName);
1661
1499
  if (existsSync6(squadMemoryPath)) {
1662
1500
  const states = getSquadState(squadName);
1663
- memoryStatus = `${colors.green}${states.length} entries${RESET}`;
1501
+ memoryStatus = `${colors.green}${states.length} ${states.length === 1 ? "entry" : "entries"}${RESET}`;
1664
1502
  let mostRecent = 0;
1665
1503
  for (const state of states) {
1666
1504
  const stat = statSync(state.path);
@@ -1686,81 +1524,824 @@ async function showOverallStatus(squadsDir, _options) {
1686
1524
  }
1687
1525
  }
1688
1526
  }
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);
1527
+ 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}`;
1528
+ writeLine(row);
1529
+ }
1530
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
1531
+ writeLine();
1532
+ writeLine(` ${colors.dim}$${RESET} squads status ${colors.cyan}<squad>${RESET} ${colors.dim}Squad details${RESET}`);
1533
+ writeLine(` ${colors.dim}$${RESET} squads dash ${colors.dim}Full dashboard${RESET}`);
1534
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}<squad>${RESET} ${colors.dim}Execute a squad${RESET}`);
1535
+ writeLine();
1536
+ }
1537
+ async function showSquadStatus(squadName, squadsDir, options) {
1538
+ const squad = loadSquad(squadName);
1539
+ if (!squad) {
1540
+ writeLine(`${colors.red}Squad "${squadName}" not found.${RESET}`);
1541
+ process.exit(1);
1542
+ }
1543
+ writeLine();
1544
+ writeLine(` ${gradient("squads")} ${colors.dim}status${RESET} ${colors.cyan}${squad.name}${RESET}`);
1545
+ writeLine();
1546
+ if (squad.mission) {
1547
+ writeLine(` ${colors.dim}${squad.mission}${RESET}`);
1548
+ writeLine();
1549
+ }
1550
+ const agents = listAgents(squadsDir, squadName);
1551
+ const w = { name: 24, role: 36 };
1552
+ const tableWidth = w.name + w.role + 4;
1553
+ writeLine(` ${bold}Agents${RESET} ${colors.dim}(${agents.length})${RESET}`);
1554
+ writeLine();
1555
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
1556
+ for (const agent of agents) {
1557
+ const status = agent.status?.toLowerCase() === "active" ? icons.active : icons.pending;
1558
+ const role = options.verbose && agent.role ? `${colors.dim}${agent.role.substring(0, w.role - 2)}${RESET}` : "";
1559
+ const row = ` ${colors.purple}${box.vertical}${RESET} ${status} ${padEnd(agent.name, w.name - 2)}${padEnd(role, w.role)}${colors.purple}${box.vertical}${RESET}`;
1560
+ writeLine(row);
1561
+ }
1562
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
1563
+ if (squad.pipelines.length > 0) {
1564
+ writeLine();
1565
+ writeLine(` ${bold}Pipelines${RESET}`);
1566
+ for (const pipeline of squad.pipelines) {
1567
+ writeLine(` ${colors.dim}${pipeline.agents.join(" \u2192 ")}${RESET}`);
1568
+ }
1569
+ }
1570
+ const memoryDir = findMemoryDir();
1571
+ if (memoryDir) {
1572
+ const states = getSquadState(squadName);
1573
+ if (states.length > 0) {
1574
+ writeLine();
1575
+ writeLine(` ${bold}Memory${RESET} ${colors.dim}(${states.length} ${states.length === 1 ? "entry" : "entries"})${RESET}`);
1576
+ writeLine();
1577
+ for (const state of states) {
1578
+ const updated = state.content.match(/Updated:\s*(\S+)/)?.[1] || "unknown";
1579
+ writeLine(` ${icons.progress} ${colors.white}${state.agent}${RESET}`);
1580
+ writeLine(` ${colors.dim}\u2514 updated: ${updated}${RESET}`);
1581
+ if (options.verbose) {
1582
+ const signalsMatch = state.content.match(/## Active Signals([\s\S]*?)(?=##|$)/);
1583
+ if (signalsMatch) {
1584
+ const signalLines = signalsMatch[1].split("\n").filter((l) => l.match(/^\d+\./)).slice(0, 3);
1585
+ for (const sig of signalLines) {
1586
+ writeLine(` ${colors.dim} ${sig.trim()}${RESET}`);
1587
+ }
1588
+ }
1589
+ }
1590
+ }
1591
+ }
1592
+ }
1593
+ writeLine();
1594
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} ${colors.dim}Run the squad${RESET}`);
1595
+ writeLine(` ${colors.dim}$${RESET} squads memory show ${colors.cyan}${squadName}${RESET} ${colors.dim}View full memory${RESET}`);
1596
+ writeLine(` ${colors.dim}$${RESET} squads status ${colors.cyan}${squadName}${RESET} -v ${colors.dim}Verbose status${RESET}`);
1597
+ writeLine();
1598
+ }
1599
+
1600
+ // src/commands/stack.ts
1601
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, copyFileSync } from "fs";
1602
+ import { join as join7, dirname as dirname3 } from "path";
1603
+ import { homedir as homedir3 } from "os";
1604
+ import { execSync as execSync3, spawn as spawn2 } from "child_process";
1605
+ import { createInterface } from "readline";
1606
+ import { fileURLToPath as fileURLToPath2 } from "url";
1607
+ var __filename2 = fileURLToPath2(import.meta.url);
1608
+ var __dirname2 = dirname3(__filename2);
1609
+ var DEFAULT_CONFIG = {
1610
+ SQUADS_DATABASE_URL: "postgresql://squads:squads@localhost:5433/squads",
1611
+ SQUADS_BRIDGE_URL: "http://localhost:8088",
1612
+ LANGFUSE_HOST: "http://localhost:3100",
1613
+ LANGFUSE_PUBLIC_KEY: "",
1614
+ LANGFUSE_SECRET_KEY: "",
1615
+ REDIS_URL: "redis://localhost:6379"
1616
+ };
1617
+ var CONFIG_PATH2 = join7(homedir3(), ".squadsrc");
1618
+ var SQUADS_DATA_DIR = join7(homedir3(), ".squads");
1619
+ var SERVICES = {
1620
+ bridge: {
1621
+ name: "Bridge API",
1622
+ description: "Captures conversations and telemetry",
1623
+ required: true,
1624
+ healthUrl: "http://localhost:8088/health",
1625
+ envVars: ["SQUADS_BRIDGE_URL"],
1626
+ setupGuide: [
1627
+ "Run: squads stack up",
1628
+ "Or manually: docker compose up -d bridge"
1629
+ ]
1630
+ },
1631
+ postgres: {
1632
+ name: "PostgreSQL",
1633
+ description: "Stores conversations and telemetry data",
1634
+ required: true,
1635
+ envVars: ["SQUADS_DATABASE_URL"],
1636
+ setupGuide: [
1637
+ "Run: squads stack up",
1638
+ "Or manually: docker compose up -d postgres"
1639
+ ]
1640
+ },
1641
+ mem0: {
1642
+ name: "Mem0 (Engram)",
1643
+ description: "Extracts and stores memories from conversations",
1644
+ required: false,
1645
+ healthUrl: "http://localhost:8000/health",
1646
+ envVars: ["MEM0_API_URL"],
1647
+ setupGuide: [
1648
+ "Run: squads stack up",
1649
+ "Or manually: docker compose -f docker-compose.engram.yml up -d mem0",
1650
+ "",
1651
+ "Mem0 requires an LLM provider. Configure in docker/.env:",
1652
+ " LLM_PROVIDER=ollama # For local (free)",
1653
+ " LLM_PROVIDER=openai # Requires OPENAI_API_KEY"
1654
+ ]
1655
+ },
1656
+ langfuse: {
1657
+ name: "Langfuse",
1658
+ description: "Telemetry dashboard and cost tracking",
1659
+ required: false,
1660
+ healthUrl: "http://localhost:3100/api/public/health",
1661
+ envVars: ["LANGFUSE_HOST", "LANGFUSE_PUBLIC_KEY", "LANGFUSE_SECRET_KEY"],
1662
+ setupGuide: [
1663
+ "Run: squads stack up",
1664
+ "Then get API keys from: http://localhost:3100",
1665
+ " 1. Create account / login",
1666
+ " 2. Create project",
1667
+ " 3. Copy API keys to docker/.env"
1668
+ ]
1669
+ },
1670
+ redis: {
1671
+ name: "Redis",
1672
+ description: "Caching and rate limiting",
1673
+ required: false,
1674
+ envVars: ["REDIS_URL"],
1675
+ setupGuide: [
1676
+ "Run: squads stack up"
1677
+ ]
1678
+ }
1679
+ };
1680
+ async function checkServiceAvailable(serviceName, showGuidance = true) {
1681
+ const service = SERVICES[serviceName];
1682
+ if (!service) return false;
1683
+ const containerName = `squads-${serviceName === "mem0" ? "mem0" : serviceName}`;
1684
+ const status = getContainerStatus(containerName);
1685
+ if (!status.running) {
1686
+ if (showGuidance) {
1687
+ showServiceSetupGuide(serviceName, "not running");
1688
+ }
1689
+ return false;
1690
+ }
1691
+ if (service.healthUrl) {
1692
+ const healthy = await checkService(service.healthUrl);
1693
+ if (!healthy) {
1694
+ if (showGuidance) {
1695
+ showServiceSetupGuide(serviceName, "not responding");
1696
+ }
1697
+ return false;
1698
+ }
1699
+ }
1700
+ return true;
1701
+ }
1702
+ function showServiceSetupGuide(serviceName, issue) {
1703
+ const service = SERVICES[serviceName];
1704
+ if (!service) return;
1705
+ writeLine();
1706
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} ${bold}${service.name}${RESET} is ${issue}`);
1707
+ writeLine(` ${colors.dim}${service.description}${RESET}`);
1708
+ writeLine();
1709
+ writeLine(` ${bold}To fix:${RESET}`);
1710
+ for (const step of service.setupGuide) {
1711
+ if (step === "") {
1712
+ writeLine();
1713
+ } else {
1714
+ writeLine(` ${colors.dim}${step}${RESET}`);
1715
+ }
1716
+ }
1717
+ if (service.envVars.length > 0) {
1718
+ writeLine();
1719
+ writeLine(` ${bold}Environment variables:${RESET}`);
1720
+ for (const envVar of service.envVars) {
1721
+ const value = process.env[envVar];
1722
+ const status = value ? `${colors.green}\u2713${RESET}` : `${colors.red}\u2717${RESET}`;
1723
+ writeLine(` ${status} ${colors.cyan}${envVar}${RESET}${value ? ` = ${colors.dim}${value}${RESET}` : ""}`);
1724
+ }
1725
+ }
1726
+ writeLine();
1727
+ writeLine(` ${colors.dim}Full setup: squads stack init${RESET}`);
1728
+ writeLine();
1729
+ }
1730
+ async function prompt(question, defaultValue) {
1731
+ const rl = createInterface({
1732
+ input: process.stdin,
1733
+ output: process.stdout
1734
+ });
1735
+ const suffix = defaultValue ? ` [${defaultValue}]` : "";
1736
+ return new Promise((resolve) => {
1737
+ rl.question(` ${question}${suffix}: `, (answer) => {
1738
+ rl.close();
1739
+ resolve(answer.trim() || defaultValue || "");
1740
+ });
1741
+ });
1742
+ }
1743
+ async function confirm(question, defaultYes = true) {
1744
+ const suffix = defaultYes ? "[Y/n]" : "[y/N]";
1745
+ const answer = await prompt(`${question} ${suffix}`);
1746
+ if (!answer) return defaultYes;
1747
+ return answer.toLowerCase().startsWith("y");
1748
+ }
1749
+ function findPackageDockerDir() {
1750
+ const candidates = [
1751
+ // From npm package (relative to dist/commands/stack.js)
1752
+ join7(__dirname2, "..", "..", "docker"),
1753
+ // Local development
1754
+ join7(process.cwd(), "docker"),
1755
+ join7(process.cwd(), "..", "squads-cli", "docker"),
1756
+ join7(homedir3(), "agents-squads", "squads-cli", "docker")
1757
+ ];
1758
+ for (const dir of candidates) {
1759
+ if (existsSync7(join7(dir, "docker-compose.yml"))) {
1760
+ return dir;
1761
+ }
1762
+ }
1763
+ return null;
1764
+ }
1765
+ function loadStackConfig() {
1766
+ if (!existsSync7(CONFIG_PATH2)) {
1767
+ return null;
1768
+ }
1769
+ try {
1770
+ const content = readFileSync5(CONFIG_PATH2, "utf-8");
1771
+ const config2 = {};
1772
+ for (const line of content.split("\n")) {
1773
+ const trimmed = line.trim();
1774
+ if (!trimmed || trimmed.startsWith("#")) continue;
1775
+ const match = trimmed.match(/^export\s+(\w+)=["']?([^"'\n]*)["']?$/);
1776
+ if (match) {
1777
+ const [, key, value] = match;
1778
+ if (key in DEFAULT_CONFIG) {
1779
+ config2[key] = value;
1780
+ }
1781
+ }
1782
+ }
1783
+ return config2;
1784
+ } catch {
1785
+ return null;
1786
+ }
1787
+ }
1788
+ function saveStackConfig(config2) {
1789
+ const lines = [
1790
+ "# Squads CLI Local Stack Configuration",
1791
+ "# Generated by: squads stack init",
1792
+ `# Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
1793
+ "",
1794
+ "# Database",
1795
+ `export SQUADS_DATABASE_URL="${config2.SQUADS_DATABASE_URL}"`,
1796
+ "",
1797
+ "# Bridge API",
1798
+ `export SQUADS_BRIDGE_URL="${config2.SQUADS_BRIDGE_URL}"`,
1799
+ "",
1800
+ "# Langfuse",
1801
+ `export LANGFUSE_HOST="${config2.LANGFUSE_HOST}"`,
1802
+ `export LANGFUSE_PUBLIC_KEY="${config2.LANGFUSE_PUBLIC_KEY}"`,
1803
+ `export LANGFUSE_SECRET_KEY="${config2.LANGFUSE_SECRET_KEY}"`,
1804
+ "",
1805
+ "# Redis",
1806
+ `export REDIS_URL="${config2.REDIS_URL}"`,
1807
+ "",
1808
+ "# To activate: source ~/.squadsrc",
1809
+ ""
1810
+ ];
1811
+ writeFileSync5(CONFIG_PATH2, lines.join("\n"));
1812
+ }
1813
+ function applyStackConfig() {
1814
+ const config2 = loadStackConfig();
1815
+ if (!config2) return;
1816
+ for (const [key, value] of Object.entries(config2)) {
1817
+ if (value && !process.env[key]) {
1818
+ process.env[key] = value;
1819
+ }
1820
+ }
1821
+ }
1822
+ function isDockerRunning() {
1823
+ try {
1824
+ execSync3("docker info", { stdio: "ignore" });
1825
+ return true;
1826
+ } catch {
1827
+ return false;
1828
+ }
1829
+ }
1830
+ function getContainerStatus(name) {
1831
+ try {
1832
+ const runningOutput = execSync3(
1833
+ `docker inspect ${name} --format '{{.State.Running}}'`,
1834
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1835
+ ).trim();
1836
+ const running = runningOutput === "true";
1837
+ if (!running) {
1838
+ return { name, running: false, healthy: false };
1839
+ }
1840
+ let port;
1841
+ try {
1842
+ const portOutput = execSync3(
1843
+ `docker inspect ${name} --format '{{range .NetworkSettings.Ports}}{{range .}}{{.HostPort}}{{end}}{{end}}'`,
1844
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1845
+ ).trim();
1846
+ port = portOutput || void 0;
1847
+ } catch {
1848
+ }
1849
+ let healthy = true;
1850
+ try {
1851
+ const healthOutput = execSync3(
1852
+ `docker inspect ${name} --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}'`,
1853
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1854
+ ).trim();
1855
+ if (healthOutput === "healthy" || healthOutput === "none") {
1856
+ healthy = true;
1857
+ } else if (healthOutput === "starting") {
1858
+ healthy = false;
1859
+ } else {
1860
+ healthy = false;
1861
+ }
1862
+ } catch {
1863
+ healthy = true;
1864
+ }
1865
+ return { name, running, healthy, port };
1866
+ } catch {
1867
+ return { name, running: false, healthy: false };
1868
+ }
1869
+ }
1870
+ async function checkService(url, timeout = 2e3) {
1871
+ try {
1872
+ const controller = new AbortController();
1873
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1874
+ const response = await fetch(url, { signal: controller.signal });
1875
+ clearTimeout(timeoutId);
1876
+ return response.ok;
1877
+ } catch {
1878
+ return false;
1879
+ }
1880
+ }
1881
+ function getLangfuseKeysFromDockerEnv() {
1882
+ const envPaths2 = [
1883
+ join7(process.cwd(), "docker", ".env"),
1884
+ join7(process.cwd(), "..", "squads-cli", "docker", ".env"),
1885
+ join7(homedir3(), "agents-squads", "squads-cli", "docker", ".env")
1886
+ ];
1887
+ for (const envPath of envPaths2) {
1888
+ if (existsSync7(envPath)) {
1889
+ const content = readFileSync5(envPath, "utf-8");
1890
+ const publicMatch = content.match(/LANGFUSE_PUBLIC_KEY=(\S+)/);
1891
+ const secretMatch = content.match(/LANGFUSE_SECRET_KEY=(\S+)/);
1892
+ if (publicMatch && secretMatch) {
1893
+ return {
1894
+ publicKey: publicMatch[1],
1895
+ secretKey: secretMatch[1]
1896
+ };
1897
+ }
1898
+ }
1899
+ }
1900
+ return null;
1901
+ }
1902
+ function findDockerComposeDir() {
1903
+ const candidates = [
1904
+ join7(process.cwd(), "docker"),
1905
+ join7(process.cwd(), "..", "squads-cli", "docker"),
1906
+ join7(homedir3(), "agents-squads", "squads-cli", "docker")
1907
+ ];
1908
+ for (const dir of candidates) {
1909
+ if (existsSync7(join7(dir, "docker-compose.yml"))) {
1910
+ return dir;
1911
+ }
1912
+ }
1913
+ return null;
1914
+ }
1915
+ async function stackInitCommand() {
1916
+ writeLine();
1917
+ writeLine(` ${gradient("squads")} ${colors.dim}stack init${RESET}`);
1918
+ writeLine();
1919
+ writeLine(` ${bold}Local Infrastructure Setup Wizard${RESET}`);
1920
+ writeLine(` ${colors.dim}This will configure Docker services for conversation capture,${RESET}`);
1921
+ writeLine(` ${colors.dim}memory extraction, and telemetry.${RESET}`);
1922
+ writeLine();
1923
+ writeLine(` ${bold}Step 1: Docker${RESET}`);
1924
+ if (!isDockerRunning()) {
1925
+ writeLine(` ${colors.red}${icons.error}${RESET} Docker is not running`);
1926
+ writeLine();
1927
+ writeLine(` ${colors.dim}Please start Docker Desktop and run this command again.${RESET}`);
1928
+ writeLine();
1929
+ return;
1930
+ }
1931
+ writeLine(` ${colors.green}${icons.success}${RESET} Docker is running`);
1932
+ writeLine();
1933
+ writeLine(` ${bold}Step 2: Docker Compose Files${RESET}`);
1934
+ let composeDir = findPackageDockerDir();
1935
+ const targetDir = join7(SQUADS_DATA_DIR, "docker");
1936
+ if (!composeDir) {
1937
+ writeLine(` ${colors.red}${icons.error}${RESET} Docker compose files not found`);
1938
+ writeLine(` ${colors.dim}This shouldn't happen if you installed via npm.${RESET}`);
1939
+ writeLine(` ${colors.dim}Try reinstalling: npm install -g squads-cli${RESET}`);
1940
+ writeLine();
1941
+ return;
1942
+ }
1943
+ if (composeDir !== targetDir && !existsSync7(targetDir)) {
1944
+ writeLine(` ${colors.cyan}${icons.progress}${RESET} Copying docker files to ${colors.dim}~/.squads/docker${RESET}`);
1945
+ try {
1946
+ mkdirSync4(SQUADS_DATA_DIR, { recursive: true });
1947
+ mkdirSync4(targetDir, { recursive: true });
1948
+ const filesToCopy = [
1949
+ "docker-compose.yml",
1950
+ "docker-compose.engram.yml",
1951
+ ".env.example"
1952
+ ];
1953
+ for (const file of filesToCopy) {
1954
+ const src = join7(composeDir, file);
1955
+ const dst = join7(targetDir, file);
1956
+ if (existsSync7(src)) {
1957
+ copyFileSync(src, dst);
1958
+ }
1959
+ }
1960
+ composeDir = targetDir;
1961
+ writeLine(` ${colors.green}${icons.success}${RESET} Files copied`);
1962
+ } catch (err) {
1963
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} Could not copy files, using source location`);
1964
+ }
1965
+ } else if (existsSync7(targetDir)) {
1966
+ composeDir = targetDir;
1967
+ writeLine(` ${colors.green}${icons.success}${RESET} Using ${colors.dim}~/.squads/docker${RESET}`);
1968
+ } else {
1969
+ writeLine(` ${colors.green}${icons.success}${RESET} Using ${colors.dim}${composeDir}${RESET}`);
1970
+ }
1971
+ writeLine();
1972
+ writeLine(` ${bold}Step 3: Environment Configuration${RESET}`);
1973
+ const envPath = join7(composeDir, ".env");
1974
+ const envExamplePath = join7(composeDir, ".env.example");
1975
+ if (!existsSync7(envPath)) {
1976
+ if (existsSync7(envExamplePath)) {
1977
+ copyFileSync(envExamplePath, envPath);
1978
+ writeLine(` ${colors.cyan}${icons.progress}${RESET} Created .env from template`);
1979
+ } else {
1980
+ const minimalEnv = `# Squads Local Stack Configuration
1981
+ # Generated by: squads stack init
1982
+
1983
+ # Langfuse API Keys (get from http://localhost:3100 after first run)
1984
+ LANGFUSE_PUBLIC_KEY=
1985
+ LANGFUSE_SECRET_KEY=
1986
+ LANGFUSE_HOST=http://langfuse:3000
1987
+
1988
+ # Mem0/Engram LLM Provider
1989
+ # Options: ollama (free, local), openai (requires API key)
1990
+ LLM_PROVIDER=ollama
1991
+ OLLAMA_BASE_URL=http://host.docker.internal:11434
1992
+ OLLAMA_LLM_MODEL=qwen3:latest
1993
+ OLLAMA_EMBEDDING_MODEL=nomic-embed-text:latest
1994
+
1995
+ # For OpenAI (if LLM_PROVIDER=openai)
1996
+ # OPENAI_API_KEY=sk-...
1997
+
1998
+ # Ports (defaults)
1999
+ POSTGRES_PORT=5433
2000
+ REDIS_PORT=6379
2001
+ LANGFUSE_PORT=3100
2002
+ OTEL_PORT=4318
2003
+ BRIDGE_PORT=8088
2004
+ `;
2005
+ writeFileSync5(envPath, minimalEnv);
2006
+ writeLine(` ${colors.cyan}${icons.progress}${RESET} Created default .env`);
2007
+ }
2008
+ } else {
2009
+ writeLine(` ${colors.green}${icons.success}${RESET} .env exists`);
2010
+ }
2011
+ const envContent = readFileSync5(envPath, "utf-8");
2012
+ const missingSecrets = [];
2013
+ const llmProvider = envContent.match(/LLM_PROVIDER=(\w+)/)?.[1] || "ollama";
2014
+ if (llmProvider === "openai") {
2015
+ if (!envContent.match(/OPENAI_API_KEY=\S+/)) {
2016
+ missingSecrets.push("OPENAI_API_KEY");
2017
+ }
2018
+ }
2019
+ if (missingSecrets.length > 0) {
2020
+ writeLine();
2021
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} Missing secrets in .env:`);
2022
+ for (const secret of missingSecrets) {
2023
+ writeLine(` ${colors.red}\u2022${RESET} ${secret}`);
2024
+ }
2025
+ writeLine();
2026
+ writeLine(` ${colors.dim}Edit: ${envPath}${RESET}`);
2027
+ }
2028
+ writeLine();
2029
+ writeLine(` ${bold}Step 4: Start Services${RESET}`);
2030
+ const containers = [
2031
+ { name: "squads-postgres", required: true },
2032
+ { name: "squads-redis", required: true },
2033
+ { name: "squads-bridge", required: true },
2034
+ { name: "squads-langfuse", required: false },
2035
+ { name: "squads-otel-collector", required: false },
2036
+ { name: "squads-mem0", required: false }
2037
+ ];
2038
+ const statuses = containers.map((c) => ({ ...c, ...getContainerStatus(c.name) }));
2039
+ const requiredNotRunning = statuses.filter((s) => s.required && !s.running);
2040
+ for (const status of statuses) {
2041
+ const icon = status.running ? status.healthy ? `${colors.green}\u25CF${RESET}` : `${colors.yellow}\u25CF${RESET}` : `${colors.red}\u25CB${RESET}`;
2042
+ const suffix = status.required ? "" : `${colors.dim}(optional)${RESET}`;
2043
+ writeLine(` ${icon} ${status.name} ${suffix}`);
2044
+ }
2045
+ writeLine();
2046
+ if (requiredNotRunning.length > 0) {
2047
+ const shouldStart = await confirm("Start required services now?");
2048
+ if (shouldStart) {
2049
+ writeLine();
2050
+ writeLine(` ${colors.cyan}${icons.progress}${RESET} Starting containers...`);
2051
+ try {
2052
+ execSync3("docker compose up -d", {
2053
+ cwd: composeDir,
2054
+ stdio: "inherit"
2055
+ });
2056
+ writeLine(` ${colors.cyan}${icons.progress}${RESET} Waiting for services to be ready...`);
2057
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
2058
+ writeLine(` ${colors.green}${icons.success}${RESET} Services started`);
2059
+ } catch (err) {
2060
+ writeLine(` ${colors.red}${icons.error}${RESET} Failed to start services`);
2061
+ writeLine(` ${colors.dim}Try manually: cd ${composeDir} && docker compose up -d${RESET}`);
2062
+ }
2063
+ }
2064
+ writeLine();
2065
+ }
2066
+ writeLine(` ${bold}Step 5: Langfuse API Keys${RESET}`);
2067
+ const langfuseKeys = getLangfuseKeysFromDockerEnv();
2068
+ if (!langfuseKeys?.publicKey || !langfuseKeys?.secretKey) {
2069
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} Langfuse API keys not configured`);
2070
+ writeLine();
2071
+ writeLine(` ${colors.dim}To enable telemetry:${RESET}`);
2072
+ writeLine(` ${colors.dim}1. Open http://localhost:3100${RESET}`);
2073
+ writeLine(` ${colors.dim}2. Create account and project${RESET}`);
2074
+ writeLine(` ${colors.dim}3. Copy API keys to ${envPath}${RESET}`);
2075
+ writeLine(` ${colors.dim}4. Run: squads stack init (again)${RESET}`);
2076
+ } else {
2077
+ writeLine(` ${colors.green}${icons.success}${RESET} Langfuse keys configured`);
2078
+ }
2079
+ writeLine();
2080
+ writeLine(` ${bold}Step 6: CLI Configuration${RESET}`);
2081
+ const config2 = {
2082
+ ...DEFAULT_CONFIG,
2083
+ LANGFUSE_PUBLIC_KEY: langfuseKeys?.publicKey || "",
2084
+ LANGFUSE_SECRET_KEY: langfuseKeys?.secretKey || ""
2085
+ };
2086
+ saveStackConfig(config2);
2087
+ writeLine(` ${colors.green}${icons.success}${RESET} Config saved to ${colors.cyan}~/.squadsrc${RESET}`);
2088
+ writeLine();
2089
+ writeLine(` ${colors.green}${bold}Setup Complete!${RESET}`);
2090
+ writeLine();
2091
+ writeLine(` ${colors.dim}To activate environment:${RESET}`);
2092
+ writeLine(` ${colors.cyan}source ~/.squadsrc${RESET}`);
2093
+ writeLine();
2094
+ writeLine(` ${colors.dim}Add to ~/.zshrc for persistence:${RESET}`);
2095
+ writeLine(` ${colors.cyan}[ -f ~/.squadsrc ] && source ~/.squadsrc${RESET}`);
2096
+ writeLine();
2097
+ writeLine(` ${colors.dim}Check status anytime:${RESET}`);
2098
+ writeLine(` ${colors.cyan}squads stack health${RESET}`);
2099
+ writeLine();
2100
+ }
2101
+ async function stackStatusCommand() {
2102
+ writeLine();
2103
+ writeLine(` ${gradient("squads")} ${colors.dim}stack status${RESET}`);
2104
+ writeLine();
2105
+ if (!isDockerRunning()) {
2106
+ writeLine(` ${colors.red}${icons.error}${RESET} Docker is not running`);
2107
+ writeLine();
2108
+ return;
2109
+ }
2110
+ const containers = [
2111
+ { name: "squads-postgres", service: "Database" },
2112
+ { name: "squads-redis", service: "Cache" },
2113
+ { name: "squads-bridge", service: "Bridge API" },
2114
+ { name: "squads-langfuse", service: "Langfuse" },
2115
+ { name: "squads-otel-collector", service: "Telemetry" }
2116
+ ];
2117
+ const w = { service: 12, container: 22, status: 12 };
2118
+ const tableWidth = w.service + w.container + w.status + 6;
2119
+ writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
2120
+ writeLine(
2121
+ ` ${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}`
2122
+ );
2123
+ writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
2124
+ for (const { name, service } of containers) {
2125
+ const status = getContainerStatus(name);
2126
+ const statusIcon = status.running ? status.healthy ? `${colors.green}\u25CF healthy${RESET}` : `${colors.yellow}\u25CF starting${RESET}` : `${colors.red}\u25CB stopped${RESET}`;
2127
+ writeLine(
2128
+ ` ${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}`
2129
+ );
2130
+ }
2131
+ writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
2132
+ writeLine();
2133
+ const config2 = loadStackConfig();
2134
+ if (config2) {
2135
+ writeLine(` ${colors.green}${icons.success}${RESET} Config loaded from ${colors.cyan}~/.squadsrc${RESET}`);
2136
+ const envVars = ["SQUADS_DATABASE_URL", "SQUADS_BRIDGE_URL", "LANGFUSE_HOST"];
2137
+ const missing = envVars.filter((v) => !process.env[v]);
2138
+ if (missing.length > 0) {
2139
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} Env vars not in shell: ${colors.dim}${missing.join(", ")}${RESET}`);
2140
+ writeLine(` ${colors.dim}Run: source ~/.squadsrc${RESET}`);
2141
+ } else {
2142
+ writeLine(` ${colors.green}${icons.success}${RESET} All env vars loaded`);
2143
+ }
2144
+ } else {
2145
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} No config found`);
2146
+ writeLine(` ${colors.dim}Run: squads stack init${RESET}`);
2147
+ }
2148
+ writeLine();
2149
+ }
2150
+ function stackEnvCommand() {
2151
+ const config2 = loadStackConfig();
2152
+ if (!config2) {
2153
+ console.log(`# Squads Local Stack - Default Config`);
2154
+ console.log(`# Run 'squads stack init' to auto-detect and save`);
2155
+ console.log();
2156
+ for (const [key, value] of Object.entries(DEFAULT_CONFIG)) {
2157
+ console.log(`export ${key}="${value}"`);
2158
+ }
2159
+ return;
2160
+ }
2161
+ console.log(`# Squads Local Stack Config`);
2162
+ console.log(`# From: ~/.squadsrc`);
2163
+ console.log();
2164
+ for (const [key, value] of Object.entries(config2)) {
2165
+ console.log(`export ${key}="${value}"`);
2166
+ }
2167
+ }
2168
+ async function stackUpCommand() {
2169
+ writeLine();
2170
+ writeLine(` ${gradient("squads")} ${colors.dim}stack up${RESET}`);
2171
+ writeLine();
2172
+ const composeDir = findDockerComposeDir();
2173
+ if (!composeDir) {
2174
+ writeLine(` ${colors.red}${icons.error}${RESET} docker-compose.yml not found`);
2175
+ writeLine();
2176
+ writeLine(` ${colors.dim}Expected locations:${RESET}`);
2177
+ writeLine(` ${colors.dim} ./docker/docker-compose.yml${RESET}`);
2178
+ writeLine(` ${colors.dim} ~/agents-squads/squads-cli/docker/docker-compose.yml${RESET}`);
2179
+ writeLine();
2180
+ return;
2181
+ }
2182
+ writeLine(` ${colors.cyan}${icons.progress}${RESET} Starting containers from ${colors.dim}${composeDir}${RESET}`);
2183
+ writeLine();
2184
+ try {
2185
+ const child = spawn2("docker-compose", ["up", "-d"], {
2186
+ cwd: composeDir,
2187
+ stdio: "inherit"
2188
+ });
2189
+ await new Promise((resolve, reject) => {
2190
+ child.on("close", (code) => {
2191
+ if (code === 0) resolve();
2192
+ else reject(new Error(`docker-compose exited with code ${code}`));
2193
+ });
2194
+ child.on("error", reject);
2195
+ });
2196
+ writeLine();
2197
+ writeLine(` ${colors.green}${icons.success}${RESET} Stack started`);
2198
+ writeLine(` ${colors.dim}Run: squads stack init${RESET} to configure CLI`);
2199
+ writeLine();
2200
+ } catch (error) {
2201
+ writeLine(` ${colors.red}${icons.error}${RESET} Failed to start stack`);
2202
+ writeLine(` ${colors.dim}${error}${RESET}`);
2203
+ writeLine();
2204
+ }
2205
+ }
2206
+ async function stackHealthCommand(verbose = false) {
2207
+ writeLine();
2208
+ writeLine(` ${gradient("squads")} ${colors.dim}stack health${RESET}`);
2209
+ writeLine();
2210
+ if (!isDockerRunning()) {
2211
+ writeLine(` ${colors.red}${icons.error}${RESET} Docker is not running`);
2212
+ writeLine();
2213
+ process.exitCode = 1;
2214
+ return;
2215
+ }
2216
+ const containers = [
2217
+ { name: "squads-postgres", service: "postgres", healthUrl: "", critical: true },
2218
+ { name: "squads-redis", service: "redis", healthUrl: "", critical: true },
2219
+ { name: "squads-neo4j", service: "neo4j", healthUrl: "http://localhost:7474", critical: false },
2220
+ { name: "squads-bridge", service: "bridge", healthUrl: "http://localhost:8088/health", critical: true },
2221
+ { name: "squads-otel-collector", service: "otel", healthUrl: "", critical: false },
2222
+ { name: "squads-langfuse", service: "langfuse", healthUrl: "http://localhost:3100/api/public/health", critical: false },
2223
+ { name: "squads-mem0", service: "mem0", healthUrl: "http://localhost:8000/health", critical: false },
2224
+ { name: "squads-engram-mcp", service: "engram", healthUrl: "http://localhost:8080/", critical: false }
2225
+ ];
2226
+ let hasFailures = false;
2227
+ const results = [];
2228
+ for (const container of containers) {
2229
+ const status = getContainerStatus(container.name);
2230
+ let ok = status.running && status.healthy;
2231
+ let statusText = "";
2232
+ if (!status.running) {
2233
+ statusText = "stopped";
2234
+ ok = false;
2235
+ } else if (!status.healthy) {
2236
+ statusText = "unhealthy";
2237
+ ok = false;
2238
+ } else if (container.healthUrl) {
2239
+ const httpOk = await checkService(container.healthUrl);
2240
+ if (!httpOk) {
2241
+ statusText = "not responding";
2242
+ ok = false;
2243
+ } else {
2244
+ statusText = "healthy";
2245
+ }
2246
+ } else {
2247
+ statusText = "healthy";
2248
+ }
2249
+ let logs;
2250
+ if (!ok && verbose) {
2251
+ try {
2252
+ logs = execSync3(`docker logs ${container.name} --tail 10 2>&1`, {
2253
+ encoding: "utf-8",
2254
+ stdio: ["pipe", "pipe", "pipe"]
2255
+ });
2256
+ } catch {
2257
+ logs = "(no logs available)";
2258
+ }
2259
+ }
2260
+ if (!ok && container.critical) {
2261
+ hasFailures = true;
2262
+ }
2263
+ results.push({ name: container.name, service: container.service, ok, status: statusText, logs });
2264
+ }
2265
+ for (const r of results) {
2266
+ const icon = r.ok ? `${colors.green}\u2713${RESET}` : `${colors.red}\u2717${RESET}`;
2267
+ const statusColor = r.ok ? colors.green : colors.red;
2268
+ writeLine(` ${icon} ${padEnd(r.service, 10)} ${statusColor}${r.status}${RESET}`);
2269
+ if (r.logs && !r.ok) {
2270
+ writeLine(` ${colors.dim}${"-".repeat(40)}${RESET}`);
2271
+ for (const line of r.logs.split("\n").slice(0, 5)) {
2272
+ writeLine(` ${colors.dim}${line.substring(0, 70)}${RESET}`);
2273
+ }
2274
+ }
1691
2275
  }
1692
- writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
1693
2276
  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}`);
2277
+ const healthy = results.filter((r) => r.ok).length;
2278
+ const total = results.length;
2279
+ if (hasFailures) {
2280
+ writeLine(` ${colors.red}${icons.error}${RESET} ${healthy}/${total} services healthy - critical failures`);
2281
+ writeLine(` ${colors.dim}Run with -v for logs: squads stack health -v${RESET}`);
2282
+ process.exitCode = 1;
2283
+ } else if (healthy < total) {
2284
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} ${healthy}/${total} services healthy - non-critical issues`);
2285
+ } else {
2286
+ writeLine(` ${colors.green}${icons.success}${RESET} ${healthy}/${total} services healthy`);
2287
+ }
1697
2288
  writeLine();
1698
2289
  }
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);
2290
+ function stackLogsCommand(service, tail = 50) {
2291
+ const containerMap = {
2292
+ postgres: "squads-postgres",
2293
+ redis: "squads-redis",
2294
+ neo4j: "squads-neo4j",
2295
+ bridge: "squads-bridge",
2296
+ otel: "squads-otel-collector",
2297
+ langfuse: "squads-langfuse",
2298
+ mem0: "squads-mem0",
2299
+ engram: "squads-engram-mcp"
2300
+ };
2301
+ const container = containerMap[service] || `squads-${service}`;
2302
+ try {
2303
+ execSync3(`docker logs ${container} --tail ${tail}`, { stdio: "inherit" });
2304
+ } catch {
2305
+ writeLine(` ${colors.red}${icons.error}${RESET} Container ${container} not found`);
1704
2306
  }
2307
+ }
2308
+ async function stackDownCommand() {
1705
2309
  writeLine();
1706
- writeLine(` ${gradient("squads")} ${colors.dim}status${RESET} ${colors.cyan}${squad.name}${RESET}`);
2310
+ writeLine(` ${gradient("squads")} ${colors.dim}stack down${RESET}`);
1707
2311
  writeLine();
1708
- if (squad.mission) {
1709
- writeLine(` ${colors.dim}${squad.mission}${RESET}`);
2312
+ const composeDir = findDockerComposeDir();
2313
+ if (!composeDir) {
2314
+ writeLine(` ${colors.red}${icons.error}${RESET} docker-compose.yml not found`);
1710
2315
  writeLine();
2316
+ return;
1711
2317
  }
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}`);
2318
+ writeLine(` ${colors.cyan}${icons.progress}${RESET} Stopping containers...`);
1716
2319
  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);
1723
- }
1724
- writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
1725
- if (squad.pipelines.length > 0) {
2320
+ try {
2321
+ const child = spawn2("docker-compose", ["down"], {
2322
+ cwd: composeDir,
2323
+ stdio: "inherit"
2324
+ });
2325
+ await new Promise((resolve, reject) => {
2326
+ child.on("close", (code) => {
2327
+ if (code === 0) resolve();
2328
+ else reject(new Error(`docker-compose exited with code ${code}`));
2329
+ });
2330
+ child.on("error", reject);
2331
+ });
2332
+ writeLine();
2333
+ writeLine(` ${colors.green}${icons.success}${RESET} Stack stopped`);
2334
+ writeLine();
2335
+ } catch (error) {
2336
+ writeLine(` ${colors.red}${icons.error}${RESET} Failed to stop stack`);
2337
+ writeLine(` ${colors.dim}${error}${RESET}`);
1726
2338
  writeLine();
1727
- writeLine(` ${bold}Pipelines${RESET}`);
1728
- for (const pipeline of squad.pipelines) {
1729
- writeLine(` ${colors.dim}${pipeline.agents.join(" \u2192 ")}${RESET}`);
1730
- }
1731
- }
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
- }
1751
- }
1752
- }
1753
- }
1754
2339
  }
1755
- writeLine();
1756
- writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} ${colors.dim}Run the squad${RESET}`);
1757
- writeLine(` ${colors.dim}$${RESET} squads memory show ${colors.cyan}${squadName}${RESET} ${colors.dim}View full memory${RESET}`);
1758
- writeLine(` ${colors.dim}$${RESET} squads status ${colors.cyan}${squadName}${RESET} -v ${colors.dim}Verbose status${RESET}`);
1759
- writeLine();
1760
2340
  }
1761
2341
 
1762
2342
  // src/commands/memory.ts
1763
2343
  var SQUADS_BRIDGE_URL = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
2344
+ var MEM0_API_URL = process.env.MEM0_API_URL || "http://localhost:8000";
1764
2345
  async function memoryQueryCommand(query, options) {
1765
2346
  const memoryDir = findMemoryDir();
1766
2347
  if (!memoryDir) {
@@ -1986,20 +2567,125 @@ async function memorySearchCommand(query, options = {}) {
1986
2567
  writeLine();
1987
2568
  } catch (error) {
1988
2569
  const errorMessage = error instanceof Error ? error.message : String(error);
1989
- if (errorMessage.includes("ECONNREFUSED")) {
1990
- writeLine(` ${colors.yellow}Cannot connect to squads-bridge${RESET}`);
1991
- writeLine(` ${colors.dim}Run: docker compose up -d${RESET}`);
2570
+ if (errorMessage.includes("ECONNREFUSED") || errorMessage.includes("fetch failed")) {
2571
+ showServiceSetupGuide("bridge", "not responding");
1992
2572
  } else {
1993
2573
  writeLine(` ${colors.red}Error searching conversations: ${errorMessage}${RESET}`);
2574
+ writeLine();
2575
+ }
2576
+ }
2577
+ }
2578
+ async function memoryExtractCommand(options = {}) {
2579
+ writeLine();
2580
+ writeLine(` ${gradient("squads")} ${colors.dim}memory extract${RESET}`);
2581
+ writeLine();
2582
+ const hours = options.hours || 24;
2583
+ try {
2584
+ writeLine(` ${colors.dim}Fetching conversations from last ${hours}h...${RESET}`);
2585
+ const bridgeResponse = await fetch(`${SQUADS_BRIDGE_URL}/api/conversations/recent`);
2586
+ if (!bridgeResponse.ok) {
2587
+ throw new Error(`Bridge API error: ${bridgeResponse.status}`);
2588
+ }
2589
+ const { conversations, count } = await bridgeResponse.json();
2590
+ if (count === 0) {
2591
+ writeLine(` ${colors.yellow}No recent conversations to extract${RESET}`);
2592
+ writeLine();
2593
+ return;
2594
+ }
2595
+ writeLine(` ${colors.green}${count}${RESET} conversations found`);
2596
+ writeLine();
2597
+ const sessions2 = /* @__PURE__ */ new Map();
2598
+ for (const conv of conversations) {
2599
+ const sessionId = conv.session_id || "unknown";
2600
+ if (!sessions2.has(sessionId)) {
2601
+ sessions2.set(sessionId, []);
2602
+ }
2603
+ sessions2.get(sessionId).push(conv);
2604
+ }
2605
+ writeLine(` ${colors.cyan}${sessions2.size}${RESET} sessions to process`);
2606
+ writeLine();
2607
+ if (options.dryRun) {
2608
+ writeLine(` ${colors.yellow}Dry run - not sending to Engram${RESET}`);
2609
+ writeLine();
2610
+ for (const [sessionId, convs] of sessions2) {
2611
+ const squad = convs[0]?.squad || "unknown";
2612
+ const agent = convs[0]?.agent || "unknown";
2613
+ writeLine(` ${icons.progress} ${colors.dim}${sessionId.slice(0, 8)}${RESET} ${colors.cyan}${squad}/${agent}${RESET} ${colors.dim}(${convs.length} messages)${RESET}`);
2614
+ }
2615
+ writeLine();
2616
+ return;
2617
+ }
2618
+ let extracted = 0;
2619
+ let failed = 0;
2620
+ for (const [sessionId, convs] of sessions2) {
2621
+ const squad = convs[0]?.squad || "hq";
2622
+ const agent = convs[0]?.agent || "unknown";
2623
+ const messages = convs.map((c) => ({
2624
+ role: c.role === "assistant" ? "assistant" : c.role === "user" ? "user" : "system",
2625
+ content: c.content
2626
+ }));
2627
+ try {
2628
+ const mem0Response = await fetch(`${MEM0_API_URL}/memories`, {
2629
+ method: "POST",
2630
+ headers: { "Content-Type": "application/json" },
2631
+ body: JSON.stringify({
2632
+ messages,
2633
+ user_id: squad,
2634
+ agent_id: agent,
2635
+ run_id: sessionId,
2636
+ metadata: {
2637
+ source: "squads-cli",
2638
+ extracted_at: (/* @__PURE__ */ new Date()).toISOString()
2639
+ }
2640
+ })
2641
+ });
2642
+ if (mem0Response.ok) {
2643
+ const result = await mem0Response.json();
2644
+ const memCount = result.results?.length || 0;
2645
+ writeLine(` ${colors.green}${icons.success}${RESET} ${colors.dim}${sessionId.slice(0, 8)}${RESET} ${colors.cyan}${squad}/${agent}${RESET} \u2192 ${colors.green}${memCount}${RESET} memories`);
2646
+ extracted++;
2647
+ } else {
2648
+ writeLine(` ${colors.red}${icons.error}${RESET} ${colors.dim}${sessionId.slice(0, 8)}${RESET} ${colors.red}Failed: ${mem0Response.status}${RESET}`);
2649
+ failed++;
2650
+ }
2651
+ } catch (err) {
2652
+ writeLine(` ${colors.red}${icons.error}${RESET} ${colors.dim}${sessionId.slice(0, 8)}${RESET} ${colors.red}Error: ${err}${RESET}`);
2653
+ failed++;
2654
+ }
1994
2655
  }
1995
2656
  writeLine();
2657
+ if (failed === 0) {
2658
+ writeLine(` ${colors.green}${icons.success}${RESET} Extracted memories from ${extracted} sessions`);
2659
+ } else {
2660
+ writeLine(` ${colors.yellow}${icons.warning}${RESET} Extracted: ${extracted}, Failed: ${failed}`);
2661
+ }
2662
+ writeLine();
2663
+ writeLine(` ${colors.dim}$${RESET} squads memory query ${colors.cyan}"<term>"${RESET} ${colors.dim}Search extracted memories${RESET}`);
2664
+ writeLine();
2665
+ } catch (error) {
2666
+ const errorMessage = error instanceof Error ? error.message : String(error);
2667
+ if (errorMessage.includes("ECONNREFUSED") || errorMessage.includes("fetch failed")) {
2668
+ const bridgeOk = await checkServiceAvailable("bridge", false);
2669
+ const mem0Ok = await checkServiceAvailable("mem0", false);
2670
+ if (!bridgeOk) {
2671
+ showServiceSetupGuide("bridge", "not responding");
2672
+ } else if (!mem0Ok) {
2673
+ showServiceSetupGuide("mem0", "not responding");
2674
+ } else {
2675
+ writeLine(` ${colors.red}Error: ${errorMessage}${RESET}`);
2676
+ writeLine();
2677
+ }
2678
+ } else {
2679
+ writeLine(` ${colors.red}Error: ${errorMessage}${RESET}`);
2680
+ writeLine();
2681
+ }
1996
2682
  }
1997
2683
  }
1998
2684
 
1999
2685
  // 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";
2686
+ import { execSync as execSync4 } from "child_process";
2687
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync2 } from "fs";
2688
+ import { join as join8 } from "path";
2003
2689
  var PATH_TO_SQUAD = {
2004
2690
  "squads-cli": "product",
2005
2691
  "agents-squads-web": "website",
@@ -2027,21 +2713,21 @@ var MESSAGE_TO_SQUAD = {
2027
2713
  "infra": "engineering"
2028
2714
  };
2029
2715
  function getLastSyncTime(memoryDir) {
2030
- const syncFile = join7(memoryDir, ".last-sync");
2031
- if (existsSync7(syncFile)) {
2032
- return readFileSync5(syncFile, "utf-8").trim();
2716
+ const syncFile = join8(memoryDir, ".last-sync");
2717
+ if (existsSync8(syncFile)) {
2718
+ return readFileSync6(syncFile, "utf-8").trim();
2033
2719
  }
2034
2720
  return null;
2035
2721
  }
2036
2722
  function updateLastSyncTime(memoryDir) {
2037
- const syncFile = join7(memoryDir, ".last-sync");
2038
- writeFileSync5(syncFile, (/* @__PURE__ */ new Date()).toISOString());
2723
+ const syncFile = join8(memoryDir, ".last-sync");
2724
+ writeFileSync6(syncFile, (/* @__PURE__ */ new Date()).toISOString());
2039
2725
  }
2040
2726
  function getRecentCommits(since) {
2041
2727
  const commits = [];
2042
2728
  try {
2043
2729
  const sinceArg = since ? `--since="${since}"` : "-n 20";
2044
- const logOutput = execSync2(
2730
+ const logOutput = execSync4(
2045
2731
  `git log ${sinceArg} --format="%H|%aI|%s" --name-only`,
2046
2732
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
2047
2733
  ).trim();
@@ -2107,22 +2793,22 @@ ${messages}
2107
2793
  `;
2108
2794
  }
2109
2795
  function appendToSquadMemory(memoryDir, squad, summary) {
2110
- const squadMemoryDir = join7(memoryDir, squad);
2111
- if (!existsSync7(squadMemoryDir)) {
2112
- mkdirSync4(squadMemoryDir, { recursive: true });
2796
+ const squadMemoryDir = join8(memoryDir, squad);
2797
+ if (!existsSync8(squadMemoryDir)) {
2798
+ mkdirSync5(squadMemoryDir, { recursive: true });
2113
2799
  }
2114
2800
  let agentDir;
2115
- const existingDirs = existsSync7(squadMemoryDir) ? readdirSync3(squadMemoryDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) : [];
2801
+ const existingDirs = existsSync8(squadMemoryDir) ? readdirSync2(squadMemoryDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) : [];
2116
2802
  if (existingDirs.length > 0) {
2117
- agentDir = join7(squadMemoryDir, existingDirs[0]);
2803
+ agentDir = join8(squadMemoryDir, existingDirs[0]);
2118
2804
  } else {
2119
- agentDir = join7(squadMemoryDir, `${squad}-lead`);
2120
- mkdirSync4(agentDir, { recursive: true });
2805
+ agentDir = join8(squadMemoryDir, `${squad}-lead`);
2806
+ mkdirSync5(agentDir, { recursive: true });
2121
2807
  }
2122
- const statePath = join7(agentDir, "state.md");
2808
+ const statePath = join8(agentDir, "state.md");
2123
2809
  let content = "";
2124
- if (existsSync7(statePath)) {
2125
- content = readFileSync5(statePath, "utf-8");
2810
+ if (existsSync8(statePath)) {
2811
+ content = readFileSync6(statePath, "utf-8");
2126
2812
  } else {
2127
2813
  content = `# ${squad} Squad - State
2128
2814
 
@@ -2134,9 +2820,53 @@ Updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
2134
2820
  `Updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
2135
2821
  );
2136
2822
  content += summary;
2137
- writeFileSync5(statePath, content);
2823
+ writeFileSync6(statePath, content);
2138
2824
  return true;
2139
2825
  }
2826
+ function gitPullMemory() {
2827
+ try {
2828
+ execSync4("git fetch origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
2829
+ const status = execSync4("git status -sb", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
2830
+ const behindMatch = status.match(/behind (\d+)/);
2831
+ const aheadMatch = status.match(/ahead (\d+)/);
2832
+ const behind = behindMatch ? parseInt(behindMatch[1]) : 0;
2833
+ const ahead = aheadMatch ? parseInt(aheadMatch[1]) : 0;
2834
+ if (behind === 0) {
2835
+ return { success: true, output: "Already up to date", behind: 0, ahead };
2836
+ }
2837
+ const output = execSync4("git pull --rebase origin main", {
2838
+ encoding: "utf-8",
2839
+ stdio: ["pipe", "pipe", "pipe"]
2840
+ });
2841
+ return { success: true, output: output.trim(), behind, ahead };
2842
+ } catch (error) {
2843
+ const err = error;
2844
+ return { success: false, output: err.message || "Pull failed", behind: 0, ahead: 0 };
2845
+ }
2846
+ }
2847
+ function gitPushMemory() {
2848
+ try {
2849
+ const status = execSync4("git status --porcelain .agents/memory/", {
2850
+ encoding: "utf-8",
2851
+ stdio: ["pipe", "pipe", "pipe"]
2852
+ }).trim();
2853
+ if (status) {
2854
+ execSync4("git add .agents/memory/", { stdio: ["pipe", "pipe", "pipe"] });
2855
+ execSync4('git commit -m "chore: sync squad memory"', {
2856
+ encoding: "utf-8",
2857
+ stdio: ["pipe", "pipe", "pipe"]
2858
+ });
2859
+ }
2860
+ const output = execSync4("git push origin main", {
2861
+ encoding: "utf-8",
2862
+ stdio: ["pipe", "pipe", "pipe"]
2863
+ });
2864
+ return { success: true, output: output.trim() || "Pushed successfully" };
2865
+ } catch (error) {
2866
+ const err = error;
2867
+ return { success: false, output: err.message || "Push failed" };
2868
+ }
2869
+ }
2140
2870
  async function syncCommand(options = {}) {
2141
2871
  const memoryDir = findMemoryDir();
2142
2872
  const squadsDir = findSquadsDir();
@@ -2148,6 +2878,25 @@ async function syncCommand(options = {}) {
2148
2878
  writeLine();
2149
2879
  writeLine(` ${gradient("squads")} ${colors.dim}memory sync${RESET}`);
2150
2880
  writeLine();
2881
+ const doPull = options.pull !== false;
2882
+ const doPush = options.push === true;
2883
+ if (doPull) {
2884
+ writeLine(` ${icons.progress} Pulling from remote...`);
2885
+ const pullResult = gitPullMemory();
2886
+ if (pullResult.success) {
2887
+ if (pullResult.behind > 0) {
2888
+ writeLine(` ${icons.success} Pulled ${colors.cyan}${pullResult.behind}${RESET} commits from remote`);
2889
+ } else {
2890
+ writeLine(` ${icons.success} ${colors.dim}Already up to date${RESET}`);
2891
+ }
2892
+ if (pullResult.ahead > 0) {
2893
+ writeLine(` ${colors.dim} ${pullResult.ahead} local commits to push${RESET}`);
2894
+ }
2895
+ } else {
2896
+ writeLine(` ${icons.error} ${colors.red}Pull failed: ${pullResult.output}${RESET}`);
2897
+ }
2898
+ writeLine();
2899
+ }
2151
2900
  const lastSync = getLastSyncTime(memoryDir);
2152
2901
  if (lastSync) {
2153
2902
  writeLine(` ${colors.dim}Last sync: ${lastSync.split("T")[0]}${RESET}`);
@@ -2189,8 +2938,21 @@ async function syncCommand(options = {}) {
2189
2938
  writeLine(` ${colors.green}${updated}${RESET} squad memories updated`);
2190
2939
  writeLine();
2191
2940
  updateLastSyncTime(memoryDir);
2941
+ if (doPush) {
2942
+ writeLine(` ${icons.progress} Pushing to remote...`);
2943
+ const pushResult = gitPushMemory();
2944
+ if (pushResult.success) {
2945
+ writeLine(` ${icons.success} ${colors.green}Pushed memory updates to remote${RESET}`);
2946
+ } else {
2947
+ writeLine(` ${icons.error} ${colors.red}Push failed: ${pushResult.output}${RESET}`);
2948
+ }
2949
+ writeLine();
2950
+ }
2192
2951
  writeLine(` ${colors.dim}$${RESET} squads memory show ${colors.cyan}<squad>${RESET} ${colors.dim}View updated memory${RESET}`);
2193
2952
  writeLine(` ${colors.dim}$${RESET} squads status ${colors.dim}See all squads${RESET}`);
2953
+ if (!doPush && updated > 0) {
2954
+ writeLine(` ${colors.dim}$${RESET} squads memory sync --push ${colors.dim}Push changes to remote${RESET}`);
2955
+ }
2194
2956
  writeLine();
2195
2957
  }
2196
2958
 
@@ -2318,28 +3080,28 @@ async function goalProgressCommand(squadName, goalIndex, progress2) {
2318
3080
  }
2319
3081
 
2320
3082
  // 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";
3083
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
3084
+ import { join as join9, dirname as dirname5 } from "path";
2323
3085
  function getFeedbackPath(squadName) {
2324
3086
  const memoryDir = findMemoryDir();
2325
3087
  if (!memoryDir) return null;
2326
3088
  const squad = loadSquad(squadName);
2327
3089
  const agentName = squad?.agents[0]?.name || `${squadName}-lead`;
2328
- return join8(memoryDir, squadName, agentName, "feedback.md");
3090
+ return join9(memoryDir, squadName, agentName, "feedback.md");
2329
3091
  }
2330
3092
  function getOutputPath(squadName) {
2331
3093
  const memoryDir = findMemoryDir();
2332
3094
  if (!memoryDir) return null;
2333
3095
  const squad = loadSquad(squadName);
2334
3096
  const agentName = squad?.agents[0]?.name || `${squadName}-lead`;
2335
- return join8(memoryDir, squadName, agentName, "output.md");
3097
+ return join9(memoryDir, squadName, agentName, "output.md");
2336
3098
  }
2337
3099
  function getLastExecution(squadName) {
2338
3100
  const outputPath = getOutputPath(squadName);
2339
- if (!outputPath || !existsSync8(outputPath)) {
3101
+ if (!outputPath || !existsSync9(outputPath)) {
2340
3102
  return null;
2341
3103
  }
2342
- const content = readFileSync6(outputPath, "utf-8");
3104
+ const content = readFileSync7(outputPath, "utf-8");
2343
3105
  const lines = content.split("\n");
2344
3106
  let date = "unknown";
2345
3107
  let summary = lines.slice(0, 5).join("\n");
@@ -2390,9 +3152,9 @@ async function feedbackAddCommand(squadName, rating, feedback2, options) {
2390
3152
  return;
2391
3153
  }
2392
3154
  const lastExec = getLastExecution(squadName);
2393
- const dir = dirname4(feedbackPath);
2394
- if (!existsSync8(dir)) {
2395
- mkdirSync5(dir, { recursive: true });
3155
+ const dir = dirname5(feedbackPath);
3156
+ if (!existsSync9(dir)) {
3157
+ mkdirSync6(dir, { recursive: true });
2396
3158
  }
2397
3159
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2398
3160
  let entry = `
@@ -2420,15 +3182,15 @@ _Date: ${date}_
2420
3182
  }
2421
3183
  }
2422
3184
  let existing = "";
2423
- if (existsSync8(feedbackPath)) {
2424
- existing = readFileSync6(feedbackPath, "utf-8");
3185
+ if (existsSync9(feedbackPath)) {
3186
+ existing = readFileSync7(feedbackPath, "utf-8");
2425
3187
  } else {
2426
3188
  existing = `# ${squadName} - Feedback Log
2427
3189
 
2428
3190
  > Execution feedback and learnings
2429
3191
  `;
2430
3192
  }
2431
- writeFileSync6(feedbackPath, existing + entry);
3193
+ writeFileSync7(feedbackPath, existing + entry);
2432
3194
  const stars = `${colors.yellow}${"\u2605".repeat(ratingNum)}${"\u2606".repeat(5 - ratingNum)}${RESET}`;
2433
3195
  writeLine();
2434
3196
  writeLine(` ${icons.success} Feedback recorded for ${colors.cyan}${squadName}${RESET}`);
@@ -2441,11 +3203,11 @@ _Date: ${date}_
2441
3203
  }
2442
3204
  async function feedbackShowCommand(squadName, options) {
2443
3205
  const feedbackPath = getFeedbackPath(squadName);
2444
- if (!feedbackPath || !existsSync8(feedbackPath)) {
3206
+ if (!feedbackPath || !existsSync9(feedbackPath)) {
2445
3207
  writeLine(` ${colors.yellow}No feedback recorded for ${squadName}${RESET}`);
2446
3208
  return;
2447
3209
  }
2448
- const content = readFileSync6(feedbackPath, "utf-8");
3210
+ const content = readFileSync7(feedbackPath, "utf-8");
2449
3211
  const entries = parseFeedbackHistory(content);
2450
3212
  const limit = options.limit ? parseInt(options.limit) : 5;
2451
3213
  const recent = entries.slice(-limit).reverse();
@@ -2480,7 +3242,7 @@ async function feedbackStatsCommand() {
2480
3242
  writeLine();
2481
3243
  writeLine(` ${gradient("squads")} ${colors.dim}feedback stats${RESET}`);
2482
3244
  writeLine();
2483
- const squads = readdirSync4(memoryDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3245
+ const squads = readdirSync3(memoryDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
2484
3246
  const w = { squad: 18, avg: 12, count: 8, trend: 6 };
2485
3247
  const tableWidth = w.squad + w.avg + w.count + w.trend + 4;
2486
3248
  writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
@@ -2489,10 +3251,10 @@ async function feedbackStatsCommand() {
2489
3251
  writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
2490
3252
  for (const squad of squads) {
2491
3253
  const feedbackPath = getFeedbackPath(squad);
2492
- if (!feedbackPath || !existsSync8(feedbackPath)) {
3254
+ if (!feedbackPath || !existsSync9(feedbackPath)) {
2493
3255
  continue;
2494
3256
  }
2495
- const content = readFileSync6(feedbackPath, "utf-8");
3257
+ const content = readFileSync7(feedbackPath, "utf-8");
2496
3258
  const entries = parseFeedbackHistory(content);
2497
3259
  if (entries.length === 0) continue;
2498
3260
  const avgRating = entries.reduce((sum, e) => sum + e.rating, 0) / entries.length;
@@ -2515,9 +3277,9 @@ async function feedbackStatsCommand() {
2515
3277
  }
2516
3278
 
2517
3279
  // 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";
3280
+ import { readdirSync as readdirSync4, existsSync as existsSync10, statSync as statSync2 } from "fs";
3281
+ import { join as join10 } from "path";
3282
+ import { homedir as homedir4 } from "os";
2521
3283
 
2522
3284
  // src/lib/costs.ts
2523
3285
  var MODEL_PRICING = {
@@ -2528,16 +3290,29 @@ var MODEL_PRICING = {
2528
3290
  "claude-3-5-haiku-20241022": { input: 0.8, output: 4 },
2529
3291
  default: { input: 3, output: 15 }
2530
3292
  };
2531
- var DEFAULT_DAILY_BUDGET = 50;
3293
+ var DEFAULT_DAILY_BUDGET = 200;
2532
3294
  var DEFAULT_DAILY_CALL_LIMIT = 1e3;
2533
3295
  var BRIDGE_URL = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
3296
+ var FETCH_TIMEOUT_MS = 2e3;
3297
+ async function fetchWithTimeout(url, options = {}, timeoutMs = FETCH_TIMEOUT_MS) {
3298
+ const controller = new AbortController();
3299
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
3300
+ try {
3301
+ const response = await fetch(url, { ...options, signal: controller.signal });
3302
+ clearTimeout(timeoutId);
3303
+ return response;
3304
+ } catch (error) {
3305
+ clearTimeout(timeoutId);
3306
+ throw error;
3307
+ }
3308
+ }
2534
3309
  function calcCost(model, inputTokens, outputTokens) {
2535
3310
  const pricing = MODEL_PRICING[model] || MODEL_PRICING.default;
2536
3311
  return inputTokens / 1e6 * pricing.input + outputTokens / 1e6 * pricing.output;
2537
3312
  }
2538
3313
  async function fetchFromBridge(period = "day") {
2539
3314
  try {
2540
- const response = await fetch(`${BRIDGE_URL}/api/cost/summary?period=${period}`, {
3315
+ const response = await fetchWithTimeout(`${BRIDGE_URL}/api/cost/summary?period=${period}`, {
2541
3316
  headers: { "Content-Type": "application/json" }
2542
3317
  });
2543
3318
  if (!response.ok) {
@@ -2589,7 +3364,7 @@ async function fetchFromLangfuse(limit = 100) {
2589
3364
  try {
2590
3365
  const auth = Buffer.from(`${publicKey}:${secretKey}`).toString("base64");
2591
3366
  const url = `${host}/api/public/observations?limit=${limit}`;
2592
- const response = await fetch(url, {
3367
+ const response = await fetchWithTimeout(url, {
2593
3368
  headers: {
2594
3369
  Authorization: `Basic ${auth}`,
2595
3370
  "Content-Type": "application/json"
@@ -2684,41 +3459,78 @@ function formatCostBar(usedPercent, width = 20) {
2684
3459
  const empty = width - filled;
2685
3460
  return "\u2588".repeat(filled) + "\u2591".repeat(empty);
2686
3461
  }
2687
- async function fetchRateLimits() {
3462
+ async function fetchBridgeStats() {
2688
3463
  try {
2689
- const response = await fetch(`${BRIDGE_URL}/api/rate-limits`, {
3464
+ const statsResponse = await fetchWithTimeout(`${BRIDGE_URL}/stats`, {
2690
3465
  headers: { "Content-Type": "application/json" }
2691
3466
  });
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
- };
3467
+ if (!statsResponse.ok) {
3468
+ return null;
2713
3469
  }
2714
- return { limits, source: "proxy" };
3470
+ const stats = await statsResponse.json();
3471
+ const healthResponse = await fetchWithTimeout(`${BRIDGE_URL}/health`, {
3472
+ headers: { "Content-Type": "application/json" }
3473
+ });
3474
+ const health = healthResponse.ok ? await healthResponse.json() : {};
3475
+ const [costResponse, weekResponse] = await Promise.all([
3476
+ fetchWithTimeout(`${BRIDGE_URL}/api/cost/summary?period=day`, {
3477
+ headers: { "Content-Type": "application/json" }
3478
+ }),
3479
+ fetchWithTimeout(`${BRIDGE_URL}/api/cost/summary?period=week`, {
3480
+ headers: { "Content-Type": "application/json" }
3481
+ })
3482
+ ]);
3483
+ const costData = costResponse.ok ? await costResponse.json() : {};
3484
+ const weekData = weekResponse.ok ? await weekResponse.json() : {};
3485
+ return {
3486
+ status: stats.status || "unknown",
3487
+ source: stats.source || "none",
3488
+ today: {
3489
+ generations: stats.today?.generations || 0,
3490
+ inputTokens: stats.today?.input_tokens || 0,
3491
+ outputTokens: stats.today?.output_tokens || 0,
3492
+ costUsd: stats.today?.cost_usd || 0
3493
+ },
3494
+ week: weekData.totals ? {
3495
+ generations: weekData.totals.generations || 0,
3496
+ inputTokens: weekData.totals.input_tokens || 0,
3497
+ outputTokens: weekData.totals.output_tokens || 0,
3498
+ costUsd: weekData.totals.cost_usd || 0,
3499
+ byModel: (weekData.by_model || []).map((m) => ({
3500
+ model: m.model || "unknown",
3501
+ generations: m.generations || 0,
3502
+ costUsd: m.cost_usd || 0
3503
+ }))
3504
+ } : void 0,
3505
+ budget: {
3506
+ daily: stats.budget?.daily || DEFAULT_DAILY_BUDGET,
3507
+ used: stats.budget?.used || 0,
3508
+ remaining: stats.budget?.remaining || DEFAULT_DAILY_BUDGET,
3509
+ usedPct: stats.budget?.used_pct || 0
3510
+ },
3511
+ bySquad: (stats.by_squad || []).map((s) => ({
3512
+ squad: s.squad || "unknown",
3513
+ costUsd: s.cost_usd || 0,
3514
+ generations: s.generations || 0
3515
+ })),
3516
+ byModel: (costData.by_model || []).map((m) => ({
3517
+ model: m.model || "unknown",
3518
+ generations: m.generations || 0,
3519
+ costUsd: m.cost_usd || 0
3520
+ })),
3521
+ health: {
3522
+ postgres: health.postgres || "unknown",
3523
+ redis: health.redis || "unknown",
3524
+ langfuse: health.langfuse || "unknown"
3525
+ }
3526
+ };
2715
3527
  } catch {
2716
- return { limits: {}, source: "none" };
3528
+ return null;
2717
3529
  }
2718
3530
  }
2719
3531
  async function fetchInsights(period = "week") {
2720
3532
  try {
2721
- const response = await fetch(`${BRIDGE_URL}/api/insights?period=${period}`, {
3533
+ const response = await fetchWithTimeout(`${BRIDGE_URL}/api/insights?period=${period}`, {
2722
3534
  headers: { "Content-Type": "application/json" }
2723
3535
  });
2724
3536
  if (!response.ok) {
@@ -2793,7 +3605,8 @@ function getPool() {
2793
3605
  connectionString: DATABASE_URL,
2794
3606
  max: 5,
2795
3607
  idleTimeoutMillis: 3e4,
2796
- connectionTimeoutMillis: 5e3
3608
+ connectionTimeoutMillis: 1500
3609
+ // Fast timeout for CLI responsiveness
2797
3610
  });
2798
3611
  pool.on("error", (err) => {
2799
3612
  console.error("Unexpected database pool error:", err);
@@ -2895,21 +3708,27 @@ async function getDashboardHistory(limit = 30) {
2895
3708
  return [];
2896
3709
  }
2897
3710
  }
3711
+ async function closeDatabase() {
3712
+ if (pool) {
3713
+ await pool.end();
3714
+ pool = null;
3715
+ }
3716
+ }
2898
3717
 
2899
3718
  // src/commands/dashboard.ts
2900
3719
  function getLastActivityDate(squadName) {
2901
3720
  const memoryDir = findMemoryDir();
2902
3721
  if (!memoryDir) return "unknown";
2903
- const squadMemory = join9(memoryDir, squadName);
2904
- if (!existsSync9(squadMemory)) return "\u2014";
3722
+ const squadMemory = join10(memoryDir, squadName);
3723
+ if (!existsSync10(squadMemory)) return "\u2014";
2905
3724
  let latestTime = 0;
2906
3725
  try {
2907
- const agents = readdirSync5(squadMemory, { withFileTypes: true }).filter((e) => e.isDirectory());
3726
+ const agents = readdirSync4(squadMemory, { withFileTypes: true }).filter((e) => e.isDirectory());
2908
3727
  for (const agent of agents) {
2909
- const agentPath = join9(squadMemory, agent.name);
2910
- const files = readdirSync5(agentPath).filter((f) => f.endsWith(".md"));
3728
+ const agentPath = join10(squadMemory, agent.name);
3729
+ const files = readdirSync4(agentPath).filter((f) => f.endsWith(".md"));
2911
3730
  for (const file of files) {
2912
- const filePath = join9(agentPath, file);
3731
+ const filePath = join10(agentPath, file);
2913
3732
  const stats = statSync2(filePath);
2914
3733
  if (stats.mtimeMs > latestTime) {
2915
3734
  latestTime = stats.mtimeMs;
@@ -2937,54 +3756,119 @@ async function dashboardCommand(options = {}) {
2937
3756
  await renderCeoReport(squadsDir);
2938
3757
  return;
2939
3758
  }
3759
+ const baseDir = findAgentsSquadsDir();
2940
3760
  const squadNames = listSquads(squadsDir);
3761
+ const skipGitHub = options.fast !== false;
3762
+ const timeout = (promise, ms, fallback) => Promise.race([promise, new Promise((resolve) => setTimeout(() => resolve(fallback), ms))]);
3763
+ const [gitStats, ghStats, costs, bridgeStats, activity, dbAvailable, history, insights] = await Promise.all([
3764
+ // Git stats (local, ~1s)
3765
+ Promise.resolve(baseDir ? getMultiRepoGitStats(baseDir, 30) : null),
3766
+ // GitHub stats (network, ~20-30s) - skip by default for fast mode
3767
+ skipGitHub ? Promise.resolve(null) : Promise.resolve(baseDir ? getGitHubStatsOptimized(baseDir, 30) : null),
3768
+ // Langfuse costs (network, 2s timeout)
3769
+ timeout(fetchCostSummary(100), 2e3, null),
3770
+ // Bridge stats (local network, 2s timeout)
3771
+ timeout(fetchBridgeStats(), 2e3, null),
3772
+ // Activity sparkline (local, <1s)
3773
+ Promise.resolve(baseDir ? getActivitySparkline(baseDir, 14) : []),
3774
+ // Database availability check (1.5s timeout)
3775
+ timeout(isDatabaseAvailable(), 1500, false),
3776
+ // Dashboard history (1.5s timeout)
3777
+ timeout(getDashboardHistory(14).catch(() => []), 1500, []),
3778
+ // Insights (2s timeout)
3779
+ timeout(fetchInsights("week").catch(() => null), 2e3, null)
3780
+ ]);
3781
+ const cache = { gitStats, ghStats, costs, bridgeStats, activity, dbAvailable, history, insights };
2941
3782
  const squadData = [];
2942
- const baseDir = findAgentsSquadsDir();
2943
- const ghStats = baseDir ? getGitHubStats(baseDir, 30) : null;
2944
3783
  for (const name of squadNames) {
2945
3784
  const squad = loadSquad(name);
2946
3785
  if (!squad) continue;
2947
3786
  const lastActivity = getLastActivityDate(name);
2948
3787
  const github = ghStats?.bySquad.get(name) || null;
2949
3788
  let status = "active";
2950
- const activeGoals2 = squad.goals.filter((g) => !g.completed);
2951
- if (activeGoals2.length === 0) {
3789
+ const activeGoals = squad.goals.filter((g) => !g.completed);
3790
+ if (activeGoals.length === 0) {
2952
3791
  status = "needs-goal";
2953
3792
  } else if (lastActivity.includes("w") || lastActivity === "\u2014") {
2954
3793
  status = "stale";
2955
3794
  }
2956
- const totalGoals2 = squad.goals.length;
2957
- const completedGoals2 = squad.goals.filter((g) => g.completed).length;
3795
+ const totalGoals = squad.goals.length;
3796
+ const completedGoals = squad.goals.filter((g) => g.completed).length;
2958
3797
  const hasProgress = squad.goals.filter((g) => g.progress).length;
2959
- const goalProgress = totalGoals2 > 0 ? Math.round((completedGoals2 + hasProgress * 0.3) / totalGoals2 * 100) : 0;
3798
+ const goalProgress = totalGoals > 0 ? Math.round((completedGoals + hasProgress * 0.3) / totalGoals * 100) : 0;
3799
+ const repoSquadMap = {
3800
+ website: ["agents-squads-web"],
3801
+ product: ["squads-cli"],
3802
+ engineering: ["hq", "squads-cli"],
3803
+ research: ["research"],
3804
+ intelligence: ["intelligence"],
3805
+ customer: ["customer"],
3806
+ finance: ["finance"],
3807
+ company: ["company", "hq"],
3808
+ marketing: ["marketing", "agents-squads-web"],
3809
+ cli: ["squads-cli"]
3810
+ };
3811
+ let squadCommits = 0;
3812
+ if (gitStats) {
3813
+ for (const [repo, commits] of gitStats.commitsByRepo) {
3814
+ if (repoSquadMap[name]?.includes(repo)) {
3815
+ squadCommits += commits;
3816
+ }
3817
+ }
3818
+ }
3819
+ const githubStats = github || {
3820
+ prsOpened: 0,
3821
+ prsMerged: 0,
3822
+ issuesClosed: 0,
3823
+ issuesOpen: 0,
3824
+ commits: 0,
3825
+ recentIssues: [],
3826
+ recentPRs: []
3827
+ };
3828
+ githubStats.commits = squadCommits;
2960
3829
  squadData.push({
2961
3830
  name,
2962
3831
  mission: squad.mission,
2963
3832
  goals: squad.goals,
2964
3833
  lastActivity,
2965
3834
  status,
2966
- github,
3835
+ github: githubStats,
2967
3836
  goalProgress
2968
3837
  });
2969
3838
  }
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
3839
  const activeSquads = squadData.filter((s) => s.status === "active").length;
2975
3840
  const totalPRs = ghStats ? ghStats.prsMerged : 0;
2976
3841
  const totalIssuesClosed = ghStats ? ghStats.issuesClosed : 0;
2977
3842
  const totalIssuesOpen = ghStats ? ghStats.issuesOpen : 0;
3843
+ cleanupStaleSessions();
3844
+ const sessionSummary = getLiveSessionSummary();
2978
3845
  writeLine();
2979
3846
  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}`);
3847
+ const updateInfo = checkForUpdate();
3848
+ if (updateInfo.updateAvailable) {
3849
+ writeLine(` ${colors.cyan}\u2B06${RESET} Update available: ${colors.dim}${updateInfo.currentVersion}${RESET} \u2192 ${colors.green}${updateInfo.latestVersion}${RESET} ${colors.dim}(run \`squads update\`)${RESET}`);
3850
+ }
3851
+ if (sessionSummary.totalSessions > 0) {
3852
+ const sessionText = sessionSummary.totalSessions === 1 ? "session" : "sessions";
3853
+ const squadText = sessionSummary.squadCount === 1 ? "squad" : "squads";
3854
+ let toolInfo = "";
3855
+ if (sessionSummary.byTool && Object.keys(sessionSummary.byTool).length > 0) {
3856
+ const toolParts = Object.entries(sessionSummary.byTool).sort((a, b) => b[1] - a[1]).map(([tool, count]) => `${colors.dim}${tool}${RESET} ${colors.cyan}${count}${RESET}`);
3857
+ toolInfo = ` ${colors.dim}(${RESET}${toolParts.join(` ${colors.dim}\xB7${RESET} `)}${colors.dim})${RESET}`;
3858
+ }
3859
+ writeLine(` ${colors.green}${icons.active}${RESET} ${colors.white}${sessionSummary.totalSessions}${RESET} active ${sessionText} ${colors.dim}across${RESET} ${colors.cyan}${sessionSummary.squadCount}${RESET} ${squadText}${toolInfo}`);
3860
+ }
3861
+ writeLine();
3862
+ const statsParts = [`${colors.cyan}${activeSquads}${RESET}/${squadData.length} squads`];
3863
+ if (ghStats) {
3864
+ statsParts.push(`${colors.green}${totalPRs}${RESET} PRs merged`);
3865
+ statsParts.push(`${colors.purple}${totalIssuesClosed}${RESET} closed`);
3866
+ statsParts.push(`${colors.yellow}${totalIssuesOpen}${RESET} open`);
3867
+ } else {
3868
+ statsParts.push(`${colors.cyan}${gitStats?.totalCommits || 0}${RESET} commits`);
3869
+ statsParts.push(`${colors.dim}use -f for PRs/issues${RESET}`);
3870
+ }
3871
+ writeLine(` ${statsParts.join(` ${colors.dim}\u2502${RESET} `)}`);
2988
3872
  writeLine();
2989
3873
  const overallProgress = squadData.length > 0 ? Math.round(squadData.reduce((sum, s) => sum + s.goalProgress, 0) / squadData.length) : 0;
2990
3874
  writeLine(` ${progressBar(overallProgress, 32)} ${colors.dim}${overallProgress}% goal progress${RESET}`);
@@ -2992,8 +3876,7 @@ async function dashboardCommand(options = {}) {
2992
3876
  const w = { name: 13, commits: 7, prs: 4, issues: 6, goals: 6, bar: 10 };
2993
3877
  const tableWidth = w.name + w.commits + w.prs + w.issues + w.goals + w.bar + 12;
2994
3878
  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);
3879
+ 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
3880
  writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
2998
3881
  const sortedSquads = [...squadData].sort((a, b) => {
2999
3882
  const aActivity = (a.github?.commits || 0) + (a.github?.prsMerged || 0) * 5;
@@ -3011,16 +3894,15 @@ async function dashboardCommand(options = {}) {
3011
3894
  const commitColor = commits > 10 ? colors.green : commits > 0 ? colors.cyan : colors.dim;
3012
3895
  const prColor = prs > 0 ? colors.green : colors.dim;
3013
3896
  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);
3897
+ 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
3898
  }
3018
3899
  writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
3019
3900
  writeLine();
3020
- await renderGitPerformance();
3021
- await renderTokenEconomics(squadData.map((s) => s.name));
3022
- await renderHistoricalTrends();
3023
- await renderInsights();
3901
+ renderGitPerformanceCached(cache);
3902
+ renderTokenEconomicsCached(cache);
3903
+ renderInfrastructureCached(cache);
3904
+ renderHistoricalTrendsCached(cache);
3905
+ renderInsightsCached(cache);
3024
3906
  const allActiveGoals = squadData.flatMap(
3025
3907
  (s) => s.goals.filter((g) => !g.completed).map((g) => ({ squad: s.name, goal: g }))
3026
3908
  );
@@ -3031,12 +3913,9 @@ async function dashboardCommand(options = {}) {
3031
3913
  for (const { squad, goal: goal2 } of allActiveGoals.slice(0, maxGoals)) {
3032
3914
  const hasProgress = goal2.progress && goal2.progress.length > 0;
3033
3915
  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}`);
3916
+ writeLine(` ${icon} ${colors.dim}${squad}${RESET} ${truncate(goal2.description, 48)}`);
3037
3917
  if (hasProgress) {
3038
- const progressText = truncate(goal2.progress, 52);
3039
- writeLine(` ${colors.dim}\u2514${RESET} ${colors.green}${progressText}${RESET}`);
3918
+ writeLine(` ${colors.dim}\u2514${RESET} ${colors.green}${truncate(goal2.progress, 52)}${RESET}`);
3040
3919
  }
3041
3920
  }
3042
3921
  if (allActiveGoals.length > maxGoals) {
@@ -3047,82 +3926,41 @@ async function dashboardCommand(options = {}) {
3047
3926
  writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}<squad>${RESET} ${colors.dim}Execute a squad${RESET}`);
3048
3927
  writeLine(` ${colors.dim}$${RESET} squads goal set ${colors.dim}Add a goal${RESET}`);
3049
3928
  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);
3929
+ saveSnapshotCached(squadData, cache, baseDir).catch(() => {
3930
+ });
3931
+ await closeDatabase();
3094
3932
  }
3095
3933
  function findAgentsSquadsDir() {
3096
3934
  const candidates = [
3097
- join9(process.cwd(), ".."),
3098
- join9(homedir2(), "agents-squads")
3935
+ join10(process.cwd(), ".."),
3936
+ join10(homedir4(), "agents-squads")
3099
3937
  ];
3100
3938
  for (const dir of candidates) {
3101
- if (existsSync9(join9(dir, "hq"))) {
3939
+ if (existsSync10(join10(dir, "hq"))) {
3102
3940
  return dir;
3103
3941
  }
3104
3942
  }
3105
3943
  return null;
3106
3944
  }
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) {
3945
+ function formatK(n) {
3946
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
3947
+ if (n >= 1e3) return (n / 1e3).toFixed(0) + "k";
3948
+ return String(n);
3949
+ }
3950
+ function renderGitPerformanceCached(cache) {
3951
+ const { gitStats: stats, activity } = cache;
3952
+ if (!stats || stats.totalCommits === 0) {
3117
3953
  writeLine(` ${bold}Git Activity${RESET} ${colors.dim}(no commits in 30d)${RESET}`);
3118
3954
  writeLine();
3119
3955
  return;
3120
3956
  }
3121
3957
  writeLine(` ${bold}Git Activity${RESET} ${colors.dim}(30d)${RESET}`);
3122
3958
  writeLine();
3123
- const spark = sparkline(activity);
3124
- writeLine(` ${colors.dim}Last 14d:${RESET} ${spark}`);
3125
- writeLine();
3959
+ if (activity.length > 0) {
3960
+ const spark = sparkline(activity);
3961
+ writeLine(` ${colors.dim}Last 14d:${RESET} ${spark}`);
3962
+ writeLine();
3963
+ }
3126
3964
  const metrics = [
3127
3965
  `${colors.cyan}${stats.totalCommits}${RESET} commits`,
3128
3966
  `${colors.green}${stats.avgCommitsPerDay}${RESET}/day`,
@@ -3149,8 +3987,8 @@ async function renderGitPerformance() {
3149
3987
  writeLine();
3150
3988
  }
3151
3989
  }
3152
- async function renderTokenEconomics(squadNames) {
3153
- const costs = await fetchCostSummary(100);
3990
+ function renderTokenEconomicsCached(cache) {
3991
+ const costs = cache.costs;
3154
3992
  if (!costs) {
3155
3993
  writeLine(` ${bold}Token Economics${RESET} ${colors.dim}(no data)${RESET}`);
3156
3994
  writeLine(` ${colors.dim}Set LANGFUSE_PUBLIC_KEY & LANGFUSE_SECRET_KEY for cost tracking${RESET}`);
@@ -3164,82 +4002,14 @@ async function renderTokenEconomics(squadNames) {
3164
4002
  writeLine(` ${colors.dim}Budget $${costs.dailyBudget}${RESET} [${costBar}] ${costs.usedPercent.toFixed(1)}%`);
3165
4003
  writeLine(` ${colors.green}$${costs.totalCost.toFixed(2)}${RESET} used ${colors.dim}\u2502${RESET} ${colors.cyan}$${costs.idleBudget.toFixed(2)}${RESET} idle`);
3166
4004
  writeLine();
3167
- 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;
4005
+ const tier = parseInt(process.env.ANTHROPIC_TIER || "4", 10);
4006
+ writeLine(` ${colors.dim}Rate Limits (Tier ${tier})${RESET}`);
4007
+ writeLine();
4008
+ const now = /* @__PURE__ */ new Date();
4009
+ const hoursElapsed = Math.max(now.getHours() + now.getMinutes() / 60, 1);
4010
+ const hourlyRate = costs.totalCost / hoursElapsed;
3209
4011
  const dailyProjection = hourlyRate * 24;
3210
4012
  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
4013
  writeLine(` ${colors.dim}Projections${RESET}`);
3244
4014
  const projColor = dailyProjection > costs.dailyBudget ? colors.red : colors.green;
3245
4015
  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 +4021,83 @@ async function renderTokenEconomics(squadNames) {
3251
4021
  }
3252
4022
  writeLine();
3253
4023
  }
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) {
4024
+ function renderInfrastructureCached(cache) {
4025
+ const stats = cache.bridgeStats;
4026
+ if (!stats) {
4027
+ writeLine(` ${bold}Infrastructure${RESET} ${colors.dim}(bridge offline)${RESET}`);
4028
+ writeLine(` ${colors.dim}Start with: cd docker && docker-compose up -d${RESET}`);
4029
+ writeLine();
3287
4030
  return;
3288
4031
  }
3289
- writeLine(` ${bold}Agent Insights${RESET} ${colors.dim}(${insights.days}d)${RESET}`);
4032
+ writeLine(` ${bold}Infrastructure${RESET} ${colors.dim}(${stats.source})${RESET}`);
3290
4033
  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}`);
4034
+ const pgStatus = stats.health.postgres === "connected" ? `${colors.green}\u25CF${RESET}` : `${colors.red}\u25CF${RESET}`;
4035
+ const redisStatus = stats.health.redis === "connected" ? `${colors.green}\u25CF${RESET}` : stats.health.redis === "disabled" ? `${colors.dim}\u25CB${RESET}` : `${colors.red}\u25CF${RESET}`;
4036
+ const otelWorking = stats.health.postgres === "connected" && stats.today.generations > 0;
4037
+ const otelStatus = otelWorking ? `${colors.green}\u25CF${RESET}` : `${colors.dim}\u25CB${RESET}`;
4038
+ writeLine(` ${pgStatus} postgres ${redisStatus} redis ${otelStatus} otel`);
4039
+ writeLine();
4040
+ if (stats.today.generations > 0 || stats.today.costUsd > 0) {
4041
+ const costColor = stats.budget.usedPct > 80 ? colors.red : stats.budget.usedPct > 50 ? colors.yellow : colors.green;
4042
+ 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}`);
4043
+ if (stats.byModel && stats.byModel.length > 0) {
4044
+ const modelLine = stats.byModel.map((m) => {
4045
+ const shortName = m.model.includes("opus") ? "opus" : m.model.includes("sonnet") ? "sonnet" : m.model.includes("haiku") ? "haiku" : m.model.slice(0, 10);
4046
+ return `${colors.dim}${shortName}${RESET} ${colors.cyan}${m.generations}${RESET}`;
4047
+ }).join(" ");
4048
+ writeLine(` ${colors.dim}Models:${RESET} ${modelLine}`);
3309
4049
  }
3310
4050
  }
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();
4051
+ if (stats.week && stats.week.generations > 0) {
4052
+ const weekModelLine = stats.week.byModel?.map((m) => {
4053
+ const shortName = m.model.includes("opus") ? "opus" : m.model.includes("sonnet") ? "sonnet" : m.model.includes("haiku") ? "haiku" : m.model.slice(0, 10);
4054
+ return `${colors.dim}${shortName}${RESET} ${colors.purple}$${m.costUsd.toFixed(0)}${RESET}`;
4055
+ }).join(" ") || "";
4056
+ 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
4057
  }
4058
+ writeLine();
4059
+ }
4060
+ async function saveSnapshotCached(squadData, cache, baseDir) {
4061
+ if (!cache.dbAvailable) return;
4062
+ const { gitStats, ghStats, costs } = cache;
4063
+ const squadsData = squadData.map((s) => ({
4064
+ name: s.name,
4065
+ commits: s.github?.commits || 0,
4066
+ prsOpened: s.github?.prsOpened || 0,
4067
+ prsMerged: s.github?.prsMerged || 0,
4068
+ issuesClosed: s.github?.issuesClosed || 0,
4069
+ issuesOpen: s.github?.issuesOpen || 0,
4070
+ goalsActive: s.goals.filter((g) => !g.completed).length,
4071
+ goalsTotal: s.goals.length,
4072
+ progress: s.goalProgress
4073
+ }));
4074
+ const authorsData = gitStats ? Array.from(gitStats.commitsByAuthor.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, commits]) => ({ name, commits })) : [];
4075
+ const reposData = gitStats ? Array.from(gitStats.commitsByRepo.entries()).sort((a, b) => b[1] - a[1]).map(([name, commits]) => ({ name, commits })) : [];
4076
+ const totalInputTokens = costs?.bySquad.reduce((sum, s) => sum + s.inputTokens, 0) || 0;
4077
+ const totalOutputTokens = costs?.bySquad.reduce((sum, s) => sum + s.outputTokens, 0) || 0;
4078
+ const overallProgress = squadData.length > 0 ? Math.round(squadData.reduce((sum, s) => sum + s.goalProgress, 0) / squadData.length) : 0;
4079
+ const snapshot = {
4080
+ totalSquads: squadData.length,
4081
+ totalCommits: gitStats?.totalCommits || 0,
4082
+ totalPrsMerged: ghStats?.prsMerged || 0,
4083
+ totalIssuesClosed: ghStats?.issuesClosed || 0,
4084
+ totalIssuesOpen: ghStats?.issuesOpen || 0,
4085
+ goalProgressPct: overallProgress,
4086
+ costUsd: costs?.totalCost || 0,
4087
+ dailyBudgetUsd: costs?.dailyBudget || 50,
4088
+ inputTokens: totalInputTokens,
4089
+ outputTokens: totalOutputTokens,
4090
+ commits30d: gitStats?.totalCommits || 0,
4091
+ avgCommitsPerDay: gitStats?.avgCommitsPerDay || 0,
4092
+ activeDays: gitStats?.activeDays || 0,
4093
+ peakCommits: gitStats?.peakDay?.count || 0,
4094
+ peakDate: gitStats?.peakDay?.date || null,
4095
+ squadsData,
4096
+ authorsData,
4097
+ reposData
4098
+ };
4099
+ const saveTimeout = new Promise((resolve) => setTimeout(resolve, 2e3));
4100
+ await Promise.race([saveDashboardSnapshot(snapshot), saveTimeout]);
3348
4101
  }
3349
4102
  var P0_KEYWORDS = ["revenue", "first", "launch", "publish", "ship", "critical", "urgent"];
3350
4103
  var P1_KEYWORDS = ["track", "establish", "identify", "define", "fix"];
@@ -3453,9 +4206,62 @@ async function renderCeoReport(squadsDir) {
3453
4206
  writeLine(` ${colors.dim}$${RESET} squads goal list ${colors.dim}All active goals${RESET}`);
3454
4207
  writeLine();
3455
4208
  }
4209
+ function renderHistoricalTrendsCached(cache) {
4210
+ if (!cache.dbAvailable) return;
4211
+ const history = cache.history;
4212
+ if (history.length < 2) return;
4213
+ writeLine(` ${bold}Usage Trends${RESET} ${colors.dim}(${history.length}d history)${RESET}`);
4214
+ writeLine();
4215
+ const dailyCosts = history.map((h) => h.costUsd).reverse();
4216
+ const costSparkStr = sparkline(dailyCosts);
4217
+ const totalSpend = dailyCosts.reduce((sum, c) => sum + c, 0);
4218
+ const avgDaily = totalSpend / dailyCosts.length;
4219
+ writeLine(` ${colors.dim}Cost:${RESET} ${costSparkStr} ${colors.green}$${totalSpend.toFixed(2)}${RESET} total ${colors.dim}($${avgDaily.toFixed(2)}/day avg)${RESET}`);
4220
+ const inputTokens = history.map((h) => h.inputTokens).reverse();
4221
+ const totalInput = inputTokens.reduce((sum, t) => sum + t, 0);
4222
+ const tokenSparkStr = sparkline(inputTokens);
4223
+ writeLine(` ${colors.dim}Tokens:${RESET} ${tokenSparkStr} ${colors.cyan}${formatK(totalInput)}${RESET} input ${colors.dim}(${formatK(Math.round(totalInput / inputTokens.length))}/day)${RESET}`);
4224
+ const goalProgress = history.map((h) => h.goalProgressPct).reverse();
4225
+ const latestProgress = goalProgress[goalProgress.length - 1] || 0;
4226
+ const earliestProgress = goalProgress[0] || 0;
4227
+ const progressDelta = latestProgress - earliestProgress;
4228
+ const progressColor = progressDelta > 0 ? colors.green : progressDelta < 0 ? colors.red : colors.dim;
4229
+ const progressSign = progressDelta > 0 ? "+" : "";
4230
+ writeLine(` ${colors.dim}Goals:${RESET} ${sparkline(goalProgress)} ${colors.purple}${latestProgress}%${RESET} ${progressColor}${progressSign}${progressDelta.toFixed(0)}%${RESET}${colors.dim} vs start${RESET}`);
4231
+ writeLine();
4232
+ }
4233
+ function renderInsightsCached(cache) {
4234
+ const insights = cache.insights;
4235
+ if (!insights || insights.source === "none" || insights.taskMetrics.length === 0) {
4236
+ return;
4237
+ }
4238
+ writeLine(` ${bold}Agent Insights${RESET} ${colors.dim}(${insights.days}d)${RESET}`);
4239
+ writeLine();
4240
+ const totals = insights.taskMetrics.reduce(
4241
+ (acc, t) => ({
4242
+ tasks: acc.tasks + t.tasksTotal,
4243
+ completed: acc.completed + t.tasksCompleted,
4244
+ failed: acc.failed + t.tasksFailed,
4245
+ retries: acc.retries + t.totalRetries,
4246
+ withRetries: acc.withRetries + t.tasksWithRetries
4247
+ }),
4248
+ { tasks: 0, completed: 0, failed: 0, retries: 0, withRetries: 0 }
4249
+ );
4250
+ if (totals.tasks > 0) {
4251
+ const successRate = totals.tasks > 0 ? (totals.completed / totals.tasks * 100).toFixed(0) : "0";
4252
+ const successColor = parseInt(successRate) >= 80 ? colors.green : parseInt(successRate) >= 60 ? colors.yellow : colors.red;
4253
+ 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}`);
4254
+ if (totals.retries > 0) {
4255
+ const retryRate = totals.tasks > 0 ? (totals.withRetries / totals.tasks * 100).toFixed(0) : "0";
4256
+ const retryColor = parseInt(retryRate) > 30 ? colors.red : parseInt(retryRate) > 15 ? colors.yellow : colors.green;
4257
+ writeLine(` ${colors.dim}Retries:${RESET} ${retryColor}${totals.retries}${RESET}${colors.dim} total${RESET} ${retryColor}${retryRate}%${RESET}${colors.dim} of tasks needed retry${RESET}`);
4258
+ }
4259
+ }
4260
+ writeLine();
4261
+ }
3456
4262
 
3457
4263
  // src/commands/issues.ts
3458
- import { execSync as execSync3 } from "child_process";
4264
+ import { execSync as execSync5 } from "child_process";
3459
4265
  var DEFAULT_ORG = "agents-squads";
3460
4266
  var DEFAULT_REPOS = ["hq", "agents-squads-web", "squads-cli"];
3461
4267
  async function issuesCommand(options = {}) {
@@ -3465,7 +4271,7 @@ async function issuesCommand(options = {}) {
3465
4271
  writeLine(` ${gradient("squads")} ${colors.dim}issues${RESET}`);
3466
4272
  writeLine();
3467
4273
  try {
3468
- execSync3("gh --version", { stdio: "pipe" });
4274
+ execSync5("gh --version", { stdio: "pipe" });
3469
4275
  } catch {
3470
4276
  writeLine(` ${colors.red}GitHub CLI (gh) not found${RESET}`);
3471
4277
  writeLine(` ${colors.dim}Install: brew install gh${RESET}`);
@@ -3476,7 +4282,7 @@ async function issuesCommand(options = {}) {
3476
4282
  let totalOpen = 0;
3477
4283
  for (const repo of repos) {
3478
4284
  try {
3479
- const result = execSync3(
4285
+ const result = execSync5(
3480
4286
  `gh issue list -R ${org}/${repo} --state open --json number,title,state,labels,createdAt --limit 50`,
3481
4287
  { stdio: "pipe", encoding: "utf-8" }
3482
4288
  );
@@ -3527,7 +4333,7 @@ async function issuesCommand(options = {}) {
3527
4333
  }
3528
4334
 
3529
4335
  // src/commands/solve-issues.ts
3530
- import { execSync as execSync4, spawn as spawn2 } from "child_process";
4336
+ import { execSync as execSync6, spawn as spawn3 } from "child_process";
3531
4337
  import ora3 from "ora";
3532
4338
  var DEFAULT_ORG2 = "agents-squads";
3533
4339
  var DEFAULT_REPOS2 = ["hq", "agents-squads-web", "squads-cli", "agents-squads"];
@@ -3537,7 +4343,7 @@ async function solveIssuesCommand(options = {}) {
3537
4343
  writeLine(` ${gradient("squads")} ${colors.dim}solve-issues${RESET}`);
3538
4344
  writeLine();
3539
4345
  try {
3540
- execSync4("gh --version", { stdio: "pipe" });
4346
+ execSync6("gh --version", { stdio: "pipe" });
3541
4347
  } catch {
3542
4348
  writeLine(` ${colors.red}GitHub CLI (gh) not found${RESET}`);
3543
4349
  writeLine(` ${colors.dim}Install: brew install gh${RESET}`);
@@ -3547,7 +4353,7 @@ async function solveIssuesCommand(options = {}) {
3547
4353
  if (options.issue) {
3548
4354
  const repo = options.repo || "hq";
3549
4355
  try {
3550
- const result = execSync4(
4356
+ const result = execSync6(
3551
4357
  `gh issue view ${options.issue} -R ${DEFAULT_ORG2}/${repo} --json number,title,labels,body`,
3552
4358
  { stdio: "pipe", encoding: "utf-8" }
3553
4359
  );
@@ -3560,7 +4366,7 @@ async function solveIssuesCommand(options = {}) {
3560
4366
  } else {
3561
4367
  for (const repo of repos) {
3562
4368
  try {
3563
- const result = execSync4(
4369
+ const result = execSync6(
3564
4370
  `gh issue list -R ${DEFAULT_ORG2}/${repo} --label "ready-to-fix" --state open --json number,title,labels --limit 20`,
3565
4371
  { stdio: "pipe", encoding: "utf-8" }
3566
4372
  );
@@ -3614,7 +4420,7 @@ function showSolveInstructions(issues) {
3614
4420
  async function solveWithClaude(issues) {
3615
4421
  const spinner = ora3("Starting issue solver...").start();
3616
4422
  try {
3617
- execSync4("which claude", { stdio: "pipe" });
4423
+ execSync6("which claude", { stdio: "pipe" });
3618
4424
  } catch {
3619
4425
  spinner.fail("Claude CLI not found");
3620
4426
  writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
@@ -3622,9 +4428,9 @@ async function solveWithClaude(issues) {
3622
4428
  }
3623
4429
  for (const issue of issues) {
3624
4430
  spinner.text = `Solving #${issue.number}: ${truncate(issue.title, 40)}`;
3625
- const prompt = buildSolvePrompt(issue);
4431
+ const prompt2 = buildSolvePrompt(issue);
3626
4432
  try {
3627
- const result = await executeClaudePrompt(prompt);
4433
+ const result = await executeClaudePrompt(prompt2);
3628
4434
  spinner.succeed(`Solved #${issue.number}`);
3629
4435
  const prMatch = result.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/);
3630
4436
  if (prMatch) {
@@ -3671,9 +4477,9 @@ If reading the same file twice, STOP and move forward.
3671
4477
 
3672
4478
  Return the PR URL when done.`;
3673
4479
  }
3674
- function executeClaudePrompt(prompt) {
4480
+ function executeClaudePrompt(prompt2) {
3675
4481
  return new Promise((resolve, reject) => {
3676
- const claude = spawn2("claude", ["--print", prompt], {
4482
+ const claude = spawn3("claude", ["--print", prompt2], {
3677
4483
  stdio: ["pipe", "pipe", "pipe"]
3678
4484
  });
3679
4485
  let output = "";
@@ -3700,9 +4506,9 @@ function executeClaudePrompt(prompt) {
3700
4506
  }
3701
4507
 
3702
4508
  // 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";
4509
+ import { execSync as execSync7, spawn as spawn4 } from "child_process";
4510
+ import { readdirSync as readdirSync5 } from "fs";
4511
+ import { join as join11 } from "path";
3706
4512
  import ora4 from "ora";
3707
4513
  var ISSUE_FINDER_PATTERNS = [
3708
4514
  "*-eval.md",
@@ -3757,10 +4563,10 @@ async function openIssuesCommand(options = {}) {
3757
4563
  }
3758
4564
  function findEvalAgents(squadsDir, filterSquad) {
3759
4565
  const agents = [];
3760
- const squads = readdirSync6(squadsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).filter((d) => !filterSquad || d.name === filterSquad).map((d) => d.name);
4566
+ const squads = readdirSync5(squadsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).filter((d) => !filterSquad || d.name === filterSquad).map((d) => d.name);
3761
4567
  for (const squad of squads) {
3762
- const squadPath = join10(squadsDir, squad);
3763
- const files = readdirSync6(squadPath).filter((f) => f.endsWith(".md"));
4568
+ const squadPath = join11(squadsDir, squad);
4569
+ const files = readdirSync5(squadPath).filter((f) => f.endsWith(".md"));
3764
4570
  for (const file of files) {
3765
4571
  const isEval = ISSUE_FINDER_PATTERNS.some((pattern) => {
3766
4572
  const regex = new RegExp("^" + pattern.replace("*", ".*") + "$");
@@ -3770,7 +4576,7 @@ function findEvalAgents(squadsDir, filterSquad) {
3770
4576
  agents.push({
3771
4577
  name: file,
3772
4578
  squad,
3773
- path: join10(squadPath, file)
4579
+ path: join11(squadPath, file)
3774
4580
  });
3775
4581
  }
3776
4582
  }
@@ -3796,7 +4602,7 @@ function showRunInstructions(agents) {
3796
4602
  async function runEvaluators(agents) {
3797
4603
  const spinner = ora4("Starting evaluators...").start();
3798
4604
  try {
3799
- execSync5("which claude", { stdio: "pipe" });
4605
+ execSync7("which claude", { stdio: "pipe" });
3800
4606
  } catch {
3801
4607
  spinner.fail("Claude CLI not found");
3802
4608
  writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
@@ -3805,9 +4611,9 @@ async function runEvaluators(agents) {
3805
4611
  let issuesCreated = 0;
3806
4612
  for (const agent of agents) {
3807
4613
  spinner.text = `Running ${agent.squad}/${agent.name.replace(".md", "")}...`;
3808
- const prompt = buildEvalPrompt(agent);
4614
+ const prompt2 = buildEvalPrompt(agent);
3809
4615
  try {
3810
- const result = await executeClaudePrompt2(prompt);
4616
+ const result = await executeClaudePrompt2(prompt2);
3811
4617
  spinner.succeed(`${agent.name.replace(".md", "")}`);
3812
4618
  const issueMatches = result.match(/Created issue #\d+/g) || [];
3813
4619
  issuesCreated += issueMatches.length;
@@ -3845,9 +4651,9 @@ Read the agent definition at ${agent.path} and follow its instructions exactly.
3845
4651
 
3846
4652
  Do NOT get stuck re-reading files. Evaluate, report findings, create issues, done.`;
3847
4653
  }
3848
- function executeClaudePrompt2(prompt) {
4654
+ function executeClaudePrompt2(prompt2) {
3849
4655
  return new Promise((resolve, reject) => {
3850
- const claude = spawn3("claude", ["--print", prompt], {
4656
+ const claude = spawn4("claude", ["--print", prompt2], {
3851
4657
  stdio: ["pipe", "pipe", "pipe"]
3852
4658
  });
3853
4659
  let output = "";
@@ -3880,9 +4686,9 @@ import open from "open";
3880
4686
 
3881
4687
  // src/lib/auth.ts
3882
4688
  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";
4689
+ import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
4690
+ import { join as join12 } from "path";
4691
+ import { homedir as homedir5 } from "os";
3886
4692
  import http from "http";
3887
4693
  var PERSONAL_DOMAINS = [
3888
4694
  "gmail.com",
@@ -3910,8 +4716,8 @@ var PERSONAL_DOMAINS = [
3910
4716
  "tutanota.com",
3911
4717
  "hey.com"
3912
4718
  ];
3913
- var AUTH_DIR = join11(homedir3(), ".squads-cli");
3914
- var AUTH_PATH = join11(AUTH_DIR, "auth.json");
4719
+ var AUTH_DIR = join12(homedir5(), ".squads-cli");
4720
+ var AUTH_PATH = join12(AUTH_DIR, "auth.json");
3915
4721
  function isPersonalEmail(email) {
3916
4722
  const domain = email.split("@")[1]?.toLowerCase();
3917
4723
  return PERSONAL_DOMAINS.includes(domain);
@@ -3919,23 +4725,23 @@ function isPersonalEmail(email) {
3919
4725
  function getEmailDomain(email) {
3920
4726
  return email.split("@")[1]?.toLowerCase() || "";
3921
4727
  }
3922
- function saveSession(session) {
3923
- if (!existsSync11(AUTH_DIR)) {
3924
- mkdirSync6(AUTH_DIR, { recursive: true });
4728
+ function saveSession(session2) {
4729
+ if (!existsSync12(AUTH_DIR)) {
4730
+ mkdirSync7(AUTH_DIR, { recursive: true });
3925
4731
  }
3926
- writeFileSync7(AUTH_PATH, JSON.stringify(session, null, 2));
4732
+ writeFileSync8(AUTH_PATH, JSON.stringify(session2, null, 2));
3927
4733
  }
3928
4734
  function loadSession() {
3929
- if (!existsSync11(AUTH_PATH)) return null;
4735
+ if (!existsSync12(AUTH_PATH)) return null;
3930
4736
  try {
3931
- return JSON.parse(readFileSync8(AUTH_PATH, "utf-8"));
4737
+ return JSON.parse(readFileSync9(AUTH_PATH, "utf-8"));
3932
4738
  } catch {
3933
4739
  return null;
3934
4740
  }
3935
4741
  }
3936
4742
  function clearSession() {
3937
- if (existsSync11(AUTH_PATH)) {
3938
- writeFileSync7(AUTH_PATH, "");
4743
+ if (existsSync12(AUTH_PATH)) {
4744
+ writeFileSync8(AUTH_PATH, "");
3939
4745
  }
3940
4746
  }
3941
4747
  function startAuthCallbackServer(port = 54321) {
@@ -4032,7 +4838,7 @@ ${chalk2.dim("Want to stay updated?")}
4032
4838
  await track("cli.login.personal_email", { domain: getEmailDomain(email) });
4033
4839
  return;
4034
4840
  }
4035
- const session = {
4841
+ const session2 = {
4036
4842
  email,
4037
4843
  domain: getEmailDomain(email),
4038
4844
  status: "pending",
@@ -4040,9 +4846,9 @@ ${chalk2.dim("Want to stay updated?")}
4040
4846
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4041
4847
  accessToken: token
4042
4848
  };
4043
- saveSession(session);
4849
+ saveSession(session2);
4044
4850
  spinner.succeed(`Logged in as ${chalk2.cyan(email)}`);
4045
- await track("cli.login.success", { domain: session.domain });
4851
+ await track("cli.login.success", { domain: session2.domain });
4046
4852
  console.log(`
4047
4853
  ${chalk2.green("\u2713 Thanks for signing up!")}
4048
4854
 
@@ -4064,18 +4870,18 @@ ${chalk2.dim("Questions? Email us at")} ${chalk2.cyan("hello@agents-squads.com")
4064
4870
  }
4065
4871
  }
4066
4872
  async function logoutCommand() {
4067
- const session = loadSession();
4068
- if (!session) {
4873
+ const session2 = loadSession();
4874
+ if (!session2) {
4069
4875
  console.log(chalk2.yellow("Not logged in."));
4070
4876
  return;
4071
4877
  }
4072
4878
  clearSession();
4073
- console.log(chalk2.green(`\u2713 Logged out from ${session.email}`));
4879
+ console.log(chalk2.green(`\u2713 Logged out from ${session2.email}`));
4074
4880
  await track("cli.logout");
4075
4881
  }
4076
4882
  async function whoamiCommand() {
4077
- const session = loadSession();
4078
- if (!session) {
4883
+ const session2 = loadSession();
4884
+ if (!session2) {
4079
4885
  console.log(chalk2.yellow("Not logged in."));
4080
4886
  console.log(chalk2.dim("Run: squads login"));
4081
4887
  return;
@@ -4083,34 +4889,100 @@ async function whoamiCommand() {
4083
4889
  console.log(`
4084
4890
  ${chalk2.bold("Current Session")}
4085
4891
  ${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()}
4892
+ Email: ${chalk2.cyan(session2.email)}
4893
+ Domain: ${session2.domain}
4894
+ Status: ${session2.status === "active" ? chalk2.green("Active") : chalk2.yellow("Pending")}
4895
+ Since: ${new Date(session2.createdAt).toLocaleDateString()}
4090
4896
  `);
4091
4897
  }
4092
4898
 
4899
+ // src/commands/update.ts
4900
+ import { createInterface as createInterface2 } from "readline";
4901
+ async function confirm2(message) {
4902
+ const rl = createInterface2({
4903
+ input: process.stdin,
4904
+ output: process.stdout
4905
+ });
4906
+ return new Promise((resolve) => {
4907
+ rl.question(` ${message} [y/N]: `, (answer) => {
4908
+ rl.close();
4909
+ resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
4910
+ });
4911
+ });
4912
+ }
4913
+ async function updateCommand(options = {}) {
4914
+ writeLine();
4915
+ writeLine(` ${gradient("squads")} ${colors.dim}update${RESET}`);
4916
+ writeLine();
4917
+ if (options.check) {
4918
+ writeLine(` ${colors.dim}Checking for updates...${RESET}`);
4919
+ const info2 = refreshVersionCache();
4920
+ writeLine();
4921
+ if (info2.updateAvailable) {
4922
+ writeLine(` ${colors.cyan}\u2B06${RESET} Update available: ${colors.dim}${info2.currentVersion}${RESET} \u2192 ${colors.green}${info2.latestVersion}${RESET}`);
4923
+ writeLine();
4924
+ writeLine(` ${colors.dim}Run \`squads update\` to install${RESET}`);
4925
+ } else {
4926
+ writeLine(` ${colors.green}${icons.success}${RESET} Already on latest version ${colors.cyan}${info2.currentVersion}${RESET}`);
4927
+ }
4928
+ writeLine();
4929
+ return;
4930
+ }
4931
+ writeLine(` ${colors.dim}Checking npm registry...${RESET}`);
4932
+ const info = refreshVersionCache();
4933
+ if (!info.updateAvailable) {
4934
+ writeLine();
4935
+ writeLine(` ${colors.green}${icons.success}${RESET} Already on latest version ${colors.cyan}${info.currentVersion}${RESET}`);
4936
+ writeLine();
4937
+ return;
4938
+ }
4939
+ writeLine();
4940
+ writeLine(` ${colors.cyan}\u2B06${RESET} Update available: ${colors.dim}${info.currentVersion}${RESET} \u2192 ${colors.green}${info.latestVersion}${RESET}`);
4941
+ writeLine();
4942
+ const shouldUpdate = options.yes || await confirm2("Update now?");
4943
+ if (!shouldUpdate) {
4944
+ writeLine();
4945
+ writeLine(` ${colors.dim}Update skipped${RESET}`);
4946
+ writeLine();
4947
+ return;
4948
+ }
4949
+ writeLine();
4950
+ writeLine(` ${colors.dim}Installing update...${RESET}`);
4951
+ writeLine();
4952
+ const result = performUpdate();
4953
+ writeLine();
4954
+ if (result.success) {
4955
+ writeLine(` ${colors.green}${icons.success}${RESET} Updated to ${colors.green}${info.latestVersion}${RESET}`);
4956
+ writeLine(` ${colors.dim}Restart your terminal to use the new version${RESET}`);
4957
+ } else {
4958
+ writeLine(` ${colors.red}${icons.error}${RESET} Update failed: ${result.error}`);
4959
+ writeLine(` ${colors.dim}Try manually: npm update -g squads-cli${RESET}`);
4960
+ process.exitCode = 1;
4961
+ }
4962
+ writeLine();
4963
+ }
4964
+
4093
4965
  // 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";
4966
+ import { execSync as execSync8 } from "child_process";
4967
+ import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8 } from "fs";
4968
+ import { join as join13 } from "path";
4097
4969
  function getTasksFilePath() {
4098
4970
  const memoryDir = findMemoryDir();
4099
4971
  if (!memoryDir) {
4100
4972
  const cwd = process.cwd();
4101
- const agentsDir = join12(cwd, ".agents");
4102
- if (!existsSync12(agentsDir)) {
4103
- mkdirSync7(agentsDir, { recursive: true });
4973
+ const agentsDir = join13(cwd, ".agents");
4974
+ if (!existsSync13(agentsDir)) {
4975
+ mkdirSync8(agentsDir, { recursive: true });
4104
4976
  }
4105
- return join12(agentsDir, "tasks.json");
4977
+ return join13(agentsDir, "tasks.json");
4106
4978
  }
4107
- return join12(memoryDir, "..", "tasks.json");
4979
+ return join13(memoryDir, "..", "tasks.json");
4108
4980
  }
4109
4981
  function loadTasks() {
4110
4982
  const tasksPath = getTasksFilePath();
4111
- if (existsSync12(tasksPath)) {
4983
+ if (existsSync13(tasksPath)) {
4112
4984
  try {
4113
- return JSON.parse(readFileSync9(tasksPath, "utf-8"));
4985
+ return JSON.parse(readFileSync10(tasksPath, "utf-8"));
4114
4986
  } catch {
4115
4987
  return { tasks: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
4116
4988
  }
@@ -4120,7 +4992,7 @@ function loadTasks() {
4120
4992
  function saveTasks(data) {
4121
4993
  const tasksPath = getTasksFilePath();
4122
4994
  data.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
4123
- writeFileSync8(tasksPath, JSON.stringify(data, null, 2));
4995
+ writeFileSync9(tasksPath, JSON.stringify(data, null, 2));
4124
4996
  }
4125
4997
  function getRecentActivity() {
4126
4998
  const activity = [];
@@ -4136,7 +5008,7 @@ function getRecentActivity() {
4136
5008
  marketing: ["marketing", "content", "social"]
4137
5009
  };
4138
5010
  try {
4139
- const logOutput = execSync6(
5011
+ const logOutput = execSync8(
4140
5012
  'git log --since="24 hours ago" --format="%h|%aI|%s" 2>/dev/null',
4141
5013
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4142
5014
  ).trim();
@@ -4270,7 +5142,7 @@ function getElapsedTime(startTime) {
4270
5142
  }
4271
5143
 
4272
5144
  // src/commands/results.ts
4273
- import { execSync as execSync7 } from "child_process";
5145
+ import { execSync as execSync9 } from "child_process";
4274
5146
  function getGitStats(days = 7) {
4275
5147
  const stats = /* @__PURE__ */ new Map();
4276
5148
  const squadKeywords = {
@@ -4285,7 +5157,7 @@ function getGitStats(days = 7) {
4285
5157
  marketing: ["marketing"]
4286
5158
  };
4287
5159
  try {
4288
- const logOutput = execSync7(
5160
+ const logOutput = execSync9(
4289
5161
  `git log --since="${days} days ago" --format="%s" --name-only 2>/dev/null`,
4290
5162
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4291
5163
  ).trim();
@@ -4324,7 +5196,7 @@ function getGitHubStats2(days = 7) {
4324
5196
  const prsMerged = /* @__PURE__ */ new Map();
4325
5197
  const issuesClosed = /* @__PURE__ */ new Map();
4326
5198
  try {
4327
- const prsOutput = execSync7(
5199
+ const prsOutput = execSync9(
4328
5200
  `gh pr list --state all --json title,createdAt,mergedAt --limit 50 2>/dev/null`,
4329
5201
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4330
5202
  );
@@ -4339,7 +5211,7 @@ function getGitHubStats2(days = 7) {
4339
5211
  prsMerged.set(squad, (prsMerged.get(squad) || 0) + 1);
4340
5212
  }
4341
5213
  }
4342
- const issuesOutput = execSync7(
5214
+ const issuesOutput = execSync9(
4343
5215
  `gh issue list --state closed --json title,closedAt --limit 50 2>/dev/null`,
4344
5216
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4345
5217
  );
@@ -4471,19 +5343,19 @@ async function resultsCommand(options = {}) {
4471
5343
  }
4472
5344
 
4473
5345
  // 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";
5346
+ import { execSync as execSync10 } from "child_process";
5347
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
5348
+ import { join as join14 } from "path";
4477
5349
  function getTasksFilePath2() {
4478
5350
  const memoryDir = findMemoryDir();
4479
5351
  if (!memoryDir) return null;
4480
- return join13(memoryDir, "..", "tasks.json");
5352
+ return join14(memoryDir, "..", "tasks.json");
4481
5353
  }
4482
5354
  function loadActiveTasks() {
4483
5355
  const tasksPath = getTasksFilePath2();
4484
- if (!tasksPath || !existsSync13(tasksPath)) return [];
5356
+ if (!tasksPath || !existsSync14(tasksPath)) return [];
4485
5357
  try {
4486
- const data = JSON.parse(readFileSync10(tasksPath, "utf-8"));
5358
+ const data = JSON.parse(readFileSync11(tasksPath, "utf-8"));
4487
5359
  return data.tasks?.filter((t) => t.status === "active") || [];
4488
5360
  } catch {
4489
5361
  return [];
@@ -4492,7 +5364,7 @@ function loadActiveTasks() {
4492
5364
  function getRunningProcesses() {
4493
5365
  const processes = [];
4494
5366
  try {
4495
- const psOutput = execSync8(
5367
+ const psOutput = execSync10(
4496
5368
  'ps aux | grep -E "claude|squads|astro|node.*agent" | grep -v grep',
4497
5369
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4498
5370
  ).trim();
@@ -4543,7 +5415,7 @@ async function workersCommand(options = {}) {
4543
5415
  writeLine();
4544
5416
  if (options.kill) {
4545
5417
  try {
4546
- execSync8(`kill ${options.kill}`, { stdio: "pipe" });
5418
+ execSync10(`kill ${options.kill}`, { stdio: "pipe" });
4547
5419
  writeLine(` ${icons.success} Killed process ${colors.cyan}${options.kill}${RESET}`);
4548
5420
  writeLine();
4549
5421
  return;
@@ -4629,26 +5501,200 @@ function getElapsedTime2(startTime) {
4629
5501
  return "<1m";
4630
5502
  }
4631
5503
 
5504
+ // src/commands/session.ts
5505
+ async function sessionStartCommand(options = {}) {
5506
+ cleanupStaleSessions();
5507
+ const session2 = startSession(options.squad);
5508
+ if (!options.quiet) {
5509
+ if (session2) {
5510
+ writeLine(`${icons.active} Session started: ${colors.cyan}${session2.sessionId}${RESET}`);
5511
+ if (session2.squad) {
5512
+ writeLine(` ${colors.dim}Squad: ${session2.squad}${RESET}`);
5513
+ }
5514
+ } else {
5515
+ writeLine(`${colors.yellow}Could not start session (no .agents directory)${RESET}`);
5516
+ }
5517
+ }
5518
+ }
5519
+ async function sessionStopCommand(options = {}) {
5520
+ const stopped = stopSession();
5521
+ if (!options.quiet) {
5522
+ if (stopped) {
5523
+ writeLine(`${icons.progress} Session stopped`);
5524
+ } else {
5525
+ writeLine(`${colors.dim}No active session to stop${RESET}`);
5526
+ }
5527
+ }
5528
+ }
5529
+ async function sessionHeartbeatCommand(options = {}) {
5530
+ const updated = updateHeartbeat();
5531
+ if (!options.quiet) {
5532
+ if (updated) {
5533
+ writeLine(`${icons.active} Heartbeat updated`);
5534
+ } else {
5535
+ writeLine(`${colors.dim}No session to update${RESET}`);
5536
+ }
5537
+ }
5538
+ }
5539
+ async function detectSquadCommand() {
5540
+ const squad = detectSquad();
5541
+ if (squad) {
5542
+ process.stdout.write(squad);
5543
+ }
5544
+ }
5545
+
5546
+ // src/commands/trigger.ts
5547
+ import chalk3 from "chalk";
5548
+ var SCHEDULER_URL = process.env.SCHEDULER_URL || "http://localhost:8090";
5549
+ async function fetchScheduler(path2, options) {
5550
+ const res = await fetch(`${SCHEDULER_URL}${path2}`, {
5551
+ ...options,
5552
+ headers: {
5553
+ "Content-Type": "application/json",
5554
+ ...options?.headers
5555
+ }
5556
+ });
5557
+ if (!res.ok) {
5558
+ const error = await res.text();
5559
+ throw new Error(`Scheduler error: ${res.status} ${error}`);
5560
+ }
5561
+ return res.json();
5562
+ }
5563
+ async function listTriggers(squad) {
5564
+ const params = squad ? `?squad=${squad}` : "";
5565
+ const triggers = await fetchScheduler(`/triggers${params}`);
5566
+ if (triggers.length === 0) {
5567
+ console.log(chalk3.gray("No triggers found"));
5568
+ return;
5569
+ }
5570
+ console.log(chalk3.bold("\nSmart Triggers\n"));
5571
+ const grouped = triggers.reduce(
5572
+ (acc, t) => {
5573
+ (acc[t.squad] = acc[t.squad] || []).push(t);
5574
+ return acc;
5575
+ },
5576
+ {}
5577
+ );
5578
+ for (const [squadName, squadTriggers] of Object.entries(grouped)) {
5579
+ console.log(chalk3.cyan(` ${squadName}`));
5580
+ for (const t of squadTriggers) {
5581
+ const status = t.enabled ? chalk3.green("\u25CF") : chalk3.gray("\u25CB");
5582
+ const agent = t.agent ? `/${t.agent}` : "";
5583
+ const fires = t.fire_count > 0 ? chalk3.gray(` (${t.fire_count}x)`) : "";
5584
+ console.log(
5585
+ ` ${status} ${t.name}${chalk3.gray(agent)} P${t.priority}${fires}`
5586
+ );
5587
+ }
5588
+ console.log();
5589
+ }
5590
+ }
5591
+ async function syncTriggers() {
5592
+ console.log(chalk3.gray("Syncing triggers from SQUAD.md files...\n"));
5593
+ const { execSync: execSync11 } = await import("child_process");
5594
+ const hqPath = process.env.HQ_PATH || `${process.env.HOME}/agents-squads/hq`;
5595
+ try {
5596
+ const output = execSync11(
5597
+ `python ${hqPath}/squads-scheduler/sync_triggers.py`,
5598
+ { encoding: "utf-8", cwd: hqPath }
5599
+ );
5600
+ console.log(output);
5601
+ } catch (error) {
5602
+ const execError = error;
5603
+ console.error(chalk3.red("Sync failed:"), execError.stderr || execError);
5604
+ }
5605
+ }
5606
+ async function fireTrigger(name) {
5607
+ const triggers = await fetchScheduler(`/triggers`);
5608
+ const trigger = triggers.find((t) => t.name === name);
5609
+ if (!trigger) {
5610
+ console.error(chalk3.red(`Trigger '${name}' not found`));
5611
+ return;
5612
+ }
5613
+ console.log(
5614
+ chalk3.gray(`Firing ${trigger.squad}/${trigger.agent || "*"}...`)
5615
+ );
5616
+ const execution = await fetchScheduler(
5617
+ `/triggers/${trigger.id}/fire`,
5618
+ { method: "POST" }
5619
+ );
5620
+ console.log(chalk3.green(`\u2713 Queued execution ${execution.id.slice(0, 8)}`));
5621
+ }
5622
+ async function toggleTrigger(name, enable) {
5623
+ const triggers = await fetchScheduler(`/triggers`);
5624
+ const trigger = triggers.find((t) => t.name === name);
5625
+ if (!trigger) {
5626
+ console.error(chalk3.red(`Trigger '${name}' not found`));
5627
+ return;
5628
+ }
5629
+ await fetchScheduler(`/triggers/${trigger.id}`, {
5630
+ method: "PATCH",
5631
+ body: JSON.stringify({ enabled: enable })
5632
+ });
5633
+ const status = enable ? chalk3.green("enabled") : chalk3.gray("disabled");
5634
+ console.log(`${trigger.name} ${status}`);
5635
+ }
5636
+ async function showStatus() {
5637
+ try {
5638
+ const stats = await fetchScheduler("/stats");
5639
+ console.log(chalk3.bold("\nScheduler Status\n"));
5640
+ console.log(chalk3.cyan(" Triggers"));
5641
+ console.log(` Total: ${stats.triggers.total}`);
5642
+ console.log(` Enabled: ${chalk3.green(stats.triggers.enabled)}`);
5643
+ console.log(` Fired 24h: ${stats.triggers.fired_24h}`);
5644
+ console.log(chalk3.cyan("\n Executions (24h)"));
5645
+ console.log(` Completed: ${chalk3.green(stats.executions_24h.completed)}`);
5646
+ console.log(` Failed: ${chalk3.red(stats.executions_24h.failed)}`);
5647
+ console.log(` Running: ${chalk3.yellow(stats.executions_24h.running)}`);
5648
+ console.log(` Queued: ${stats.executions_24h.queued}`);
5649
+ console.log();
5650
+ } catch {
5651
+ console.error(chalk3.red("Scheduler not running or unreachable"));
5652
+ console.log(chalk3.gray(` Expected at: ${SCHEDULER_URL}`));
5653
+ }
5654
+ }
5655
+ function registerTriggerCommand(program2) {
5656
+ const trigger = program2.command("trigger").description("Manage smart triggers");
5657
+ trigger.command("list [squad]").description("List triggers").action(async (squad) => {
5658
+ await listTriggers(squad);
5659
+ });
5660
+ trigger.command("sync").description("Sync SQUAD.md triggers to scheduler").action(async () => {
5661
+ await syncTriggers();
5662
+ });
5663
+ trigger.command("fire <name>").description("Manually fire a trigger").action(async (name) => {
5664
+ await fireTrigger(name);
5665
+ });
5666
+ trigger.command("enable <name>").description("Enable a trigger").action(async (name) => {
5667
+ await toggleTrigger(name, true);
5668
+ });
5669
+ trigger.command("disable <name>").description("Disable a trigger").action(async (name) => {
5670
+ await toggleTrigger(name, false);
5671
+ });
5672
+ trigger.command("status").description("Show scheduler status").action(async () => {
5673
+ await showStatus();
5674
+ });
5675
+ }
5676
+
4632
5677
  // src/cli.ts
4633
5678
  var envPaths = [
4634
- join14(process.cwd(), ".env"),
4635
- join14(process.cwd(), "..", "hq", ".env"),
4636
- join14(homedir4(), "agents-squads", "hq", ".env")
5679
+ join15(process.cwd(), ".env"),
5680
+ join15(process.cwd(), "..", "hq", ".env"),
5681
+ join15(homedir6(), "agents-squads", "hq", ".env")
4637
5682
  ];
4638
5683
  for (const envPath of envPaths) {
4639
- if (existsSync14(envPath)) {
5684
+ if (existsSync15(envPath)) {
4640
5685
  config({ path: envPath, quiet: true });
4641
5686
  break;
4642
5687
  }
4643
5688
  }
5689
+ applyStackConfig();
4644
5690
  registerExitHandler();
4645
5691
  var program = new Command();
4646
5692
  program.name("squads").description("A CLI for humans and agents").version(version);
4647
5693
  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);
5694
+ 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
5695
  program.command("list").description("List agents and squads").option("-s, --squads", "List squads only").option("-a, --agents", "List agents only").action(listCommand);
4650
5696
  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);
5697
+ 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
5698
  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
5699
  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
5700
  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,12 +5708,17 @@ memory.command("query <query>").description("Search across all squad memory").op
4662
5708
  memory.command("show <squad>").description("Show memory for a squad").action(memoryShowCommand);
4663
5709
  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
5710
  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);
5711
+ 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
5712
  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
5713
  limit: parseInt(opts.limit, 10),
4668
5714
  role: opts.role,
4669
5715
  importance: opts.importance
4670
5716
  }));
5717
+ memory.command("extract").description("Extract memories from recent conversations into Engram").option("-s, --session <session>", "Extract specific session only").option("-h, --hours <hours>", "Look back period in hours", "24").option("-d, --dry-run", "Preview without sending to Engram").action((opts) => memoryExtractCommand({
5718
+ session: opts.session,
5719
+ hours: parseInt(opts.hours, 10),
5720
+ dryRun: opts.dryRun
5721
+ }));
4671
5722
  var goal = program.command("goal").description("Manage squad goals");
4672
5723
  goal.command("set <squad> <description>").description("Set a goal for a squad").option("-m, --metric <metrics...>", "Metrics to track").action(goalSetCommand);
4673
5724
  goal.command("list [squad]").description("List goals for squad(s)").option("-a, --all", "Show completed goals too").action(goalListCommand);
@@ -4677,30 +5728,80 @@ var feedback = program.command("feedback").description("Record and view executio
4677
5728
  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
5729
  feedback.command("show <squad>").description("Show feedback history").option("-n, --limit <n>", "Number of entries to show", "5").action(feedbackShowCommand);
4679
5730
  feedback.command("stats").description("Show feedback summary across all squads").action(feedbackStatsCommand);
5731
+ 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);
5732
+ 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({
5733
+ days: parseInt(options.days, 10),
5734
+ squad: options.squad,
5735
+ json: options.json
5736
+ }));
5737
+ sessions.command("summary").description("Show pretty session summary (auto-detects current session or pass JSON)").option("-d, --data <json>", "JSON data for summary (overrides auto-detection)").option("-f, --file <path>", "Path to JSON file with summary data").option("-j, --json", "Output as JSON instead of pretty format").action(async (options) => {
5738
+ const { buildCurrentSessionSummary } = await import("./sessions-UR3YGSLR.js");
5739
+ let data;
5740
+ if (options.file) {
5741
+ const { readFileSync: readFileSync12 } = await import("fs");
5742
+ data = JSON.parse(readFileSync12(options.file, "utf-8"));
5743
+ } else if (options.data) {
5744
+ data = JSON.parse(options.data);
5745
+ } else if (!process.stdin.isTTY) {
5746
+ const chunks = [];
5747
+ for await (const chunk of process.stdin) {
5748
+ chunks.push(chunk);
5749
+ }
5750
+ const input = Buffer.concat(chunks).toString("utf-8").trim();
5751
+ if (input) {
5752
+ data = JSON.parse(input);
5753
+ } else {
5754
+ data = await buildCurrentSessionSummary();
5755
+ }
5756
+ } else {
5757
+ data = await buildCurrentSessionSummary();
5758
+ }
5759
+ await sessionsSummaryCommand(data, { json: options.json });
5760
+ });
5761
+ var session = program.command("session").description("Manage current session lifecycle");
5762
+ 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 }));
5763
+ session.command("stop").description("End current session").option("-q, --quiet", "Suppress output").action((options) => sessionStopCommand({ quiet: options.quiet }));
5764
+ session.command("heartbeat").description("Update session heartbeat").option("-q, --quiet", "Suppress output").action((options) => sessionHeartbeatCommand({ quiet: options.quiet }));
5765
+ program.command("detect-squad").description("Detect current squad based on cwd (for use in hooks)").action(detectSquadCommand);
5766
+ var stack = program.command("stack").description("Manage local Docker stack (postgres, redis, langfuse, bridge)");
5767
+ stack.command("init").description("Auto-detect Docker containers and configure CLI connection").action(stackInitCommand);
5768
+ stack.command("status").description("Show container health and connection status").action(stackStatusCommand);
5769
+ stack.command("env").description("Print environment variables for shell export").action(stackEnvCommand);
5770
+ stack.command("up").description("Start Docker containers via docker-compose").action(stackUpCommand);
5771
+ stack.command("down").description("Stop Docker containers").action(stackDownCommand);
5772
+ stack.command("health").description("Comprehensive health check with diagnostics").option("-v, --verbose", "Show logs for unhealthy services").action((options) => stackHealthCommand(options.verbose));
5773
+ 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)));
5774
+ registerTriggerCommand(program);
4680
5775
  program.command("login").description("Log in to Squads (Pro & Enterprise)").action(loginCommand);
4681
5776
  program.command("logout").description("Log out from Squads").action(logoutCommand);
4682
5777
  program.command("whoami").description("Show current logged in user").action(whoamiCommand);
5778
+ 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
5779
  await program.parseAsync();
4684
5780
  if (!process.argv.slice(2).length) {
4685
5781
  console.log(`
4686
- ${chalk3.bold.magenta("squads")} - AI agent squad management
5782
+ ${chalk4.bold.magenta("squads")} - AI agent squad management
5783
+
5784
+ ${chalk4.dim("Quick start:")}
5785
+ ${chalk4.cyan("squads status")} View all squads status
5786
+ ${chalk4.cyan("squads run <squad>")} Run a squad
5787
+ ${chalk4.cyan('squads memory query "<term>"')} Search squad memory
4687
5788
 
4688
- ${chalk3.dim("Quick start:")}
4689
- ${chalk3.cyan("squads status")} View all squads status
4690
- ${chalk3.cyan("squads run <squad>")} Run a squad
4691
- ${chalk3.cyan('squads memory query "<term>"')} Search squad memory
5789
+ ${chalk4.dim("Goals & Feedback:")}
5790
+ ${chalk4.cyan('squads goal set <squad> "<goal>"')} Set a goal
5791
+ ${chalk4.cyan("squads goal list")} View active goals
5792
+ ${chalk4.cyan('squads feedback add <squad> 4 "msg"')} Rate last execution
4692
5793
 
4693
- ${chalk3.dim("Goals & Feedback:")}
4694
- ${chalk3.cyan('squads goal set <squad> "<goal>"')} Set a goal
4695
- ${chalk3.cyan("squads goal list")} View active goals
4696
- ${chalk3.cyan('squads feedback add <squad> 4 "msg"')} Rate last execution
5794
+ ${chalk4.dim("Smart Triggers:")}
5795
+ ${chalk4.cyan("squads trigger list")} View all triggers
5796
+ ${chalk4.cyan("squads trigger sync")} Sync from SQUAD.md
5797
+ ${chalk4.cyan("squads trigger fire <name>")} Manually fire trigger
4697
5798
 
4698
- ${chalk3.dim("Examples:")}
4699
- ${chalk3.cyan("squads run website")} Run website squad
4700
- ${chalk3.cyan('squads goal set finance "Track costs"')} Set finance goal
4701
- ${chalk3.cyan("squads feedback stats")} View feedback summary
5799
+ ${chalk4.dim("Examples:")}
5800
+ ${chalk4.cyan("squads run website")} Run website squad
5801
+ ${chalk4.cyan('squads goal set finance "Track costs"')} Set finance goal
5802
+ ${chalk4.cyan("squads trigger status")} Scheduler health
4702
5803
 
4703
- ${chalk3.dim("Run")} ${chalk3.cyan("squads --help")} ${chalk3.dim("for all commands.")}
5804
+ ${chalk4.dim("Run")} ${chalk4.cyan("squads --help")} ${chalk4.dim("for all commands.")}
4704
5805
  `);
4705
5806
  }
4706
5807
  //# sourceMappingURL=cli.js.map