squads-cli 0.4.6 → 0.4.8
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 +38 -0
- package/dist/{chunk-G63RBKDH.js → chunk-IDZYXBZY.js} +56 -13
- package/dist/chunk-IDZYXBZY.js.map +1 -0
- package/dist/cli.js +2140 -324
- package/dist/cli.js.map +1 -1
- package/dist/{sessions-JCQ34BEU.js → sessions-B6GVXJ2H.js} +2 -2
- package/docker/init-db.sql +79 -0
- package/docker/squads-bridge/squads_bridge.py +179 -16
- package/package.json +4 -1
- package/dist/chunk-G63RBKDH.js.map +0 -1
- /package/dist/{sessions-JCQ34BEU.js.map → sessions-B6GVXJ2H.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -28,15 +28,15 @@ import {
|
|
|
28
28
|
truncate,
|
|
29
29
|
updateHeartbeat,
|
|
30
30
|
writeLine
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-IDZYXBZY.js";
|
|
32
32
|
import {
|
|
33
33
|
__require
|
|
34
34
|
} from "./chunk-7OCVIDC7.js";
|
|
35
35
|
|
|
36
36
|
// src/cli.ts
|
|
37
37
|
import { config } from "dotenv";
|
|
38
|
-
import { existsSync as
|
|
39
|
-
import { join as
|
|
38
|
+
import { existsSync as existsSync19 } from "fs";
|
|
39
|
+
import { join as join21 } from "path";
|
|
40
40
|
import { homedir as homedir5 } from "os";
|
|
41
41
|
import { Command } from "commander";
|
|
42
42
|
import chalk4 from "chalk";
|
|
@@ -47,18 +47,227 @@ var require2 = createRequire(import.meta.url);
|
|
|
47
47
|
var pkg = require2("../package.json");
|
|
48
48
|
var version = pkg.version;
|
|
49
49
|
|
|
50
|
+
// src/lib/update.ts
|
|
51
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
|
|
52
|
+
import { join, dirname } from "path";
|
|
53
|
+
import { homedir } from "os";
|
|
54
|
+
import { execSync } from "child_process";
|
|
55
|
+
import { fileURLToPath } from "url";
|
|
56
|
+
function getPackageVersion() {
|
|
57
|
+
try {
|
|
58
|
+
const __filename3 = fileURLToPath(import.meta.url);
|
|
59
|
+
const __dirname3 = dirname(__filename3);
|
|
60
|
+
const possiblePaths = [
|
|
61
|
+
join(__dirname3, "..", "..", "package.json"),
|
|
62
|
+
// From dist/lib/
|
|
63
|
+
join(__dirname3, "..", "package.json"),
|
|
64
|
+
// From dist/
|
|
65
|
+
join(__dirname3, "package.json")
|
|
66
|
+
// Same dir
|
|
67
|
+
];
|
|
68
|
+
for (const pkgPath of possiblePaths) {
|
|
69
|
+
if (existsSync(pkgPath)) {
|
|
70
|
+
const pkg2 = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
71
|
+
return pkg2.version || "0.0.0";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
return "0.0.0";
|
|
77
|
+
}
|
|
78
|
+
var CURRENT_VERSION = getPackageVersion();
|
|
79
|
+
var CACHE_DIR = join(homedir(), ".squads");
|
|
80
|
+
var CACHE_FILE = join(CACHE_DIR, "update-check.json");
|
|
81
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
82
|
+
function isNewerVersion(v1, v2) {
|
|
83
|
+
const parts1 = v1.replace(/^v/, "").split(".").map(Number);
|
|
84
|
+
const parts2 = v2.replace(/^v/, "").split(".").map(Number);
|
|
85
|
+
for (let i = 0; i < 3; i++) {
|
|
86
|
+
const p1 = parts1[i] || 0;
|
|
87
|
+
const p2 = parts2[i] || 0;
|
|
88
|
+
if (p2 > p1) return true;
|
|
89
|
+
if (p2 < p1) return false;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
function readCache() {
|
|
94
|
+
try {
|
|
95
|
+
if (!existsSync(CACHE_FILE)) return null;
|
|
96
|
+
const data = JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
|
|
97
|
+
return data;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function writeCache(latestVersion) {
|
|
103
|
+
try {
|
|
104
|
+
if (!existsSync(CACHE_DIR)) {
|
|
105
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
106
|
+
}
|
|
107
|
+
const cache = {
|
|
108
|
+
latestVersion,
|
|
109
|
+
checkedAt: Date.now()
|
|
110
|
+
};
|
|
111
|
+
writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function fetchLatestVersion() {
|
|
116
|
+
try {
|
|
117
|
+
const result = execSync("npm view squads-cli version 2>/dev/null", {
|
|
118
|
+
encoding: "utf-8",
|
|
119
|
+
timeout: 5e3
|
|
120
|
+
}).trim();
|
|
121
|
+
return result || null;
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function checkForUpdate() {
|
|
127
|
+
const result = {
|
|
128
|
+
currentVersion: CURRENT_VERSION,
|
|
129
|
+
latestVersion: CURRENT_VERSION,
|
|
130
|
+
updateAvailable: false
|
|
131
|
+
};
|
|
132
|
+
const cache = readCache();
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
if (cache) {
|
|
135
|
+
result.latestVersion = cache.latestVersion;
|
|
136
|
+
result.updateAvailable = isNewerVersion(CURRENT_VERSION, cache.latestVersion);
|
|
137
|
+
if (now - cache.checkedAt >= CACHE_TTL_MS) {
|
|
138
|
+
triggerBackgroundRefresh();
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
triggerBackgroundRefresh();
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
function triggerBackgroundRefresh() {
|
|
146
|
+
try {
|
|
147
|
+
const { spawn: spawn8 } = __require("child_process");
|
|
148
|
+
const child = spawn8("npm", ["view", "squads-cli", "version"], {
|
|
149
|
+
detached: true,
|
|
150
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
151
|
+
shell: true
|
|
152
|
+
});
|
|
153
|
+
let output = "";
|
|
154
|
+
child.stdout?.on("data", (data) => {
|
|
155
|
+
output += data.toString();
|
|
156
|
+
});
|
|
157
|
+
child.on("close", () => {
|
|
158
|
+
const version2 = output.trim();
|
|
159
|
+
if (version2 && /^\d+\.\d+\.\d+/.test(version2)) {
|
|
160
|
+
writeCache(version2);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
child.unref();
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function performUpdate() {
|
|
168
|
+
try {
|
|
169
|
+
execSync("npm update -g squads-cli", {
|
|
170
|
+
encoding: "utf-8",
|
|
171
|
+
stdio: "inherit",
|
|
172
|
+
timeout: 12e4
|
|
173
|
+
// 2 minutes
|
|
174
|
+
});
|
|
175
|
+
try {
|
|
176
|
+
unlinkSync(CACHE_FILE);
|
|
177
|
+
} catch {
|
|
178
|
+
}
|
|
179
|
+
return { success: true };
|
|
180
|
+
} catch (err) {
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function refreshVersionCache() {
|
|
188
|
+
const latestVersion = fetchLatestVersion();
|
|
189
|
+
if (latestVersion) {
|
|
190
|
+
writeCache(latestVersion);
|
|
191
|
+
return {
|
|
192
|
+
currentVersion: CURRENT_VERSION,
|
|
193
|
+
latestVersion,
|
|
194
|
+
updateAvailable: isNewerVersion(CURRENT_VERSION, latestVersion)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return checkForUpdate();
|
|
198
|
+
}
|
|
199
|
+
var AUTO_UPDATE_CACHE_FILE = join(CACHE_DIR, "auto-update.json");
|
|
200
|
+
var AUTO_UPDATE_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
201
|
+
function readAutoUpdateCache() {
|
|
202
|
+
try {
|
|
203
|
+
if (!existsSync(AUTO_UPDATE_CACHE_FILE)) return null;
|
|
204
|
+
return JSON.parse(readFileSync(AUTO_UPDATE_CACHE_FILE, "utf-8"));
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function writeAutoUpdateCache(cache) {
|
|
210
|
+
try {
|
|
211
|
+
if (!existsSync(CACHE_DIR)) {
|
|
212
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
213
|
+
}
|
|
214
|
+
writeFileSync(AUTO_UPDATE_CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function autoUpdateOnStartup(silent = false) {
|
|
219
|
+
if (process.env.CI || process.env.SQUADS_NO_AUTO_UPDATE) return;
|
|
220
|
+
const autoCache = readAutoUpdateCache();
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
if (autoCache && now - autoCache.lastAttempt < AUTO_UPDATE_COOLDOWN_MS) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const info = checkForUpdate();
|
|
226
|
+
if (!info.updateAvailable) return;
|
|
227
|
+
writeAutoUpdateCache({ lastAttempt: now, lastSuccess: autoCache?.lastSuccess });
|
|
228
|
+
try {
|
|
229
|
+
const { spawn: spawn8 } = await import("child_process");
|
|
230
|
+
const child = spawn8("npm", ["update", "-g", "squads-cli"], {
|
|
231
|
+
detached: true,
|
|
232
|
+
stdio: silent ? "ignore" : ["ignore", "pipe", "pipe"],
|
|
233
|
+
shell: true
|
|
234
|
+
});
|
|
235
|
+
if (!silent && child.stdout) {
|
|
236
|
+
await new Promise((resolve) => {
|
|
237
|
+
child.on("close", (code) => {
|
|
238
|
+
if (code === 0) {
|
|
239
|
+
console.log(`
|
|
240
|
+
\x1B[32m\u2713\x1B[0m Update successful! v${info.latestVersion} will be used on your next run.
|
|
241
|
+
`);
|
|
242
|
+
writeAutoUpdateCache({ lastAttempt: now, lastSuccess: now });
|
|
243
|
+
try {
|
|
244
|
+
unlinkSync(CACHE_FILE);
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
resolve();
|
|
249
|
+
});
|
|
250
|
+
setTimeout(() => resolve(), 3e4);
|
|
251
|
+
});
|
|
252
|
+
} else {
|
|
253
|
+
child.unref();
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
50
259
|
// src/commands/init.ts
|
|
51
260
|
import chalk from "chalk";
|
|
52
261
|
import ora from "ora";
|
|
53
262
|
import fs from "fs/promises";
|
|
54
263
|
import path from "path";
|
|
55
|
-
import { execSync as
|
|
264
|
+
import { execSync as execSync3, spawn } from "child_process";
|
|
56
265
|
import { createInterface } from "readline";
|
|
57
266
|
|
|
58
267
|
// src/lib/git.ts
|
|
59
|
-
import { execSync, exec } from "child_process";
|
|
60
|
-
import { existsSync } from "fs";
|
|
61
|
-
import { join } from "path";
|
|
268
|
+
import { execSync as execSync2, exec } from "child_process";
|
|
269
|
+
import { existsSync as existsSync2 } from "fs";
|
|
270
|
+
import { join as join2 } from "path";
|
|
62
271
|
import { promisify } from "util";
|
|
63
272
|
var execAsync = promisify(exec);
|
|
64
273
|
function checkGitStatus(cwd = process.cwd()) {
|
|
@@ -68,12 +277,12 @@ function checkGitStatus(cwd = process.cwd()) {
|
|
|
68
277
|
isDirty: false,
|
|
69
278
|
uncommittedCount: 0
|
|
70
279
|
};
|
|
71
|
-
if (!
|
|
280
|
+
if (!existsSync2(join2(cwd, ".git"))) {
|
|
72
281
|
return status;
|
|
73
282
|
}
|
|
74
283
|
status.isGitRepo = true;
|
|
75
284
|
try {
|
|
76
|
-
const combined =
|
|
285
|
+
const combined = execSync2(
|
|
77
286
|
'echo "BRANCH:" && git rev-parse --abbrev-ref HEAD && echo "REMOTES:" && git remote -v && echo "STATUS:" && git status --porcelain',
|
|
78
287
|
{ cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
|
|
79
288
|
);
|
|
@@ -157,10 +366,10 @@ function getGitHubStatsOptimized(basePath, days = 30) {
|
|
|
157
366
|
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
|
|
158
367
|
const results = [];
|
|
159
368
|
for (const repo of repos) {
|
|
160
|
-
const repoPath =
|
|
161
|
-
if (!
|
|
369
|
+
const repoPath = join2(basePath, repo);
|
|
370
|
+
if (!existsSync2(repoPath)) continue;
|
|
162
371
|
try {
|
|
163
|
-
const output =
|
|
372
|
+
const output = execSync2(
|
|
164
373
|
`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 '}'`,
|
|
165
374
|
{ cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
|
|
166
375
|
);
|
|
@@ -171,11 +380,11 @@ function getGitHubStatsOptimized(basePath, days = 30) {
|
|
|
171
380
|
results.push({ repo, prs, issues });
|
|
172
381
|
} catch {
|
|
173
382
|
try {
|
|
174
|
-
const prsOutput =
|
|
383
|
+
const prsOutput = execSync2(
|
|
175
384
|
`gh pr list --state all --json number,title,createdAt,mergedAt,labels --limit 50 2>/dev/null`,
|
|
176
385
|
{ cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
|
|
177
386
|
);
|
|
178
|
-
const issuesOutput =
|
|
387
|
+
const issuesOutput = execSync2(
|
|
179
388
|
`gh issue list --state all --json number,title,state,closedAt,labels --limit 50 2>/dev/null`,
|
|
180
389
|
{ cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
|
|
181
390
|
);
|
|
@@ -286,15 +495,17 @@ function getMultiRepoGitStats(basePath, days = 30) {
|
|
|
286
495
|
activeDays: 0,
|
|
287
496
|
avgCommitsPerDay: 0,
|
|
288
497
|
peakDay: null,
|
|
289
|
-
repos: []
|
|
498
|
+
repos: [],
|
|
499
|
+
recentCommits: []
|
|
290
500
|
};
|
|
501
|
+
const allCommits = [];
|
|
291
502
|
for (const repo of SQUAD_REPOS) {
|
|
292
|
-
const repoPath =
|
|
293
|
-
if (!
|
|
503
|
+
const repoPath = join2(basePath, repo);
|
|
504
|
+
if (!existsSync2(repoPath) || !existsSync2(join2(repoPath, ".git"))) {
|
|
294
505
|
continue;
|
|
295
506
|
}
|
|
296
507
|
try {
|
|
297
|
-
const logOutput =
|
|
508
|
+
const logOutput = execSync2(
|
|
298
509
|
`git log --since="${days} days ago" --format="%H|%aN|%ad|%s" --date=short 2>/dev/null`,
|
|
299
510
|
{ cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
300
511
|
).trim();
|
|
@@ -303,7 +514,9 @@ function getMultiRepoGitStats(basePath, days = 30) {
|
|
|
303
514
|
const authors = /* @__PURE__ */ new Set();
|
|
304
515
|
let lastCommit = "";
|
|
305
516
|
for (const line of commits) {
|
|
306
|
-
const
|
|
517
|
+
const parts = line.split("|");
|
|
518
|
+
const [hash, author, date, ...messageParts] = parts;
|
|
519
|
+
const message = messageParts.join("|");
|
|
307
520
|
if (!hash) continue;
|
|
308
521
|
stats.totalCommits++;
|
|
309
522
|
authors.add(author);
|
|
@@ -314,6 +527,7 @@ function getMultiRepoGitStats(basePath, days = 30) {
|
|
|
314
527
|
stats.commitsByAuthor.set(author, authorCount + 1);
|
|
315
528
|
const repoCount = stats.commitsByRepo.get(repo) || 0;
|
|
316
529
|
stats.commitsByRepo.set(repo, repoCount + 1);
|
|
530
|
+
allCommits.push({ hash, author, date, message, repo });
|
|
317
531
|
}
|
|
318
532
|
stats.repos.push({
|
|
319
533
|
name: repo,
|
|
@@ -338,6 +552,7 @@ function getMultiRepoGitStats(basePath, days = 30) {
|
|
|
338
552
|
if (peakDate) {
|
|
339
553
|
stats.peakDay = { date: peakDate, count: peakCount };
|
|
340
554
|
}
|
|
555
|
+
stats.recentCommits = allCommits.sort((a, b) => b.date.localeCompare(a.date) || b.hash.localeCompare(a.hash)).slice(0, 5);
|
|
341
556
|
return stats;
|
|
342
557
|
}
|
|
343
558
|
function getActivitySparkline(basePath, days = 7) {
|
|
@@ -347,12 +562,12 @@ function getActivitySparkline(basePath, days = 7) {
|
|
|
347
562
|
activity.push(0);
|
|
348
563
|
}
|
|
349
564
|
for (const repo of SQUAD_REPOS) {
|
|
350
|
-
const repoPath =
|
|
351
|
-
if (!
|
|
565
|
+
const repoPath = join2(basePath, repo);
|
|
566
|
+
if (!existsSync2(repoPath) || !existsSync2(join2(repoPath, ".git"))) {
|
|
352
567
|
continue;
|
|
353
568
|
}
|
|
354
569
|
try {
|
|
355
|
-
const logOutput =
|
|
570
|
+
const logOutput = execSync2(
|
|
356
571
|
`git log --since="${days} days ago" --format="%ad" --date=short 2>/dev/null`,
|
|
357
572
|
{ cwd: repoPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
358
573
|
).trim();
|
|
@@ -372,13 +587,13 @@ function getActivitySparkline(basePath, days = 7) {
|
|
|
372
587
|
}
|
|
373
588
|
|
|
374
589
|
// src/lib/telemetry.ts
|
|
375
|
-
import { existsSync as
|
|
376
|
-
import { join as
|
|
377
|
-
import { homedir } from "os";
|
|
590
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
591
|
+
import { join as join3 } from "path";
|
|
592
|
+
import { homedir as homedir2 } from "os";
|
|
378
593
|
import { randomUUID } from "crypto";
|
|
379
|
-
var TELEMETRY_DIR =
|
|
380
|
-
var CONFIG_PATH =
|
|
381
|
-
var EVENTS_PATH =
|
|
594
|
+
var TELEMETRY_DIR = join3(homedir2(), ".squads-cli");
|
|
595
|
+
var CONFIG_PATH = join3(TELEMETRY_DIR, "telemetry.json");
|
|
596
|
+
var EVENTS_PATH = join3(TELEMETRY_DIR, "events.json");
|
|
382
597
|
var TELEMETRY_ENDPOINT = Buffer.from(
|
|
383
598
|
"aHR0cHM6Ly9zcXVhZHMtdGVsZW1ldHJ5LTk3ODg3MTgxNzYxMC51cy1jZW50cmFsMS5ydW4uYXBwL3Bpbmc=",
|
|
384
599
|
"base64"
|
|
@@ -387,24 +602,24 @@ var TELEMETRY_KEY = Buffer.from("c3FfdGVsX3YxXzdmOGE5YjJjM2Q0ZTVmNmE=", "base64"
|
|
|
387
602
|
var eventQueue = [];
|
|
388
603
|
var flushScheduled = false;
|
|
389
604
|
function ensureDir() {
|
|
390
|
-
if (!
|
|
391
|
-
|
|
605
|
+
if (!existsSync3(TELEMETRY_DIR)) {
|
|
606
|
+
mkdirSync2(TELEMETRY_DIR, { recursive: true });
|
|
392
607
|
}
|
|
393
608
|
}
|
|
394
609
|
function getConfig() {
|
|
395
610
|
ensureDir();
|
|
396
|
-
if (!
|
|
611
|
+
if (!existsSync3(CONFIG_PATH)) {
|
|
397
612
|
const config2 = {
|
|
398
613
|
enabled: true,
|
|
399
614
|
// Opt-out by default (common for CLIs)
|
|
400
615
|
anonymousId: randomUUID(),
|
|
401
616
|
firstRun: (/* @__PURE__ */ new Date()).toISOString()
|
|
402
617
|
};
|
|
403
|
-
|
|
618
|
+
writeFileSync2(CONFIG_PATH, JSON.stringify(config2, null, 2));
|
|
404
619
|
return config2;
|
|
405
620
|
}
|
|
406
621
|
try {
|
|
407
|
-
return JSON.parse(
|
|
622
|
+
return JSON.parse(readFileSync2(CONFIG_PATH, "utf-8"));
|
|
408
623
|
} catch {
|
|
409
624
|
return { enabled: false, anonymousId: "", firstRun: "" };
|
|
410
625
|
}
|
|
@@ -464,9 +679,9 @@ async function flushEvents() {
|
|
|
464
679
|
function storeEventLocally(event) {
|
|
465
680
|
ensureDir();
|
|
466
681
|
let events = [];
|
|
467
|
-
if (
|
|
682
|
+
if (existsSync3(EVENTS_PATH)) {
|
|
468
683
|
try {
|
|
469
|
-
events = JSON.parse(
|
|
684
|
+
events = JSON.parse(readFileSync2(EVENTS_PATH, "utf-8"));
|
|
470
685
|
} catch {
|
|
471
686
|
events = [];
|
|
472
687
|
}
|
|
@@ -475,7 +690,7 @@ function storeEventLocally(event) {
|
|
|
475
690
|
if (events.length > 1e3) {
|
|
476
691
|
events = events.slice(-1e3);
|
|
477
692
|
}
|
|
478
|
-
|
|
693
|
+
writeFileSync2(EVENTS_PATH, JSON.stringify(events, null, 2));
|
|
479
694
|
}
|
|
480
695
|
var Events = {
|
|
481
696
|
// Lifecycle
|
|
@@ -487,6 +702,9 @@ var Events = {
|
|
|
487
702
|
CLI_DASHBOARD: "cli.dashboard",
|
|
488
703
|
CLI_WORKERS: "cli.workers",
|
|
489
704
|
CLI_TONIGHT: "cli.tonight",
|
|
705
|
+
CLI_CONTEXT: "cli.context",
|
|
706
|
+
CLI_COST: "cli.cost",
|
|
707
|
+
CLI_EXEC: "cli.exec",
|
|
490
708
|
// Goals
|
|
491
709
|
CLI_GOAL_SET: "cli.goal.set",
|
|
492
710
|
CLI_GOAL_LIST: "cli.goal.list",
|
|
@@ -569,7 +787,7 @@ async function promptEmail() {
|
|
|
569
787
|
}
|
|
570
788
|
function commandExists(cmd) {
|
|
571
789
|
try {
|
|
572
|
-
|
|
790
|
+
execSync3(`which ${cmd}`, { stdio: "ignore" });
|
|
573
791
|
return true;
|
|
574
792
|
} catch {
|
|
575
793
|
return false;
|
|
@@ -577,7 +795,7 @@ function commandExists(cmd) {
|
|
|
577
795
|
}
|
|
578
796
|
function dockerRunning() {
|
|
579
797
|
try {
|
|
580
|
-
|
|
798
|
+
execSync3("docker info", { stdio: "ignore" });
|
|
581
799
|
return true;
|
|
582
800
|
} catch {
|
|
583
801
|
return false;
|
|
@@ -585,12 +803,12 @@ function dockerRunning() {
|
|
|
585
803
|
}
|
|
586
804
|
function checkClaudeAuth() {
|
|
587
805
|
try {
|
|
588
|
-
|
|
806
|
+
execSync3("which claude", { stdio: "ignore" });
|
|
589
807
|
} catch {
|
|
590
808
|
return { installed: false, loggedIn: false };
|
|
591
809
|
}
|
|
592
810
|
try {
|
|
593
|
-
const result =
|
|
811
|
+
const result = execSync3("claude --version", { stdio: "pipe" }).toString();
|
|
594
812
|
return { installed: true, loggedIn: result.includes("claude") };
|
|
595
813
|
} catch {
|
|
596
814
|
return { installed: true, loggedIn: false };
|
|
@@ -598,7 +816,7 @@ function checkClaudeAuth() {
|
|
|
598
816
|
}
|
|
599
817
|
function checkGhAuth() {
|
|
600
818
|
try {
|
|
601
|
-
|
|
819
|
+
execSync3("gh auth status", { stdio: "ignore" });
|
|
602
820
|
return true;
|
|
603
821
|
} catch {
|
|
604
822
|
return false;
|
|
@@ -735,7 +953,7 @@ async function setupInfrastructure(cwd) {
|
|
|
735
953
|
let allRunning = true;
|
|
736
954
|
for (const service of services) {
|
|
737
955
|
try {
|
|
738
|
-
|
|
956
|
+
execSync3(`docker ps --filter "name=${service}" --filter "status=running" -q`, { stdio: "pipe" });
|
|
739
957
|
} catch {
|
|
740
958
|
allRunning = false;
|
|
741
959
|
}
|
|
@@ -1147,20 +1365,21 @@ squads goal list # View goals
|
|
|
1147
1365
|
// src/commands/run.ts
|
|
1148
1366
|
import ora2 from "ora";
|
|
1149
1367
|
import { spawn as spawn2 } from "child_process";
|
|
1150
|
-
import { join as
|
|
1151
|
-
import { existsSync as
|
|
1368
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
1369
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
1152
1370
|
|
|
1153
1371
|
// src/lib/squad-parser.ts
|
|
1154
|
-
import { readFileSync as
|
|
1155
|
-
import { join as
|
|
1372
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1373
|
+
import { join as join4, basename } from "path";
|
|
1374
|
+
import matter from "gray-matter";
|
|
1156
1375
|
function findSquadsDir() {
|
|
1157
1376
|
let dir = process.cwd();
|
|
1158
1377
|
for (let i = 0; i < 5; i++) {
|
|
1159
|
-
const squadsPath =
|
|
1160
|
-
if (
|
|
1378
|
+
const squadsPath = join4(dir, ".agents", "squads");
|
|
1379
|
+
if (existsSync4(squadsPath)) {
|
|
1161
1380
|
return squadsPath;
|
|
1162
1381
|
}
|
|
1163
|
-
const parent =
|
|
1382
|
+
const parent = join4(dir, "..");
|
|
1164
1383
|
if (parent === dir) break;
|
|
1165
1384
|
dir = parent;
|
|
1166
1385
|
}
|
|
@@ -1169,14 +1388,14 @@ function findSquadsDir() {
|
|
|
1169
1388
|
function findProjectRoot() {
|
|
1170
1389
|
const squadsDir = findSquadsDir();
|
|
1171
1390
|
if (!squadsDir) return null;
|
|
1172
|
-
return
|
|
1391
|
+
return join4(squadsDir, "..", "..");
|
|
1173
1392
|
}
|
|
1174
1393
|
function hasLocalInfraConfig() {
|
|
1175
1394
|
const projectRoot = findProjectRoot();
|
|
1176
1395
|
if (!projectRoot) return false;
|
|
1177
|
-
const envPath =
|
|
1178
|
-
if (!
|
|
1179
|
-
const content =
|
|
1396
|
+
const envPath = join4(projectRoot, ".env");
|
|
1397
|
+
if (!existsSync4(envPath)) return false;
|
|
1398
|
+
const content = readFileSync3(envPath, "utf-8");
|
|
1180
1399
|
const infraKeys = ["LANGFUSE_", "SQUADS_BRIDGE", "SQUADS_POSTGRES", "SQUADS_REDIS"];
|
|
1181
1400
|
return infraKeys.some((key) => content.includes(key));
|
|
1182
1401
|
}
|
|
@@ -1185,8 +1404,8 @@ function listSquads(squadsDir) {
|
|
|
1185
1404
|
const entries = readdirSync(squadsDir, { withFileTypes: true });
|
|
1186
1405
|
for (const entry of entries) {
|
|
1187
1406
|
if (entry.isDirectory() && !entry.name.startsWith("_")) {
|
|
1188
|
-
const squadFile =
|
|
1189
|
-
if (
|
|
1407
|
+
const squadFile = join4(squadsDir, entry.name, "SQUAD.md");
|
|
1408
|
+
if (existsSync4(squadFile)) {
|
|
1190
1409
|
squads.push(entry.name);
|
|
1191
1410
|
}
|
|
1192
1411
|
}
|
|
@@ -1197,8 +1416,8 @@ function listAgents(squadsDir, squadName) {
|
|
|
1197
1416
|
const agents = [];
|
|
1198
1417
|
const dirs = squadName ? [squadName] : readdirSync(squadsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_")).map((e) => e.name);
|
|
1199
1418
|
for (const dir of dirs) {
|
|
1200
|
-
const squadPath =
|
|
1201
|
-
if (!
|
|
1419
|
+
const squadPath = join4(squadsDir, dir);
|
|
1420
|
+
if (!existsSync4(squadPath)) continue;
|
|
1202
1421
|
const files = readdirSync(squadPath);
|
|
1203
1422
|
for (const file of files) {
|
|
1204
1423
|
if (file.endsWith(".md") && file !== "SQUAD.md") {
|
|
@@ -1207,7 +1426,7 @@ function listAgents(squadsDir, squadName) {
|
|
|
1207
1426
|
name: agentName,
|
|
1208
1427
|
role: `Agent in ${dir}`,
|
|
1209
1428
|
trigger: "manual",
|
|
1210
|
-
filePath:
|
|
1429
|
+
filePath: join4(squadPath, file)
|
|
1211
1430
|
});
|
|
1212
1431
|
}
|
|
1213
1432
|
}
|
|
@@ -1215,17 +1434,24 @@ function listAgents(squadsDir, squadName) {
|
|
|
1215
1434
|
return agents;
|
|
1216
1435
|
}
|
|
1217
1436
|
function parseSquadFile(filePath) {
|
|
1218
|
-
const
|
|
1219
|
-
const
|
|
1437
|
+
const rawContent = readFileSync3(filePath, "utf-8");
|
|
1438
|
+
const { data: frontmatter, content: bodyContent } = matter(rawContent);
|
|
1439
|
+
const fm = frontmatter;
|
|
1440
|
+
const lines = bodyContent.split("\n");
|
|
1220
1441
|
const squad = {
|
|
1221
|
-
name: basename(filePath).replace(".md", ""),
|
|
1222
|
-
mission: "",
|
|
1442
|
+
name: fm.name || basename(filePath).replace(".md", ""),
|
|
1443
|
+
mission: fm.mission || "",
|
|
1223
1444
|
agents: [],
|
|
1224
1445
|
pipelines: [],
|
|
1225
1446
|
triggers: { scheduled: [], event: [], manual: [] },
|
|
1226
1447
|
dependencies: [],
|
|
1227
1448
|
outputPath: "",
|
|
1228
|
-
goals: []
|
|
1449
|
+
goals: [],
|
|
1450
|
+
// Apply frontmatter fields
|
|
1451
|
+
effort: fm.effort,
|
|
1452
|
+
context: fm.context,
|
|
1453
|
+
repo: fm.repo,
|
|
1454
|
+
stack: fm.stack
|
|
1229
1455
|
};
|
|
1230
1456
|
let currentSection = "";
|
|
1231
1457
|
let inTable = false;
|
|
@@ -1245,6 +1471,10 @@ function parseSquadFile(filePath) {
|
|
|
1245
1471
|
squad.mission = line.trim();
|
|
1246
1472
|
}
|
|
1247
1473
|
}
|
|
1474
|
+
const effortMatch = line.match(/^effort:\s*(high|medium|low)/i);
|
|
1475
|
+
if (effortMatch && !squad.effort) {
|
|
1476
|
+
squad.effort = effortMatch[1].toLowerCase();
|
|
1477
|
+
}
|
|
1248
1478
|
if (currentSection.includes("agent") || currentSection.includes("orchestrator") || currentSection.includes("evaluator") || currentSection.includes("builder") || currentSection.includes("priority")) {
|
|
1249
1479
|
if (line.includes("|") && line.includes("Agent")) {
|
|
1250
1480
|
inTable = true;
|
|
@@ -1257,12 +1487,16 @@ function parseSquadFile(filePath) {
|
|
|
1257
1487
|
const roleIdx = tableHeaders.findIndex((h) => h === "role");
|
|
1258
1488
|
const triggerIdx = tableHeaders.findIndex((h) => h === "trigger");
|
|
1259
1489
|
const statusIdx = tableHeaders.findIndex((h) => h === "status");
|
|
1490
|
+
const effortIdx = tableHeaders.findIndex((h) => h === "effort");
|
|
1260
1491
|
if (agentIdx >= 0 && cells[agentIdx]) {
|
|
1492
|
+
const effortValue = effortIdx >= 0 ? cells[effortIdx]?.toLowerCase() : void 0;
|
|
1493
|
+
const effort = ["high", "medium", "low"].includes(effortValue || "") ? effortValue : void 0;
|
|
1261
1494
|
squad.agents.push({
|
|
1262
1495
|
name: cells[agentIdx],
|
|
1263
1496
|
role: roleIdx >= 0 ? cells[roleIdx] : "",
|
|
1264
1497
|
trigger: triggerIdx >= 0 ? cells[triggerIdx] : "manual",
|
|
1265
|
-
status: statusIdx >= 0 ? cells[statusIdx] : "active"
|
|
1498
|
+
status: statusIdx >= 0 ? cells[statusIdx] : "active",
|
|
1499
|
+
effort
|
|
1266
1500
|
});
|
|
1267
1501
|
}
|
|
1268
1502
|
}
|
|
@@ -1321,20 +1555,20 @@ function parseSquadFile(filePath) {
|
|
|
1321
1555
|
function loadSquad(squadName) {
|
|
1322
1556
|
const squadsDir = findSquadsDir();
|
|
1323
1557
|
if (!squadsDir) return null;
|
|
1324
|
-
const squadFile =
|
|
1325
|
-
if (!
|
|
1558
|
+
const squadFile = join4(squadsDir, squadName, "SQUAD.md");
|
|
1559
|
+
if (!existsSync4(squadFile)) return null;
|
|
1326
1560
|
return parseSquadFile(squadFile);
|
|
1327
1561
|
}
|
|
1328
1562
|
function loadAgentDefinition(agentPath) {
|
|
1329
|
-
if (!
|
|
1330
|
-
return
|
|
1563
|
+
if (!existsSync4(agentPath)) return "";
|
|
1564
|
+
return readFileSync3(agentPath, "utf-8");
|
|
1331
1565
|
}
|
|
1332
1566
|
function addGoalToSquad(squadName, goal2) {
|
|
1333
1567
|
const squadsDir = findSquadsDir();
|
|
1334
1568
|
if (!squadsDir) return false;
|
|
1335
|
-
const squadFile =
|
|
1336
|
-
if (!
|
|
1337
|
-
let content =
|
|
1569
|
+
const squadFile = join4(squadsDir, squadName, "SQUAD.md");
|
|
1570
|
+
if (!existsSync4(squadFile)) return false;
|
|
1571
|
+
let content = readFileSync3(squadFile, "utf-8");
|
|
1338
1572
|
if (!content.includes("## Goals")) {
|
|
1339
1573
|
const insertPoint = content.indexOf("## Dependencies");
|
|
1340
1574
|
if (insertPoint > 0) {
|
|
@@ -1369,15 +1603,15 @@ function addGoalToSquad(squadName, goal2) {
|
|
|
1369
1603
|
- [ ] ${goal2}` + content.slice(headerEnd);
|
|
1370
1604
|
}
|
|
1371
1605
|
}
|
|
1372
|
-
|
|
1606
|
+
writeFileSync3(squadFile, content);
|
|
1373
1607
|
return true;
|
|
1374
1608
|
}
|
|
1375
1609
|
function updateGoalInSquad(squadName, goalIndex, updates) {
|
|
1376
1610
|
const squadsDir = findSquadsDir();
|
|
1377
1611
|
if (!squadsDir) return false;
|
|
1378
|
-
const squadFile =
|
|
1379
|
-
if (!
|
|
1380
|
-
const content =
|
|
1612
|
+
const squadFile = join4(squadsDir, squadName, "SQUAD.md");
|
|
1613
|
+
if (!existsSync4(squadFile)) return false;
|
|
1614
|
+
const content = readFileSync3(squadFile, "utf-8");
|
|
1381
1615
|
const lines = content.split("\n");
|
|
1382
1616
|
let currentSection = "";
|
|
1383
1617
|
let goalCount = 0;
|
|
@@ -1399,7 +1633,7 @@ function updateGoalInSquad(squadName, goalIndex, updates) {
|
|
|
1399
1633
|
}
|
|
1400
1634
|
}
|
|
1401
1635
|
lines[i] = newLine;
|
|
1402
|
-
|
|
1636
|
+
writeFileSync3(squadFile, lines.join("\n"));
|
|
1403
1637
|
return true;
|
|
1404
1638
|
}
|
|
1405
1639
|
goalCount++;
|
|
@@ -1409,14 +1643,287 @@ function updateGoalInSquad(squadName, goalIndex, updates) {
|
|
|
1409
1643
|
return false;
|
|
1410
1644
|
}
|
|
1411
1645
|
|
|
1646
|
+
// src/lib/permissions.ts
|
|
1647
|
+
import { minimatch } from "minimatch";
|
|
1648
|
+
function getDefaultContext(squad, agent) {
|
|
1649
|
+
return {
|
|
1650
|
+
squad,
|
|
1651
|
+
agent,
|
|
1652
|
+
permissions: {
|
|
1653
|
+
mode: "warn",
|
|
1654
|
+
bash: ["*"],
|
|
1655
|
+
// All commands allowed by default
|
|
1656
|
+
write: ["**"],
|
|
1657
|
+
// All paths writable by default
|
|
1658
|
+
read: ["**"],
|
|
1659
|
+
// All paths readable by default
|
|
1660
|
+
mcp: {
|
|
1661
|
+
allow: ["*"],
|
|
1662
|
+
// All MCP servers allowed
|
|
1663
|
+
deny: []
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
function validateBashCommand(command, allowedCommands) {
|
|
1669
|
+
const baseCommand = command.trim().split(/\s+/)[0];
|
|
1670
|
+
if (allowedCommands.includes("*")) {
|
|
1671
|
+
return null;
|
|
1672
|
+
}
|
|
1673
|
+
const isAllowed = allowedCommands.some((allowed) => {
|
|
1674
|
+
if (allowed === baseCommand) return true;
|
|
1675
|
+
if (allowed.includes("*")) {
|
|
1676
|
+
return minimatch(baseCommand, allowed);
|
|
1677
|
+
}
|
|
1678
|
+
return false;
|
|
1679
|
+
});
|
|
1680
|
+
if (!isAllowed) {
|
|
1681
|
+
return {
|
|
1682
|
+
type: "bash",
|
|
1683
|
+
requested: baseCommand,
|
|
1684
|
+
reason: `Bash command '${baseCommand}' not in allowlist: [${allowedCommands.join(", ")}]`,
|
|
1685
|
+
severity: "error"
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
return null;
|
|
1689
|
+
}
|
|
1690
|
+
function validateFilePath(path3, allowedGlobs, operation) {
|
|
1691
|
+
if (allowedGlobs.includes("**") || allowedGlobs.includes("*")) {
|
|
1692
|
+
return null;
|
|
1693
|
+
}
|
|
1694
|
+
const isAllowed = allowedGlobs.some((glob) => minimatch(path3, glob));
|
|
1695
|
+
if (!isAllowed) {
|
|
1696
|
+
return {
|
|
1697
|
+
type: operation,
|
|
1698
|
+
requested: path3,
|
|
1699
|
+
reason: `${operation === "write" ? "Write" : "Read"} to '${path3}' not allowed. Permitted paths: [${allowedGlobs.join(", ")}]`,
|
|
1700
|
+
severity: "error"
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
return null;
|
|
1704
|
+
}
|
|
1705
|
+
function validateMcpServer(server, allow, deny) {
|
|
1706
|
+
const isDenied = deny.some((pattern) => {
|
|
1707
|
+
if (pattern === server) return true;
|
|
1708
|
+
if (pattern.includes("*")) return minimatch(server, pattern);
|
|
1709
|
+
return false;
|
|
1710
|
+
});
|
|
1711
|
+
if (isDenied) {
|
|
1712
|
+
return {
|
|
1713
|
+
type: "mcp",
|
|
1714
|
+
requested: server,
|
|
1715
|
+
reason: `MCP server '${server}' is explicitly denied`,
|
|
1716
|
+
severity: "error"
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
if (allow.includes("*")) {
|
|
1720
|
+
return null;
|
|
1721
|
+
}
|
|
1722
|
+
const isAllowed = allow.some((pattern) => {
|
|
1723
|
+
if (pattern === server) return true;
|
|
1724
|
+
if (pattern.includes("*")) return minimatch(server, pattern);
|
|
1725
|
+
return false;
|
|
1726
|
+
});
|
|
1727
|
+
if (!isAllowed) {
|
|
1728
|
+
return {
|
|
1729
|
+
type: "mcp",
|
|
1730
|
+
requested: server,
|
|
1731
|
+
reason: `MCP server '${server}' not in allowlist: [${allow.join(", ")}]`,
|
|
1732
|
+
severity: "error"
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1737
|
+
function validateExecution(context2, request) {
|
|
1738
|
+
const violations = [];
|
|
1739
|
+
if (request.bashCommands) {
|
|
1740
|
+
for (const cmd of request.bashCommands) {
|
|
1741
|
+
const violation = validateBashCommand(cmd, context2.permissions.bash);
|
|
1742
|
+
if (violation) {
|
|
1743
|
+
violations.push(violation);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
if (request.writePaths) {
|
|
1748
|
+
for (const path3 of request.writePaths) {
|
|
1749
|
+
const violation = validateFilePath(path3, context2.permissions.write, "write");
|
|
1750
|
+
if (violation) {
|
|
1751
|
+
violations.push(violation);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
if (request.readPaths) {
|
|
1756
|
+
for (const path3 of request.readPaths) {
|
|
1757
|
+
const violation = validateFilePath(path3, context2.permissions.read, "read");
|
|
1758
|
+
if (violation) {
|
|
1759
|
+
violations.push(violation);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
if (request.mcpServers) {
|
|
1764
|
+
for (const server of request.mcpServers) {
|
|
1765
|
+
const violation = validateMcpServer(
|
|
1766
|
+
server,
|
|
1767
|
+
context2.permissions.mcp.allow,
|
|
1768
|
+
context2.permissions.mcp.deny
|
|
1769
|
+
);
|
|
1770
|
+
if (violation) {
|
|
1771
|
+
violations.push(violation);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
const hasErrors = violations.some((v) => v.severity === "error");
|
|
1776
|
+
const allowed = context2.permissions.mode !== "strict" || !hasErrors;
|
|
1777
|
+
return {
|
|
1778
|
+
allowed,
|
|
1779
|
+
violations,
|
|
1780
|
+
mode: context2.permissions.mode
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
function formatViolations(result) {
|
|
1784
|
+
const lines = [];
|
|
1785
|
+
if (result.violations.length === 0) {
|
|
1786
|
+
return lines;
|
|
1787
|
+
}
|
|
1788
|
+
const modeLabel = {
|
|
1789
|
+
warn: "\u26A0\uFE0F PERMISSION WARNING",
|
|
1790
|
+
strict: "\u{1F6AB} PERMISSION DENIED",
|
|
1791
|
+
audit: "\u{1F4DD} PERMISSION AUDIT"
|
|
1792
|
+
}[result.mode];
|
|
1793
|
+
lines.push(modeLabel);
|
|
1794
|
+
lines.push("");
|
|
1795
|
+
for (const v of result.violations) {
|
|
1796
|
+
const icon = v.severity === "error" ? "\u2717" : "\u26A0";
|
|
1797
|
+
lines.push(` ${icon} [${v.type}] ${v.reason}`);
|
|
1798
|
+
}
|
|
1799
|
+
if (result.mode === "warn") {
|
|
1800
|
+
lines.push("");
|
|
1801
|
+
lines.push(" Continuing with warnings (mode: warn)");
|
|
1802
|
+
} else if (result.mode === "audit") {
|
|
1803
|
+
lines.push("");
|
|
1804
|
+
lines.push(" Logged for audit, continuing (mode: audit)");
|
|
1805
|
+
} else if (!result.allowed) {
|
|
1806
|
+
lines.push("");
|
|
1807
|
+
lines.push(" Execution blocked (mode: strict)");
|
|
1808
|
+
}
|
|
1809
|
+
return lines;
|
|
1810
|
+
}
|
|
1811
|
+
function parsePermissionsYaml(content) {
|
|
1812
|
+
const yamlMatch = content.match(/```ya?ml\n([\s\S]*?)```/);
|
|
1813
|
+
if (!yamlMatch) return null;
|
|
1814
|
+
const yamlContent = yamlMatch[1];
|
|
1815
|
+
const permissions = {};
|
|
1816
|
+
const modeMatch = yamlContent.match(/^\s*mode:\s*(warn|strict|audit)/m);
|
|
1817
|
+
if (modeMatch) {
|
|
1818
|
+
permissions.mode = modeMatch[1];
|
|
1819
|
+
}
|
|
1820
|
+
const bashMatch = yamlContent.match(/^\s*bash:\s*\[(.*?)\]/m);
|
|
1821
|
+
if (bashMatch) {
|
|
1822
|
+
permissions.bash = bashMatch[1].split(",").map((s) => s.trim());
|
|
1823
|
+
}
|
|
1824
|
+
const writeMatch = yamlContent.match(/^\s*write:\s*\[(.*?)\]/m);
|
|
1825
|
+
if (writeMatch) {
|
|
1826
|
+
permissions.write = writeMatch[1].split(",").map((s) => s.trim());
|
|
1827
|
+
}
|
|
1828
|
+
const readMatch = yamlContent.match(/^\s*read:\s*\[(.*?)\]/m);
|
|
1829
|
+
if (readMatch) {
|
|
1830
|
+
permissions.read = readMatch[1].split(",").map((s) => s.trim());
|
|
1831
|
+
}
|
|
1832
|
+
const mcpAllowMatch = yamlContent.match(/^\s*allow:\s*\[(.*?)\]/m);
|
|
1833
|
+
const mcpDenyMatch = yamlContent.match(/^\s*deny:\s*\[(.*?)\]/m);
|
|
1834
|
+
if (mcpAllowMatch || mcpDenyMatch) {
|
|
1835
|
+
permissions.mcp = {
|
|
1836
|
+
allow: mcpAllowMatch ? mcpAllowMatch[1].split(",").map((s) => s.trim()) : ["*"],
|
|
1837
|
+
deny: mcpDenyMatch ? mcpDenyMatch[1].split(",").map((s) => s.trim()) : []
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
return Object.keys(permissions).length > 0 ? permissions : null;
|
|
1841
|
+
}
|
|
1842
|
+
function buildContextFromSquad(squadName, squadContent, agentName) {
|
|
1843
|
+
const context2 = getDefaultContext(squadName, agentName);
|
|
1844
|
+
const parsed = parsePermissionsYaml(squadContent);
|
|
1845
|
+
if (parsed) {
|
|
1846
|
+
if (parsed.mode) context2.permissions.mode = parsed.mode;
|
|
1847
|
+
if (parsed.bash) context2.permissions.bash = parsed.bash;
|
|
1848
|
+
if (parsed.write) context2.permissions.write = parsed.write;
|
|
1849
|
+
if (parsed.read) context2.permissions.read = parsed.read;
|
|
1850
|
+
if (parsed.mcp) context2.permissions.mcp = parsed.mcp;
|
|
1851
|
+
}
|
|
1852
|
+
return context2;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1412
1855
|
// src/commands/run.ts
|
|
1856
|
+
async function registerContextWithBridge(ctx) {
|
|
1857
|
+
const bridgeUrl = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
|
|
1858
|
+
try {
|
|
1859
|
+
const response = await fetch(`${bridgeUrl}/api/context/register`, {
|
|
1860
|
+
method: "POST",
|
|
1861
|
+
headers: { "Content-Type": "application/json" },
|
|
1862
|
+
body: JSON.stringify({
|
|
1863
|
+
execution_id: ctx.executionId,
|
|
1864
|
+
squad: ctx.squad,
|
|
1865
|
+
agent: ctx.agent,
|
|
1866
|
+
task_type: ctx.taskType,
|
|
1867
|
+
trigger: ctx.trigger
|
|
1868
|
+
})
|
|
1869
|
+
});
|
|
1870
|
+
if (!response.ok) {
|
|
1871
|
+
return false;
|
|
1872
|
+
}
|
|
1873
|
+
return true;
|
|
1874
|
+
} catch {
|
|
1875
|
+
return false;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
function generateExecutionId() {
|
|
1879
|
+
const timestamp = Date.now().toString(36);
|
|
1880
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
1881
|
+
return `exec_${timestamp}_${random}`;
|
|
1882
|
+
}
|
|
1883
|
+
function selectMcpConfig(squadName) {
|
|
1884
|
+
const home = process.env.HOME || "";
|
|
1885
|
+
const configsDir = join5(home, ".claude", "mcp-configs");
|
|
1886
|
+
const squadConfigs = {
|
|
1887
|
+
website: "website.json",
|
|
1888
|
+
// chrome-devtools, nano-banana
|
|
1889
|
+
research: "research.json",
|
|
1890
|
+
// x-mcp
|
|
1891
|
+
intelligence: "research.json",
|
|
1892
|
+
// x-mcp
|
|
1893
|
+
analytics: "data.json",
|
|
1894
|
+
// supabase, grafana, ga4-admin
|
|
1895
|
+
engineering: "data.json"
|
|
1896
|
+
// supabase, grafana
|
|
1897
|
+
};
|
|
1898
|
+
const configFile = squadConfigs[squadName.toLowerCase()];
|
|
1899
|
+
if (configFile) {
|
|
1900
|
+
const configPath = join5(configsDir, configFile);
|
|
1901
|
+
if (existsSync5(configPath)) {
|
|
1902
|
+
return configPath;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
return join5(home, ".claude.json");
|
|
1906
|
+
}
|
|
1907
|
+
function detectTaskType(agentName) {
|
|
1908
|
+
const name = agentName.toLowerCase();
|
|
1909
|
+
if (name.includes("eval") || name.includes("critic") || name.includes("review") || name.includes("test")) {
|
|
1910
|
+
return "evaluation";
|
|
1911
|
+
}
|
|
1912
|
+
if (name.includes("lead") || name.includes("orchestrator")) {
|
|
1913
|
+
return "lead";
|
|
1914
|
+
}
|
|
1915
|
+
if (name.includes("research") || name.includes("analyst") || name.includes("intel")) {
|
|
1916
|
+
return "research";
|
|
1917
|
+
}
|
|
1918
|
+
return "execution";
|
|
1919
|
+
}
|
|
1413
1920
|
function ensureProjectTrusted(projectPath) {
|
|
1414
|
-
const configPath =
|
|
1415
|
-
if (!
|
|
1921
|
+
const configPath = join5(process.env.HOME || "", ".claude.json");
|
|
1922
|
+
if (!existsSync5(configPath)) {
|
|
1416
1923
|
return;
|
|
1417
1924
|
}
|
|
1418
1925
|
try {
|
|
1419
|
-
const config2 = JSON.parse(
|
|
1926
|
+
const config2 = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
1420
1927
|
if (!config2.projects) {
|
|
1421
1928
|
config2.projects = {};
|
|
1422
1929
|
}
|
|
@@ -1425,7 +1932,7 @@ function ensureProjectTrusted(projectPath) {
|
|
|
1425
1932
|
}
|
|
1426
1933
|
if (!config2.projects[projectPath].hasTrustDialogAccepted) {
|
|
1427
1934
|
config2.projects[projectPath].hasTrustDialogAccepted = true;
|
|
1428
|
-
|
|
1935
|
+
writeFileSync4(configPath, JSON.stringify(config2, null, 2));
|
|
1429
1936
|
}
|
|
1430
1937
|
} catch {
|
|
1431
1938
|
}
|
|
@@ -1433,51 +1940,92 @@ function ensureProjectTrusted(projectPath) {
|
|
|
1433
1940
|
function getProjectRoot() {
|
|
1434
1941
|
const squadsDir = findSquadsDir();
|
|
1435
1942
|
if (squadsDir) {
|
|
1436
|
-
return
|
|
1943
|
+
return dirname2(dirname2(squadsDir));
|
|
1437
1944
|
}
|
|
1438
1945
|
return process.cwd();
|
|
1439
1946
|
}
|
|
1440
1947
|
function getExecutionLogPath(squadName, agentName) {
|
|
1441
1948
|
const memoryDir = findMemoryDir();
|
|
1442
1949
|
if (!memoryDir) return null;
|
|
1443
|
-
return
|
|
1950
|
+
return join5(memoryDir, squadName, agentName, "executions.md");
|
|
1444
1951
|
}
|
|
1445
1952
|
function logExecution(record) {
|
|
1446
1953
|
const logPath = getExecutionLogPath(record.squadName, record.agentName);
|
|
1447
1954
|
if (!logPath) return;
|
|
1448
|
-
const dir =
|
|
1449
|
-
if (!
|
|
1450
|
-
|
|
1955
|
+
const dir = dirname2(logPath);
|
|
1956
|
+
if (!existsSync5(dir)) {
|
|
1957
|
+
mkdirSync3(dir, { recursive: true });
|
|
1451
1958
|
}
|
|
1452
1959
|
let content = "";
|
|
1453
|
-
if (
|
|
1454
|
-
content =
|
|
1960
|
+
if (existsSync5(logPath)) {
|
|
1961
|
+
content = readFileSync4(logPath, "utf-8").trimEnd();
|
|
1455
1962
|
} else {
|
|
1456
|
-
content = `# ${record.squadName}/${record.agentName} - Execution Log
|
|
1457
|
-
|
|
1458
|
-
`;
|
|
1963
|
+
content = `# ${record.squadName}/${record.agentName} - Execution Log`;
|
|
1459
1964
|
}
|
|
1460
1965
|
const entry = `
|
|
1966
|
+
|
|
1461
1967
|
---
|
|
1968
|
+
<!-- exec:${record.executionId} -->
|
|
1462
1969
|
**${record.startTime}** | Status: ${record.status}
|
|
1463
|
-
|
|
1464
|
-
|
|
1970
|
+
- ID: \`${record.executionId}\`
|
|
1971
|
+
- Trigger: ${record.trigger || "manual"}
|
|
1972
|
+
- Task Type: ${record.taskType || "execution"}
|
|
1465
1973
|
`;
|
|
1466
|
-
|
|
1974
|
+
writeFileSync4(logPath, content + entry);
|
|
1467
1975
|
}
|
|
1468
|
-
function updateExecutionStatus(squadName, agentName, status,
|
|
1976
|
+
function updateExecutionStatus(squadName, agentName, executionId, status, details) {
|
|
1469
1977
|
const logPath = getExecutionLogPath(squadName, agentName);
|
|
1470
|
-
if (!logPath || !
|
|
1471
|
-
let content =
|
|
1978
|
+
if (!logPath || !existsSync5(logPath)) return;
|
|
1979
|
+
let content = readFileSync4(logPath, "utf-8");
|
|
1472
1980
|
const endTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
);
|
|
1480
|
-
|
|
1981
|
+
const execMarker = `<!-- exec:${executionId} -->`;
|
|
1982
|
+
const markerIndex = content.indexOf(execMarker);
|
|
1983
|
+
if (markerIndex === -1) return;
|
|
1984
|
+
const nextEntryIndex = content.indexOf("\n---\n", markerIndex + 1);
|
|
1985
|
+
const entryEnd = nextEntryIndex === -1 ? content.length : nextEntryIndex;
|
|
1986
|
+
const entryStart = content.lastIndexOf("\n---\n", markerIndex);
|
|
1987
|
+
const currentEntry = content.slice(entryStart, entryEnd);
|
|
1988
|
+
const durationStr = details?.durationMs ? `${(details.durationMs / 1e3).toFixed(1)}s` : "unknown";
|
|
1989
|
+
let updatedEntry = currentEntry.replace(/Status: running/, `Status: ${status}`) + `- Completed: ${endTime}
|
|
1990
|
+
- Duration: ${durationStr}`;
|
|
1991
|
+
if (details?.outcome) {
|
|
1992
|
+
updatedEntry += `
|
|
1993
|
+
- Outcome: ${details.outcome}`;
|
|
1994
|
+
}
|
|
1995
|
+
if (details?.error) {
|
|
1996
|
+
updatedEntry += `
|
|
1997
|
+
- Error: ${details.error}`;
|
|
1998
|
+
}
|
|
1999
|
+
content = content.slice(0, entryStart) + updatedEntry + content.slice(entryEnd);
|
|
2000
|
+
writeFileSync4(logPath, content);
|
|
2001
|
+
}
|
|
2002
|
+
function extractMcpServersFromDefinition(definition) {
|
|
2003
|
+
const servers = /* @__PURE__ */ new Set();
|
|
2004
|
+
const knownServers = [
|
|
2005
|
+
"chrome-devtools",
|
|
2006
|
+
"firecrawl",
|
|
2007
|
+
"supabase",
|
|
2008
|
+
"grafana",
|
|
2009
|
+
"context7",
|
|
2010
|
+
"huggingface",
|
|
2011
|
+
"nano-banana"
|
|
2012
|
+
];
|
|
2013
|
+
for (const server of knownServers) {
|
|
2014
|
+
if (definition.toLowerCase().includes(server)) {
|
|
2015
|
+
servers.add(server);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
const mcpMatch = definition.match(/mcp:\s*\n((?:\s*-\s*\S+\s*\n?)+)/i);
|
|
2019
|
+
if (mcpMatch) {
|
|
2020
|
+
const lines = mcpMatch[1].split("\n");
|
|
2021
|
+
for (const line of lines) {
|
|
2022
|
+
const serverMatch = line.match(/^\s*-\s*(\S+)/);
|
|
2023
|
+
if (serverMatch) {
|
|
2024
|
+
servers.add(serverMatch[1]);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
return Array.from(servers);
|
|
1481
2029
|
}
|
|
1482
2030
|
async function runCommand(target, options) {
|
|
1483
2031
|
const squadsDir = findSquadsDir();
|
|
@@ -1508,6 +2056,9 @@ async function runCommand(target, options) {
|
|
|
1508
2056
|
}
|
|
1509
2057
|
async function runSquad(squad, squadsDir, options) {
|
|
1510
2058
|
if (!squad) return;
|
|
2059
|
+
if (!options.effort && squad.effort) {
|
|
2060
|
+
options.effort = squad.effort;
|
|
2061
|
+
}
|
|
1511
2062
|
const startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1512
2063
|
writeLine();
|
|
1513
2064
|
writeLine(` ${gradient("squads")} ${colors.dim}run${RESET} ${colors.cyan}${squad.name}${RESET}`);
|
|
@@ -1525,8 +2076,8 @@ async function runSquad(squad, squadsDir, options) {
|
|
|
1525
2076
|
if (options.parallel) {
|
|
1526
2077
|
const agentFiles = squad.agents.map((a) => ({
|
|
1527
2078
|
name: a.name,
|
|
1528
|
-
path:
|
|
1529
|
-
})).filter((a) =>
|
|
2079
|
+
path: join5(squadsDir, squad.name, `${a.name}.md`)
|
|
2080
|
+
})).filter((a) => existsSync5(a.path));
|
|
1530
2081
|
if (agentFiles.length === 0) {
|
|
1531
2082
|
writeLine(` ${icons.error} ${colors.red}No agent files found${RESET}`);
|
|
1532
2083
|
return;
|
|
@@ -1562,8 +2113,8 @@ async function runSquad(squad, squadsDir, options) {
|
|
|
1562
2113
|
writeLine();
|
|
1563
2114
|
for (let i = 0; i < pipeline.agents.length; i++) {
|
|
1564
2115
|
const agentName = pipeline.agents[i];
|
|
1565
|
-
const agentPath =
|
|
1566
|
-
if (
|
|
2116
|
+
const agentPath = join5(squadsDir, squad.name, `${agentName}.md`);
|
|
2117
|
+
if (existsSync5(agentPath)) {
|
|
1567
2118
|
writeLine(` ${colors.dim}[${i + 1}/${pipeline.agents.length}]${RESET}`);
|
|
1568
2119
|
await runAgent(agentName, agentPath, squad.name, options);
|
|
1569
2120
|
writeLine();
|
|
@@ -1573,8 +2124,8 @@ async function runSquad(squad, squadsDir, options) {
|
|
|
1573
2124
|
}
|
|
1574
2125
|
} else {
|
|
1575
2126
|
if (options.agent) {
|
|
1576
|
-
const agentPath =
|
|
1577
|
-
if (
|
|
2127
|
+
const agentPath = join5(squadsDir, squad.name, `${options.agent}.md`);
|
|
2128
|
+
if (existsSync5(agentPath)) {
|
|
1578
2129
|
await runAgent(options.agent, agentPath, squad.name, options);
|
|
1579
2130
|
} else {
|
|
1580
2131
|
writeLine(` ${icons.error} ${colors.red}Agent ${options.agent} not found${RESET}`);
|
|
@@ -1585,8 +2136,8 @@ async function runSquad(squad, squadsDir, options) {
|
|
|
1585
2136
|
(a) => a.name.includes("lead") || a.trigger === "Manual"
|
|
1586
2137
|
);
|
|
1587
2138
|
if (orchestrator) {
|
|
1588
|
-
const agentPath =
|
|
1589
|
-
if (
|
|
2139
|
+
const agentPath = join5(squadsDir, squad.name, `${orchestrator.name}.md`);
|
|
2140
|
+
if (existsSync5(agentPath)) {
|
|
1590
2141
|
await runAgent(orchestrator.name, agentPath, squad.name, options);
|
|
1591
2142
|
}
|
|
1592
2143
|
} else {
|
|
@@ -1612,9 +2163,9 @@ async function runLeadMode(squad, squadsDir, options) {
|
|
|
1612
2163
|
if (!squad) return;
|
|
1613
2164
|
const agentFiles = squad.agents.map((a) => ({
|
|
1614
2165
|
name: a.name,
|
|
1615
|
-
path:
|
|
2166
|
+
path: join5(squadsDir, squad.name, `${a.name}.md`),
|
|
1616
2167
|
role: a.role || ""
|
|
1617
|
-
})).filter((a) =>
|
|
2168
|
+
})).filter((a) => existsSync5(a.path));
|
|
1618
2169
|
if (agentFiles.length === 0) {
|
|
1619
2170
|
writeLine(` ${icons.error} ${colors.red}No agent files found${RESET}`);
|
|
1620
2171
|
return;
|
|
@@ -1697,7 +2248,7 @@ Begin by assessing pending work, then delegate to agents via Task tool.`;
|
|
|
1697
2248
|
writeLine(` ${gradient("Launching")} lead session${options.foreground ? " (foreground)" : ""}...`);
|
|
1698
2249
|
writeLine();
|
|
1699
2250
|
try {
|
|
1700
|
-
const result = await executeWithClaude(prompt2, options.verbose, timeoutMins, options.foreground, options.useApi);
|
|
2251
|
+
const result = await executeWithClaude(prompt2, options.verbose, timeoutMins, options.foreground, options.useApi, options.effort, options.skills, options.trigger || "manual");
|
|
1701
2252
|
if (options.foreground) {
|
|
1702
2253
|
writeLine();
|
|
1703
2254
|
writeLine(` ${icons.success} Lead session completed`);
|
|
@@ -1718,7 +2269,10 @@ Begin by assessing pending work, then delegate to agents via Task tool.`;
|
|
|
1718
2269
|
}
|
|
1719
2270
|
async function runAgent(agentName, agentPath, squadName, options) {
|
|
1720
2271
|
const spinner = ora2(`Running agent: ${agentName}`).start();
|
|
1721
|
-
const
|
|
2272
|
+
const startMs = Date.now();
|
|
2273
|
+
const startTime = new Date(startMs).toISOString();
|
|
2274
|
+
const executionId = generateExecutionId();
|
|
2275
|
+
const taskType = detectTaskType(agentName);
|
|
1722
2276
|
const definition = loadAgentDefinition(agentPath);
|
|
1723
2277
|
if (options.dryRun) {
|
|
1724
2278
|
spinner.info(`[DRY RUN] Would run ${agentName}`);
|
|
@@ -1728,11 +2282,40 @@ async function runAgent(agentName, agentPath, squadName, options) {
|
|
|
1728
2282
|
}
|
|
1729
2283
|
return;
|
|
1730
2284
|
}
|
|
2285
|
+
const squadsDir = findSquadsDir();
|
|
2286
|
+
if (squadsDir) {
|
|
2287
|
+
const squadFilePath = join5(squadsDir, squadName, "SQUAD.md");
|
|
2288
|
+
if (existsSync5(squadFilePath)) {
|
|
2289
|
+
const squadContent = readFileSync4(squadFilePath, "utf-8");
|
|
2290
|
+
const permContext = buildContextFromSquad(squadName, squadContent, agentName);
|
|
2291
|
+
const mcpServers = extractMcpServersFromDefinition(definition);
|
|
2292
|
+
const execRequest = {
|
|
2293
|
+
mcpServers
|
|
2294
|
+
};
|
|
2295
|
+
const permResult = validateExecution(permContext, execRequest);
|
|
2296
|
+
if (permResult.violations.length > 0) {
|
|
2297
|
+
spinner.stop();
|
|
2298
|
+
const violationLines = formatViolations(permResult);
|
|
2299
|
+
for (const line of violationLines) {
|
|
2300
|
+
writeLine(` ${line}`);
|
|
2301
|
+
}
|
|
2302
|
+
writeLine();
|
|
2303
|
+
if (!permResult.allowed) {
|
|
2304
|
+
writeLine(` ${colors.red}Execution blocked due to permission violations.${RESET}`);
|
|
2305
|
+
writeLine(` ${colors.dim}Configure permissions in ${squadFilePath}${RESET}`);
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
1731
2311
|
logExecution({
|
|
1732
2312
|
squadName,
|
|
1733
2313
|
agentName,
|
|
2314
|
+
executionId,
|
|
1734
2315
|
startTime,
|
|
1735
|
-
status: "running"
|
|
2316
|
+
status: "running",
|
|
2317
|
+
trigger: options.trigger || "manual",
|
|
2318
|
+
taskType
|
|
1736
2319
|
});
|
|
1737
2320
|
const timeoutMins = options.timeout || 30;
|
|
1738
2321
|
const prompt2 = `Execute the ${agentName} agent from squad ${squadName}.
|
|
@@ -1764,7 +2347,7 @@ CRITICAL: When you have completed your tasks OR reached the time limit:
|
|
|
1764
2347
|
if (options.execute && claudeAvailable) {
|
|
1765
2348
|
spinner.text = options.foreground ? `Running ${agentName} in foreground...` : `Launching ${agentName} as background task...`;
|
|
1766
2349
|
try {
|
|
1767
|
-
const result = await executeWithClaude(prompt2, options.verbose, options.timeout || 30, options.foreground, options.useApi);
|
|
2350
|
+
const result = await executeWithClaude(prompt2, options.verbose, options.timeout || 30, options.foreground, options.useApi, options.effort, options.skills, options.trigger || "manual");
|
|
1768
2351
|
if (options.foreground) {
|
|
1769
2352
|
spinner.succeed(`Agent ${agentName} completed`);
|
|
1770
2353
|
} else {
|
|
@@ -1776,7 +2359,10 @@ CRITICAL: When you have completed your tasks OR reached the time limit:
|
|
|
1776
2359
|
}
|
|
1777
2360
|
} catch (error) {
|
|
1778
2361
|
spinner.fail(`Agent ${agentName} failed to launch`);
|
|
1779
|
-
updateExecutionStatus(squadName, agentName, "failed",
|
|
2362
|
+
updateExecutionStatus(squadName, agentName, executionId, "failed", {
|
|
2363
|
+
error: String(error),
|
|
2364
|
+
durationMs: Date.now() - startMs
|
|
2365
|
+
});
|
|
1780
2366
|
writeLine(` ${colors.red}${String(error)}${RESET}`);
|
|
1781
2367
|
}
|
|
1782
2368
|
} else {
|
|
@@ -1802,28 +2388,45 @@ async function checkClaudeCliAvailable() {
|
|
|
1802
2388
|
check.on("error", () => resolve(false));
|
|
1803
2389
|
});
|
|
1804
2390
|
}
|
|
1805
|
-
async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foreground, useApi) {
|
|
1806
|
-
const userConfigPath = join4(process.env.HOME || "", ".claude.json");
|
|
2391
|
+
async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foreground, useApi, effort, skills, trigger = "manual") {
|
|
1807
2392
|
const projectRoot = getProjectRoot();
|
|
1808
2393
|
ensureProjectTrusted(projectRoot);
|
|
1809
2394
|
const squadMatch = prompt2.match(/squad (\w+)/);
|
|
1810
2395
|
const agentMatch = prompt2.match(/(\w+) agent/);
|
|
1811
2396
|
const squadName = process.env.SQUADS_SQUAD || squadMatch?.[1] || "unknown";
|
|
1812
2397
|
const agentName = process.env.SQUADS_AGENT || agentMatch?.[1] || "unknown";
|
|
2398
|
+
const mcpConfigPath = selectMcpConfig(squadName);
|
|
2399
|
+
const execContext = {
|
|
2400
|
+
squad: squadName,
|
|
2401
|
+
agent: agentName,
|
|
2402
|
+
taskType: detectTaskType(agentName),
|
|
2403
|
+
trigger,
|
|
2404
|
+
executionId: generateExecutionId()
|
|
2405
|
+
};
|
|
1813
2406
|
const { ANTHROPIC_API_KEY: _apiKey, ...envWithoutApiKey } = process.env;
|
|
1814
2407
|
const spawnEnv = useApi ? process.env : envWithoutApiKey;
|
|
1815
2408
|
const escapedPrompt = prompt2.replace(/'/g, "'\\''");
|
|
2409
|
+
await registerContextWithBridge(execContext);
|
|
1816
2410
|
if (foreground) {
|
|
1817
2411
|
if (verbose) {
|
|
1818
2412
|
writeLine(` ${colors.dim}Project: ${projectRoot}${RESET}`);
|
|
1819
2413
|
writeLine(` ${colors.dim}Mode: foreground${RESET}`);
|
|
1820
2414
|
writeLine(` ${colors.dim}Auth: ${useApi ? "API credits" : "subscription"}${RESET}`);
|
|
2415
|
+
writeLine(` ${colors.dim}Execution: ${execContext.executionId}${RESET}`);
|
|
2416
|
+
writeLine(` ${colors.dim}Task type: ${execContext.taskType}${RESET}`);
|
|
2417
|
+
writeLine(` ${colors.dim}Trigger: ${execContext.trigger}${RESET}`);
|
|
2418
|
+
if (effort) {
|
|
2419
|
+
writeLine(` ${colors.dim}Effort: ${effort}${RESET}`);
|
|
2420
|
+
}
|
|
2421
|
+
if (skills && skills.length > 0) {
|
|
2422
|
+
writeLine(` ${colors.dim}Skills: ${skills.join(", ")}${RESET}`);
|
|
2423
|
+
}
|
|
1821
2424
|
}
|
|
1822
2425
|
return new Promise((resolve, reject) => {
|
|
1823
2426
|
const claude = spawn2("claude", [
|
|
1824
2427
|
"--dangerously-skip-permissions",
|
|
1825
2428
|
"--mcp-config",
|
|
1826
|
-
|
|
2429
|
+
mcpConfigPath,
|
|
1827
2430
|
"--",
|
|
1828
2431
|
prompt2
|
|
1829
2432
|
], {
|
|
@@ -1831,8 +2434,17 @@ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foregro
|
|
|
1831
2434
|
cwd: projectRoot,
|
|
1832
2435
|
env: {
|
|
1833
2436
|
...spawnEnv,
|
|
1834
|
-
|
|
1835
|
-
|
|
2437
|
+
// Telemetry context for per-agent cost tracking
|
|
2438
|
+
SQUADS_SQUAD: execContext.squad,
|
|
2439
|
+
SQUADS_AGENT: execContext.agent,
|
|
2440
|
+
SQUADS_TASK_TYPE: execContext.taskType,
|
|
2441
|
+
SQUADS_TRIGGER: execContext.trigger,
|
|
2442
|
+
SQUADS_EXECUTION_ID: execContext.executionId,
|
|
2443
|
+
// OTel resource attributes for telemetry pipeline
|
|
2444
|
+
OTEL_RESOURCE_ATTRIBUTES: `squads.squad=${execContext.squad},squads.agent=${execContext.agent},squads.task_type=${execContext.taskType},squads.trigger=${execContext.trigger},squads.execution_id=${execContext.executionId}`,
|
|
2445
|
+
// Claude-specific options
|
|
2446
|
+
...effort && { CLAUDE_EFFORT: effort },
|
|
2447
|
+
...skills && skills.length > 0 && { CLAUDE_SKILLS: skills.join(",") }
|
|
1836
2448
|
}
|
|
1837
2449
|
});
|
|
1838
2450
|
claude.on("close", (code) => {
|
|
@@ -1851,9 +2463,19 @@ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foregro
|
|
|
1851
2463
|
if (verbose) {
|
|
1852
2464
|
writeLine(` ${colors.dim}Project: ${projectRoot}${RESET}`);
|
|
1853
2465
|
writeLine(` ${colors.dim}Session: ${sessionName}${RESET}`);
|
|
2466
|
+
writeLine(` ${colors.dim}MCP config: ${mcpConfigPath}${RESET}`);
|
|
1854
2467
|
writeLine(` ${colors.dim}Auth: ${useApi ? "API credits" : "subscription"}${RESET}`);
|
|
2468
|
+
writeLine(` ${colors.dim}Execution: ${execContext.executionId}${RESET}`);
|
|
2469
|
+
writeLine(` ${colors.dim}Task type: ${execContext.taskType}${RESET}`);
|
|
2470
|
+
writeLine(` ${colors.dim}Trigger: ${execContext.trigger}${RESET}`);
|
|
2471
|
+
if (effort) {
|
|
2472
|
+
writeLine(` ${colors.dim}Effort: ${effort}${RESET}`);
|
|
2473
|
+
}
|
|
2474
|
+
if (skills && skills.length > 0) {
|
|
2475
|
+
writeLine(` ${colors.dim}Skills: ${skills.join(", ")}${RESET}`);
|
|
2476
|
+
}
|
|
1855
2477
|
}
|
|
1856
|
-
const claudeCmd = `cd '${projectRoot}' && claude --dangerously-skip-permissions --mcp-config '${
|
|
2478
|
+
const claudeCmd = `cd '${projectRoot}' && claude --dangerously-skip-permissions --mcp-config '${mcpConfigPath}' -- '${escapedPrompt}'; tmux kill-session -t ${sessionName} 2>/dev/null`;
|
|
1857
2479
|
const tmux = spawn2("tmux", [
|
|
1858
2480
|
"new-session",
|
|
1859
2481
|
"-d",
|
|
@@ -1873,8 +2495,17 @@ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foregro
|
|
|
1873
2495
|
detached: true,
|
|
1874
2496
|
env: {
|
|
1875
2497
|
...spawnEnv,
|
|
1876
|
-
|
|
1877
|
-
|
|
2498
|
+
// Telemetry context for per-agent cost tracking
|
|
2499
|
+
SQUADS_SQUAD: execContext.squad,
|
|
2500
|
+
SQUADS_AGENT: execContext.agent,
|
|
2501
|
+
SQUADS_TASK_TYPE: execContext.taskType,
|
|
2502
|
+
SQUADS_TRIGGER: execContext.trigger,
|
|
2503
|
+
SQUADS_EXECUTION_ID: execContext.executionId,
|
|
2504
|
+
// OTel resource attributes for telemetry pipeline
|
|
2505
|
+
OTEL_RESOURCE_ATTRIBUTES: `squads.squad=${execContext.squad},squads.agent=${execContext.agent},squads.task_type=${execContext.taskType},squads.trigger=${execContext.trigger},squads.execution_id=${execContext.executionId}`,
|
|
2506
|
+
// Claude-specific options
|
|
2507
|
+
...effort && { CLAUDE_EFFORT: effort },
|
|
2508
|
+
...skills && skills.length > 0 && { CLAUDE_SKILLS: skills.join(",") }
|
|
1878
2509
|
}
|
|
1879
2510
|
});
|
|
1880
2511
|
tmux.unref();
|
|
@@ -1937,158 +2568,6 @@ async function listCommand(options) {
|
|
|
1937
2568
|
// src/commands/status.ts
|
|
1938
2569
|
import { existsSync as existsSync6, statSync } from "fs";
|
|
1939
2570
|
import { join as join6 } from "path";
|
|
1940
|
-
|
|
1941
|
-
// src/lib/update.ts
|
|
1942
|
-
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, unlinkSync } from "fs";
|
|
1943
|
-
import { join as join5, dirname as dirname2 } from "path";
|
|
1944
|
-
import { homedir as homedir2 } from "os";
|
|
1945
|
-
import { execSync as execSync3 } from "child_process";
|
|
1946
|
-
import { fileURLToPath } from "url";
|
|
1947
|
-
function getPackageVersion() {
|
|
1948
|
-
try {
|
|
1949
|
-
const __filename3 = fileURLToPath(import.meta.url);
|
|
1950
|
-
const __dirname3 = dirname2(__filename3);
|
|
1951
|
-
const possiblePaths = [
|
|
1952
|
-
join5(__dirname3, "..", "..", "package.json"),
|
|
1953
|
-
// From dist/lib/
|
|
1954
|
-
join5(__dirname3, "..", "package.json"),
|
|
1955
|
-
// From dist/
|
|
1956
|
-
join5(__dirname3, "package.json")
|
|
1957
|
-
// Same dir
|
|
1958
|
-
];
|
|
1959
|
-
for (const pkgPath of possiblePaths) {
|
|
1960
|
-
if (existsSync5(pkgPath)) {
|
|
1961
|
-
const pkg2 = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
1962
|
-
return pkg2.version || "0.0.0";
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
} catch {
|
|
1966
|
-
}
|
|
1967
|
-
return "0.0.0";
|
|
1968
|
-
}
|
|
1969
|
-
var CURRENT_VERSION = getPackageVersion();
|
|
1970
|
-
var CACHE_DIR = join5(homedir2(), ".squads");
|
|
1971
|
-
var CACHE_FILE = join5(CACHE_DIR, "update-check.json");
|
|
1972
|
-
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1973
|
-
function isNewerVersion(v1, v2) {
|
|
1974
|
-
const parts1 = v1.replace(/^v/, "").split(".").map(Number);
|
|
1975
|
-
const parts2 = v2.replace(/^v/, "").split(".").map(Number);
|
|
1976
|
-
for (let i = 0; i < 3; i++) {
|
|
1977
|
-
const p1 = parts1[i] || 0;
|
|
1978
|
-
const p2 = parts2[i] || 0;
|
|
1979
|
-
if (p2 > p1) return true;
|
|
1980
|
-
if (p2 < p1) return false;
|
|
1981
|
-
}
|
|
1982
|
-
return false;
|
|
1983
|
-
}
|
|
1984
|
-
function readCache() {
|
|
1985
|
-
try {
|
|
1986
|
-
if (!existsSync5(CACHE_FILE)) return null;
|
|
1987
|
-
const data = JSON.parse(readFileSync4(CACHE_FILE, "utf-8"));
|
|
1988
|
-
return data;
|
|
1989
|
-
} catch {
|
|
1990
|
-
return null;
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
function writeCache(latestVersion) {
|
|
1994
|
-
try {
|
|
1995
|
-
if (!existsSync5(CACHE_DIR)) {
|
|
1996
|
-
mkdirSync3(CACHE_DIR, { recursive: true });
|
|
1997
|
-
}
|
|
1998
|
-
const cache = {
|
|
1999
|
-
latestVersion,
|
|
2000
|
-
checkedAt: Date.now()
|
|
2001
|
-
};
|
|
2002
|
-
writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
2003
|
-
} catch {
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
function fetchLatestVersion() {
|
|
2007
|
-
try {
|
|
2008
|
-
const result = execSync3("npm view squads-cli version 2>/dev/null", {
|
|
2009
|
-
encoding: "utf-8",
|
|
2010
|
-
timeout: 5e3
|
|
2011
|
-
}).trim();
|
|
2012
|
-
return result || null;
|
|
2013
|
-
} catch {
|
|
2014
|
-
return null;
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
function checkForUpdate() {
|
|
2018
|
-
const result = {
|
|
2019
|
-
currentVersion: CURRENT_VERSION,
|
|
2020
|
-
latestVersion: CURRENT_VERSION,
|
|
2021
|
-
updateAvailable: false
|
|
2022
|
-
};
|
|
2023
|
-
const cache = readCache();
|
|
2024
|
-
const now = Date.now();
|
|
2025
|
-
if (cache) {
|
|
2026
|
-
result.latestVersion = cache.latestVersion;
|
|
2027
|
-
result.updateAvailable = isNewerVersion(CURRENT_VERSION, cache.latestVersion);
|
|
2028
|
-
if (now - cache.checkedAt >= CACHE_TTL_MS) {
|
|
2029
|
-
triggerBackgroundRefresh();
|
|
2030
|
-
}
|
|
2031
|
-
return result;
|
|
2032
|
-
}
|
|
2033
|
-
triggerBackgroundRefresh();
|
|
2034
|
-
return result;
|
|
2035
|
-
}
|
|
2036
|
-
function triggerBackgroundRefresh() {
|
|
2037
|
-
try {
|
|
2038
|
-
const { spawn: spawn8 } = __require("child_process");
|
|
2039
|
-
const child = spawn8("npm", ["view", "squads-cli", "version"], {
|
|
2040
|
-
detached: true,
|
|
2041
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
2042
|
-
shell: true
|
|
2043
|
-
});
|
|
2044
|
-
let output = "";
|
|
2045
|
-
child.stdout?.on("data", (data) => {
|
|
2046
|
-
output += data.toString();
|
|
2047
|
-
});
|
|
2048
|
-
child.on("close", () => {
|
|
2049
|
-
const version2 = output.trim();
|
|
2050
|
-
if (version2 && /^\d+\.\d+\.\d+/.test(version2)) {
|
|
2051
|
-
writeCache(version2);
|
|
2052
|
-
}
|
|
2053
|
-
});
|
|
2054
|
-
child.unref();
|
|
2055
|
-
} catch {
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
function performUpdate() {
|
|
2059
|
-
try {
|
|
2060
|
-
execSync3("npm update -g squads-cli", {
|
|
2061
|
-
encoding: "utf-8",
|
|
2062
|
-
stdio: "inherit",
|
|
2063
|
-
timeout: 12e4
|
|
2064
|
-
// 2 minutes
|
|
2065
|
-
});
|
|
2066
|
-
try {
|
|
2067
|
-
unlinkSync(CACHE_FILE);
|
|
2068
|
-
} catch {
|
|
2069
|
-
}
|
|
2070
|
-
return { success: true };
|
|
2071
|
-
} catch (err) {
|
|
2072
|
-
return {
|
|
2073
|
-
success: false,
|
|
2074
|
-
error: err instanceof Error ? err.message : "Unknown error"
|
|
2075
|
-
};
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
function refreshVersionCache() {
|
|
2079
|
-
const latestVersion = fetchLatestVersion();
|
|
2080
|
-
if (latestVersion) {
|
|
2081
|
-
writeCache(latestVersion);
|
|
2082
|
-
return {
|
|
2083
|
-
currentVersion: CURRENT_VERSION,
|
|
2084
|
-
latestVersion,
|
|
2085
|
-
updateAvailable: isNewerVersion(CURRENT_VERSION, latestVersion)
|
|
2086
|
-
};
|
|
2087
|
-
}
|
|
2088
|
-
return checkForUpdate();
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
|
-
// src/commands/status.ts
|
|
2092
2571
|
async function statusCommand(squadName, options = {}) {
|
|
2093
2572
|
await track(Events.CLI_STATUS, { squad: squadName || "all", verbose: options.verbose });
|
|
2094
2573
|
const squadsDir = findSquadsDir();
|
|
@@ -4705,9 +5184,9 @@ function getPool() {
|
|
|
4705
5184
|
async function isDatabaseAvailable() {
|
|
4706
5185
|
try {
|
|
4707
5186
|
const pool2 = getPool();
|
|
4708
|
-
const
|
|
4709
|
-
await
|
|
4710
|
-
|
|
5187
|
+
const client2 = await pool2.connect();
|
|
5188
|
+
await client2.query("SELECT 1");
|
|
5189
|
+
client2.release();
|
|
4711
5190
|
return true;
|
|
4712
5191
|
} catch (err) {
|
|
4713
5192
|
if (process.env.DEBUG) {
|
|
@@ -4719,8 +5198,8 @@ async function isDatabaseAvailable() {
|
|
|
4719
5198
|
async function saveDashboardSnapshot(snapshot) {
|
|
4720
5199
|
try {
|
|
4721
5200
|
const pool2 = getPool();
|
|
4722
|
-
const
|
|
4723
|
-
const result = await
|
|
5201
|
+
const client2 = await pool2.connect();
|
|
5202
|
+
const result = await client2.query(`
|
|
4724
5203
|
INSERT INTO squads.dashboard_snapshots (
|
|
4725
5204
|
total_squads, total_commits, total_prs_merged, total_issues_closed, total_issues_open,
|
|
4726
5205
|
goal_progress_pct, cost_usd, daily_budget_usd, input_tokens, output_tokens,
|
|
@@ -4749,7 +5228,7 @@ async function saveDashboardSnapshot(snapshot) {
|
|
|
4749
5228
|
JSON.stringify(snapshot.authorsData),
|
|
4750
5229
|
JSON.stringify(snapshot.reposData)
|
|
4751
5230
|
]);
|
|
4752
|
-
|
|
5231
|
+
client2.release();
|
|
4753
5232
|
return result.rows[0]?.id ?? null;
|
|
4754
5233
|
} catch (err) {
|
|
4755
5234
|
if (process.env.DEBUG) {
|
|
@@ -4760,8 +5239,8 @@ async function saveDashboardSnapshot(snapshot) {
|
|
|
4760
5239
|
}
|
|
4761
5240
|
async function getDashboardHistory(limit = 30) {
|
|
4762
5241
|
try {
|
|
4763
|
-
const
|
|
4764
|
-
const result = await
|
|
5242
|
+
const client2 = await getPool().connect();
|
|
5243
|
+
const result = await client2.query(`
|
|
4765
5244
|
SELECT
|
|
4766
5245
|
total_squads, total_commits, total_prs_merged, total_issues_closed, total_issues_open,
|
|
4767
5246
|
goal_progress_pct, cost_usd, daily_budget_usd, input_tokens, output_tokens,
|
|
@@ -4771,7 +5250,7 @@ async function getDashboardHistory(limit = 30) {
|
|
|
4771
5250
|
ORDER BY captured_at DESC
|
|
4772
5251
|
LIMIT $1
|
|
4773
5252
|
`, [limit]);
|
|
4774
|
-
|
|
5253
|
+
client2.release();
|
|
4775
5254
|
return result.rows.map((row) => ({
|
|
4776
5255
|
totalSquads: row.total_squads,
|
|
4777
5256
|
totalCommits: row.total_commits,
|
|
@@ -5001,16 +5480,50 @@ async function dashboardCommand(options = {}) {
|
|
|
5001
5480
|
renderAcquisitionCached(cache);
|
|
5002
5481
|
renderHistoricalTrendsCached(cache);
|
|
5003
5482
|
renderInsightsCached(cache);
|
|
5483
|
+
if (gitStats && gitStats.recentCommits && gitStats.recentCommits.length > 0) {
|
|
5484
|
+
writeLine(` ${bold}Working On${RESET}`);
|
|
5485
|
+
writeLine();
|
|
5486
|
+
for (const commit of gitStats.recentCommits.slice(0, 3)) {
|
|
5487
|
+
const shortHash = commit.hash.slice(0, 7);
|
|
5488
|
+
const shortMsg = truncate(commit.message, 45);
|
|
5489
|
+
writeLine(` ${colors.dim}${shortHash}${RESET} ${shortMsg} ${colors.dim}(${commit.repo})${RESET}`);
|
|
5490
|
+
}
|
|
5491
|
+
writeLine();
|
|
5492
|
+
}
|
|
5004
5493
|
const allActiveGoals = squadData.flatMap(
|
|
5005
5494
|
(s) => s.goals.filter((g) => !g.completed).map((g) => ({ squad: s.name, goal: g }))
|
|
5006
5495
|
);
|
|
5007
5496
|
if (allActiveGoals.length > 0) {
|
|
5008
|
-
|
|
5497
|
+
const activeSquads2 = new Set(gitStats?.recentCommits?.map((c) => {
|
|
5498
|
+
const repoSquadMap = {
|
|
5499
|
+
"agents-squads-web": "website",
|
|
5500
|
+
"squads-cli": "cli",
|
|
5501
|
+
"hq": "engineering",
|
|
5502
|
+
"company": "company",
|
|
5503
|
+
"product": "product",
|
|
5504
|
+
"research": "research",
|
|
5505
|
+
"intelligence": "intelligence",
|
|
5506
|
+
"customer": "customer",
|
|
5507
|
+
"finance": "finance",
|
|
5508
|
+
"marketing": "marketing"
|
|
5509
|
+
};
|
|
5510
|
+
return repoSquadMap[c.repo] || c.repo;
|
|
5511
|
+
}) || []);
|
|
5512
|
+
const sortedGoals = [...allActiveGoals].sort((a, b) => {
|
|
5513
|
+
const aActive = activeSquads2.has(a.squad) ? 1 : 0;
|
|
5514
|
+
const bActive = activeSquads2.has(b.squad) ? 1 : 0;
|
|
5515
|
+
if (aActive !== bActive) return bActive - aActive;
|
|
5516
|
+
const aHasProgress = a.goal.progress ? 1 : 0;
|
|
5517
|
+
const bHasProgress = b.goal.progress ? 1 : 0;
|
|
5518
|
+
return bHasProgress - aHasProgress;
|
|
5519
|
+
});
|
|
5520
|
+
writeLine(` ${bold}Goals${RESET} ${colors.dim}(${allActiveGoals.length} active)${RESET}`);
|
|
5009
5521
|
writeLine();
|
|
5010
|
-
const maxGoals =
|
|
5011
|
-
for (const { squad, goal: goal2 } of
|
|
5522
|
+
const maxGoals = 3;
|
|
5523
|
+
for (const { squad, goal: goal2 } of sortedGoals.slice(0, maxGoals)) {
|
|
5012
5524
|
const hasProgress = goal2.progress && goal2.progress.length > 0;
|
|
5013
|
-
const
|
|
5525
|
+
const isActive = activeSquads2.has(squad);
|
|
5526
|
+
const icon = isActive ? icons.active : hasProgress ? icons.progress : icons.empty;
|
|
5014
5527
|
writeLine(` ${icon} ${colors.dim}${squad}${RESET} ${truncate(goal2.description, 48)}`);
|
|
5015
5528
|
if (hasProgress) {
|
|
5016
5529
|
writeLine(` ${colors.dim}\u2514${RESET} ${colors.green}${truncate(goal2.progress, 52)}${RESET}`);
|
|
@@ -6611,12 +7124,12 @@ function formatDuration(ms) {
|
|
|
6611
7124
|
}
|
|
6612
7125
|
function groupByDate(executions) {
|
|
6613
7126
|
const groups = /* @__PURE__ */ new Map();
|
|
6614
|
-
for (const
|
|
6615
|
-
const dateKey =
|
|
7127
|
+
for (const exec3 of executions) {
|
|
7128
|
+
const dateKey = exec3.startedAt.toISOString().split("T")[0];
|
|
6616
7129
|
if (!groups.has(dateKey)) {
|
|
6617
7130
|
groups.set(dateKey, []);
|
|
6618
7131
|
}
|
|
6619
|
-
groups.get(dateKey).push(
|
|
7132
|
+
groups.get(dateKey).push(exec3);
|
|
6620
7133
|
}
|
|
6621
7134
|
return groups;
|
|
6622
7135
|
}
|
|
@@ -6647,13 +7160,13 @@ async function historyCommand(options = {}) {
|
|
|
6647
7160
|
]);
|
|
6648
7161
|
const seenIds = /* @__PURE__ */ new Set();
|
|
6649
7162
|
const allExecutions = [];
|
|
6650
|
-
for (const
|
|
6651
|
-
seenIds.add(
|
|
6652
|
-
allExecutions.push(
|
|
7163
|
+
for (const exec3 of bridgeExecs) {
|
|
7164
|
+
seenIds.add(exec3.id);
|
|
7165
|
+
allExecutions.push(exec3);
|
|
6653
7166
|
}
|
|
6654
|
-
for (const
|
|
6655
|
-
if (!seenIds.has(
|
|
6656
|
-
allExecutions.push(
|
|
7167
|
+
for (const exec3 of localExecs) {
|
|
7168
|
+
if (!seenIds.has(exec3.id)) {
|
|
7169
|
+
allExecutions.push(exec3);
|
|
6657
7170
|
}
|
|
6658
7171
|
}
|
|
6659
7172
|
allExecutions.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
|
|
@@ -6677,14 +7190,14 @@ async function historyCommand(options = {}) {
|
|
|
6677
7190
|
writeLine(` ${colors.purple}\u250C${"\u2500".repeat(60)}\u2510${RESET}`);
|
|
6678
7191
|
writeLine(` ${colors.purple}\u2502${RESET} ${padEnd("TIME", 7)}${padEnd("SQUAD", 13)}${padEnd("AGENT", 16)}${padEnd("DURATION", 10)}${padEnd("STATUS", 8)}${colors.purple}\u2502${RESET}`);
|
|
6679
7192
|
writeLine(` ${colors.purple}\u251C${"\u2500".repeat(60)}\u2524${RESET}`);
|
|
6680
|
-
for (const
|
|
6681
|
-
const time =
|
|
6682
|
-
const squadName = truncate(
|
|
6683
|
-
const agentName = truncate(
|
|
6684
|
-
const duration = formatDuration(
|
|
7193
|
+
for (const exec3 of execs) {
|
|
7194
|
+
const time = exec3.startedAt.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
7195
|
+
const squadName = truncate(exec3.squad, 11);
|
|
7196
|
+
const agentName = truncate(exec3.agent, 14);
|
|
7197
|
+
const duration = formatDuration(exec3.durationMs);
|
|
6685
7198
|
let statusIcon;
|
|
6686
7199
|
let statusColor;
|
|
6687
|
-
switch (
|
|
7200
|
+
switch (exec3.status) {
|
|
6688
7201
|
case "success":
|
|
6689
7202
|
statusIcon = icons.success;
|
|
6690
7203
|
statusColor = colors.green;
|
|
@@ -6702,14 +7215,14 @@ async function historyCommand(options = {}) {
|
|
|
6702
7215
|
statusColor = colors.dim;
|
|
6703
7216
|
}
|
|
6704
7217
|
writeLine(` ${colors.purple}\u2502${RESET} ${colors.dim}${time}${RESET} ${colors.cyan}${padEnd(squadName, 12)}${RESET}${padEnd(agentName, 16)}${padEnd(duration, 10)}${statusColor}${statusIcon}${RESET} ${colors.purple}\u2502${RESET}`);
|
|
6705
|
-
if (verbose && (
|
|
6706
|
-
const costStr =
|
|
6707
|
-
const tokenStr =
|
|
7218
|
+
if (verbose && (exec3.cost || exec3.tokens)) {
|
|
7219
|
+
const costStr = exec3.cost ? `$${exec3.cost.toFixed(2)}` : "";
|
|
7220
|
+
const tokenStr = exec3.tokens ? `${exec3.tokens.toLocaleString()} tokens` : "";
|
|
6708
7221
|
const details = [costStr, tokenStr].filter(Boolean).join(" \u2502 ");
|
|
6709
7222
|
writeLine(` ${colors.purple}\u2502${RESET} ${colors.dim}\u2514 ${details}${RESET}${" ".repeat(Math.max(0, 45 - details.length))}${colors.purple}\u2502${RESET}`);
|
|
6710
7223
|
}
|
|
6711
|
-
if (
|
|
6712
|
-
writeLine(` ${colors.purple}\u2502${RESET} ${colors.red}\u2514 ${truncate(
|
|
7224
|
+
if (exec3.error) {
|
|
7225
|
+
writeLine(` ${colors.purple}\u2502${RESET} ${colors.red}\u2514 ${truncate(exec3.error, 45)}${RESET}${" ".repeat(Math.max(0, 45 - exec3.error.length))}${colors.purple}\u2502${RESET}`);
|
|
6713
7226
|
}
|
|
6714
7227
|
}
|
|
6715
7228
|
writeLine(` ${colors.purple}\u2514${"\u2500".repeat(60)}\u2518${RESET}`);
|
|
@@ -7959,15 +8472,1305 @@ function registerTriggerCommand(program2) {
|
|
|
7959
8472
|
});
|
|
7960
8473
|
}
|
|
7961
8474
|
|
|
7962
|
-
// src/commands/
|
|
8475
|
+
// src/commands/skill.ts
|
|
7963
8476
|
import ora6 from "ora";
|
|
8477
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync14 } from "fs";
|
|
8478
|
+
import { join as join18, basename as basename2, dirname as dirname5 } from "path";
|
|
8479
|
+
|
|
8480
|
+
// src/lib/anthropic.ts
|
|
8481
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
8482
|
+
import { readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
|
|
8483
|
+
import { join as join17 } from "path";
|
|
8484
|
+
var client = null;
|
|
8485
|
+
function getClient() {
|
|
8486
|
+
if (!client) {
|
|
8487
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
8488
|
+
if (!apiKey) {
|
|
8489
|
+
throw new Error("ANTHROPIC_API_KEY environment variable is required for skills management");
|
|
8490
|
+
}
|
|
8491
|
+
client = new Anthropic({ apiKey });
|
|
8492
|
+
}
|
|
8493
|
+
return client;
|
|
8494
|
+
}
|
|
8495
|
+
async function listSkills() {
|
|
8496
|
+
getClient();
|
|
8497
|
+
try {
|
|
8498
|
+
console.warn("Skills API: Using placeholder implementation. Actual API may differ.");
|
|
8499
|
+
return [];
|
|
8500
|
+
} catch (error) {
|
|
8501
|
+
if (error instanceof Error && error.message.includes("404")) {
|
|
8502
|
+
return [];
|
|
8503
|
+
}
|
|
8504
|
+
throw error;
|
|
8505
|
+
}
|
|
8506
|
+
}
|
|
8507
|
+
function loadSkillFiles(skillPath) {
|
|
8508
|
+
const files = [];
|
|
8509
|
+
function walkDir(dir, prefix = "") {
|
|
8510
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
8511
|
+
for (const entry of entries) {
|
|
8512
|
+
const fullPath = join17(dir, entry.name);
|
|
8513
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
8514
|
+
if (entry.isDirectory()) {
|
|
8515
|
+
walkDir(fullPath, relativePath);
|
|
8516
|
+
} else if (entry.isFile()) {
|
|
8517
|
+
const content = readFileSync13(fullPath, "utf-8");
|
|
8518
|
+
files.push({
|
|
8519
|
+
name: relativePath,
|
|
8520
|
+
content
|
|
8521
|
+
});
|
|
8522
|
+
}
|
|
8523
|
+
}
|
|
8524
|
+
}
|
|
8525
|
+
walkDir(skillPath);
|
|
8526
|
+
return files;
|
|
8527
|
+
}
|
|
8528
|
+
function extractSkillTitle(files) {
|
|
8529
|
+
const skillMd = files.find((f) => f.name === "SKILL.md" || f.name.endsWith("/SKILL.md"));
|
|
8530
|
+
if (!skillMd) {
|
|
8531
|
+
throw new Error("SKILL.md not found in skill directory");
|
|
8532
|
+
}
|
|
8533
|
+
const content = skillMd.content;
|
|
8534
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
8535
|
+
if (frontmatterMatch) {
|
|
8536
|
+
const nameMatch = frontmatterMatch[1].match(/^name:\s*(.+)$/m);
|
|
8537
|
+
if (nameMatch) {
|
|
8538
|
+
return nameMatch[1].trim();
|
|
8539
|
+
}
|
|
8540
|
+
}
|
|
8541
|
+
const headingMatch = content.match(/^#\s+(.+)$/m);
|
|
8542
|
+
if (headingMatch) {
|
|
8543
|
+
return headingMatch[1].trim();
|
|
8544
|
+
}
|
|
8545
|
+
throw new Error("Could not extract skill title from SKILL.md");
|
|
8546
|
+
}
|
|
8547
|
+
function extractSkillDescription(files) {
|
|
8548
|
+
const skillMd = files.find((f) => f.name === "SKILL.md" || f.name.endsWith("/SKILL.md"));
|
|
8549
|
+
if (!skillMd) return void 0;
|
|
8550
|
+
const content = skillMd.content;
|
|
8551
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
8552
|
+
if (frontmatterMatch) {
|
|
8553
|
+
const descMatch = frontmatterMatch[1].match(/^description:\s*(.+)$/m);
|
|
8554
|
+
if (descMatch) {
|
|
8555
|
+
return descMatch[1].trim();
|
|
8556
|
+
}
|
|
8557
|
+
}
|
|
8558
|
+
const paragraphMatch = content.match(/^#.+\n+(.+)/m);
|
|
8559
|
+
if (paragraphMatch) {
|
|
8560
|
+
return paragraphMatch[1].trim();
|
|
8561
|
+
}
|
|
8562
|
+
return void 0;
|
|
8563
|
+
}
|
|
8564
|
+
async function uploadSkill(skillPath) {
|
|
8565
|
+
getClient();
|
|
8566
|
+
const files = loadSkillFiles(skillPath);
|
|
8567
|
+
if (files.length === 0) {
|
|
8568
|
+
throw new Error(`No files found in skill directory: ${skillPath}`);
|
|
8569
|
+
}
|
|
8570
|
+
const totalSize = files.reduce((sum, f) => sum + Buffer.byteLength(f.content, "utf-8"), 0);
|
|
8571
|
+
const maxSize = 8 * 1024 * 1024;
|
|
8572
|
+
if (totalSize > maxSize) {
|
|
8573
|
+
throw new Error(`Skill size ${(totalSize / 1024 / 1024).toFixed(2)}MB exceeds 8MB limit`);
|
|
8574
|
+
}
|
|
8575
|
+
const displayTitle = extractSkillTitle(files);
|
|
8576
|
+
const description = extractSkillDescription(files);
|
|
8577
|
+
try {
|
|
8578
|
+
console.log(`Uploading skill: ${displayTitle}`);
|
|
8579
|
+
console.log(`Files: ${files.map((f) => f.name).join(", ")}`);
|
|
8580
|
+
console.log(`Total size: ${(totalSize / 1024).toFixed(2)}KB`);
|
|
8581
|
+
const skill = {
|
|
8582
|
+
id: `skill_${Date.now()}`,
|
|
8583
|
+
display_title: displayTitle,
|
|
8584
|
+
description,
|
|
8585
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8586
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8587
|
+
files
|
|
8588
|
+
};
|
|
8589
|
+
console.warn("Skills API: Using placeholder implementation. Skill not actually uploaded.");
|
|
8590
|
+
return skill;
|
|
8591
|
+
} catch (error) {
|
|
8592
|
+
throw new Error(`Failed to upload skill: ${error instanceof Error ? error.message : String(error)}`);
|
|
8593
|
+
}
|
|
8594
|
+
}
|
|
8595
|
+
async function deleteSkill(skillId) {
|
|
8596
|
+
getClient();
|
|
8597
|
+
try {
|
|
8598
|
+
console.log(`Deleting skill: ${skillId}`);
|
|
8599
|
+
console.warn("Skills API: Using placeholder implementation. Skill not actually deleted.");
|
|
8600
|
+
} catch (error) {
|
|
8601
|
+
throw new Error(`Failed to delete skill: ${error instanceof Error ? error.message : String(error)}`);
|
|
8602
|
+
}
|
|
8603
|
+
}
|
|
8604
|
+
async function getSkill(skillId) {
|
|
8605
|
+
getClient();
|
|
8606
|
+
try {
|
|
8607
|
+
console.log(`Getting skill: ${skillId}`);
|
|
8608
|
+
console.warn("Skills API: Using placeholder implementation.");
|
|
8609
|
+
return null;
|
|
8610
|
+
} catch (error) {
|
|
8611
|
+
if (error instanceof Error && error.message.includes("404")) {
|
|
8612
|
+
return null;
|
|
8613
|
+
}
|
|
8614
|
+
throw error;
|
|
8615
|
+
}
|
|
8616
|
+
}
|
|
8617
|
+
function isApiKeyConfigured() {
|
|
8618
|
+
return !!process.env.ANTHROPIC_API_KEY;
|
|
8619
|
+
}
|
|
8620
|
+
|
|
8621
|
+
// src/commands/skill.ts
|
|
8622
|
+
function registerSkillCommand(program2) {
|
|
8623
|
+
const skill = program2.command("skill").description("Manage Agent Skills (upload, list, delete)");
|
|
8624
|
+
skill.command("list").description("List uploaded skills from Anthropic API").action(skillListCommand);
|
|
8625
|
+
skill.command("upload <path>").description("Upload a skill directory to Anthropic API").action(skillUploadCommand);
|
|
8626
|
+
skill.command("delete <skillId>").description("Delete a skill from Anthropic API").option("-f, --force", "Skip confirmation prompt").action(skillDeleteCommand);
|
|
8627
|
+
skill.command("show <skillId>").description("Show details for a skill").action(skillShowCommand);
|
|
8628
|
+
skill.command("convert <agent>").description("Convert an agent.md file to SKILL.md format").option("-o, --output <path>", "Output directory (default: .agents/skills/<agent-name>)").action(skillConvertCommand);
|
|
8629
|
+
}
|
|
8630
|
+
async function skillListCommand() {
|
|
8631
|
+
writeLine();
|
|
8632
|
+
writeLine(` ${gradient("Skills")} ${colors.dim}list${RESET}`);
|
|
8633
|
+
writeLine();
|
|
8634
|
+
if (!isApiKeyConfigured()) {
|
|
8635
|
+
writeLine(` ${icons.error} ${colors.red}ANTHROPIC_API_KEY not configured${RESET}`);
|
|
8636
|
+
writeLine(` ${colors.dim}Set the environment variable to use skills management.${RESET}`);
|
|
8637
|
+
writeLine();
|
|
8638
|
+
return;
|
|
8639
|
+
}
|
|
8640
|
+
const spinner = ora6("Fetching skills...").start();
|
|
8641
|
+
try {
|
|
8642
|
+
const skills = await listSkills();
|
|
8643
|
+
if (skills.length === 0) {
|
|
8644
|
+
spinner.info("No skills uploaded yet");
|
|
8645
|
+
writeLine();
|
|
8646
|
+
writeLine(` ${colors.dim}Upload a skill:${RESET}`);
|
|
8647
|
+
writeLine(` ${colors.cyan}squads skill upload <path>${RESET}`);
|
|
8648
|
+
writeLine();
|
|
8649
|
+
writeLine(` ${colors.dim}Convert an agent to skill format:${RESET}`);
|
|
8650
|
+
writeLine(` ${colors.cyan}squads skill convert <squad>/<agent>${RESET}`);
|
|
8651
|
+
writeLine();
|
|
8652
|
+
return;
|
|
8653
|
+
}
|
|
8654
|
+
spinner.succeed(`Found ${skills.length} skill(s)`);
|
|
8655
|
+
writeLine();
|
|
8656
|
+
writeLine(` ${colors.dim}ID${RESET} ${colors.dim}Title${RESET} ${colors.dim}Updated${RESET}`);
|
|
8657
|
+
writeLine(` ${colors.dim}${"\u2500".repeat(60)}${RESET}`);
|
|
8658
|
+
for (const skill of skills) {
|
|
8659
|
+
const id = skill.id.slice(0, 20).padEnd(22);
|
|
8660
|
+
const title = (skill.display_title || "Untitled").slice(0, 20).padEnd(22);
|
|
8661
|
+
const updated = new Date(skill.updated_at).toLocaleDateString();
|
|
8662
|
+
writeLine(` ${colors.cyan}${id}${RESET}${title}${colors.dim}${updated}${RESET}`);
|
|
8663
|
+
}
|
|
8664
|
+
writeLine();
|
|
8665
|
+
} catch (error) {
|
|
8666
|
+
spinner.fail("Failed to list skills");
|
|
8667
|
+
writeLine(` ${colors.red}${error instanceof Error ? error.message : String(error)}${RESET}`);
|
|
8668
|
+
writeLine();
|
|
8669
|
+
}
|
|
8670
|
+
}
|
|
8671
|
+
async function skillUploadCommand(skillPath) {
|
|
8672
|
+
writeLine();
|
|
8673
|
+
writeLine(` ${gradient("Skills")} ${colors.dim}upload${RESET}`);
|
|
8674
|
+
writeLine();
|
|
8675
|
+
if (!isApiKeyConfigured()) {
|
|
8676
|
+
writeLine(` ${icons.error} ${colors.red}ANTHROPIC_API_KEY not configured${RESET}`);
|
|
8677
|
+
writeLine(` ${colors.dim}Set the environment variable to use skills management.${RESET}`);
|
|
8678
|
+
writeLine();
|
|
8679
|
+
return;
|
|
8680
|
+
}
|
|
8681
|
+
const fullPath = skillPath.startsWith("/") ? skillPath : join18(process.cwd(), skillPath);
|
|
8682
|
+
if (!existsSync17(fullPath)) {
|
|
8683
|
+
writeLine(` ${icons.error} ${colors.red}Directory not found: ${skillPath}${RESET}`);
|
|
8684
|
+
writeLine();
|
|
8685
|
+
return;
|
|
8686
|
+
}
|
|
8687
|
+
const skillMdPath = join18(fullPath, "SKILL.md");
|
|
8688
|
+
if (!existsSync17(skillMdPath)) {
|
|
8689
|
+
writeLine(` ${icons.error} ${colors.red}SKILL.md not found in ${skillPath}${RESET}`);
|
|
8690
|
+
writeLine();
|
|
8691
|
+
writeLine(` ${colors.dim}Create a SKILL.md file or use:${RESET}`);
|
|
8692
|
+
writeLine(` ${colors.cyan}squads skill convert <squad>/<agent>${RESET}`);
|
|
8693
|
+
writeLine();
|
|
8694
|
+
return;
|
|
8695
|
+
}
|
|
8696
|
+
const spinner = ora6("Uploading skill...").start();
|
|
8697
|
+
try {
|
|
8698
|
+
const skill = await uploadSkill(fullPath);
|
|
8699
|
+
spinner.succeed(`Skill uploaded: ${skill.display_title}`);
|
|
8700
|
+
writeLine();
|
|
8701
|
+
writeLine(` ${colors.dim}ID:${RESET} ${colors.cyan}${skill.id}${RESET}`);
|
|
8702
|
+
if (skill.description) {
|
|
8703
|
+
writeLine(` ${colors.dim}Description:${RESET} ${skill.description}`);
|
|
8704
|
+
}
|
|
8705
|
+
writeLine();
|
|
8706
|
+
writeLine(` ${colors.dim}Use in run command:${RESET}`);
|
|
8707
|
+
writeLine(` ${colors.cyan}squads run <squad> --skills ${skill.id}${RESET}`);
|
|
8708
|
+
writeLine();
|
|
8709
|
+
} catch (error) {
|
|
8710
|
+
spinner.fail("Failed to upload skill");
|
|
8711
|
+
writeLine(` ${colors.red}${error instanceof Error ? error.message : String(error)}${RESET}`);
|
|
8712
|
+
writeLine();
|
|
8713
|
+
}
|
|
8714
|
+
}
|
|
8715
|
+
async function skillDeleteCommand(skillId, options) {
|
|
8716
|
+
writeLine();
|
|
8717
|
+
writeLine(` ${gradient("Skills")} ${colors.dim}delete${RESET}`);
|
|
8718
|
+
writeLine();
|
|
8719
|
+
if (!isApiKeyConfigured()) {
|
|
8720
|
+
writeLine(` ${icons.error} ${colors.red}ANTHROPIC_API_KEY not configured${RESET}`);
|
|
8721
|
+
writeLine();
|
|
8722
|
+
return;
|
|
8723
|
+
}
|
|
8724
|
+
if (!options.force) {
|
|
8725
|
+
writeLine(` ${colors.yellow}Warning: This will permanently delete the skill.${RESET}`);
|
|
8726
|
+
writeLine(` ${colors.dim}Use --force to skip this confirmation.${RESET}`);
|
|
8727
|
+
writeLine();
|
|
8728
|
+
return;
|
|
8729
|
+
}
|
|
8730
|
+
const spinner = ora6("Deleting skill...").start();
|
|
8731
|
+
try {
|
|
8732
|
+
await deleteSkill(skillId);
|
|
8733
|
+
spinner.succeed(`Skill deleted: ${skillId}`);
|
|
8734
|
+
writeLine();
|
|
8735
|
+
} catch (error) {
|
|
8736
|
+
spinner.fail("Failed to delete skill");
|
|
8737
|
+
writeLine(` ${colors.red}${error instanceof Error ? error.message : String(error)}${RESET}`);
|
|
8738
|
+
writeLine();
|
|
8739
|
+
}
|
|
8740
|
+
}
|
|
8741
|
+
async function skillShowCommand(skillId) {
|
|
8742
|
+
writeLine();
|
|
8743
|
+
writeLine(` ${gradient("Skills")} ${colors.dim}show${RESET}`);
|
|
8744
|
+
writeLine();
|
|
8745
|
+
if (!isApiKeyConfigured()) {
|
|
8746
|
+
writeLine(` ${icons.error} ${colors.red}ANTHROPIC_API_KEY not configured${RESET}`);
|
|
8747
|
+
writeLine();
|
|
8748
|
+
return;
|
|
8749
|
+
}
|
|
8750
|
+
const spinner = ora6("Fetching skill...").start();
|
|
8751
|
+
try {
|
|
8752
|
+
const skill = await getSkill(skillId);
|
|
8753
|
+
if (!skill) {
|
|
8754
|
+
spinner.fail(`Skill not found: ${skillId}`);
|
|
8755
|
+
writeLine();
|
|
8756
|
+
return;
|
|
8757
|
+
}
|
|
8758
|
+
spinner.succeed(`Found skill: ${skill.display_title}`);
|
|
8759
|
+
writeLine();
|
|
8760
|
+
writeLine(` ${colors.dim}ID:${RESET} ${colors.cyan}${skill.id}${RESET}`);
|
|
8761
|
+
writeLine(` ${colors.dim}Title:${RESET} ${skill.display_title}`);
|
|
8762
|
+
if (skill.description) {
|
|
8763
|
+
writeLine(` ${colors.dim}Description:${RESET} ${skill.description}`);
|
|
8764
|
+
}
|
|
8765
|
+
writeLine(` ${colors.dim}Created:${RESET} ${new Date(skill.created_at).toLocaleString()}`);
|
|
8766
|
+
writeLine(` ${colors.dim}Updated:${RESET} ${new Date(skill.updated_at).toLocaleString()}`);
|
|
8767
|
+
if (skill.files && skill.files.length > 0) {
|
|
8768
|
+
writeLine();
|
|
8769
|
+
writeLine(` ${colors.dim}Files:${RESET}`);
|
|
8770
|
+
for (const file of skill.files) {
|
|
8771
|
+
const size = Buffer.byteLength(file.content, "utf-8");
|
|
8772
|
+
writeLine(` ${colors.cyan}${file.name}${RESET} ${colors.dim}(${formatBytes(size)})${RESET}`);
|
|
8773
|
+
}
|
|
8774
|
+
}
|
|
8775
|
+
writeLine();
|
|
8776
|
+
} catch (error) {
|
|
8777
|
+
spinner.fail("Failed to fetch skill");
|
|
8778
|
+
writeLine(` ${colors.red}${error instanceof Error ? error.message : String(error)}${RESET}`);
|
|
8779
|
+
writeLine();
|
|
8780
|
+
}
|
|
8781
|
+
}
|
|
8782
|
+
async function skillConvertCommand(agentPath, options) {
|
|
8783
|
+
writeLine();
|
|
8784
|
+
writeLine(` ${gradient("Skills")} ${colors.dim}convert${RESET}`);
|
|
8785
|
+
writeLine();
|
|
8786
|
+
const squadsDir = findSquadsDir();
|
|
8787
|
+
if (!squadsDir) {
|
|
8788
|
+
writeLine(` ${icons.error} ${colors.red}No .agents/squads directory found${RESET}`);
|
|
8789
|
+
writeLine();
|
|
8790
|
+
return;
|
|
8791
|
+
}
|
|
8792
|
+
let agentFilePath;
|
|
8793
|
+
let squadName;
|
|
8794
|
+
let agentName;
|
|
8795
|
+
if (agentPath.includes("/")) {
|
|
8796
|
+
const [squad, agent] = agentPath.split("/");
|
|
8797
|
+
squadName = squad;
|
|
8798
|
+
agentName = agent.replace(".md", "");
|
|
8799
|
+
agentFilePath = join18(squadsDir, squad, `${agentName}.md`);
|
|
8800
|
+
} else {
|
|
8801
|
+
agentName = agentPath.replace(".md", "");
|
|
8802
|
+
const foundPath = findAgentFile(squadsDir, agentName);
|
|
8803
|
+
if (!foundPath) {
|
|
8804
|
+
writeLine(` ${icons.error} ${colors.red}Agent not found: ${agentPath}${RESET}`);
|
|
8805
|
+
writeLine(` ${colors.dim}Use format: squad/agent (e.g., cli/code-eval)${RESET}`);
|
|
8806
|
+
writeLine();
|
|
8807
|
+
return;
|
|
8808
|
+
}
|
|
8809
|
+
agentFilePath = foundPath;
|
|
8810
|
+
squadName = basename2(dirname5(agentFilePath));
|
|
8811
|
+
}
|
|
8812
|
+
if (!existsSync17(agentFilePath)) {
|
|
8813
|
+
writeLine(` ${icons.error} ${colors.red}Agent file not found: ${agentFilePath}${RESET}`);
|
|
8814
|
+
writeLine();
|
|
8815
|
+
return;
|
|
8816
|
+
}
|
|
8817
|
+
const agentContent = readFileSync14(agentFilePath, "utf-8");
|
|
8818
|
+
const skillName = `${squadName}-${agentName}`;
|
|
8819
|
+
const outputDir = options.output || join18(dirname5(squadsDir), "skills", skillName);
|
|
8820
|
+
if (!existsSync17(outputDir)) {
|
|
8821
|
+
mkdirSync9(outputDir, { recursive: true });
|
|
8822
|
+
}
|
|
8823
|
+
const skillMd = convertAgentToSkill(agentContent, squadName, agentName);
|
|
8824
|
+
const skillMdPath = join18(outputDir, "SKILL.md");
|
|
8825
|
+
writeFileSync10(skillMdPath, skillMd);
|
|
8826
|
+
writeLine(` ${icons.success} ${colors.green}Converted:${RESET} ${agentPath}`);
|
|
8827
|
+
writeLine();
|
|
8828
|
+
writeLine(` ${colors.dim}Output:${RESET} ${outputDir}`);
|
|
8829
|
+
writeLine(` ${colors.dim}Files:${RESET}`);
|
|
8830
|
+
writeLine(` ${colors.cyan}SKILL.md${RESET}`);
|
|
8831
|
+
writeLine();
|
|
8832
|
+
writeLine(` ${colors.dim}Next steps:${RESET}`);
|
|
8833
|
+
writeLine(` 1. Review and edit ${colors.cyan}SKILL.md${RESET}`);
|
|
8834
|
+
writeLine(` 2. Add scripts or examples if needed`);
|
|
8835
|
+
writeLine(` 3. Upload: ${colors.cyan}squads skill upload ${outputDir}${RESET}`);
|
|
8836
|
+
writeLine();
|
|
8837
|
+
}
|
|
8838
|
+
function findAgentFile(squadsDir, agentName) {
|
|
8839
|
+
const { readdirSync: readdirSync9 } = __require("fs");
|
|
8840
|
+
const squads = readdirSync9(squadsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("_")).map((d) => d.name);
|
|
8841
|
+
for (const squad of squads) {
|
|
8842
|
+
const agentPath = join18(squadsDir, squad, `${agentName}.md`);
|
|
8843
|
+
if (existsSync17(agentPath)) {
|
|
8844
|
+
return agentPath;
|
|
8845
|
+
}
|
|
8846
|
+
}
|
|
8847
|
+
return null;
|
|
8848
|
+
}
|
|
8849
|
+
function convertAgentToSkill(agentContent, squadName, agentName) {
|
|
8850
|
+
const existingMeta = {};
|
|
8851
|
+
let bodyContent = agentContent;
|
|
8852
|
+
const frontmatterMatch = agentContent.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)/);
|
|
8853
|
+
if (frontmatterMatch) {
|
|
8854
|
+
const frontmatter = frontmatterMatch[1];
|
|
8855
|
+
bodyContent = frontmatterMatch[2];
|
|
8856
|
+
for (const line of frontmatter.split("\n")) {
|
|
8857
|
+
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
8858
|
+
if (match) {
|
|
8859
|
+
existingMeta[match[1]] = match[2].trim();
|
|
8860
|
+
}
|
|
8861
|
+
}
|
|
8862
|
+
}
|
|
8863
|
+
const title = existingMeta.name || bodyContent.match(/^#\s+(?:Agent:\s*)?(.+)$/m)?.[1] || `${squadName}-${agentName}`;
|
|
8864
|
+
const roleMatch = bodyContent.match(/^(?:role|description):\s*(.+)$/mi) || bodyContent.match(/^##?\s*(?:Role|Purpose|Description)\s*\n+(.+)/mi);
|
|
8865
|
+
const description = existingMeta.description || roleMatch?.[1] || `Agent from ${squadName} squad. Use when working on ${agentName}-related tasks.`;
|
|
8866
|
+
const skillMd = `---
|
|
8867
|
+
name: ${squadName}-${agentName}
|
|
8868
|
+
description: ${description}
|
|
8869
|
+
---
|
|
8870
|
+
|
|
8871
|
+
# ${title}
|
|
8872
|
+
|
|
8873
|
+
${bodyContent.trim()}
|
|
8874
|
+
`;
|
|
8875
|
+
return skillMd;
|
|
8876
|
+
}
|
|
8877
|
+
function formatBytes(bytes) {
|
|
8878
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
8879
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
8880
|
+
return `${(bytes / 1024 / 1024).toFixed(2)}MB`;
|
|
8881
|
+
}
|
|
8882
|
+
|
|
8883
|
+
// src/commands/permissions.ts
|
|
8884
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
8885
|
+
import { join as join19 } from "path";
|
|
8886
|
+
function registerPermissionsCommand(program2) {
|
|
8887
|
+
const permissions = program2.command("permissions").alias("perms").description("Manage and validate squad permissions");
|
|
8888
|
+
permissions.command("show <squad>").description("Show permission context for a squad").action(permissionsShowCommand);
|
|
8889
|
+
permissions.command("check <squad>").description("Validate permissions before execution").option("-a, --agent <agent>", "Specify agent for context").option("--mcp <servers...>", "MCP servers to validate").option("--bash <commands...>", "Bash commands to validate").option("--write <paths...>", "Write paths to validate").option("--read <paths...>", "Read paths to validate").action(permissionsCheckCommand);
|
|
8890
|
+
}
|
|
8891
|
+
async function permissionsShowCommand(squadName) {
|
|
8892
|
+
writeLine();
|
|
8893
|
+
writeLine(` ${gradient("Permissions")} ${colors.dim}show${RESET} ${colors.cyan}${squadName}${RESET}`);
|
|
8894
|
+
writeLine();
|
|
8895
|
+
const squadsDir = findSquadsDir();
|
|
8896
|
+
if (!squadsDir) {
|
|
8897
|
+
writeLine(` ${icons.error} ${colors.red}No .agents/squads directory found${RESET}`);
|
|
8898
|
+
writeLine();
|
|
8899
|
+
return;
|
|
8900
|
+
}
|
|
8901
|
+
const squad = loadSquad(squadName);
|
|
8902
|
+
if (!squad) {
|
|
8903
|
+
writeLine(` ${icons.error} ${colors.red}Squad "${squadName}" not found${RESET}`);
|
|
8904
|
+
writeLine();
|
|
8905
|
+
return;
|
|
8906
|
+
}
|
|
8907
|
+
const squadFilePath = join19(squadsDir, squadName, "SQUAD.md");
|
|
8908
|
+
const squadContent = readFileSync15(squadFilePath, "utf-8");
|
|
8909
|
+
const context2 = buildContextFromSquad(squadName, squadContent);
|
|
8910
|
+
const defaults = getDefaultContext(squadName);
|
|
8911
|
+
const isDefault = JSON.stringify(context2.permissions) === JSON.stringify(defaults.permissions);
|
|
8912
|
+
writeLine(` ${bold}Squad:${RESET} ${squadName}`);
|
|
8913
|
+
if (squad.mission) {
|
|
8914
|
+
writeLine(` ${colors.dim}${squad.mission}${RESET}`);
|
|
8915
|
+
}
|
|
8916
|
+
writeLine();
|
|
8917
|
+
if (isDefault) {
|
|
8918
|
+
writeLine(` ${icons.warning} ${colors.yellow}Using default (permissive) permissions${RESET}`);
|
|
8919
|
+
writeLine(` ${colors.dim}Add a permissions block to SQUAD.md to restrict access.${RESET}`);
|
|
8920
|
+
writeLine();
|
|
8921
|
+
}
|
|
8922
|
+
writeLine(` ${bold}Mode:${RESET} ${formatMode(context2.permissions.mode)}`);
|
|
8923
|
+
writeLine();
|
|
8924
|
+
writeLine(` ${bold}Bash Commands:${RESET}`);
|
|
8925
|
+
if (context2.permissions.bash.includes("*")) {
|
|
8926
|
+
writeLine(` ${colors.dim}All commands allowed (*)${RESET}`);
|
|
8927
|
+
} else {
|
|
8928
|
+
for (const cmd of context2.permissions.bash) {
|
|
8929
|
+
writeLine(` ${icons.active} ${cmd}`);
|
|
8930
|
+
}
|
|
8931
|
+
}
|
|
8932
|
+
writeLine();
|
|
8933
|
+
writeLine(` ${bold}Write Paths:${RESET}`);
|
|
8934
|
+
if (context2.permissions.write.includes("**") || context2.permissions.write.includes("*")) {
|
|
8935
|
+
writeLine(` ${colors.dim}All paths writable (**)${RESET}`);
|
|
8936
|
+
} else {
|
|
8937
|
+
for (const path3 of context2.permissions.write) {
|
|
8938
|
+
writeLine(` ${icons.active} ${path3}`);
|
|
8939
|
+
}
|
|
8940
|
+
}
|
|
8941
|
+
writeLine();
|
|
8942
|
+
writeLine(` ${bold}Read Paths:${RESET}`);
|
|
8943
|
+
if (context2.permissions.read.includes("**") || context2.permissions.read.includes("*")) {
|
|
8944
|
+
writeLine(` ${colors.dim}All paths readable (**)${RESET}`);
|
|
8945
|
+
} else {
|
|
8946
|
+
for (const path3 of context2.permissions.read) {
|
|
8947
|
+
writeLine(` ${icons.active} ${path3}`);
|
|
8948
|
+
}
|
|
8949
|
+
}
|
|
8950
|
+
writeLine();
|
|
8951
|
+
writeLine(` ${bold}MCP Servers:${RESET}`);
|
|
8952
|
+
if (context2.permissions.mcp.allow.includes("*")) {
|
|
8953
|
+
writeLine(` ${colors.dim}All servers allowed (*)${RESET}`);
|
|
8954
|
+
} else {
|
|
8955
|
+
writeLine(` ${colors.green}Allow:${RESET}`);
|
|
8956
|
+
for (const server of context2.permissions.mcp.allow) {
|
|
8957
|
+
writeLine(` ${icons.success} ${server}`);
|
|
8958
|
+
}
|
|
8959
|
+
}
|
|
8960
|
+
if (context2.permissions.mcp.deny.length > 0) {
|
|
8961
|
+
writeLine(` ${colors.red}Deny:${RESET}`);
|
|
8962
|
+
for (const server of context2.permissions.mcp.deny) {
|
|
8963
|
+
writeLine(` ${icons.error} ${server}`);
|
|
8964
|
+
}
|
|
8965
|
+
}
|
|
8966
|
+
writeLine();
|
|
8967
|
+
if (isDefault) {
|
|
8968
|
+
writeLine(` ${bold}Add permissions to SQUAD.md:${RESET}`);
|
|
8969
|
+
writeLine();
|
|
8970
|
+
writeLine(` ${colors.dim}\`\`\`yaml${RESET}`);
|
|
8971
|
+
writeLine(` ${colors.dim}permissions:${RESET}`);
|
|
8972
|
+
writeLine(` ${colors.dim} mode: warn # or strict, audit${RESET}`);
|
|
8973
|
+
writeLine(` ${colors.dim} bash: [npm, git, gh]${RESET}`);
|
|
8974
|
+
writeLine(` ${colors.dim} write: [agents-squads-web/**]${RESET}`);
|
|
8975
|
+
writeLine(` ${colors.dim} read: [hq/.agents/**]${RESET}`);
|
|
8976
|
+
writeLine(` ${colors.dim} mcp:${RESET}`);
|
|
8977
|
+
writeLine(` ${colors.dim} allow: [chrome-devtools]${RESET}`);
|
|
8978
|
+
writeLine(` ${colors.dim} deny: [supabase]${RESET}`);
|
|
8979
|
+
writeLine(` ${colors.dim}\`\`\`${RESET}`);
|
|
8980
|
+
writeLine();
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8983
|
+
async function permissionsCheckCommand(squadName, options) {
|
|
8984
|
+
writeLine();
|
|
8985
|
+
writeLine(` ${gradient("Permissions")} ${colors.dim}check${RESET} ${colors.cyan}${squadName}${RESET}`);
|
|
8986
|
+
if (options.agent) {
|
|
8987
|
+
writeLine(` ${colors.dim}Agent: ${options.agent}${RESET}`);
|
|
8988
|
+
}
|
|
8989
|
+
writeLine();
|
|
8990
|
+
const squadsDir = findSquadsDir();
|
|
8991
|
+
if (!squadsDir) {
|
|
8992
|
+
writeLine(` ${icons.error} ${colors.red}No .agents/squads directory found${RESET}`);
|
|
8993
|
+
writeLine();
|
|
8994
|
+
return;
|
|
8995
|
+
}
|
|
8996
|
+
const squad = loadSquad(squadName);
|
|
8997
|
+
if (!squad) {
|
|
8998
|
+
writeLine(` ${icons.error} ${colors.red}Squad "${squadName}" not found${RESET}`);
|
|
8999
|
+
writeLine();
|
|
9000
|
+
return;
|
|
9001
|
+
}
|
|
9002
|
+
const squadFilePath = join19(squadsDir, squadName, "SQUAD.md");
|
|
9003
|
+
const squadContent = readFileSync15(squadFilePath, "utf-8");
|
|
9004
|
+
const context2 = buildContextFromSquad(squadName, squadContent, options.agent);
|
|
9005
|
+
const request = {
|
|
9006
|
+
mcpServers: options.mcp,
|
|
9007
|
+
bashCommands: options.bash,
|
|
9008
|
+
writePaths: options.write,
|
|
9009
|
+
readPaths: options.read
|
|
9010
|
+
};
|
|
9011
|
+
if (!options.mcp && !options.bash && !options.write && !options.read) {
|
|
9012
|
+
writeLine(` ${colors.yellow}No resources specified to check.${RESET}`);
|
|
9013
|
+
writeLine();
|
|
9014
|
+
writeLine(` ${colors.dim}Examples:${RESET}`);
|
|
9015
|
+
writeLine(` ${colors.cyan}squads perms check ${squadName} --mcp chrome-devtools firecrawl${RESET}`);
|
|
9016
|
+
writeLine(` ${colors.cyan}squads perms check ${squadName} --bash npm git docker${RESET}`);
|
|
9017
|
+
writeLine(` ${colors.cyan}squads perms check ${squadName} --write src/** --read config/**${RESET}`);
|
|
9018
|
+
writeLine();
|
|
9019
|
+
return;
|
|
9020
|
+
}
|
|
9021
|
+
const result = validateExecution(context2, request);
|
|
9022
|
+
if (result.violations.length === 0) {
|
|
9023
|
+
writeLine(` ${icons.success} ${colors.green}All permissions valid${RESET}`);
|
|
9024
|
+
writeLine();
|
|
9025
|
+
if (options.mcp) {
|
|
9026
|
+
writeLine(` ${colors.dim}MCP:${RESET} ${options.mcp.join(", ")}`);
|
|
9027
|
+
}
|
|
9028
|
+
if (options.bash) {
|
|
9029
|
+
writeLine(` ${colors.dim}Bash:${RESET} ${options.bash.join(", ")}`);
|
|
9030
|
+
}
|
|
9031
|
+
if (options.write) {
|
|
9032
|
+
writeLine(` ${colors.dim}Write:${RESET} ${options.write.join(", ")}`);
|
|
9033
|
+
}
|
|
9034
|
+
if (options.read) {
|
|
9035
|
+
writeLine(` ${colors.dim}Read:${RESET} ${options.read.join(", ")}`);
|
|
9036
|
+
}
|
|
9037
|
+
writeLine();
|
|
9038
|
+
} else {
|
|
9039
|
+
const violationLines = formatViolations(result);
|
|
9040
|
+
for (const line of violationLines) {
|
|
9041
|
+
writeLine(` ${line}`);
|
|
9042
|
+
}
|
|
9043
|
+
writeLine();
|
|
9044
|
+
if (!result.allowed) {
|
|
9045
|
+
process.exit(1);
|
|
9046
|
+
}
|
|
9047
|
+
}
|
|
9048
|
+
}
|
|
9049
|
+
function formatMode(mode) {
|
|
9050
|
+
switch (mode) {
|
|
9051
|
+
case "strict":
|
|
9052
|
+
return `${colors.red}strict${RESET} ${colors.dim}(blocks on violation)${RESET}`;
|
|
9053
|
+
case "warn":
|
|
9054
|
+
return `${colors.yellow}warn${RESET} ${colors.dim}(logs and continues)${RESET}`;
|
|
9055
|
+
case "audit":
|
|
9056
|
+
return `${colors.cyan}audit${RESET} ${colors.dim}(logs to trail)${RESET}`;
|
|
9057
|
+
default:
|
|
9058
|
+
return mode;
|
|
9059
|
+
}
|
|
9060
|
+
}
|
|
9061
|
+
|
|
9062
|
+
// src/commands/context.ts
|
|
9063
|
+
async function contextShowCommand(squadName, options = {}) {
|
|
9064
|
+
await track(Events.CLI_CONTEXT, { squad: squadName });
|
|
9065
|
+
const squadsDir = findSquadsDir();
|
|
9066
|
+
if (!squadsDir) {
|
|
9067
|
+
writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
|
|
9068
|
+
writeLine(`${colors.dim}Run \`squads init\` to create one.${RESET}`);
|
|
9069
|
+
process.exit(1);
|
|
9070
|
+
}
|
|
9071
|
+
const squad = loadSquad(squadName);
|
|
9072
|
+
if (!squad) {
|
|
9073
|
+
writeLine(`${colors.red}Squad "${squadName}" not found.${RESET}`);
|
|
9074
|
+
process.exit(1);
|
|
9075
|
+
}
|
|
9076
|
+
if (options.json) {
|
|
9077
|
+
console.log(JSON.stringify({
|
|
9078
|
+
name: squad.name,
|
|
9079
|
+
mission: squad.mission,
|
|
9080
|
+
repo: squad.repo,
|
|
9081
|
+
stack: squad.stack,
|
|
9082
|
+
effort: squad.effort,
|
|
9083
|
+
context: squad.context
|
|
9084
|
+
}, null, 2));
|
|
9085
|
+
return;
|
|
9086
|
+
}
|
|
9087
|
+
writeLine();
|
|
9088
|
+
writeLine(` ${gradient("squads")} ${colors.dim}context${RESET} ${colors.cyan}${squad.name}${RESET}`);
|
|
9089
|
+
const ctx = squad.context;
|
|
9090
|
+
if (!ctx) {
|
|
9091
|
+
writeLine();
|
|
9092
|
+
writeLine(` ${colors.yellow}No context defined in SQUAD.md frontmatter${RESET}`);
|
|
9093
|
+
writeLine();
|
|
9094
|
+
writeLine(` ${colors.dim}Add a frontmatter block to ${squad.name}/SQUAD.md:${RESET}`);
|
|
9095
|
+
writeLine();
|
|
9096
|
+
writeLine(` ${colors.dim}---${RESET}`);
|
|
9097
|
+
writeLine(` ${colors.dim}name: ${squad.name}${RESET}`);
|
|
9098
|
+
writeLine(` ${colors.dim}context:${RESET}`);
|
|
9099
|
+
writeLine(` ${colors.dim} mcp: [chrome-devtools]${RESET}`);
|
|
9100
|
+
writeLine(` ${colors.dim} model: { default: sonnet }${RESET}`);
|
|
9101
|
+
writeLine(` ${colors.dim} budget: { daily: 10 }${RESET}`);
|
|
9102
|
+
writeLine(` ${colors.dim}---${RESET}`);
|
|
9103
|
+
writeLine();
|
|
9104
|
+
return;
|
|
9105
|
+
}
|
|
9106
|
+
const tableWidth = 54;
|
|
9107
|
+
writeLine();
|
|
9108
|
+
writeLine(` ${colors.purple}${box.horizontal.repeat(tableWidth)}${RESET}`);
|
|
9109
|
+
if (ctx.mcp && ctx.mcp.length > 0) {
|
|
9110
|
+
writeLine(` ${bold}MCP${RESET} ${colors.cyan}${ctx.mcp.join(", ")}${RESET}`);
|
|
9111
|
+
} else {
|
|
9112
|
+
writeLine(` ${bold}MCP${RESET} ${colors.dim}none${RESET}`);
|
|
9113
|
+
}
|
|
9114
|
+
if (ctx.skills && ctx.skills.length > 0) {
|
|
9115
|
+
writeLine(` ${bold}Skills${RESET} ${colors.cyan}${ctx.skills.join(", ")}${RESET}`);
|
|
9116
|
+
}
|
|
9117
|
+
if (ctx.memory?.load && ctx.memory.load.length > 0) {
|
|
9118
|
+
writeLine(` ${bold}Memory${RESET} ${colors.cyan}${ctx.memory.load.join(", ")}${RESET}`);
|
|
9119
|
+
}
|
|
9120
|
+
if (ctx.model) {
|
|
9121
|
+
const modelParts = [];
|
|
9122
|
+
if (ctx.model.default) modelParts.push(`${colors.white}${ctx.model.default}${RESET} ${colors.dim}(default)${RESET}`);
|
|
9123
|
+
if (ctx.model.expensive) modelParts.push(`${colors.yellow}${ctx.model.expensive}${RESET} ${colors.dim}(expensive)${RESET}`);
|
|
9124
|
+
if (ctx.model.cheap) modelParts.push(`${colors.green}${ctx.model.cheap}${RESET} ${colors.dim}(cheap)${RESET}`);
|
|
9125
|
+
writeLine(` ${bold}Model${RESET} ${modelParts.join(", ")}`);
|
|
9126
|
+
}
|
|
9127
|
+
if (ctx.budget) {
|
|
9128
|
+
const budgetParts = [];
|
|
9129
|
+
if (ctx.budget.daily) budgetParts.push(`$${ctx.budget.daily}/day`);
|
|
9130
|
+
if (ctx.budget.weekly) budgetParts.push(`$${ctx.budget.weekly}/week`);
|
|
9131
|
+
if (ctx.budget.perExecution) budgetParts.push(`$${ctx.budget.perExecution}/run`);
|
|
9132
|
+
writeLine(` ${bold}Budget${RESET} ${colors.green}${budgetParts.join(", ")}${RESET}`);
|
|
9133
|
+
}
|
|
9134
|
+
if (squad.effort) {
|
|
9135
|
+
const effortColor = squad.effort === "high" ? colors.red : squad.effort === "medium" ? colors.yellow : colors.green;
|
|
9136
|
+
writeLine(` ${bold}Effort${RESET} ${effortColor}${squad.effort}${RESET}`);
|
|
9137
|
+
}
|
|
9138
|
+
if (squad.repo) {
|
|
9139
|
+
writeLine(` ${bold}Repo${RESET} ${colors.dim}${squad.repo}${RESET}`);
|
|
9140
|
+
}
|
|
9141
|
+
if (squad.stack) {
|
|
9142
|
+
writeLine(` ${bold}Stack${RESET} ${colors.dim}${squad.stack}${RESET}`);
|
|
9143
|
+
}
|
|
9144
|
+
writeLine(` ${colors.purple}${box.horizontal.repeat(tableWidth)}${RESET}`);
|
|
9145
|
+
writeLine();
|
|
9146
|
+
writeLine(` ${colors.dim}$${RESET} squads context show ${colors.cyan}${squad.name}${RESET} --json ${colors.dim}JSON output${RESET}`);
|
|
9147
|
+
writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} ${colors.dim}Run with this context${RESET}`);
|
|
9148
|
+
writeLine();
|
|
9149
|
+
}
|
|
9150
|
+
async function contextListCommand(options = {}) {
|
|
9151
|
+
await track(Events.CLI_CONTEXT, { action: "list" });
|
|
9152
|
+
const squadsDir = findSquadsDir();
|
|
9153
|
+
if (!squadsDir) {
|
|
9154
|
+
writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
|
|
9155
|
+
process.exit(1);
|
|
9156
|
+
}
|
|
9157
|
+
const squads = listSquads(squadsDir);
|
|
9158
|
+
if (options.json) {
|
|
9159
|
+
const contexts = {};
|
|
9160
|
+
for (const name of squads) {
|
|
9161
|
+
const squad = loadSquad(name);
|
|
9162
|
+
if (squad) {
|
|
9163
|
+
contexts[name] = squad.context;
|
|
9164
|
+
}
|
|
9165
|
+
}
|
|
9166
|
+
console.log(JSON.stringify(contexts, null, 2));
|
|
9167
|
+
return;
|
|
9168
|
+
}
|
|
9169
|
+
writeLine();
|
|
9170
|
+
writeLine(` ${gradient("squads")} ${colors.dim}context list${RESET}`);
|
|
9171
|
+
writeLine();
|
|
9172
|
+
const w = { name: 14, mcp: 24, model: 12, budget: 12 };
|
|
9173
|
+
const tableWidth = w.name + w.mcp + w.model + w.budget + 6;
|
|
9174
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
9175
|
+
const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.name)}${RESET}${bold}${padEnd("MCP", w.mcp)}${RESET}${bold}${padEnd("MODEL", w.model)}${RESET}${bold}BUDGET${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
9176
|
+
writeLine(header);
|
|
9177
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
9178
|
+
for (const name of squads) {
|
|
9179
|
+
const squad = loadSquad(name);
|
|
9180
|
+
const ctx = squad?.context;
|
|
9181
|
+
const mcpStr = ctx?.mcp?.slice(0, 2).join(", ") || `${colors.dim}\u2014${RESET}`;
|
|
9182
|
+
const modelStr = ctx?.model?.default || `${colors.dim}\u2014${RESET}`;
|
|
9183
|
+
const budgetStr = ctx?.budget?.daily ? `$${ctx.budget.daily}/d` : `${colors.dim}\u2014${RESET}`;
|
|
9184
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(name, w.name)}${RESET}${padEnd(mcpStr, w.mcp)}${padEnd(modelStr, w.model)}${padEnd(budgetStr, w.budget)}${colors.purple}${box.vertical}${RESET}`;
|
|
9185
|
+
writeLine(row);
|
|
9186
|
+
}
|
|
9187
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
9188
|
+
writeLine();
|
|
9189
|
+
}
|
|
9190
|
+
|
|
9191
|
+
// src/commands/cost.ts
|
|
9192
|
+
function getBudgetStatus(squadName, spent, dailyBudget, weeklyBudget) {
|
|
9193
|
+
let status = "no-budget";
|
|
9194
|
+
let dailyPercent = null;
|
|
9195
|
+
let weeklyPercent = null;
|
|
9196
|
+
if (dailyBudget !== null) {
|
|
9197
|
+
dailyPercent = spent / dailyBudget * 100;
|
|
9198
|
+
if (dailyPercent >= 100) {
|
|
9199
|
+
status = "over";
|
|
9200
|
+
} else if (dailyPercent >= 80) {
|
|
9201
|
+
status = "warning";
|
|
9202
|
+
} else {
|
|
9203
|
+
status = "ok";
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
if (weeklyBudget !== null) {
|
|
9207
|
+
weeklyPercent = spent / weeklyBudget * 100;
|
|
9208
|
+
}
|
|
9209
|
+
return {
|
|
9210
|
+
squad: squadName,
|
|
9211
|
+
spent,
|
|
9212
|
+
dailyBudget,
|
|
9213
|
+
weeklyBudget,
|
|
9214
|
+
dailyPercent,
|
|
9215
|
+
weeklyPercent,
|
|
9216
|
+
status
|
|
9217
|
+
};
|
|
9218
|
+
}
|
|
9219
|
+
async function costCommand(options = {}) {
|
|
9220
|
+
await track(Events.CLI_COST, { squad: options.squad || "all" });
|
|
9221
|
+
const stats = await fetchBridgeStats();
|
|
9222
|
+
const plan = detectPlan();
|
|
9223
|
+
if (options.json) {
|
|
9224
|
+
const result = buildJsonOutput(stats, plan, options.squad);
|
|
9225
|
+
console.log(JSON.stringify(result, null, 2));
|
|
9226
|
+
return;
|
|
9227
|
+
}
|
|
9228
|
+
writeLine();
|
|
9229
|
+
writeLine(` ${gradient("squads")} ${colors.dim}cost${RESET}${options.squad ? ` ${colors.cyan}${options.squad}${RESET}` : ""}`);
|
|
9230
|
+
writeLine();
|
|
9231
|
+
if (!stats) {
|
|
9232
|
+
writeLine(` ${colors.yellow}\u26A0 Bridge unavailable${RESET}`);
|
|
9233
|
+
writeLine(` ${colors.dim}Run \`squads stack up\` to start infrastructure${RESET}`);
|
|
9234
|
+
writeLine();
|
|
9235
|
+
return;
|
|
9236
|
+
}
|
|
9237
|
+
const todaySection = ` ${bold}Today${RESET}`;
|
|
9238
|
+
writeLine(todaySection);
|
|
9239
|
+
writeLine(` ${colors.cyan}$${stats.today.costUsd.toFixed(2)}${RESET} ${colors.dim}|${RESET} ${stats.today.generations.toLocaleString()} calls ${colors.dim}|${RESET} ${formatTokens(stats.today.inputTokens + stats.today.outputTokens)} tokens`);
|
|
9240
|
+
if (stats.byModel && stats.byModel.length > 0) {
|
|
9241
|
+
const modelParts = stats.byModel.filter((m) => m.costUsd > 0).sort((a, b) => b.costUsd - a.costUsd).slice(0, 4).map((m) => `${colors.dim}${shortModelName(m.model)}${RESET} $${m.costUsd.toFixed(0)}`);
|
|
9242
|
+
if (modelParts.length > 0) {
|
|
9243
|
+
writeLine(` ${colors.dim}Models:${RESET} ${modelParts.join(" \xB7 ")}`);
|
|
9244
|
+
}
|
|
9245
|
+
}
|
|
9246
|
+
writeLine();
|
|
9247
|
+
if (stats.week) {
|
|
9248
|
+
writeLine(` ${bold}Week${RESET}`);
|
|
9249
|
+
writeLine(` ${colors.purple}$${stats.week.costUsd.toFixed(2)}${RESET} ${colors.dim}|${RESET} ${stats.week.generations.toLocaleString()} calls ${colors.dim}|${RESET} ${formatTokens(stats.week.inputTokens + stats.week.outputTokens)} tokens`);
|
|
9250
|
+
writeLine();
|
|
9251
|
+
}
|
|
9252
|
+
writeLine(` ${bold}Budget${RESET} ${colors.dim}(daily)${RESET}`);
|
|
9253
|
+
const budgetBar = createBudgetBar(stats.budget.usedPct);
|
|
9254
|
+
const budgetColor = stats.budget.usedPct >= 100 ? colors.red : stats.budget.usedPct >= 80 ? colors.yellow : colors.green;
|
|
9255
|
+
writeLine(` ${budgetBar} ${budgetColor}$${stats.budget.used.toFixed(0)}${RESET}/${colors.dim}$${stats.budget.daily}${RESET} (${stats.budget.usedPct.toFixed(0)}%)`);
|
|
9256
|
+
writeLine();
|
|
9257
|
+
if (stats.bySquad && stats.bySquad.length > 0 && !options.squad) {
|
|
9258
|
+
writeLine(` ${bold}By Squad${RESET}`);
|
|
9259
|
+
writeLine();
|
|
9260
|
+
const squadsDir = findSquadsDir();
|
|
9261
|
+
const squadBudgets = /* @__PURE__ */ new Map();
|
|
9262
|
+
if (squadsDir) {
|
|
9263
|
+
for (const name of listSquads(squadsDir)) {
|
|
9264
|
+
const squad = loadSquad(name);
|
|
9265
|
+
if (squad?.context?.budget) {
|
|
9266
|
+
squadBudgets.set(name, {
|
|
9267
|
+
daily: squad.context.budget.daily || null,
|
|
9268
|
+
weekly: squad.context.budget.weekly || null
|
|
9269
|
+
});
|
|
9270
|
+
}
|
|
9271
|
+
}
|
|
9272
|
+
}
|
|
9273
|
+
const tableWidth = 52;
|
|
9274
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
9275
|
+
const w = { name: 14, spent: 10, budget: 12, status: 14 };
|
|
9276
|
+
const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("SQUAD", w.name)}${RESET}${bold}${padEnd("SPENT", w.spent)}${RESET}${bold}${padEnd("BUDGET", w.budget)}${RESET}${bold}STATUS${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
9277
|
+
writeLine(header);
|
|
9278
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
9279
|
+
for (const sq of stats.bySquad.sort((a, b) => b.costUsd - a.costUsd)) {
|
|
9280
|
+
const budgetInfo = squadBudgets.get(sq.squad);
|
|
9281
|
+
const dailyBudget = budgetInfo?.daily || null;
|
|
9282
|
+
const spentStr = `$${sq.costUsd.toFixed(2)}`;
|
|
9283
|
+
const budgetStr = dailyBudget ? `$${dailyBudget}/d` : `${colors.dim}\u2014${RESET}`;
|
|
9284
|
+
let statusStr;
|
|
9285
|
+
if (dailyBudget) {
|
|
9286
|
+
const pct = sq.costUsd / dailyBudget * 100;
|
|
9287
|
+
if (pct >= 100) {
|
|
9288
|
+
statusStr = `${colors.red}\u25CF OVER${RESET}`;
|
|
9289
|
+
} else if (pct >= 80) {
|
|
9290
|
+
statusStr = `${colors.yellow}\u25CF ${pct.toFixed(0)}%${RESET}`;
|
|
9291
|
+
} else {
|
|
9292
|
+
statusStr = `${colors.green}\u2713 ${pct.toFixed(0)}%${RESET}`;
|
|
9293
|
+
}
|
|
9294
|
+
} else {
|
|
9295
|
+
statusStr = `${colors.dim}no budget${RESET}`;
|
|
9296
|
+
}
|
|
9297
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(sq.squad, w.name)}${RESET}${padEnd(spentStr, w.spent)}${padEnd(budgetStr, w.budget)}${padEnd(statusStr, w.status)}${colors.purple}${box.vertical}${RESET}`;
|
|
9298
|
+
writeLine(row);
|
|
9299
|
+
}
|
|
9300
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
9301
|
+
writeLine();
|
|
9302
|
+
}
|
|
9303
|
+
if (options.squad) {
|
|
9304
|
+
const squadStats = stats.bySquad?.find((s) => s.squad === options.squad);
|
|
9305
|
+
const squad = loadSquad(options.squad);
|
|
9306
|
+
if (squadStats) {
|
|
9307
|
+
writeLine(` ${bold}Squad: ${options.squad}${RESET}`);
|
|
9308
|
+
writeLine(` Spent today: ${colors.cyan}$${squadStats.costUsd.toFixed(2)}${RESET}`);
|
|
9309
|
+
writeLine(` Calls: ${squadStats.generations.toLocaleString()}`);
|
|
9310
|
+
if (squad?.context?.budget) {
|
|
9311
|
+
const daily = squad.context.budget.daily;
|
|
9312
|
+
const weekly = squad.context.budget.weekly;
|
|
9313
|
+
if (daily) {
|
|
9314
|
+
const pct = squadStats.costUsd / daily * 100;
|
|
9315
|
+
const statusIcon = pct >= 100 ? `${colors.red}\u25CF${RESET}` : pct >= 80 ? `${colors.yellow}\u25CF${RESET}` : `${colors.green}\u2713${RESET}`;
|
|
9316
|
+
writeLine(` Daily budget: ${statusIcon} $${squadStats.costUsd.toFixed(2)}/$${daily} (${pct.toFixed(0)}%)`);
|
|
9317
|
+
}
|
|
9318
|
+
if (weekly) {
|
|
9319
|
+
writeLine(` Weekly budget: $${weekly}/week`);
|
|
9320
|
+
}
|
|
9321
|
+
} else {
|
|
9322
|
+
writeLine(` ${colors.dim}No budget defined in SQUAD.md frontmatter${RESET}`);
|
|
9323
|
+
}
|
|
9324
|
+
} else {
|
|
9325
|
+
writeLine(` ${colors.dim}No cost data for squad "${options.squad}"${RESET}`);
|
|
9326
|
+
}
|
|
9327
|
+
writeLine();
|
|
9328
|
+
}
|
|
9329
|
+
writeLine(` ${colors.dim}Plan: ${plan.plan} (${plan.reason})${RESET}`);
|
|
9330
|
+
writeLine();
|
|
9331
|
+
}
|
|
9332
|
+
async function budgetCheckCommand(squadName, options = {}) {
|
|
9333
|
+
await track(Events.CLI_COST, { action: "budget-check", squad: squadName });
|
|
9334
|
+
const stats = await fetchBridgeStats();
|
|
9335
|
+
const squad = loadSquad(squadName);
|
|
9336
|
+
if (!stats) {
|
|
9337
|
+
if (options.json) {
|
|
9338
|
+
console.log(JSON.stringify({ error: "Bridge unavailable" }));
|
|
9339
|
+
} else {
|
|
9340
|
+
writeLine(` ${colors.yellow}\u26A0 Bridge unavailable${RESET}`);
|
|
9341
|
+
}
|
|
9342
|
+
return;
|
|
9343
|
+
}
|
|
9344
|
+
const squadStats = stats.bySquad?.find((s) => s.squad === squadName);
|
|
9345
|
+
const spent = squadStats?.costUsd || 0;
|
|
9346
|
+
const dailyBudget = squad?.context?.budget?.daily || null;
|
|
9347
|
+
const weeklyBudget = squad?.context?.budget?.weekly || null;
|
|
9348
|
+
const status = getBudgetStatus(squadName, spent, dailyBudget, weeklyBudget);
|
|
9349
|
+
if (options.json) {
|
|
9350
|
+
console.log(JSON.stringify(status, null, 2));
|
|
9351
|
+
return;
|
|
9352
|
+
}
|
|
9353
|
+
writeLine();
|
|
9354
|
+
if (status.status === "no-budget") {
|
|
9355
|
+
writeLine(` ${colors.dim}\u25CB${RESET} ${colors.cyan}${squadName}${RESET}: No budget defined`);
|
|
9356
|
+
writeLine(` ${colors.dim}Add budget to SQUAD.md frontmatter:${RESET}`);
|
|
9357
|
+
writeLine(` ${colors.dim} context:${RESET}`);
|
|
9358
|
+
writeLine(` ${colors.dim} budget: { daily: 50 }${RESET}`);
|
|
9359
|
+
} else if (status.status === "over") {
|
|
9360
|
+
writeLine(` ${colors.red}\u25CF${RESET} ${colors.cyan}${squadName}${RESET}: ${colors.red}OVER BUDGET${RESET}`);
|
|
9361
|
+
writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);
|
|
9362
|
+
} else if (status.status === "warning") {
|
|
9363
|
+
writeLine(` ${colors.yellow}\u25CF${RESET} ${colors.cyan}${squadName}${RESET}: ${colors.yellow}Approaching limit${RESET}`);
|
|
9364
|
+
writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);
|
|
9365
|
+
} else {
|
|
9366
|
+
writeLine(` ${colors.green}\u2713${RESET} ${colors.cyan}${squadName}${RESET}: OK to proceed`);
|
|
9367
|
+
writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);
|
|
9368
|
+
}
|
|
9369
|
+
writeLine();
|
|
9370
|
+
}
|
|
9371
|
+
function buildJsonOutput(stats, plan, squadFilter) {
|
|
9372
|
+
if (!stats) {
|
|
9373
|
+
return { error: "Bridge unavailable", plan };
|
|
9374
|
+
}
|
|
9375
|
+
const squadsDir = findSquadsDir();
|
|
9376
|
+
const squadBudgets = {};
|
|
9377
|
+
if (squadsDir) {
|
|
9378
|
+
for (const name of listSquads(squadsDir)) {
|
|
9379
|
+
const squad = loadSquad(name);
|
|
9380
|
+
if (squad?.context?.budget) {
|
|
9381
|
+
squadBudgets[name] = {
|
|
9382
|
+
daily: squad.context.budget.daily || null,
|
|
9383
|
+
weekly: squad.context.budget.weekly || null
|
|
9384
|
+
};
|
|
9385
|
+
}
|
|
9386
|
+
}
|
|
9387
|
+
}
|
|
9388
|
+
const bySquadWithBudget = stats.bySquad?.map((sq) => ({
|
|
9389
|
+
...sq,
|
|
9390
|
+
budget: squadBudgets[sq.squad] || null,
|
|
9391
|
+
budgetStatus: squadBudgets[sq.squad]?.daily ? getBudgetStatus(sq.squad, sq.costUsd, squadBudgets[sq.squad].daily, squadBudgets[sq.squad].weekly) : null
|
|
9392
|
+
}));
|
|
9393
|
+
if (squadFilter) {
|
|
9394
|
+
const filtered = bySquadWithBudget?.find((s) => s.squad === squadFilter);
|
|
9395
|
+
return {
|
|
9396
|
+
squad: squadFilter,
|
|
9397
|
+
...filtered,
|
|
9398
|
+
plan
|
|
9399
|
+
};
|
|
9400
|
+
}
|
|
9401
|
+
return {
|
|
9402
|
+
today: stats.today,
|
|
9403
|
+
week: stats.week,
|
|
9404
|
+
budget: stats.budget,
|
|
9405
|
+
bySquad: bySquadWithBudget,
|
|
9406
|
+
byModel: stats.byModel,
|
|
9407
|
+
plan,
|
|
9408
|
+
source: stats.source
|
|
9409
|
+
};
|
|
9410
|
+
}
|
|
9411
|
+
function shortModelName(model) {
|
|
9412
|
+
if (model.includes("opus")) return "opus";
|
|
9413
|
+
if (model.includes("sonnet")) return "sonnet";
|
|
9414
|
+
if (model.includes("haiku")) return "haiku";
|
|
9415
|
+
return model.split("-")[0];
|
|
9416
|
+
}
|
|
9417
|
+
function formatTokens(tokens) {
|
|
9418
|
+
if (tokens >= 1e6) {
|
|
9419
|
+
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
9420
|
+
}
|
|
9421
|
+
if (tokens >= 1e3) {
|
|
9422
|
+
return `${(tokens / 1e3).toFixed(0)}k`;
|
|
9423
|
+
}
|
|
9424
|
+
return tokens.toString();
|
|
9425
|
+
}
|
|
9426
|
+
function createBudgetBar(percent, width = 10) {
|
|
9427
|
+
const filled = Math.min(Math.round(percent / 100 * width), width);
|
|
9428
|
+
const empty = width - filled;
|
|
9429
|
+
const filledChar = "\u2501";
|
|
9430
|
+
const emptyChar = "\u2501";
|
|
9431
|
+
const color = percent >= 100 ? colors.red : percent >= 80 ? colors.yellow : colors.green;
|
|
9432
|
+
return `${color}${filledChar.repeat(filled)}${colors.dim}${emptyChar.repeat(empty)}${RESET}`;
|
|
9433
|
+
}
|
|
9434
|
+
|
|
9435
|
+
// src/lib/executions.ts
|
|
9436
|
+
import { readFileSync as readFileSync16, existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
|
|
9437
|
+
import { join as join20 } from "path";
|
|
9438
|
+
function parseExecutionEntry(content, squad, agent) {
|
|
9439
|
+
const idMatch = content.match(/<!-- exec:(\S+) -->/);
|
|
9440
|
+
if (!idMatch) return null;
|
|
9441
|
+
const id = idMatch[1];
|
|
9442
|
+
const headerMatch = content.match(/\*\*([^*]+)\*\* \| Status: (\w+)/);
|
|
9443
|
+
if (!headerMatch) return null;
|
|
9444
|
+
const startTime = headerMatch[1].trim();
|
|
9445
|
+
const status = headerMatch[2];
|
|
9446
|
+
const triggerMatch = content.match(/- Trigger: (\w+)/);
|
|
9447
|
+
const taskTypeMatch = content.match(/- Task Type: (\w+)/);
|
|
9448
|
+
const completedMatch = content.match(/- Completed: ([^\n]+)/);
|
|
9449
|
+
const durationMatch = content.match(/- Duration: ([^\n]+)/);
|
|
9450
|
+
const outcomeMatch = content.match(/- Outcome: ([^\n]+)/);
|
|
9451
|
+
const errorMatch = content.match(/- Error: ([^\n]+)/);
|
|
9452
|
+
let durationMs;
|
|
9453
|
+
if (durationMatch) {
|
|
9454
|
+
const durationStr = durationMatch[1].trim();
|
|
9455
|
+
const secMatch = durationStr.match(/^([\d.]+)s$/);
|
|
9456
|
+
if (secMatch) {
|
|
9457
|
+
durationMs = parseFloat(secMatch[1]) * 1e3;
|
|
9458
|
+
}
|
|
9459
|
+
}
|
|
9460
|
+
return {
|
|
9461
|
+
id,
|
|
9462
|
+
squad,
|
|
9463
|
+
agent,
|
|
9464
|
+
startTime,
|
|
9465
|
+
endTime: completedMatch?.[1]?.trim(),
|
|
9466
|
+
durationMs,
|
|
9467
|
+
status,
|
|
9468
|
+
trigger: triggerMatch?.[1] || "manual",
|
|
9469
|
+
taskType: taskTypeMatch?.[1] || "execution",
|
|
9470
|
+
outcome: outcomeMatch?.[1]?.trim(),
|
|
9471
|
+
error: errorMatch?.[1]?.trim()
|
|
9472
|
+
};
|
|
9473
|
+
}
|
|
9474
|
+
function parseExecutionLog(filePath, squad, agent) {
|
|
9475
|
+
if (!existsSync18(filePath)) return [];
|
|
9476
|
+
const content = readFileSync16(filePath, "utf-8");
|
|
9477
|
+
const executions = [];
|
|
9478
|
+
const entries = content.split(/\n---\n/);
|
|
9479
|
+
for (const entry of entries) {
|
|
9480
|
+
if (!entry.includes("<!-- exec:")) continue;
|
|
9481
|
+
const execution = parseExecutionEntry(entry, squad, agent);
|
|
9482
|
+
if (execution) {
|
|
9483
|
+
executions.push(execution);
|
|
9484
|
+
}
|
|
9485
|
+
}
|
|
9486
|
+
for (const entry of entries) {
|
|
9487
|
+
if (entry.includes("<!-- exec:")) continue;
|
|
9488
|
+
const headerMatch = entry.match(/\*\*([^*]+)\*\* \| Status: (\w+)/);
|
|
9489
|
+
if (!headerMatch) continue;
|
|
9490
|
+
const startTime = headerMatch[1].trim();
|
|
9491
|
+
const status = headerMatch[2];
|
|
9492
|
+
const legacyId = `legacy_${startTime.replace(/[^a-z0-9]/gi, "")}`;
|
|
9493
|
+
if (executions.some((e) => e.startTime === startTime)) continue;
|
|
9494
|
+
executions.push({
|
|
9495
|
+
id: legacyId,
|
|
9496
|
+
squad,
|
|
9497
|
+
agent,
|
|
9498
|
+
startTime,
|
|
9499
|
+
status,
|
|
9500
|
+
trigger: "manual",
|
|
9501
|
+
taskType: "execution"
|
|
9502
|
+
});
|
|
9503
|
+
}
|
|
9504
|
+
return executions;
|
|
9505
|
+
}
|
|
9506
|
+
function listExecutions(options = {}) {
|
|
9507
|
+
const memoryDir = findMemoryDir();
|
|
9508
|
+
if (!memoryDir) return [];
|
|
9509
|
+
const executions = [];
|
|
9510
|
+
const { squad: filterSquad, agent: filterAgent, status: filterStatus, limit, since } = options;
|
|
9511
|
+
const squads = readdirSync8(memoryDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
9512
|
+
for (const squad of squads) {
|
|
9513
|
+
if (filterSquad && squad !== filterSquad) continue;
|
|
9514
|
+
const squadPath = join20(memoryDir, squad);
|
|
9515
|
+
const agents = readdirSync8(squadPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
9516
|
+
for (const agent of agents) {
|
|
9517
|
+
if (filterAgent && agent !== filterAgent) continue;
|
|
9518
|
+
const logPath = join20(squadPath, agent, "executions.md");
|
|
9519
|
+
const agentExecutions = parseExecutionLog(logPath, squad, agent);
|
|
9520
|
+
executions.push(...agentExecutions);
|
|
9521
|
+
}
|
|
9522
|
+
}
|
|
9523
|
+
let filtered = filterStatus ? executions.filter((e) => e.status === filterStatus) : executions;
|
|
9524
|
+
if (since) {
|
|
9525
|
+
const sinceMs = since.getTime();
|
|
9526
|
+
filtered = filtered.filter((e) => {
|
|
9527
|
+
const execDate = new Date(e.startTime).getTime();
|
|
9528
|
+
return !isNaN(execDate) && execDate >= sinceMs;
|
|
9529
|
+
});
|
|
9530
|
+
}
|
|
9531
|
+
filtered.sort((a, b) => {
|
|
9532
|
+
const aTime = new Date(a.startTime).getTime();
|
|
9533
|
+
const bTime = new Date(b.startTime).getTime();
|
|
9534
|
+
if (isNaN(aTime) || isNaN(bTime)) return 0;
|
|
9535
|
+
return bTime - aTime;
|
|
9536
|
+
});
|
|
9537
|
+
if (limit && limit > 0) {
|
|
9538
|
+
filtered = filtered.slice(0, limit);
|
|
9539
|
+
}
|
|
9540
|
+
return filtered;
|
|
9541
|
+
}
|
|
9542
|
+
function getExecutionStats(options = {}) {
|
|
9543
|
+
const executions = listExecutions(options);
|
|
9544
|
+
const running = executions.filter((e) => e.status === "running").length;
|
|
9545
|
+
const completed = executions.filter((e) => e.status === "completed").length;
|
|
9546
|
+
const failed = executions.filter((e) => e.status === "failed").length;
|
|
9547
|
+
const durations = executions.filter((e) => e.status === "completed" && e.durationMs).map((e) => e.durationMs);
|
|
9548
|
+
const avgDurationMs = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : null;
|
|
9549
|
+
const bySquad = {};
|
|
9550
|
+
for (const e of executions) {
|
|
9551
|
+
bySquad[e.squad] = (bySquad[e.squad] || 0) + 1;
|
|
9552
|
+
}
|
|
9553
|
+
const byAgent = {};
|
|
9554
|
+
for (const e of executions) {
|
|
9555
|
+
const key = `${e.squad}/${e.agent}`;
|
|
9556
|
+
byAgent[key] = (byAgent[key] || 0) + 1;
|
|
9557
|
+
}
|
|
9558
|
+
return {
|
|
9559
|
+
total: executions.length,
|
|
9560
|
+
running,
|
|
9561
|
+
completed,
|
|
9562
|
+
failed,
|
|
9563
|
+
avgDurationMs,
|
|
9564
|
+
bySquad,
|
|
9565
|
+
byAgent
|
|
9566
|
+
};
|
|
9567
|
+
}
|
|
9568
|
+
function formatDuration2(ms) {
|
|
9569
|
+
if (!ms) return "\u2014";
|
|
9570
|
+
if (ms < 1e3) {
|
|
9571
|
+
return `${ms}ms`;
|
|
9572
|
+
}
|
|
9573
|
+
if (ms < 6e4) {
|
|
9574
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
9575
|
+
}
|
|
9576
|
+
if (ms < 36e5) {
|
|
9577
|
+
const mins2 = Math.floor(ms / 6e4);
|
|
9578
|
+
const secs = Math.round(ms % 6e4 / 1e3);
|
|
9579
|
+
return `${mins2}m ${secs}s`;
|
|
9580
|
+
}
|
|
9581
|
+
const hours = Math.floor(ms / 36e5);
|
|
9582
|
+
const mins = Math.round(ms % 36e5 / 6e4);
|
|
9583
|
+
return `${hours}h ${mins}m`;
|
|
9584
|
+
}
|
|
9585
|
+
function formatRelativeTime(isoTime) {
|
|
9586
|
+
const date = new Date(isoTime);
|
|
9587
|
+
if (isNaN(date.getTime())) return isoTime;
|
|
9588
|
+
const now = Date.now();
|
|
9589
|
+
const diff = now - date.getTime();
|
|
9590
|
+
if (diff < 6e4) return "just now";
|
|
9591
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
9592
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
|
|
9593
|
+
if (diff < 6048e5) return `${Math.floor(diff / 864e5)}d ago`;
|
|
9594
|
+
return date.toLocaleDateString();
|
|
9595
|
+
}
|
|
9596
|
+
|
|
9597
|
+
// src/commands/exec.ts
|
|
9598
|
+
async function execListCommand(options = {}) {
|
|
9599
|
+
await track(Events.CLI_EXEC, { action: "list", squad: options.squad });
|
|
9600
|
+
const listOptions = {
|
|
9601
|
+
squad: options.squad,
|
|
9602
|
+
agent: options.agent,
|
|
9603
|
+
limit: options.limit || 20
|
|
9604
|
+
};
|
|
9605
|
+
if (options.status) {
|
|
9606
|
+
listOptions.status = options.status;
|
|
9607
|
+
}
|
|
9608
|
+
const executions = listExecutions(listOptions);
|
|
9609
|
+
if (options.json) {
|
|
9610
|
+
console.log(JSON.stringify(executions, null, 2));
|
|
9611
|
+
return;
|
|
9612
|
+
}
|
|
9613
|
+
writeLine();
|
|
9614
|
+
writeLine(` ${gradient("squads")} ${colors.dim}exec list${RESET}${options.squad ? ` ${colors.cyan}${options.squad}${RESET}` : ""}`);
|
|
9615
|
+
writeLine();
|
|
9616
|
+
if (executions.length === 0) {
|
|
9617
|
+
writeLine(` ${colors.dim}No executions found${RESET}`);
|
|
9618
|
+
writeLine();
|
|
9619
|
+
writeLine(` ${colors.dim}Executions are logged when running agents:${RESET}`);
|
|
9620
|
+
writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}<squad>${RESET} --execute`);
|
|
9621
|
+
writeLine();
|
|
9622
|
+
return;
|
|
9623
|
+
}
|
|
9624
|
+
const w = { agent: 22, status: 12, duration: 10, time: 14, id: 18 };
|
|
9625
|
+
const tableWidth = w.agent + w.status + w.duration + w.time + w.id + 8;
|
|
9626
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
9627
|
+
const header = ` ${colors.purple}${box.vertical}${RESET} ${bold}${padEnd("AGENT", w.agent)}${RESET}${bold}${padEnd("STATUS", w.status)}${RESET}${bold}${padEnd("DURATION", w.duration)}${RESET}${bold}${padEnd("TIME", w.time)}${RESET}${bold}ID${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
9628
|
+
writeLine(header);
|
|
9629
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
9630
|
+
for (const exec3 of executions) {
|
|
9631
|
+
const agentName = `${exec3.squad}/${exec3.agent}`;
|
|
9632
|
+
const truncatedAgent = agentName.length > w.agent - 1 ? agentName.slice(0, w.agent - 4) + "..." : agentName;
|
|
9633
|
+
const statusIcon = exec3.status === "running" ? icons.running : exec3.status === "completed" ? icons.success : icons.error;
|
|
9634
|
+
const statusColor = exec3.status === "running" ? colors.yellow : exec3.status === "completed" ? colors.green : colors.red;
|
|
9635
|
+
const statusStr = `${statusColor}${statusIcon} ${exec3.status}${RESET}`;
|
|
9636
|
+
const durationStr = formatDuration2(exec3.durationMs);
|
|
9637
|
+
const timeStr = formatRelativeTime(exec3.startTime);
|
|
9638
|
+
const shortId = exec3.id.slice(0, 16);
|
|
9639
|
+
const row = ` ${colors.purple}${box.vertical}${RESET} ${colors.cyan}${padEnd(truncatedAgent, w.agent)}${RESET}${padEnd(statusStr, w.status + 10)}${padEnd(durationStr, w.duration)}${colors.dim}${padEnd(timeStr, w.time)}${RESET}${colors.dim}${shortId}${RESET} ${colors.purple}${box.vertical}${RESET}`;
|
|
9640
|
+
writeLine(row);
|
|
9641
|
+
}
|
|
9642
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
9643
|
+
writeLine();
|
|
9644
|
+
const stats = getExecutionStats(listOptions);
|
|
9645
|
+
const parts = [];
|
|
9646
|
+
if (stats.running > 0) parts.push(`${colors.yellow}${stats.running} running${RESET}`);
|
|
9647
|
+
if (stats.completed > 0) parts.push(`${colors.green}${stats.completed} completed${RESET}`);
|
|
9648
|
+
if (stats.failed > 0) parts.push(`${colors.red}${stats.failed} failed${RESET}`);
|
|
9649
|
+
if (parts.length > 0) {
|
|
9650
|
+
writeLine(` ${parts.join(` ${colors.dim}|${RESET} `)}`);
|
|
9651
|
+
if (stats.avgDurationMs) {
|
|
9652
|
+
writeLine(` ${colors.dim}Avg duration: ${formatDuration2(stats.avgDurationMs)}${RESET}`);
|
|
9653
|
+
}
|
|
9654
|
+
writeLine();
|
|
9655
|
+
}
|
|
9656
|
+
writeLine(` ${colors.dim}$${RESET} squads exec show ${colors.cyan}<id>${RESET} ${colors.dim}Execution details${RESET}`);
|
|
9657
|
+
writeLine(` ${colors.dim}$${RESET} squads exec --json ${colors.dim}JSON output${RESET}`);
|
|
9658
|
+
writeLine();
|
|
9659
|
+
}
|
|
9660
|
+
async function execShowCommand(executionId, options = {}) {
|
|
9661
|
+
await track(Events.CLI_EXEC, { action: "show", id: executionId });
|
|
9662
|
+
const executions = listExecutions();
|
|
9663
|
+
const matches = executions.filter(
|
|
9664
|
+
(e) => e.id === executionId || e.id.startsWith(executionId)
|
|
9665
|
+
);
|
|
9666
|
+
if (matches.length === 0) {
|
|
9667
|
+
writeLine();
|
|
9668
|
+
writeLine(` ${colors.red}Execution not found: ${executionId}${RESET}`);
|
|
9669
|
+
writeLine();
|
|
9670
|
+
writeLine(` ${colors.dim}List recent executions:${RESET}`);
|
|
9671
|
+
writeLine(` ${colors.dim}$${RESET} squads exec list`);
|
|
9672
|
+
writeLine();
|
|
9673
|
+
return;
|
|
9674
|
+
}
|
|
9675
|
+
if (matches.length > 1) {
|
|
9676
|
+
writeLine();
|
|
9677
|
+
writeLine(` ${colors.yellow}Multiple matches for "${executionId}":${RESET}`);
|
|
9678
|
+
for (const m of matches.slice(0, 5)) {
|
|
9679
|
+
writeLine(` ${colors.dim}${m.id}${RESET} - ${m.squad}/${m.agent}`);
|
|
9680
|
+
}
|
|
9681
|
+
writeLine();
|
|
9682
|
+
writeLine(` ${colors.dim}Provide a more specific ID${RESET}`);
|
|
9683
|
+
writeLine();
|
|
9684
|
+
return;
|
|
9685
|
+
}
|
|
9686
|
+
const exec3 = matches[0];
|
|
9687
|
+
if (options.json) {
|
|
9688
|
+
console.log(JSON.stringify(exec3, null, 2));
|
|
9689
|
+
return;
|
|
9690
|
+
}
|
|
9691
|
+
writeLine();
|
|
9692
|
+
writeLine(` ${gradient("squads")} ${colors.dim}exec show${RESET}`);
|
|
9693
|
+
writeLine();
|
|
9694
|
+
writeLine(` ${bold}${exec3.squad}/${exec3.agent}${RESET}`);
|
|
9695
|
+
writeLine(` ${colors.dim}${exec3.id}${RESET}`);
|
|
9696
|
+
writeLine();
|
|
9697
|
+
const statusIcon = exec3.status === "running" ? icons.running : exec3.status === "completed" ? icons.success : icons.error;
|
|
9698
|
+
const statusColor = exec3.status === "running" ? colors.yellow : exec3.status === "completed" ? colors.green : colors.red;
|
|
9699
|
+
writeLine(` ${bold}Status${RESET} ${statusColor}${statusIcon} ${exec3.status}${RESET}`);
|
|
9700
|
+
writeLine(` ${bold}Task Type${RESET} ${exec3.taskType}`);
|
|
9701
|
+
writeLine(` ${bold}Trigger${RESET} ${exec3.trigger}`);
|
|
9702
|
+
writeLine();
|
|
9703
|
+
writeLine(` ${bold}Started${RESET} ${exec3.startTime}`);
|
|
9704
|
+
if (exec3.endTime) {
|
|
9705
|
+
writeLine(` ${bold}Completed${RESET} ${exec3.endTime}`);
|
|
9706
|
+
}
|
|
9707
|
+
if (exec3.durationMs) {
|
|
9708
|
+
writeLine(` ${bold}Duration${RESET} ${formatDuration2(exec3.durationMs)}`);
|
|
9709
|
+
}
|
|
9710
|
+
writeLine();
|
|
9711
|
+
if (exec3.outcome) {
|
|
9712
|
+
writeLine(` ${bold}Outcome${RESET}`);
|
|
9713
|
+
writeLine(` ${colors.dim}${exec3.outcome}${RESET}`);
|
|
9714
|
+
writeLine();
|
|
9715
|
+
}
|
|
9716
|
+
if (exec3.error) {
|
|
9717
|
+
writeLine(` ${bold}Error${RESET}`);
|
|
9718
|
+
writeLine(` ${colors.red}${exec3.error}${RESET}`);
|
|
9719
|
+
writeLine();
|
|
9720
|
+
}
|
|
9721
|
+
writeLine(` ${colors.dim}$${RESET} squads memory show ${colors.cyan}${exec3.squad}${RESET} ${colors.dim}Squad memory${RESET}`);
|
|
9722
|
+
writeLine(` ${colors.dim}$${RESET} squads exec list --squad ${colors.cyan}${exec3.squad}${RESET} ${colors.dim}Squad history${RESET}`);
|
|
9723
|
+
writeLine();
|
|
9724
|
+
}
|
|
9725
|
+
async function execStatsCommand(options = {}) {
|
|
9726
|
+
await track(Events.CLI_EXEC, { action: "stats", squad: options.squad });
|
|
9727
|
+
const listOptions = {
|
|
9728
|
+
squad: options.squad
|
|
9729
|
+
};
|
|
9730
|
+
const stats = getExecutionStats(listOptions);
|
|
9731
|
+
if (options.json) {
|
|
9732
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
9733
|
+
return;
|
|
9734
|
+
}
|
|
9735
|
+
writeLine();
|
|
9736
|
+
writeLine(` ${gradient("squads")} ${colors.dim}exec stats${RESET}${options.squad ? ` ${colors.cyan}${options.squad}${RESET}` : ""}`);
|
|
9737
|
+
writeLine();
|
|
9738
|
+
writeLine(` ${bold}Total${RESET} ${stats.total}`);
|
|
9739
|
+
writeLine(` ${colors.yellow}Running${RESET} ${stats.running}`);
|
|
9740
|
+
writeLine(` ${colors.green}Completed${RESET} ${stats.completed}`);
|
|
9741
|
+
writeLine(` ${colors.red}Failed${RESET} ${stats.failed}`);
|
|
9742
|
+
writeLine();
|
|
9743
|
+
if (stats.avgDurationMs) {
|
|
9744
|
+
writeLine(` ${bold}Avg Duration${RESET} ${formatDuration2(stats.avgDurationMs)}`);
|
|
9745
|
+
writeLine();
|
|
9746
|
+
}
|
|
9747
|
+
const squadEntries = Object.entries(stats.bySquad).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
9748
|
+
if (squadEntries.length > 0) {
|
|
9749
|
+
writeLine(` ${bold}By Squad${RESET}`);
|
|
9750
|
+
for (const [squad, count] of squadEntries) {
|
|
9751
|
+
writeLine(` ${colors.cyan}${squad}${RESET}: ${count}`);
|
|
9752
|
+
}
|
|
9753
|
+
writeLine();
|
|
9754
|
+
}
|
|
9755
|
+
const agentEntries = Object.entries(stats.byAgent).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
9756
|
+
if (agentEntries.length > 0) {
|
|
9757
|
+
writeLine(` ${bold}Top Agents${RESET}`);
|
|
9758
|
+
for (const [agent, count] of agentEntries) {
|
|
9759
|
+
writeLine(` ${colors.cyan}${agent}${RESET}: ${count}`);
|
|
9760
|
+
}
|
|
9761
|
+
writeLine();
|
|
9762
|
+
}
|
|
9763
|
+
}
|
|
9764
|
+
|
|
9765
|
+
// src/commands/tonight.ts
|
|
9766
|
+
import ora7 from "ora";
|
|
7964
9767
|
import fs2 from "fs/promises";
|
|
7965
|
-
import path2, { dirname as
|
|
9768
|
+
import path2, { dirname as dirname6 } from "path";
|
|
7966
9769
|
import { execSync as execSync14, spawn as spawn7 } from "child_process";
|
|
7967
9770
|
function getProjectRoot2() {
|
|
7968
9771
|
const squadsDir = findSquadsDir();
|
|
7969
9772
|
if (squadsDir) {
|
|
7970
|
-
return
|
|
9773
|
+
return dirname6(dirname6(squadsDir));
|
|
7971
9774
|
}
|
|
7972
9775
|
return process.cwd();
|
|
7973
9776
|
}
|
|
@@ -8107,7 +9910,7 @@ async function tonightCommand(targets, options) {
|
|
|
8107
9910
|
totalCost: await getCurrentCost(),
|
|
8108
9911
|
stopped: false
|
|
8109
9912
|
};
|
|
8110
|
-
const spinner =
|
|
9913
|
+
const spinner = ora7("Launching agents...").start();
|
|
8111
9914
|
for (const target of targets) {
|
|
8112
9915
|
const sessionId = `${target.replace("/", "-")}-${Date.now()}`;
|
|
8113
9916
|
const logFile = path2.join(logDir, `${sessionId}.log`);
|
|
@@ -8295,17 +10098,18 @@ process.stderr.on("error", (err) => {
|
|
|
8295
10098
|
throw err;
|
|
8296
10099
|
});
|
|
8297
10100
|
var envPaths = [
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
10101
|
+
join21(process.cwd(), ".env"),
|
|
10102
|
+
join21(process.cwd(), "..", "hq", ".env"),
|
|
10103
|
+
join21(homedir5(), "agents-squads", "hq", ".env")
|
|
8301
10104
|
];
|
|
8302
10105
|
for (const envPath of envPaths) {
|
|
8303
|
-
if (
|
|
10106
|
+
if (existsSync19(envPath)) {
|
|
8304
10107
|
config({ path: envPath, quiet: true });
|
|
8305
10108
|
break;
|
|
8306
10109
|
}
|
|
8307
10110
|
}
|
|
8308
10111
|
applyStackConfig();
|
|
10112
|
+
await autoUpdateOnStartup();
|
|
8309
10113
|
registerExitHandler();
|
|
8310
10114
|
var program = new Command();
|
|
8311
10115
|
program.name("squads").description("A CLI for humans and agents").version(version).showSuggestionAfterError(true).configureOutput({
|
|
@@ -8320,10 +10124,20 @@ program.name("squads").description("A CLI for humans and agents").version(versio
|
|
|
8320
10124
|
throw err;
|
|
8321
10125
|
});
|
|
8322
10126
|
program.command("init").description("Initialize a new squad project").option("-t, --template <template>", "Project template", "default").option("--skip-infra", "Skip infrastructure setup prompt").action(initCommand);
|
|
8323
|
-
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").option("-p, --parallel", "Run all agents in parallel (N tmux sessions)").option("-l, --lead", "Lead mode: single orchestrator using Task tool for parallelization").option("-f, --foreground", "Run in foreground (no tmux, blocks terminal)").option("--use-api", "Use API credits instead of subscription").action((target, options) => runCommand(target, { ...options, timeout: parseInt(options.timeout, 10) }));
|
|
10127
|
+
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").option("-p, --parallel", "Run all agents in parallel (N tmux sessions)").option("-l, --lead", "Lead mode: single orchestrator using Task tool for parallelization").option("-f, --foreground", "Run in foreground (no tmux, blocks terminal)").option("--use-api", "Use API credits instead of subscription").option("--effort <level>", "Effort level: high, medium, low (default: from SQUAD.md or high)").option("--skills <skills...>", "Skills to load (skill IDs or local paths)").action((target, options) => runCommand(target, { ...options, timeout: parseInt(options.timeout, 10) }));
|
|
8324
10128
|
program.command("list").description("List agents and squads").option("-s, --squads", "List squads only").option("-a, --agents", "List agents only").action(listCommand);
|
|
8325
10129
|
program.command("status [squad]").description("Show squad status and state").option("-v, --verbose", "Show detailed status").action(statusCommand);
|
|
8326
10130
|
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 }));
|
|
10131
|
+
var context = program.command("context").description("View and manage squad execution context");
|
|
10132
|
+
context.command("show <squad>").description("Show context for a squad (MCP, skills, model, budget)").option("--json", "Output as JSON").action(contextShowCommand);
|
|
10133
|
+
context.command("list").description("List context for all squads").option("--json", "Output as JSON").action(contextListCommand);
|
|
10134
|
+
program.command("cost").description("Show cost summary (today, week, by squad)").option("-s, --squad <squad>", "Filter to specific squad").option("--json", "Output as JSON").action(costCommand);
|
|
10135
|
+
program.command("budget").description("Check budget status for a squad").argument("<squad>", "Squad to check").option("--json", "Output as JSON").action(budgetCheckCommand);
|
|
10136
|
+
var exec2 = program.command("exec").description("View execution history and statistics");
|
|
10137
|
+
exec2.command("list").description("List recent executions").option("-s, --squad <squad>", "Filter by squad").option("-a, --agent <agent>", "Filter by agent").option("--status <status>", "Filter by status (running, completed, failed)").option("-n, --limit <n>", "Number of executions to show", "20").option("--json", "Output as JSON").action((options) => execListCommand({ ...options, limit: parseInt(options.limit, 10) }));
|
|
10138
|
+
exec2.command("show <id>").description("Show execution details").option("--json", "Output as JSON").action(execShowCommand);
|
|
10139
|
+
exec2.command("stats").description("Show execution statistics").option("-s, --squad <squad>", "Filter by squad").option("--json", "Output as JSON").action(execStatsCommand);
|
|
10140
|
+
exec2.action((options) => execListCommand(options));
|
|
8327
10141
|
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);
|
|
8328
10142
|
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);
|
|
8329
10143
|
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);
|
|
@@ -8377,11 +10191,11 @@ sessions.command("history").description("Show session history and statistics").o
|
|
|
8377
10191
|
json: options.json
|
|
8378
10192
|
}));
|
|
8379
10193
|
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) => {
|
|
8380
|
-
const { buildCurrentSessionSummary } = await import("./sessions-
|
|
10194
|
+
const { buildCurrentSessionSummary } = await import("./sessions-B6GVXJ2H.js");
|
|
8381
10195
|
let data;
|
|
8382
10196
|
if (options.file) {
|
|
8383
|
-
const { readFileSync:
|
|
8384
|
-
data = JSON.parse(
|
|
10197
|
+
const { readFileSync: readFileSync17 } = await import("fs");
|
|
10198
|
+
data = JSON.parse(readFileSync17(options.file, "utf-8"));
|
|
8385
10199
|
} else if (options.data) {
|
|
8386
10200
|
data = JSON.parse(options.data);
|
|
8387
10201
|
} else if (!process.stdin.isTTY) {
|
|
@@ -8414,6 +10228,8 @@ stack.command("down").description("Stop Docker containers").action(stackDownComm
|
|
|
8414
10228
|
stack.command("health").description("Comprehensive health check with diagnostics").option("-v, --verbose", "Show logs for unhealthy services").action((options) => stackHealthCommand(options.verbose));
|
|
8415
10229
|
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)));
|
|
8416
10230
|
registerTriggerCommand(program);
|
|
10231
|
+
registerSkillCommand(program);
|
|
10232
|
+
registerPermissionsCommand(program);
|
|
8417
10233
|
var tonight = program.command("tonight").description("Run agents autonomously overnight with safety limits");
|
|
8418
10234
|
tonight.command("run <targets...>").description("Start tonight mode with specified squads/agents").option("-c, --cost-cap <usd>", "Max USD to spend (default: 50)", "50").option("-s, --stop-at <time>", "Stop time HH:MM (default: 07:00)", "07:00").option("-r, --max-retries <n>", "Max restarts per agent (default: 3)", "3").option("-d, --dry-run", "Show what would run without executing").option("-v, --verbose", "Verbose output").action((targets, options) => tonightCommand(targets, {
|
|
8419
10235
|
costCap: parseFloat(options.costCap),
|