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/README.md +164 -21
- package/dist/chunk-266URT5W.js +915 -0
- package/dist/chunk-266URT5W.js.map +1 -0
- package/dist/chunk-7OCVIDC7.js +12 -0
- package/dist/chunk-7OCVIDC7.js.map +1 -0
- package/dist/chunk-FUHBEL3L.js +203 -0
- package/dist/chunk-FUHBEL3L.js.map +1 -0
- package/dist/cli.js +2118 -1017
- package/dist/cli.js.map +1 -1
- package/dist/memory-4PVUKIDK.js +19 -0
- package/dist/memory-4PVUKIDK.js.map +1 -0
- package/dist/sessions-UR3YGSLR.js +15 -0
- package/dist/sessions-UR3YGSLR.js.map +1 -0
- package/docker/.env.example +17 -0
- package/docker/README.md +92 -0
- package/docker/docker-compose.engram.yml +289 -0
- package/docker/docker-compose.yml +194 -0
- package/docker/init-db.sql +399 -0
- package/docker/init-engram-db.sql +148 -0
- package/docker/init-langfuse-db.sh +10 -0
- package/docker/otel-collector.yaml +34 -0
- package/docker/squads-bridge/Dockerfile +14 -0
- package/docker/squads-bridge/Dockerfile.proxy +14 -0
- package/docker/squads-bridge/anthropic_proxy.py +313 -0
- package/docker/squads-bridge/requirements.txt +7 -0
- package/docker/squads-bridge/squads_bridge.py +1457 -0
- package/docker/telemetry-ping/Dockerfile +10 -0
- package/docker/telemetry-ping/deploy.sh +69 -0
- package/docker/telemetry-ping/main.py +136 -0
- package/docker/telemetry-ping/requirements.txt +3 -0
- package/package.json +12 -2
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
12
|
-
import { join as
|
|
13
|
-
import { homedir as
|
|
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
|
|
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
|
|
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
|
|
129
|
-
`gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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: {
|
|
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
|
-
|
|
468
|
-
if (eventQueue.length > 0
|
|
469
|
-
|
|
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
|
-
|
|
482
|
-
|
|
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
|
|
731
|
-
import { existsSync as
|
|
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
|
|
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 =
|
|
1344
|
-
if (!
|
|
1345
|
-
|
|
1021
|
+
const dir = dirname(logPath);
|
|
1022
|
+
if (!existsSync4(dir)) {
|
|
1023
|
+
mkdirSync2(dir, { recursive: true });
|
|
1346
1024
|
}
|
|
1347
1025
|
let content = "";
|
|
1348
|
-
if (
|
|
1349
|
-
content =
|
|
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
|
-
|
|
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 || !
|
|
1366
|
-
let content =
|
|
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
|
-
|
|
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 =
|
|
1422
|
-
if (
|
|
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
|
-
|
|
1432
|
-
(
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
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
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
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
|
|
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 = `
|
|
1177
|
+
spinner.text = `Launching ${agentName} as background task...`;
|
|
1489
1178
|
try {
|
|
1490
|
-
const result = await executeWithClaude(
|
|
1491
|
-
spinner.succeed(`Agent ${agentName}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
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
|
|
1513
|
-
writeLine(` ${colors.dim}$${RESET}
|
|
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
|
|
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(
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
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
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
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
|
-
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
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}
|
|
2310
|
+
writeLine(` ${gradient("squads")} ${colors.dim}stack down${RESET}`);
|
|
1707
2311
|
writeLine();
|
|
1708
|
-
|
|
1709
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
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
|
-
|
|
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
|
|
2001
|
-
import { existsSync as
|
|
2002
|
-
import { join as
|
|
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 =
|
|
2031
|
-
if (
|
|
2032
|
-
return
|
|
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 =
|
|
2038
|
-
|
|
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 =
|
|
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 =
|
|
2111
|
-
if (!
|
|
2112
|
-
|
|
2796
|
+
const squadMemoryDir = join8(memoryDir, squad);
|
|
2797
|
+
if (!existsSync8(squadMemoryDir)) {
|
|
2798
|
+
mkdirSync5(squadMemoryDir, { recursive: true });
|
|
2113
2799
|
}
|
|
2114
2800
|
let agentDir;
|
|
2115
|
-
const existingDirs =
|
|
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 =
|
|
2803
|
+
agentDir = join8(squadMemoryDir, existingDirs[0]);
|
|
2118
2804
|
} else {
|
|
2119
|
-
agentDir =
|
|
2120
|
-
|
|
2805
|
+
agentDir = join8(squadMemoryDir, `${squad}-lead`);
|
|
2806
|
+
mkdirSync5(agentDir, { recursive: true });
|
|
2121
2807
|
}
|
|
2122
|
-
const statePath =
|
|
2808
|
+
const statePath = join8(agentDir, "state.md");
|
|
2123
2809
|
let content = "";
|
|
2124
|
-
if (
|
|
2125
|
-
content =
|
|
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
|
-
|
|
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
|
|
2322
|
-
import { join as
|
|
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
|
|
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
|
|
3097
|
+
return join9(memoryDir, squadName, agentName, "output.md");
|
|
2336
3098
|
}
|
|
2337
3099
|
function getLastExecution(squadName) {
|
|
2338
3100
|
const outputPath = getOutputPath(squadName);
|
|
2339
|
-
if (!outputPath || !
|
|
3101
|
+
if (!outputPath || !existsSync9(outputPath)) {
|
|
2340
3102
|
return null;
|
|
2341
3103
|
}
|
|
2342
|
-
const content =
|
|
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 =
|
|
2394
|
-
if (!
|
|
2395
|
-
|
|
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 (
|
|
2424
|
-
existing =
|
|
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
|
-
|
|
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 || !
|
|
3206
|
+
if (!feedbackPath || !existsSync9(feedbackPath)) {
|
|
2445
3207
|
writeLine(` ${colors.yellow}No feedback recorded for ${squadName}${RESET}`);
|
|
2446
3208
|
return;
|
|
2447
3209
|
}
|
|
2448
|
-
const content =
|
|
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 =
|
|
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 || !
|
|
3254
|
+
if (!feedbackPath || !existsSync9(feedbackPath)) {
|
|
2493
3255
|
continue;
|
|
2494
3256
|
}
|
|
2495
|
-
const content =
|
|
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
|
|
2519
|
-
import { join as
|
|
2520
|
-
import { homedir as
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
3462
|
+
async function fetchBridgeStats() {
|
|
2688
3463
|
try {
|
|
2689
|
-
const
|
|
3464
|
+
const statsResponse = await fetchWithTimeout(`${BRIDGE_URL}/stats`, {
|
|
2690
3465
|
headers: { "Content-Type": "application/json" }
|
|
2691
3466
|
});
|
|
2692
|
-
if (!
|
|
2693
|
-
return
|
|
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
|
-
|
|
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
|
|
3528
|
+
return null;
|
|
2717
3529
|
}
|
|
2718
3530
|
}
|
|
2719
3531
|
async function fetchInsights(period = "week") {
|
|
2720
3532
|
try {
|
|
2721
|
-
const response = await
|
|
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:
|
|
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 =
|
|
2904
|
-
if (!
|
|
3722
|
+
const squadMemory = join10(memoryDir, squadName);
|
|
3723
|
+
if (!existsSync10(squadMemory)) return "\u2014";
|
|
2905
3724
|
let latestTime = 0;
|
|
2906
3725
|
try {
|
|
2907
|
-
const agents =
|
|
3726
|
+
const agents = readdirSync4(squadMemory, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
2908
3727
|
for (const agent of agents) {
|
|
2909
|
-
const agentPath =
|
|
2910
|
-
const files =
|
|
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 =
|
|
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
|
|
2951
|
-
if (
|
|
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
|
|
2957
|
-
const
|
|
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 =
|
|
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
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3051
|
-
}
|
|
3052
|
-
|
|
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
|
-
|
|
3098
|
-
|
|
3935
|
+
join10(process.cwd(), ".."),
|
|
3936
|
+
join10(homedir4(), "agents-squads")
|
|
3099
3937
|
];
|
|
3100
3938
|
for (const dir of candidates) {
|
|
3101
|
-
if (
|
|
3939
|
+
if (existsSync10(join10(dir, "hq"))) {
|
|
3102
3940
|
return dir;
|
|
3103
3941
|
}
|
|
3104
3942
|
}
|
|
3105
3943
|
return null;
|
|
3106
3944
|
}
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
if (
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
}
|
|
3114
|
-
|
|
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
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
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
|
-
|
|
3153
|
-
const costs =
|
|
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
|
-
|
|
3169
|
-
|
|
3170
|
-
const
|
|
3171
|
-
|
|
3172
|
-
|
|
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
|
|
3255
|
-
|
|
3256
|
-
if (
|
|
3257
|
-
|
|
3258
|
-
}
|
|
3259
|
-
|
|
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}
|
|
4032
|
+
writeLine(` ${bold}Infrastructure${RESET} ${colors.dim}(${stats.source})${RESET}`);
|
|
3290
4033
|
writeLine();
|
|
3291
|
-
const
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
{
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
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
|
-
|
|
3312
|
-
(
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
4431
|
+
const prompt2 = buildSolvePrompt(issue);
|
|
3626
4432
|
try {
|
|
3627
|
-
const result = await executeClaudePrompt(
|
|
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(
|
|
4480
|
+
function executeClaudePrompt(prompt2) {
|
|
3675
4481
|
return new Promise((resolve, reject) => {
|
|
3676
|
-
const claude =
|
|
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
|
|
3704
|
-
import { readdirSync as
|
|
3705
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
3763
|
-
const files =
|
|
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:
|
|
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
|
-
|
|
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
|
|
4614
|
+
const prompt2 = buildEvalPrompt(agent);
|
|
3809
4615
|
try {
|
|
3810
|
-
const result = await executeClaudePrompt2(
|
|
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(
|
|
4654
|
+
function executeClaudePrompt2(prompt2) {
|
|
3849
4655
|
return new Promise((resolve, reject) => {
|
|
3850
|
-
const claude =
|
|
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
|
|
3884
|
-
import { join as
|
|
3885
|
-
import { homedir as
|
|
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 =
|
|
3914
|
-
var AUTH_PATH =
|
|
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(
|
|
3923
|
-
if (!
|
|
3924
|
-
|
|
4728
|
+
function saveSession(session2) {
|
|
4729
|
+
if (!existsSync12(AUTH_DIR)) {
|
|
4730
|
+
mkdirSync7(AUTH_DIR, { recursive: true });
|
|
3925
4731
|
}
|
|
3926
|
-
|
|
4732
|
+
writeFileSync8(AUTH_PATH, JSON.stringify(session2, null, 2));
|
|
3927
4733
|
}
|
|
3928
4734
|
function loadSession() {
|
|
3929
|
-
if (!
|
|
4735
|
+
if (!existsSync12(AUTH_PATH)) return null;
|
|
3930
4736
|
try {
|
|
3931
|
-
return JSON.parse(
|
|
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 (
|
|
3938
|
-
|
|
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
|
|
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(
|
|
4849
|
+
saveSession(session2);
|
|
4044
4850
|
spinner.succeed(`Logged in as ${chalk2.cyan(email)}`);
|
|
4045
|
-
await track("cli.login.success", { 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
|
|
4068
|
-
if (!
|
|
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 ${
|
|
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
|
|
4078
|
-
if (!
|
|
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(
|
|
4087
|
-
Domain: ${
|
|
4088
|
-
Status: ${
|
|
4089
|
-
Since: ${new Date(
|
|
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
|
|
4095
|
-
import { existsSync as
|
|
4096
|
-
import { join as
|
|
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 =
|
|
4102
|
-
if (!
|
|
4103
|
-
|
|
4973
|
+
const agentsDir = join13(cwd, ".agents");
|
|
4974
|
+
if (!existsSync13(agentsDir)) {
|
|
4975
|
+
mkdirSync8(agentsDir, { recursive: true });
|
|
4104
4976
|
}
|
|
4105
|
-
return
|
|
4977
|
+
return join13(agentsDir, "tasks.json");
|
|
4106
4978
|
}
|
|
4107
|
-
return
|
|
4979
|
+
return join13(memoryDir, "..", "tasks.json");
|
|
4108
4980
|
}
|
|
4109
4981
|
function loadTasks() {
|
|
4110
4982
|
const tasksPath = getTasksFilePath();
|
|
4111
|
-
if (
|
|
4983
|
+
if (existsSync13(tasksPath)) {
|
|
4112
4984
|
try {
|
|
4113
|
-
return JSON.parse(
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
4475
|
-
import { existsSync as
|
|
4476
|
-
import { join as
|
|
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
|
|
5352
|
+
return join14(memoryDir, "..", "tasks.json");
|
|
4481
5353
|
}
|
|
4482
5354
|
function loadActiveTasks() {
|
|
4483
5355
|
const tasksPath = getTasksFilePath2();
|
|
4484
|
-
if (!tasksPath || !
|
|
5356
|
+
if (!tasksPath || !existsSync14(tasksPath)) return [];
|
|
4485
5357
|
try {
|
|
4486
|
-
const data = JSON.parse(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
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 (
|
|
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
|
|
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
|
-
${
|
|
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
|
-
${
|
|
4689
|
-
${
|
|
4690
|
-
${
|
|
4691
|
-
${
|
|
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
|
-
${
|
|
4694
|
-
${
|
|
4695
|
-
${
|
|
4696
|
-
${
|
|
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
|
-
${
|
|
4699
|
-
${
|
|
4700
|
-
${
|
|
4701
|
-
${
|
|
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
|
-
${
|
|
5804
|
+
${chalk4.dim("Run")} ${chalk4.cyan("squads --help")} ${chalk4.dim("for all commands.")}
|
|
4704
5805
|
`);
|
|
4705
5806
|
}
|
|
4706
5807
|
//# sourceMappingURL=cli.js.map
|