squads-cli 0.4.6 → 0.4.7
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-EGOVJOZJ.js} +2 -1
- package/dist/chunk-EGOVJOZJ.js.map +1 -0
- package/dist/cli.js +1841 -83
- package/dist/cli.js.map +1 -1
- package/dist/{sessions-JCQ34BEU.js → sessions-SEITSWEV.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-SEITSWEV.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-EGOVJOZJ.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";
|
|
@@ -286,8 +286,10 @@ function getMultiRepoGitStats(basePath, days = 30) {
|
|
|
286
286
|
activeDays: 0,
|
|
287
287
|
avgCommitsPerDay: 0,
|
|
288
288
|
peakDay: null,
|
|
289
|
-
repos: []
|
|
289
|
+
repos: [],
|
|
290
|
+
recentCommits: []
|
|
290
291
|
};
|
|
292
|
+
const allCommits = [];
|
|
291
293
|
for (const repo of SQUAD_REPOS) {
|
|
292
294
|
const repoPath = join(basePath, repo);
|
|
293
295
|
if (!existsSync(repoPath) || !existsSync(join(repoPath, ".git"))) {
|
|
@@ -303,7 +305,9 @@ function getMultiRepoGitStats(basePath, days = 30) {
|
|
|
303
305
|
const authors = /* @__PURE__ */ new Set();
|
|
304
306
|
let lastCommit = "";
|
|
305
307
|
for (const line of commits) {
|
|
306
|
-
const
|
|
308
|
+
const parts = line.split("|");
|
|
309
|
+
const [hash, author, date, ...messageParts] = parts;
|
|
310
|
+
const message = messageParts.join("|");
|
|
307
311
|
if (!hash) continue;
|
|
308
312
|
stats.totalCommits++;
|
|
309
313
|
authors.add(author);
|
|
@@ -314,6 +318,7 @@ function getMultiRepoGitStats(basePath, days = 30) {
|
|
|
314
318
|
stats.commitsByAuthor.set(author, authorCount + 1);
|
|
315
319
|
const repoCount = stats.commitsByRepo.get(repo) || 0;
|
|
316
320
|
stats.commitsByRepo.set(repo, repoCount + 1);
|
|
321
|
+
allCommits.push({ hash, author, date, message, repo });
|
|
317
322
|
}
|
|
318
323
|
stats.repos.push({
|
|
319
324
|
name: repo,
|
|
@@ -338,6 +343,7 @@ function getMultiRepoGitStats(basePath, days = 30) {
|
|
|
338
343
|
if (peakDate) {
|
|
339
344
|
stats.peakDay = { date: peakDate, count: peakCount };
|
|
340
345
|
}
|
|
346
|
+
stats.recentCommits = allCommits.sort((a, b) => b.date.localeCompare(a.date) || b.hash.localeCompare(a.hash)).slice(0, 5);
|
|
341
347
|
return stats;
|
|
342
348
|
}
|
|
343
349
|
function getActivitySparkline(basePath, days = 7) {
|
|
@@ -487,6 +493,9 @@ var Events = {
|
|
|
487
493
|
CLI_DASHBOARD: "cli.dashboard",
|
|
488
494
|
CLI_WORKERS: "cli.workers",
|
|
489
495
|
CLI_TONIGHT: "cli.tonight",
|
|
496
|
+
CLI_CONTEXT: "cli.context",
|
|
497
|
+
CLI_COST: "cli.cost",
|
|
498
|
+
CLI_EXEC: "cli.exec",
|
|
490
499
|
// Goals
|
|
491
500
|
CLI_GOAL_SET: "cli.goal.set",
|
|
492
501
|
CLI_GOAL_LIST: "cli.goal.list",
|
|
@@ -1153,6 +1162,7 @@ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync
|
|
|
1153
1162
|
// src/lib/squad-parser.ts
|
|
1154
1163
|
import { readFileSync as readFileSync2, existsSync as existsSync3, readdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1155
1164
|
import { join as join3, basename } from "path";
|
|
1165
|
+
import matter from "gray-matter";
|
|
1156
1166
|
function findSquadsDir() {
|
|
1157
1167
|
let dir = process.cwd();
|
|
1158
1168
|
for (let i = 0; i < 5; i++) {
|
|
@@ -1215,17 +1225,24 @@ function listAgents(squadsDir, squadName) {
|
|
|
1215
1225
|
return agents;
|
|
1216
1226
|
}
|
|
1217
1227
|
function parseSquadFile(filePath) {
|
|
1218
|
-
const
|
|
1219
|
-
const
|
|
1228
|
+
const rawContent = readFileSync2(filePath, "utf-8");
|
|
1229
|
+
const { data: frontmatter, content: bodyContent } = matter(rawContent);
|
|
1230
|
+
const fm = frontmatter;
|
|
1231
|
+
const lines = bodyContent.split("\n");
|
|
1220
1232
|
const squad = {
|
|
1221
|
-
name: basename(filePath).replace(".md", ""),
|
|
1222
|
-
mission: "",
|
|
1233
|
+
name: fm.name || basename(filePath).replace(".md", ""),
|
|
1234
|
+
mission: fm.mission || "",
|
|
1223
1235
|
agents: [],
|
|
1224
1236
|
pipelines: [],
|
|
1225
1237
|
triggers: { scheduled: [], event: [], manual: [] },
|
|
1226
1238
|
dependencies: [],
|
|
1227
1239
|
outputPath: "",
|
|
1228
|
-
goals: []
|
|
1240
|
+
goals: [],
|
|
1241
|
+
// Apply frontmatter fields
|
|
1242
|
+
effort: fm.effort,
|
|
1243
|
+
context: fm.context,
|
|
1244
|
+
repo: fm.repo,
|
|
1245
|
+
stack: fm.stack
|
|
1229
1246
|
};
|
|
1230
1247
|
let currentSection = "";
|
|
1231
1248
|
let inTable = false;
|
|
@@ -1245,6 +1262,10 @@ function parseSquadFile(filePath) {
|
|
|
1245
1262
|
squad.mission = line.trim();
|
|
1246
1263
|
}
|
|
1247
1264
|
}
|
|
1265
|
+
const effortMatch = line.match(/^effort:\s*(high|medium|low)/i);
|
|
1266
|
+
if (effortMatch && !squad.effort) {
|
|
1267
|
+
squad.effort = effortMatch[1].toLowerCase();
|
|
1268
|
+
}
|
|
1248
1269
|
if (currentSection.includes("agent") || currentSection.includes("orchestrator") || currentSection.includes("evaluator") || currentSection.includes("builder") || currentSection.includes("priority")) {
|
|
1249
1270
|
if (line.includes("|") && line.includes("Agent")) {
|
|
1250
1271
|
inTable = true;
|
|
@@ -1257,12 +1278,16 @@ function parseSquadFile(filePath) {
|
|
|
1257
1278
|
const roleIdx = tableHeaders.findIndex((h) => h === "role");
|
|
1258
1279
|
const triggerIdx = tableHeaders.findIndex((h) => h === "trigger");
|
|
1259
1280
|
const statusIdx = tableHeaders.findIndex((h) => h === "status");
|
|
1281
|
+
const effortIdx = tableHeaders.findIndex((h) => h === "effort");
|
|
1260
1282
|
if (agentIdx >= 0 && cells[agentIdx]) {
|
|
1283
|
+
const effortValue = effortIdx >= 0 ? cells[effortIdx]?.toLowerCase() : void 0;
|
|
1284
|
+
const effort = ["high", "medium", "low"].includes(effortValue || "") ? effortValue : void 0;
|
|
1261
1285
|
squad.agents.push({
|
|
1262
1286
|
name: cells[agentIdx],
|
|
1263
1287
|
role: roleIdx >= 0 ? cells[roleIdx] : "",
|
|
1264
1288
|
trigger: triggerIdx >= 0 ? cells[triggerIdx] : "manual",
|
|
1265
|
-
status: statusIdx >= 0 ? cells[statusIdx] : "active"
|
|
1289
|
+
status: statusIdx >= 0 ? cells[statusIdx] : "active",
|
|
1290
|
+
effort
|
|
1266
1291
|
});
|
|
1267
1292
|
}
|
|
1268
1293
|
}
|
|
@@ -1409,7 +1434,280 @@ function updateGoalInSquad(squadName, goalIndex, updates) {
|
|
|
1409
1434
|
return false;
|
|
1410
1435
|
}
|
|
1411
1436
|
|
|
1437
|
+
// src/lib/permissions.ts
|
|
1438
|
+
import { minimatch } from "minimatch";
|
|
1439
|
+
function getDefaultContext(squad, agent) {
|
|
1440
|
+
return {
|
|
1441
|
+
squad,
|
|
1442
|
+
agent,
|
|
1443
|
+
permissions: {
|
|
1444
|
+
mode: "warn",
|
|
1445
|
+
bash: ["*"],
|
|
1446
|
+
// All commands allowed by default
|
|
1447
|
+
write: ["**"],
|
|
1448
|
+
// All paths writable by default
|
|
1449
|
+
read: ["**"],
|
|
1450
|
+
// All paths readable by default
|
|
1451
|
+
mcp: {
|
|
1452
|
+
allow: ["*"],
|
|
1453
|
+
// All MCP servers allowed
|
|
1454
|
+
deny: []
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
function validateBashCommand(command, allowedCommands) {
|
|
1460
|
+
const baseCommand = command.trim().split(/\s+/)[0];
|
|
1461
|
+
if (allowedCommands.includes("*")) {
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
const isAllowed = allowedCommands.some((allowed) => {
|
|
1465
|
+
if (allowed === baseCommand) return true;
|
|
1466
|
+
if (allowed.includes("*")) {
|
|
1467
|
+
return minimatch(baseCommand, allowed);
|
|
1468
|
+
}
|
|
1469
|
+
return false;
|
|
1470
|
+
});
|
|
1471
|
+
if (!isAllowed) {
|
|
1472
|
+
return {
|
|
1473
|
+
type: "bash",
|
|
1474
|
+
requested: baseCommand,
|
|
1475
|
+
reason: `Bash command '${baseCommand}' not in allowlist: [${allowedCommands.join(", ")}]`,
|
|
1476
|
+
severity: "error"
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
function validateFilePath(path3, allowedGlobs, operation) {
|
|
1482
|
+
if (allowedGlobs.includes("**") || allowedGlobs.includes("*")) {
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1485
|
+
const isAllowed = allowedGlobs.some((glob) => minimatch(path3, glob));
|
|
1486
|
+
if (!isAllowed) {
|
|
1487
|
+
return {
|
|
1488
|
+
type: operation,
|
|
1489
|
+
requested: path3,
|
|
1490
|
+
reason: `${operation === "write" ? "Write" : "Read"} to '${path3}' not allowed. Permitted paths: [${allowedGlobs.join(", ")}]`,
|
|
1491
|
+
severity: "error"
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
return null;
|
|
1495
|
+
}
|
|
1496
|
+
function validateMcpServer(server, allow, deny) {
|
|
1497
|
+
const isDenied = deny.some((pattern) => {
|
|
1498
|
+
if (pattern === server) return true;
|
|
1499
|
+
if (pattern.includes("*")) return minimatch(server, pattern);
|
|
1500
|
+
return false;
|
|
1501
|
+
});
|
|
1502
|
+
if (isDenied) {
|
|
1503
|
+
return {
|
|
1504
|
+
type: "mcp",
|
|
1505
|
+
requested: server,
|
|
1506
|
+
reason: `MCP server '${server}' is explicitly denied`,
|
|
1507
|
+
severity: "error"
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
if (allow.includes("*")) {
|
|
1511
|
+
return null;
|
|
1512
|
+
}
|
|
1513
|
+
const isAllowed = allow.some((pattern) => {
|
|
1514
|
+
if (pattern === server) return true;
|
|
1515
|
+
if (pattern.includes("*")) return minimatch(server, pattern);
|
|
1516
|
+
return false;
|
|
1517
|
+
});
|
|
1518
|
+
if (!isAllowed) {
|
|
1519
|
+
return {
|
|
1520
|
+
type: "mcp",
|
|
1521
|
+
requested: server,
|
|
1522
|
+
reason: `MCP server '${server}' not in allowlist: [${allow.join(", ")}]`,
|
|
1523
|
+
severity: "error"
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
function validateExecution(context2, request) {
|
|
1529
|
+
const violations = [];
|
|
1530
|
+
if (request.bashCommands) {
|
|
1531
|
+
for (const cmd of request.bashCommands) {
|
|
1532
|
+
const violation = validateBashCommand(cmd, context2.permissions.bash);
|
|
1533
|
+
if (violation) {
|
|
1534
|
+
violations.push(violation);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
if (request.writePaths) {
|
|
1539
|
+
for (const path3 of request.writePaths) {
|
|
1540
|
+
const violation = validateFilePath(path3, context2.permissions.write, "write");
|
|
1541
|
+
if (violation) {
|
|
1542
|
+
violations.push(violation);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
if (request.readPaths) {
|
|
1547
|
+
for (const path3 of request.readPaths) {
|
|
1548
|
+
const violation = validateFilePath(path3, context2.permissions.read, "read");
|
|
1549
|
+
if (violation) {
|
|
1550
|
+
violations.push(violation);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
if (request.mcpServers) {
|
|
1555
|
+
for (const server of request.mcpServers) {
|
|
1556
|
+
const violation = validateMcpServer(
|
|
1557
|
+
server,
|
|
1558
|
+
context2.permissions.mcp.allow,
|
|
1559
|
+
context2.permissions.mcp.deny
|
|
1560
|
+
);
|
|
1561
|
+
if (violation) {
|
|
1562
|
+
violations.push(violation);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
const hasErrors = violations.some((v) => v.severity === "error");
|
|
1567
|
+
const allowed = context2.permissions.mode !== "strict" || !hasErrors;
|
|
1568
|
+
return {
|
|
1569
|
+
allowed,
|
|
1570
|
+
violations,
|
|
1571
|
+
mode: context2.permissions.mode
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
function formatViolations(result) {
|
|
1575
|
+
const lines = [];
|
|
1576
|
+
if (result.violations.length === 0) {
|
|
1577
|
+
return lines;
|
|
1578
|
+
}
|
|
1579
|
+
const modeLabel = {
|
|
1580
|
+
warn: "\u26A0\uFE0F PERMISSION WARNING",
|
|
1581
|
+
strict: "\u{1F6AB} PERMISSION DENIED",
|
|
1582
|
+
audit: "\u{1F4DD} PERMISSION AUDIT"
|
|
1583
|
+
}[result.mode];
|
|
1584
|
+
lines.push(modeLabel);
|
|
1585
|
+
lines.push("");
|
|
1586
|
+
for (const v of result.violations) {
|
|
1587
|
+
const icon = v.severity === "error" ? "\u2717" : "\u26A0";
|
|
1588
|
+
lines.push(` ${icon} [${v.type}] ${v.reason}`);
|
|
1589
|
+
}
|
|
1590
|
+
if (result.mode === "warn") {
|
|
1591
|
+
lines.push("");
|
|
1592
|
+
lines.push(" Continuing with warnings (mode: warn)");
|
|
1593
|
+
} else if (result.mode === "audit") {
|
|
1594
|
+
lines.push("");
|
|
1595
|
+
lines.push(" Logged for audit, continuing (mode: audit)");
|
|
1596
|
+
} else if (!result.allowed) {
|
|
1597
|
+
lines.push("");
|
|
1598
|
+
lines.push(" Execution blocked (mode: strict)");
|
|
1599
|
+
}
|
|
1600
|
+
return lines;
|
|
1601
|
+
}
|
|
1602
|
+
function parsePermissionsYaml(content) {
|
|
1603
|
+
const yamlMatch = content.match(/```ya?ml\n([\s\S]*?)```/);
|
|
1604
|
+
if (!yamlMatch) return null;
|
|
1605
|
+
const yamlContent = yamlMatch[1];
|
|
1606
|
+
const permissions = {};
|
|
1607
|
+
const modeMatch = yamlContent.match(/^\s*mode:\s*(warn|strict|audit)/m);
|
|
1608
|
+
if (modeMatch) {
|
|
1609
|
+
permissions.mode = modeMatch[1];
|
|
1610
|
+
}
|
|
1611
|
+
const bashMatch = yamlContent.match(/^\s*bash:\s*\[(.*?)\]/m);
|
|
1612
|
+
if (bashMatch) {
|
|
1613
|
+
permissions.bash = bashMatch[1].split(",").map((s) => s.trim());
|
|
1614
|
+
}
|
|
1615
|
+
const writeMatch = yamlContent.match(/^\s*write:\s*\[(.*?)\]/m);
|
|
1616
|
+
if (writeMatch) {
|
|
1617
|
+
permissions.write = writeMatch[1].split(",").map((s) => s.trim());
|
|
1618
|
+
}
|
|
1619
|
+
const readMatch = yamlContent.match(/^\s*read:\s*\[(.*?)\]/m);
|
|
1620
|
+
if (readMatch) {
|
|
1621
|
+
permissions.read = readMatch[1].split(",").map((s) => s.trim());
|
|
1622
|
+
}
|
|
1623
|
+
const mcpAllowMatch = yamlContent.match(/^\s*allow:\s*\[(.*?)\]/m);
|
|
1624
|
+
const mcpDenyMatch = yamlContent.match(/^\s*deny:\s*\[(.*?)\]/m);
|
|
1625
|
+
if (mcpAllowMatch || mcpDenyMatch) {
|
|
1626
|
+
permissions.mcp = {
|
|
1627
|
+
allow: mcpAllowMatch ? mcpAllowMatch[1].split(",").map((s) => s.trim()) : ["*"],
|
|
1628
|
+
deny: mcpDenyMatch ? mcpDenyMatch[1].split(",").map((s) => s.trim()) : []
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
return Object.keys(permissions).length > 0 ? permissions : null;
|
|
1632
|
+
}
|
|
1633
|
+
function buildContextFromSquad(squadName, squadContent, agentName) {
|
|
1634
|
+
const context2 = getDefaultContext(squadName, agentName);
|
|
1635
|
+
const parsed = parsePermissionsYaml(squadContent);
|
|
1636
|
+
if (parsed) {
|
|
1637
|
+
if (parsed.mode) context2.permissions.mode = parsed.mode;
|
|
1638
|
+
if (parsed.bash) context2.permissions.bash = parsed.bash;
|
|
1639
|
+
if (parsed.write) context2.permissions.write = parsed.write;
|
|
1640
|
+
if (parsed.read) context2.permissions.read = parsed.read;
|
|
1641
|
+
if (parsed.mcp) context2.permissions.mcp = parsed.mcp;
|
|
1642
|
+
}
|
|
1643
|
+
return context2;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1412
1646
|
// src/commands/run.ts
|
|
1647
|
+
async function registerContextWithBridge(ctx) {
|
|
1648
|
+
const bridgeUrl = process.env.SQUADS_BRIDGE_URL || "http://localhost:8088";
|
|
1649
|
+
try {
|
|
1650
|
+
const response = await fetch(`${bridgeUrl}/api/context/register`, {
|
|
1651
|
+
method: "POST",
|
|
1652
|
+
headers: { "Content-Type": "application/json" },
|
|
1653
|
+
body: JSON.stringify({
|
|
1654
|
+
execution_id: ctx.executionId,
|
|
1655
|
+
squad: ctx.squad,
|
|
1656
|
+
agent: ctx.agent,
|
|
1657
|
+
task_type: ctx.taskType,
|
|
1658
|
+
trigger: ctx.trigger
|
|
1659
|
+
})
|
|
1660
|
+
});
|
|
1661
|
+
if (!response.ok) {
|
|
1662
|
+
return false;
|
|
1663
|
+
}
|
|
1664
|
+
return true;
|
|
1665
|
+
} catch {
|
|
1666
|
+
return false;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
function generateExecutionId() {
|
|
1670
|
+
const timestamp = Date.now().toString(36);
|
|
1671
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
1672
|
+
return `exec_${timestamp}_${random}`;
|
|
1673
|
+
}
|
|
1674
|
+
function selectMcpConfig(squadName) {
|
|
1675
|
+
const home = process.env.HOME || "";
|
|
1676
|
+
const configsDir = join4(home, ".claude", "mcp-configs");
|
|
1677
|
+
const squadConfigs = {
|
|
1678
|
+
website: "website.json",
|
|
1679
|
+
// chrome-devtools, nano-banana
|
|
1680
|
+
research: "research.json",
|
|
1681
|
+
// x-mcp
|
|
1682
|
+
intelligence: "research.json",
|
|
1683
|
+
// x-mcp
|
|
1684
|
+
analytics: "data.json",
|
|
1685
|
+
// supabase, grafana, ga4-admin
|
|
1686
|
+
engineering: "data.json"
|
|
1687
|
+
// supabase, grafana
|
|
1688
|
+
};
|
|
1689
|
+
const configFile = squadConfigs[squadName.toLowerCase()];
|
|
1690
|
+
if (configFile) {
|
|
1691
|
+
const configPath = join4(configsDir, configFile);
|
|
1692
|
+
if (existsSync4(configPath)) {
|
|
1693
|
+
return configPath;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
return join4(home, ".claude.json");
|
|
1697
|
+
}
|
|
1698
|
+
function detectTaskType(agentName) {
|
|
1699
|
+
const name = agentName.toLowerCase();
|
|
1700
|
+
if (name.includes("eval") || name.includes("critic") || name.includes("review") || name.includes("test")) {
|
|
1701
|
+
return "evaluation";
|
|
1702
|
+
}
|
|
1703
|
+
if (name.includes("lead") || name.includes("orchestrator")) {
|
|
1704
|
+
return "lead";
|
|
1705
|
+
}
|
|
1706
|
+
if (name.includes("research") || name.includes("analyst") || name.includes("intel")) {
|
|
1707
|
+
return "research";
|
|
1708
|
+
}
|
|
1709
|
+
return "execution";
|
|
1710
|
+
}
|
|
1413
1711
|
function ensureProjectTrusted(projectPath) {
|
|
1414
1712
|
const configPath = join4(process.env.HOME || "", ".claude.json");
|
|
1415
1713
|
if (!existsSync4(configPath)) {
|
|
@@ -1451,34 +1749,75 @@ function logExecution(record) {
|
|
|
1451
1749
|
}
|
|
1452
1750
|
let content = "";
|
|
1453
1751
|
if (existsSync4(logPath)) {
|
|
1454
|
-
content = readFileSync3(logPath, "utf-8");
|
|
1752
|
+
content = readFileSync3(logPath, "utf-8").trimEnd();
|
|
1455
1753
|
} else {
|
|
1456
|
-
content = `# ${record.squadName}/${record.agentName} - Execution Log
|
|
1457
|
-
|
|
1458
|
-
`;
|
|
1754
|
+
content = `# ${record.squadName}/${record.agentName} - Execution Log`;
|
|
1459
1755
|
}
|
|
1460
1756
|
const entry = `
|
|
1757
|
+
|
|
1461
1758
|
---
|
|
1759
|
+
<!-- exec:${record.executionId} -->
|
|
1462
1760
|
**${record.startTime}** | Status: ${record.status}
|
|
1463
|
-
|
|
1464
|
-
|
|
1761
|
+
- ID: \`${record.executionId}\`
|
|
1762
|
+
- Trigger: ${record.trigger || "manual"}
|
|
1763
|
+
- Task Type: ${record.taskType || "execution"}
|
|
1465
1764
|
`;
|
|
1466
1765
|
writeFileSync3(logPath, content + entry);
|
|
1467
1766
|
}
|
|
1468
|
-
function updateExecutionStatus(squadName, agentName, status,
|
|
1767
|
+
function updateExecutionStatus(squadName, agentName, executionId, status, details) {
|
|
1469
1768
|
const logPath = getExecutionLogPath(squadName, agentName);
|
|
1470
1769
|
if (!logPath || !existsSync4(logPath)) return;
|
|
1471
1770
|
let content = readFileSync3(logPath, "utf-8");
|
|
1472
1771
|
const endTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
);
|
|
1772
|
+
const execMarker = `<!-- exec:${executionId} -->`;
|
|
1773
|
+
const markerIndex = content.indexOf(execMarker);
|
|
1774
|
+
if (markerIndex === -1) return;
|
|
1775
|
+
const nextEntryIndex = content.indexOf("\n---\n", markerIndex + 1);
|
|
1776
|
+
const entryEnd = nextEntryIndex === -1 ? content.length : nextEntryIndex;
|
|
1777
|
+
const entryStart = content.lastIndexOf("\n---\n", markerIndex);
|
|
1778
|
+
const currentEntry = content.slice(entryStart, entryEnd);
|
|
1779
|
+
const durationStr = details?.durationMs ? `${(details.durationMs / 1e3).toFixed(1)}s` : "unknown";
|
|
1780
|
+
let updatedEntry = currentEntry.replace(/Status: running/, `Status: ${status}`) + `- Completed: ${endTime}
|
|
1781
|
+
- Duration: ${durationStr}`;
|
|
1782
|
+
if (details?.outcome) {
|
|
1783
|
+
updatedEntry += `
|
|
1784
|
+
- Outcome: ${details.outcome}`;
|
|
1785
|
+
}
|
|
1786
|
+
if (details?.error) {
|
|
1787
|
+
updatedEntry += `
|
|
1788
|
+
- Error: ${details.error}`;
|
|
1789
|
+
}
|
|
1790
|
+
content = content.slice(0, entryStart) + updatedEntry + content.slice(entryEnd);
|
|
1480
1791
|
writeFileSync3(logPath, content);
|
|
1481
1792
|
}
|
|
1793
|
+
function extractMcpServersFromDefinition(definition) {
|
|
1794
|
+
const servers = /* @__PURE__ */ new Set();
|
|
1795
|
+
const knownServers = [
|
|
1796
|
+
"chrome-devtools",
|
|
1797
|
+
"firecrawl",
|
|
1798
|
+
"supabase",
|
|
1799
|
+
"grafana",
|
|
1800
|
+
"context7",
|
|
1801
|
+
"huggingface",
|
|
1802
|
+
"nano-banana"
|
|
1803
|
+
];
|
|
1804
|
+
for (const server of knownServers) {
|
|
1805
|
+
if (definition.toLowerCase().includes(server)) {
|
|
1806
|
+
servers.add(server);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
const mcpMatch = definition.match(/mcp:\s*\n((?:\s*-\s*\S+\s*\n?)+)/i);
|
|
1810
|
+
if (mcpMatch) {
|
|
1811
|
+
const lines = mcpMatch[1].split("\n");
|
|
1812
|
+
for (const line of lines) {
|
|
1813
|
+
const serverMatch = line.match(/^\s*-\s*(\S+)/);
|
|
1814
|
+
if (serverMatch) {
|
|
1815
|
+
servers.add(serverMatch[1]);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
return Array.from(servers);
|
|
1820
|
+
}
|
|
1482
1821
|
async function runCommand(target, options) {
|
|
1483
1822
|
const squadsDir = findSquadsDir();
|
|
1484
1823
|
if (!squadsDir) {
|
|
@@ -1508,6 +1847,9 @@ async function runCommand(target, options) {
|
|
|
1508
1847
|
}
|
|
1509
1848
|
async function runSquad(squad, squadsDir, options) {
|
|
1510
1849
|
if (!squad) return;
|
|
1850
|
+
if (!options.effort && squad.effort) {
|
|
1851
|
+
options.effort = squad.effort;
|
|
1852
|
+
}
|
|
1511
1853
|
const startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1512
1854
|
writeLine();
|
|
1513
1855
|
writeLine(` ${gradient("squads")} ${colors.dim}run${RESET} ${colors.cyan}${squad.name}${RESET}`);
|
|
@@ -1697,7 +2039,7 @@ Begin by assessing pending work, then delegate to agents via Task tool.`;
|
|
|
1697
2039
|
writeLine(` ${gradient("Launching")} lead session${options.foreground ? " (foreground)" : ""}...`);
|
|
1698
2040
|
writeLine();
|
|
1699
2041
|
try {
|
|
1700
|
-
const result = await executeWithClaude(prompt2, options.verbose, timeoutMins, options.foreground, options.useApi);
|
|
2042
|
+
const result = await executeWithClaude(prompt2, options.verbose, timeoutMins, options.foreground, options.useApi, options.effort, options.skills, options.trigger || "manual");
|
|
1701
2043
|
if (options.foreground) {
|
|
1702
2044
|
writeLine();
|
|
1703
2045
|
writeLine(` ${icons.success} Lead session completed`);
|
|
@@ -1718,7 +2060,10 @@ Begin by assessing pending work, then delegate to agents via Task tool.`;
|
|
|
1718
2060
|
}
|
|
1719
2061
|
async function runAgent(agentName, agentPath, squadName, options) {
|
|
1720
2062
|
const spinner = ora2(`Running agent: ${agentName}`).start();
|
|
1721
|
-
const
|
|
2063
|
+
const startMs = Date.now();
|
|
2064
|
+
const startTime = new Date(startMs).toISOString();
|
|
2065
|
+
const executionId = generateExecutionId();
|
|
2066
|
+
const taskType = detectTaskType(agentName);
|
|
1722
2067
|
const definition = loadAgentDefinition(agentPath);
|
|
1723
2068
|
if (options.dryRun) {
|
|
1724
2069
|
spinner.info(`[DRY RUN] Would run ${agentName}`);
|
|
@@ -1728,11 +2073,40 @@ async function runAgent(agentName, agentPath, squadName, options) {
|
|
|
1728
2073
|
}
|
|
1729
2074
|
return;
|
|
1730
2075
|
}
|
|
2076
|
+
const squadsDir = findSquadsDir();
|
|
2077
|
+
if (squadsDir) {
|
|
2078
|
+
const squadFilePath = join4(squadsDir, squadName, "SQUAD.md");
|
|
2079
|
+
if (existsSync4(squadFilePath)) {
|
|
2080
|
+
const squadContent = readFileSync3(squadFilePath, "utf-8");
|
|
2081
|
+
const permContext = buildContextFromSquad(squadName, squadContent, agentName);
|
|
2082
|
+
const mcpServers = extractMcpServersFromDefinition(definition);
|
|
2083
|
+
const execRequest = {
|
|
2084
|
+
mcpServers
|
|
2085
|
+
};
|
|
2086
|
+
const permResult = validateExecution(permContext, execRequest);
|
|
2087
|
+
if (permResult.violations.length > 0) {
|
|
2088
|
+
spinner.stop();
|
|
2089
|
+
const violationLines = formatViolations(permResult);
|
|
2090
|
+
for (const line of violationLines) {
|
|
2091
|
+
writeLine(` ${line}`);
|
|
2092
|
+
}
|
|
2093
|
+
writeLine();
|
|
2094
|
+
if (!permResult.allowed) {
|
|
2095
|
+
writeLine(` ${colors.red}Execution blocked due to permission violations.${RESET}`);
|
|
2096
|
+
writeLine(` ${colors.dim}Configure permissions in ${squadFilePath}${RESET}`);
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
1731
2102
|
logExecution({
|
|
1732
2103
|
squadName,
|
|
1733
2104
|
agentName,
|
|
2105
|
+
executionId,
|
|
1734
2106
|
startTime,
|
|
1735
|
-
status: "running"
|
|
2107
|
+
status: "running",
|
|
2108
|
+
trigger: options.trigger || "manual",
|
|
2109
|
+
taskType
|
|
1736
2110
|
});
|
|
1737
2111
|
const timeoutMins = options.timeout || 30;
|
|
1738
2112
|
const prompt2 = `Execute the ${agentName} agent from squad ${squadName}.
|
|
@@ -1764,7 +2138,7 @@ CRITICAL: When you have completed your tasks OR reached the time limit:
|
|
|
1764
2138
|
if (options.execute && claudeAvailable) {
|
|
1765
2139
|
spinner.text = options.foreground ? `Running ${agentName} in foreground...` : `Launching ${agentName} as background task...`;
|
|
1766
2140
|
try {
|
|
1767
|
-
const result = await executeWithClaude(prompt2, options.verbose, options.timeout || 30, options.foreground, options.useApi);
|
|
2141
|
+
const result = await executeWithClaude(prompt2, options.verbose, options.timeout || 30, options.foreground, options.useApi, options.effort, options.skills, options.trigger || "manual");
|
|
1768
2142
|
if (options.foreground) {
|
|
1769
2143
|
spinner.succeed(`Agent ${agentName} completed`);
|
|
1770
2144
|
} else {
|
|
@@ -1776,7 +2150,10 @@ CRITICAL: When you have completed your tasks OR reached the time limit:
|
|
|
1776
2150
|
}
|
|
1777
2151
|
} catch (error) {
|
|
1778
2152
|
spinner.fail(`Agent ${agentName} failed to launch`);
|
|
1779
|
-
updateExecutionStatus(squadName, agentName, "failed",
|
|
2153
|
+
updateExecutionStatus(squadName, agentName, executionId, "failed", {
|
|
2154
|
+
error: String(error),
|
|
2155
|
+
durationMs: Date.now() - startMs
|
|
2156
|
+
});
|
|
1780
2157
|
writeLine(` ${colors.red}${String(error)}${RESET}`);
|
|
1781
2158
|
}
|
|
1782
2159
|
} else {
|
|
@@ -1802,28 +2179,45 @@ async function checkClaudeCliAvailable() {
|
|
|
1802
2179
|
check.on("error", () => resolve(false));
|
|
1803
2180
|
});
|
|
1804
2181
|
}
|
|
1805
|
-
async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foreground, useApi) {
|
|
1806
|
-
const userConfigPath = join4(process.env.HOME || "", ".claude.json");
|
|
2182
|
+
async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foreground, useApi, effort, skills, trigger = "manual") {
|
|
1807
2183
|
const projectRoot = getProjectRoot();
|
|
1808
2184
|
ensureProjectTrusted(projectRoot);
|
|
1809
2185
|
const squadMatch = prompt2.match(/squad (\w+)/);
|
|
1810
2186
|
const agentMatch = prompt2.match(/(\w+) agent/);
|
|
1811
2187
|
const squadName = process.env.SQUADS_SQUAD || squadMatch?.[1] || "unknown";
|
|
1812
2188
|
const agentName = process.env.SQUADS_AGENT || agentMatch?.[1] || "unknown";
|
|
2189
|
+
const mcpConfigPath = selectMcpConfig(squadName);
|
|
2190
|
+
const execContext = {
|
|
2191
|
+
squad: squadName,
|
|
2192
|
+
agent: agentName,
|
|
2193
|
+
taskType: detectTaskType(agentName),
|
|
2194
|
+
trigger,
|
|
2195
|
+
executionId: generateExecutionId()
|
|
2196
|
+
};
|
|
1813
2197
|
const { ANTHROPIC_API_KEY: _apiKey, ...envWithoutApiKey } = process.env;
|
|
1814
2198
|
const spawnEnv = useApi ? process.env : envWithoutApiKey;
|
|
1815
2199
|
const escapedPrompt = prompt2.replace(/'/g, "'\\''");
|
|
2200
|
+
await registerContextWithBridge(execContext);
|
|
1816
2201
|
if (foreground) {
|
|
1817
2202
|
if (verbose) {
|
|
1818
2203
|
writeLine(` ${colors.dim}Project: ${projectRoot}${RESET}`);
|
|
1819
2204
|
writeLine(` ${colors.dim}Mode: foreground${RESET}`);
|
|
1820
2205
|
writeLine(` ${colors.dim}Auth: ${useApi ? "API credits" : "subscription"}${RESET}`);
|
|
2206
|
+
writeLine(` ${colors.dim}Execution: ${execContext.executionId}${RESET}`);
|
|
2207
|
+
writeLine(` ${colors.dim}Task type: ${execContext.taskType}${RESET}`);
|
|
2208
|
+
writeLine(` ${colors.dim}Trigger: ${execContext.trigger}${RESET}`);
|
|
2209
|
+
if (effort) {
|
|
2210
|
+
writeLine(` ${colors.dim}Effort: ${effort}${RESET}`);
|
|
2211
|
+
}
|
|
2212
|
+
if (skills && skills.length > 0) {
|
|
2213
|
+
writeLine(` ${colors.dim}Skills: ${skills.join(", ")}${RESET}`);
|
|
2214
|
+
}
|
|
1821
2215
|
}
|
|
1822
2216
|
return new Promise((resolve, reject) => {
|
|
1823
2217
|
const claude = spawn2("claude", [
|
|
1824
2218
|
"--dangerously-skip-permissions",
|
|
1825
2219
|
"--mcp-config",
|
|
1826
|
-
|
|
2220
|
+
mcpConfigPath,
|
|
1827
2221
|
"--",
|
|
1828
2222
|
prompt2
|
|
1829
2223
|
], {
|
|
@@ -1831,8 +2225,17 @@ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foregro
|
|
|
1831
2225
|
cwd: projectRoot,
|
|
1832
2226
|
env: {
|
|
1833
2227
|
...spawnEnv,
|
|
1834
|
-
|
|
1835
|
-
|
|
2228
|
+
// Telemetry context for per-agent cost tracking
|
|
2229
|
+
SQUADS_SQUAD: execContext.squad,
|
|
2230
|
+
SQUADS_AGENT: execContext.agent,
|
|
2231
|
+
SQUADS_TASK_TYPE: execContext.taskType,
|
|
2232
|
+
SQUADS_TRIGGER: execContext.trigger,
|
|
2233
|
+
SQUADS_EXECUTION_ID: execContext.executionId,
|
|
2234
|
+
// OTel resource attributes for telemetry pipeline
|
|
2235
|
+
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}`,
|
|
2236
|
+
// Claude-specific options
|
|
2237
|
+
...effort && { CLAUDE_EFFORT: effort },
|
|
2238
|
+
...skills && skills.length > 0 && { CLAUDE_SKILLS: skills.join(",") }
|
|
1836
2239
|
}
|
|
1837
2240
|
});
|
|
1838
2241
|
claude.on("close", (code) => {
|
|
@@ -1851,9 +2254,19 @@ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foregro
|
|
|
1851
2254
|
if (verbose) {
|
|
1852
2255
|
writeLine(` ${colors.dim}Project: ${projectRoot}${RESET}`);
|
|
1853
2256
|
writeLine(` ${colors.dim}Session: ${sessionName}${RESET}`);
|
|
2257
|
+
writeLine(` ${colors.dim}MCP config: ${mcpConfigPath}${RESET}`);
|
|
1854
2258
|
writeLine(` ${colors.dim}Auth: ${useApi ? "API credits" : "subscription"}${RESET}`);
|
|
2259
|
+
writeLine(` ${colors.dim}Execution: ${execContext.executionId}${RESET}`);
|
|
2260
|
+
writeLine(` ${colors.dim}Task type: ${execContext.taskType}${RESET}`);
|
|
2261
|
+
writeLine(` ${colors.dim}Trigger: ${execContext.trigger}${RESET}`);
|
|
2262
|
+
if (effort) {
|
|
2263
|
+
writeLine(` ${colors.dim}Effort: ${effort}${RESET}`);
|
|
2264
|
+
}
|
|
2265
|
+
if (skills && skills.length > 0) {
|
|
2266
|
+
writeLine(` ${colors.dim}Skills: ${skills.join(", ")}${RESET}`);
|
|
2267
|
+
}
|
|
1855
2268
|
}
|
|
1856
|
-
const claudeCmd = `cd '${projectRoot}' && claude --dangerously-skip-permissions --mcp-config '${
|
|
2269
|
+
const claudeCmd = `cd '${projectRoot}' && claude --dangerously-skip-permissions --mcp-config '${mcpConfigPath}' -- '${escapedPrompt}'; tmux kill-session -t ${sessionName} 2>/dev/null`;
|
|
1857
2270
|
const tmux = spawn2("tmux", [
|
|
1858
2271
|
"new-session",
|
|
1859
2272
|
"-d",
|
|
@@ -1873,8 +2286,17 @@ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foregro
|
|
|
1873
2286
|
detached: true,
|
|
1874
2287
|
env: {
|
|
1875
2288
|
...spawnEnv,
|
|
1876
|
-
|
|
1877
|
-
|
|
2289
|
+
// Telemetry context for per-agent cost tracking
|
|
2290
|
+
SQUADS_SQUAD: execContext.squad,
|
|
2291
|
+
SQUADS_AGENT: execContext.agent,
|
|
2292
|
+
SQUADS_TASK_TYPE: execContext.taskType,
|
|
2293
|
+
SQUADS_TRIGGER: execContext.trigger,
|
|
2294
|
+
SQUADS_EXECUTION_ID: execContext.executionId,
|
|
2295
|
+
// OTel resource attributes for telemetry pipeline
|
|
2296
|
+
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}`,
|
|
2297
|
+
// Claude-specific options
|
|
2298
|
+
...effort && { CLAUDE_EFFORT: effort },
|
|
2299
|
+
...skills && skills.length > 0 && { CLAUDE_SKILLS: skills.join(",") }
|
|
1878
2300
|
}
|
|
1879
2301
|
});
|
|
1880
2302
|
tmux.unref();
|
|
@@ -4705,9 +5127,9 @@ function getPool() {
|
|
|
4705
5127
|
async function isDatabaseAvailable() {
|
|
4706
5128
|
try {
|
|
4707
5129
|
const pool2 = getPool();
|
|
4708
|
-
const
|
|
4709
|
-
await
|
|
4710
|
-
|
|
5130
|
+
const client2 = await pool2.connect();
|
|
5131
|
+
await client2.query("SELECT 1");
|
|
5132
|
+
client2.release();
|
|
4711
5133
|
return true;
|
|
4712
5134
|
} catch (err) {
|
|
4713
5135
|
if (process.env.DEBUG) {
|
|
@@ -4719,8 +5141,8 @@ async function isDatabaseAvailable() {
|
|
|
4719
5141
|
async function saveDashboardSnapshot(snapshot) {
|
|
4720
5142
|
try {
|
|
4721
5143
|
const pool2 = getPool();
|
|
4722
|
-
const
|
|
4723
|
-
const result = await
|
|
5144
|
+
const client2 = await pool2.connect();
|
|
5145
|
+
const result = await client2.query(`
|
|
4724
5146
|
INSERT INTO squads.dashboard_snapshots (
|
|
4725
5147
|
total_squads, total_commits, total_prs_merged, total_issues_closed, total_issues_open,
|
|
4726
5148
|
goal_progress_pct, cost_usd, daily_budget_usd, input_tokens, output_tokens,
|
|
@@ -4749,7 +5171,7 @@ async function saveDashboardSnapshot(snapshot) {
|
|
|
4749
5171
|
JSON.stringify(snapshot.authorsData),
|
|
4750
5172
|
JSON.stringify(snapshot.reposData)
|
|
4751
5173
|
]);
|
|
4752
|
-
|
|
5174
|
+
client2.release();
|
|
4753
5175
|
return result.rows[0]?.id ?? null;
|
|
4754
5176
|
} catch (err) {
|
|
4755
5177
|
if (process.env.DEBUG) {
|
|
@@ -4760,8 +5182,8 @@ async function saveDashboardSnapshot(snapshot) {
|
|
|
4760
5182
|
}
|
|
4761
5183
|
async function getDashboardHistory(limit = 30) {
|
|
4762
5184
|
try {
|
|
4763
|
-
const
|
|
4764
|
-
const result = await
|
|
5185
|
+
const client2 = await getPool().connect();
|
|
5186
|
+
const result = await client2.query(`
|
|
4765
5187
|
SELECT
|
|
4766
5188
|
total_squads, total_commits, total_prs_merged, total_issues_closed, total_issues_open,
|
|
4767
5189
|
goal_progress_pct, cost_usd, daily_budget_usd, input_tokens, output_tokens,
|
|
@@ -4771,7 +5193,7 @@ async function getDashboardHistory(limit = 30) {
|
|
|
4771
5193
|
ORDER BY captured_at DESC
|
|
4772
5194
|
LIMIT $1
|
|
4773
5195
|
`, [limit]);
|
|
4774
|
-
|
|
5196
|
+
client2.release();
|
|
4775
5197
|
return result.rows.map((row) => ({
|
|
4776
5198
|
totalSquads: row.total_squads,
|
|
4777
5199
|
totalCommits: row.total_commits,
|
|
@@ -5001,16 +5423,50 @@ async function dashboardCommand(options = {}) {
|
|
|
5001
5423
|
renderAcquisitionCached(cache);
|
|
5002
5424
|
renderHistoricalTrendsCached(cache);
|
|
5003
5425
|
renderInsightsCached(cache);
|
|
5426
|
+
if (gitStats && gitStats.recentCommits && gitStats.recentCommits.length > 0) {
|
|
5427
|
+
writeLine(` ${bold}Working On${RESET}`);
|
|
5428
|
+
writeLine();
|
|
5429
|
+
for (const commit of gitStats.recentCommits.slice(0, 3)) {
|
|
5430
|
+
const shortHash = commit.hash.slice(0, 7);
|
|
5431
|
+
const shortMsg = truncate(commit.message, 45);
|
|
5432
|
+
writeLine(` ${colors.dim}${shortHash}${RESET} ${shortMsg} ${colors.dim}(${commit.repo})${RESET}`);
|
|
5433
|
+
}
|
|
5434
|
+
writeLine();
|
|
5435
|
+
}
|
|
5004
5436
|
const allActiveGoals = squadData.flatMap(
|
|
5005
5437
|
(s) => s.goals.filter((g) => !g.completed).map((g) => ({ squad: s.name, goal: g }))
|
|
5006
5438
|
);
|
|
5007
5439
|
if (allActiveGoals.length > 0) {
|
|
5008
|
-
|
|
5440
|
+
const activeSquads2 = new Set(gitStats?.recentCommits?.map((c) => {
|
|
5441
|
+
const repoSquadMap = {
|
|
5442
|
+
"agents-squads-web": "website",
|
|
5443
|
+
"squads-cli": "cli",
|
|
5444
|
+
"hq": "engineering",
|
|
5445
|
+
"company": "company",
|
|
5446
|
+
"product": "product",
|
|
5447
|
+
"research": "research",
|
|
5448
|
+
"intelligence": "intelligence",
|
|
5449
|
+
"customer": "customer",
|
|
5450
|
+
"finance": "finance",
|
|
5451
|
+
"marketing": "marketing"
|
|
5452
|
+
};
|
|
5453
|
+
return repoSquadMap[c.repo] || c.repo;
|
|
5454
|
+
}) || []);
|
|
5455
|
+
const sortedGoals = [...allActiveGoals].sort((a, b) => {
|
|
5456
|
+
const aActive = activeSquads2.has(a.squad) ? 1 : 0;
|
|
5457
|
+
const bActive = activeSquads2.has(b.squad) ? 1 : 0;
|
|
5458
|
+
if (aActive !== bActive) return bActive - aActive;
|
|
5459
|
+
const aHasProgress = a.goal.progress ? 1 : 0;
|
|
5460
|
+
const bHasProgress = b.goal.progress ? 1 : 0;
|
|
5461
|
+
return bHasProgress - aHasProgress;
|
|
5462
|
+
});
|
|
5463
|
+
writeLine(` ${bold}Goals${RESET} ${colors.dim}(${allActiveGoals.length} active)${RESET}`);
|
|
5009
5464
|
writeLine();
|
|
5010
|
-
const maxGoals =
|
|
5011
|
-
for (const { squad, goal: goal2 } of
|
|
5465
|
+
const maxGoals = 3;
|
|
5466
|
+
for (const { squad, goal: goal2 } of sortedGoals.slice(0, maxGoals)) {
|
|
5012
5467
|
const hasProgress = goal2.progress && goal2.progress.length > 0;
|
|
5013
|
-
const
|
|
5468
|
+
const isActive = activeSquads2.has(squad);
|
|
5469
|
+
const icon = isActive ? icons.active : hasProgress ? icons.progress : icons.empty;
|
|
5014
5470
|
writeLine(` ${icon} ${colors.dim}${squad}${RESET} ${truncate(goal2.description, 48)}`);
|
|
5015
5471
|
if (hasProgress) {
|
|
5016
5472
|
writeLine(` ${colors.dim}\u2514${RESET} ${colors.green}${truncate(goal2.progress, 52)}${RESET}`);
|
|
@@ -6611,12 +7067,12 @@ function formatDuration(ms) {
|
|
|
6611
7067
|
}
|
|
6612
7068
|
function groupByDate(executions) {
|
|
6613
7069
|
const groups = /* @__PURE__ */ new Map();
|
|
6614
|
-
for (const
|
|
6615
|
-
const dateKey =
|
|
7070
|
+
for (const exec3 of executions) {
|
|
7071
|
+
const dateKey = exec3.startedAt.toISOString().split("T")[0];
|
|
6616
7072
|
if (!groups.has(dateKey)) {
|
|
6617
7073
|
groups.set(dateKey, []);
|
|
6618
7074
|
}
|
|
6619
|
-
groups.get(dateKey).push(
|
|
7075
|
+
groups.get(dateKey).push(exec3);
|
|
6620
7076
|
}
|
|
6621
7077
|
return groups;
|
|
6622
7078
|
}
|
|
@@ -6647,13 +7103,13 @@ async function historyCommand(options = {}) {
|
|
|
6647
7103
|
]);
|
|
6648
7104
|
const seenIds = /* @__PURE__ */ new Set();
|
|
6649
7105
|
const allExecutions = [];
|
|
6650
|
-
for (const
|
|
6651
|
-
seenIds.add(
|
|
6652
|
-
allExecutions.push(
|
|
7106
|
+
for (const exec3 of bridgeExecs) {
|
|
7107
|
+
seenIds.add(exec3.id);
|
|
7108
|
+
allExecutions.push(exec3);
|
|
6653
7109
|
}
|
|
6654
|
-
for (const
|
|
6655
|
-
if (!seenIds.has(
|
|
6656
|
-
allExecutions.push(
|
|
7110
|
+
for (const exec3 of localExecs) {
|
|
7111
|
+
if (!seenIds.has(exec3.id)) {
|
|
7112
|
+
allExecutions.push(exec3);
|
|
6657
7113
|
}
|
|
6658
7114
|
}
|
|
6659
7115
|
allExecutions.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
|
|
@@ -6677,14 +7133,14 @@ async function historyCommand(options = {}) {
|
|
|
6677
7133
|
writeLine(` ${colors.purple}\u250C${"\u2500".repeat(60)}\u2510${RESET}`);
|
|
6678
7134
|
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
7135
|
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(
|
|
7136
|
+
for (const exec3 of execs) {
|
|
7137
|
+
const time = exec3.startedAt.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
7138
|
+
const squadName = truncate(exec3.squad, 11);
|
|
7139
|
+
const agentName = truncate(exec3.agent, 14);
|
|
7140
|
+
const duration = formatDuration(exec3.durationMs);
|
|
6685
7141
|
let statusIcon;
|
|
6686
7142
|
let statusColor;
|
|
6687
|
-
switch (
|
|
7143
|
+
switch (exec3.status) {
|
|
6688
7144
|
case "success":
|
|
6689
7145
|
statusIcon = icons.success;
|
|
6690
7146
|
statusColor = colors.green;
|
|
@@ -6702,14 +7158,14 @@ async function historyCommand(options = {}) {
|
|
|
6702
7158
|
statusColor = colors.dim;
|
|
6703
7159
|
}
|
|
6704
7160
|
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 =
|
|
7161
|
+
if (verbose && (exec3.cost || exec3.tokens)) {
|
|
7162
|
+
const costStr = exec3.cost ? `$${exec3.cost.toFixed(2)}` : "";
|
|
7163
|
+
const tokenStr = exec3.tokens ? `${exec3.tokens.toLocaleString()} tokens` : "";
|
|
6708
7164
|
const details = [costStr, tokenStr].filter(Boolean).join(" \u2502 ");
|
|
6709
7165
|
writeLine(` ${colors.purple}\u2502${RESET} ${colors.dim}\u2514 ${details}${RESET}${" ".repeat(Math.max(0, 45 - details.length))}${colors.purple}\u2502${RESET}`);
|
|
6710
7166
|
}
|
|
6711
|
-
if (
|
|
6712
|
-
writeLine(` ${colors.purple}\u2502${RESET} ${colors.red}\u2514 ${truncate(
|
|
7167
|
+
if (exec3.error) {
|
|
7168
|
+
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
7169
|
}
|
|
6714
7170
|
}
|
|
6715
7171
|
writeLine(` ${colors.purple}\u2514${"\u2500".repeat(60)}\u2518${RESET}`);
|
|
@@ -7959,15 +8415,1305 @@ function registerTriggerCommand(program2) {
|
|
|
7959
8415
|
});
|
|
7960
8416
|
}
|
|
7961
8417
|
|
|
7962
|
-
// src/commands/
|
|
8418
|
+
// src/commands/skill.ts
|
|
7963
8419
|
import ora6 from "ora";
|
|
8420
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync14 } from "fs";
|
|
8421
|
+
import { join as join18, basename as basename2, dirname as dirname5 } from "path";
|
|
8422
|
+
|
|
8423
|
+
// src/lib/anthropic.ts
|
|
8424
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
8425
|
+
import { readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
|
|
8426
|
+
import { join as join17 } from "path";
|
|
8427
|
+
var client = null;
|
|
8428
|
+
function getClient() {
|
|
8429
|
+
if (!client) {
|
|
8430
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
8431
|
+
if (!apiKey) {
|
|
8432
|
+
throw new Error("ANTHROPIC_API_KEY environment variable is required for skills management");
|
|
8433
|
+
}
|
|
8434
|
+
client = new Anthropic({ apiKey });
|
|
8435
|
+
}
|
|
8436
|
+
return client;
|
|
8437
|
+
}
|
|
8438
|
+
async function listSkills() {
|
|
8439
|
+
getClient();
|
|
8440
|
+
try {
|
|
8441
|
+
console.warn("Skills API: Using placeholder implementation. Actual API may differ.");
|
|
8442
|
+
return [];
|
|
8443
|
+
} catch (error) {
|
|
8444
|
+
if (error instanceof Error && error.message.includes("404")) {
|
|
8445
|
+
return [];
|
|
8446
|
+
}
|
|
8447
|
+
throw error;
|
|
8448
|
+
}
|
|
8449
|
+
}
|
|
8450
|
+
function loadSkillFiles(skillPath) {
|
|
8451
|
+
const files = [];
|
|
8452
|
+
function walkDir(dir, prefix = "") {
|
|
8453
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
8454
|
+
for (const entry of entries) {
|
|
8455
|
+
const fullPath = join17(dir, entry.name);
|
|
8456
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
8457
|
+
if (entry.isDirectory()) {
|
|
8458
|
+
walkDir(fullPath, relativePath);
|
|
8459
|
+
} else if (entry.isFile()) {
|
|
8460
|
+
const content = readFileSync13(fullPath, "utf-8");
|
|
8461
|
+
files.push({
|
|
8462
|
+
name: relativePath,
|
|
8463
|
+
content
|
|
8464
|
+
});
|
|
8465
|
+
}
|
|
8466
|
+
}
|
|
8467
|
+
}
|
|
8468
|
+
walkDir(skillPath);
|
|
8469
|
+
return files;
|
|
8470
|
+
}
|
|
8471
|
+
function extractSkillTitle(files) {
|
|
8472
|
+
const skillMd = files.find((f) => f.name === "SKILL.md" || f.name.endsWith("/SKILL.md"));
|
|
8473
|
+
if (!skillMd) {
|
|
8474
|
+
throw new Error("SKILL.md not found in skill directory");
|
|
8475
|
+
}
|
|
8476
|
+
const content = skillMd.content;
|
|
8477
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
8478
|
+
if (frontmatterMatch) {
|
|
8479
|
+
const nameMatch = frontmatterMatch[1].match(/^name:\s*(.+)$/m);
|
|
8480
|
+
if (nameMatch) {
|
|
8481
|
+
return nameMatch[1].trim();
|
|
8482
|
+
}
|
|
8483
|
+
}
|
|
8484
|
+
const headingMatch = content.match(/^#\s+(.+)$/m);
|
|
8485
|
+
if (headingMatch) {
|
|
8486
|
+
return headingMatch[1].trim();
|
|
8487
|
+
}
|
|
8488
|
+
throw new Error("Could not extract skill title from SKILL.md");
|
|
8489
|
+
}
|
|
8490
|
+
function extractSkillDescription(files) {
|
|
8491
|
+
const skillMd = files.find((f) => f.name === "SKILL.md" || f.name.endsWith("/SKILL.md"));
|
|
8492
|
+
if (!skillMd) return void 0;
|
|
8493
|
+
const content = skillMd.content;
|
|
8494
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
8495
|
+
if (frontmatterMatch) {
|
|
8496
|
+
const descMatch = frontmatterMatch[1].match(/^description:\s*(.+)$/m);
|
|
8497
|
+
if (descMatch) {
|
|
8498
|
+
return descMatch[1].trim();
|
|
8499
|
+
}
|
|
8500
|
+
}
|
|
8501
|
+
const paragraphMatch = content.match(/^#.+\n+(.+)/m);
|
|
8502
|
+
if (paragraphMatch) {
|
|
8503
|
+
return paragraphMatch[1].trim();
|
|
8504
|
+
}
|
|
8505
|
+
return void 0;
|
|
8506
|
+
}
|
|
8507
|
+
async function uploadSkill(skillPath) {
|
|
8508
|
+
getClient();
|
|
8509
|
+
const files = loadSkillFiles(skillPath);
|
|
8510
|
+
if (files.length === 0) {
|
|
8511
|
+
throw new Error(`No files found in skill directory: ${skillPath}`);
|
|
8512
|
+
}
|
|
8513
|
+
const totalSize = files.reduce((sum, f) => sum + Buffer.byteLength(f.content, "utf-8"), 0);
|
|
8514
|
+
const maxSize = 8 * 1024 * 1024;
|
|
8515
|
+
if (totalSize > maxSize) {
|
|
8516
|
+
throw new Error(`Skill size ${(totalSize / 1024 / 1024).toFixed(2)}MB exceeds 8MB limit`);
|
|
8517
|
+
}
|
|
8518
|
+
const displayTitle = extractSkillTitle(files);
|
|
8519
|
+
const description = extractSkillDescription(files);
|
|
8520
|
+
try {
|
|
8521
|
+
console.log(`Uploading skill: ${displayTitle}`);
|
|
8522
|
+
console.log(`Files: ${files.map((f) => f.name).join(", ")}`);
|
|
8523
|
+
console.log(`Total size: ${(totalSize / 1024).toFixed(2)}KB`);
|
|
8524
|
+
const skill = {
|
|
8525
|
+
id: `skill_${Date.now()}`,
|
|
8526
|
+
display_title: displayTitle,
|
|
8527
|
+
description,
|
|
8528
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8529
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8530
|
+
files
|
|
8531
|
+
};
|
|
8532
|
+
console.warn("Skills API: Using placeholder implementation. Skill not actually uploaded.");
|
|
8533
|
+
return skill;
|
|
8534
|
+
} catch (error) {
|
|
8535
|
+
throw new Error(`Failed to upload skill: ${error instanceof Error ? error.message : String(error)}`);
|
|
8536
|
+
}
|
|
8537
|
+
}
|
|
8538
|
+
async function deleteSkill(skillId) {
|
|
8539
|
+
getClient();
|
|
8540
|
+
try {
|
|
8541
|
+
console.log(`Deleting skill: ${skillId}`);
|
|
8542
|
+
console.warn("Skills API: Using placeholder implementation. Skill not actually deleted.");
|
|
8543
|
+
} catch (error) {
|
|
8544
|
+
throw new Error(`Failed to delete skill: ${error instanceof Error ? error.message : String(error)}`);
|
|
8545
|
+
}
|
|
8546
|
+
}
|
|
8547
|
+
async function getSkill(skillId) {
|
|
8548
|
+
getClient();
|
|
8549
|
+
try {
|
|
8550
|
+
console.log(`Getting skill: ${skillId}`);
|
|
8551
|
+
console.warn("Skills API: Using placeholder implementation.");
|
|
8552
|
+
return null;
|
|
8553
|
+
} catch (error) {
|
|
8554
|
+
if (error instanceof Error && error.message.includes("404")) {
|
|
8555
|
+
return null;
|
|
8556
|
+
}
|
|
8557
|
+
throw error;
|
|
8558
|
+
}
|
|
8559
|
+
}
|
|
8560
|
+
function isApiKeyConfigured() {
|
|
8561
|
+
return !!process.env.ANTHROPIC_API_KEY;
|
|
8562
|
+
}
|
|
8563
|
+
|
|
8564
|
+
// src/commands/skill.ts
|
|
8565
|
+
function registerSkillCommand(program2) {
|
|
8566
|
+
const skill = program2.command("skill").description("Manage Agent Skills (upload, list, delete)");
|
|
8567
|
+
skill.command("list").description("List uploaded skills from Anthropic API").action(skillListCommand);
|
|
8568
|
+
skill.command("upload <path>").description("Upload a skill directory to Anthropic API").action(skillUploadCommand);
|
|
8569
|
+
skill.command("delete <skillId>").description("Delete a skill from Anthropic API").option("-f, --force", "Skip confirmation prompt").action(skillDeleteCommand);
|
|
8570
|
+
skill.command("show <skillId>").description("Show details for a skill").action(skillShowCommand);
|
|
8571
|
+
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);
|
|
8572
|
+
}
|
|
8573
|
+
async function skillListCommand() {
|
|
8574
|
+
writeLine();
|
|
8575
|
+
writeLine(` ${gradient("Skills")} ${colors.dim}list${RESET}`);
|
|
8576
|
+
writeLine();
|
|
8577
|
+
if (!isApiKeyConfigured()) {
|
|
8578
|
+
writeLine(` ${icons.error} ${colors.red}ANTHROPIC_API_KEY not configured${RESET}`);
|
|
8579
|
+
writeLine(` ${colors.dim}Set the environment variable to use skills management.${RESET}`);
|
|
8580
|
+
writeLine();
|
|
8581
|
+
return;
|
|
8582
|
+
}
|
|
8583
|
+
const spinner = ora6("Fetching skills...").start();
|
|
8584
|
+
try {
|
|
8585
|
+
const skills = await listSkills();
|
|
8586
|
+
if (skills.length === 0) {
|
|
8587
|
+
spinner.info("No skills uploaded yet");
|
|
8588
|
+
writeLine();
|
|
8589
|
+
writeLine(` ${colors.dim}Upload a skill:${RESET}`);
|
|
8590
|
+
writeLine(` ${colors.cyan}squads skill upload <path>${RESET}`);
|
|
8591
|
+
writeLine();
|
|
8592
|
+
writeLine(` ${colors.dim}Convert an agent to skill format:${RESET}`);
|
|
8593
|
+
writeLine(` ${colors.cyan}squads skill convert <squad>/<agent>${RESET}`);
|
|
8594
|
+
writeLine();
|
|
8595
|
+
return;
|
|
8596
|
+
}
|
|
8597
|
+
spinner.succeed(`Found ${skills.length} skill(s)`);
|
|
8598
|
+
writeLine();
|
|
8599
|
+
writeLine(` ${colors.dim}ID${RESET} ${colors.dim}Title${RESET} ${colors.dim}Updated${RESET}`);
|
|
8600
|
+
writeLine(` ${colors.dim}${"\u2500".repeat(60)}${RESET}`);
|
|
8601
|
+
for (const skill of skills) {
|
|
8602
|
+
const id = skill.id.slice(0, 20).padEnd(22);
|
|
8603
|
+
const title = (skill.display_title || "Untitled").slice(0, 20).padEnd(22);
|
|
8604
|
+
const updated = new Date(skill.updated_at).toLocaleDateString();
|
|
8605
|
+
writeLine(` ${colors.cyan}${id}${RESET}${title}${colors.dim}${updated}${RESET}`);
|
|
8606
|
+
}
|
|
8607
|
+
writeLine();
|
|
8608
|
+
} catch (error) {
|
|
8609
|
+
spinner.fail("Failed to list skills");
|
|
8610
|
+
writeLine(` ${colors.red}${error instanceof Error ? error.message : String(error)}${RESET}`);
|
|
8611
|
+
writeLine();
|
|
8612
|
+
}
|
|
8613
|
+
}
|
|
8614
|
+
async function skillUploadCommand(skillPath) {
|
|
8615
|
+
writeLine();
|
|
8616
|
+
writeLine(` ${gradient("Skills")} ${colors.dim}upload${RESET}`);
|
|
8617
|
+
writeLine();
|
|
8618
|
+
if (!isApiKeyConfigured()) {
|
|
8619
|
+
writeLine(` ${icons.error} ${colors.red}ANTHROPIC_API_KEY not configured${RESET}`);
|
|
8620
|
+
writeLine(` ${colors.dim}Set the environment variable to use skills management.${RESET}`);
|
|
8621
|
+
writeLine();
|
|
8622
|
+
return;
|
|
8623
|
+
}
|
|
8624
|
+
const fullPath = skillPath.startsWith("/") ? skillPath : join18(process.cwd(), skillPath);
|
|
8625
|
+
if (!existsSync17(fullPath)) {
|
|
8626
|
+
writeLine(` ${icons.error} ${colors.red}Directory not found: ${skillPath}${RESET}`);
|
|
8627
|
+
writeLine();
|
|
8628
|
+
return;
|
|
8629
|
+
}
|
|
8630
|
+
const skillMdPath = join18(fullPath, "SKILL.md");
|
|
8631
|
+
if (!existsSync17(skillMdPath)) {
|
|
8632
|
+
writeLine(` ${icons.error} ${colors.red}SKILL.md not found in ${skillPath}${RESET}`);
|
|
8633
|
+
writeLine();
|
|
8634
|
+
writeLine(` ${colors.dim}Create a SKILL.md file or use:${RESET}`);
|
|
8635
|
+
writeLine(` ${colors.cyan}squads skill convert <squad>/<agent>${RESET}`);
|
|
8636
|
+
writeLine();
|
|
8637
|
+
return;
|
|
8638
|
+
}
|
|
8639
|
+
const spinner = ora6("Uploading skill...").start();
|
|
8640
|
+
try {
|
|
8641
|
+
const skill = await uploadSkill(fullPath);
|
|
8642
|
+
spinner.succeed(`Skill uploaded: ${skill.display_title}`);
|
|
8643
|
+
writeLine();
|
|
8644
|
+
writeLine(` ${colors.dim}ID:${RESET} ${colors.cyan}${skill.id}${RESET}`);
|
|
8645
|
+
if (skill.description) {
|
|
8646
|
+
writeLine(` ${colors.dim}Description:${RESET} ${skill.description}`);
|
|
8647
|
+
}
|
|
8648
|
+
writeLine();
|
|
8649
|
+
writeLine(` ${colors.dim}Use in run command:${RESET}`);
|
|
8650
|
+
writeLine(` ${colors.cyan}squads run <squad> --skills ${skill.id}${RESET}`);
|
|
8651
|
+
writeLine();
|
|
8652
|
+
} catch (error) {
|
|
8653
|
+
spinner.fail("Failed to upload skill");
|
|
8654
|
+
writeLine(` ${colors.red}${error instanceof Error ? error.message : String(error)}${RESET}`);
|
|
8655
|
+
writeLine();
|
|
8656
|
+
}
|
|
8657
|
+
}
|
|
8658
|
+
async function skillDeleteCommand(skillId, options) {
|
|
8659
|
+
writeLine();
|
|
8660
|
+
writeLine(` ${gradient("Skills")} ${colors.dim}delete${RESET}`);
|
|
8661
|
+
writeLine();
|
|
8662
|
+
if (!isApiKeyConfigured()) {
|
|
8663
|
+
writeLine(` ${icons.error} ${colors.red}ANTHROPIC_API_KEY not configured${RESET}`);
|
|
8664
|
+
writeLine();
|
|
8665
|
+
return;
|
|
8666
|
+
}
|
|
8667
|
+
if (!options.force) {
|
|
8668
|
+
writeLine(` ${colors.yellow}Warning: This will permanently delete the skill.${RESET}`);
|
|
8669
|
+
writeLine(` ${colors.dim}Use --force to skip this confirmation.${RESET}`);
|
|
8670
|
+
writeLine();
|
|
8671
|
+
return;
|
|
8672
|
+
}
|
|
8673
|
+
const spinner = ora6("Deleting skill...").start();
|
|
8674
|
+
try {
|
|
8675
|
+
await deleteSkill(skillId);
|
|
8676
|
+
spinner.succeed(`Skill deleted: ${skillId}`);
|
|
8677
|
+
writeLine();
|
|
8678
|
+
} catch (error) {
|
|
8679
|
+
spinner.fail("Failed to delete skill");
|
|
8680
|
+
writeLine(` ${colors.red}${error instanceof Error ? error.message : String(error)}${RESET}`);
|
|
8681
|
+
writeLine();
|
|
8682
|
+
}
|
|
8683
|
+
}
|
|
8684
|
+
async function skillShowCommand(skillId) {
|
|
8685
|
+
writeLine();
|
|
8686
|
+
writeLine(` ${gradient("Skills")} ${colors.dim}show${RESET}`);
|
|
8687
|
+
writeLine();
|
|
8688
|
+
if (!isApiKeyConfigured()) {
|
|
8689
|
+
writeLine(` ${icons.error} ${colors.red}ANTHROPIC_API_KEY not configured${RESET}`);
|
|
8690
|
+
writeLine();
|
|
8691
|
+
return;
|
|
8692
|
+
}
|
|
8693
|
+
const spinner = ora6("Fetching skill...").start();
|
|
8694
|
+
try {
|
|
8695
|
+
const skill = await getSkill(skillId);
|
|
8696
|
+
if (!skill) {
|
|
8697
|
+
spinner.fail(`Skill not found: ${skillId}`);
|
|
8698
|
+
writeLine();
|
|
8699
|
+
return;
|
|
8700
|
+
}
|
|
8701
|
+
spinner.succeed(`Found skill: ${skill.display_title}`);
|
|
8702
|
+
writeLine();
|
|
8703
|
+
writeLine(` ${colors.dim}ID:${RESET} ${colors.cyan}${skill.id}${RESET}`);
|
|
8704
|
+
writeLine(` ${colors.dim}Title:${RESET} ${skill.display_title}`);
|
|
8705
|
+
if (skill.description) {
|
|
8706
|
+
writeLine(` ${colors.dim}Description:${RESET} ${skill.description}`);
|
|
8707
|
+
}
|
|
8708
|
+
writeLine(` ${colors.dim}Created:${RESET} ${new Date(skill.created_at).toLocaleString()}`);
|
|
8709
|
+
writeLine(` ${colors.dim}Updated:${RESET} ${new Date(skill.updated_at).toLocaleString()}`);
|
|
8710
|
+
if (skill.files && skill.files.length > 0) {
|
|
8711
|
+
writeLine();
|
|
8712
|
+
writeLine(` ${colors.dim}Files:${RESET}`);
|
|
8713
|
+
for (const file of skill.files) {
|
|
8714
|
+
const size = Buffer.byteLength(file.content, "utf-8");
|
|
8715
|
+
writeLine(` ${colors.cyan}${file.name}${RESET} ${colors.dim}(${formatBytes(size)})${RESET}`);
|
|
8716
|
+
}
|
|
8717
|
+
}
|
|
8718
|
+
writeLine();
|
|
8719
|
+
} catch (error) {
|
|
8720
|
+
spinner.fail("Failed to fetch skill");
|
|
8721
|
+
writeLine(` ${colors.red}${error instanceof Error ? error.message : String(error)}${RESET}`);
|
|
8722
|
+
writeLine();
|
|
8723
|
+
}
|
|
8724
|
+
}
|
|
8725
|
+
async function skillConvertCommand(agentPath, options) {
|
|
8726
|
+
writeLine();
|
|
8727
|
+
writeLine(` ${gradient("Skills")} ${colors.dim}convert${RESET}`);
|
|
8728
|
+
writeLine();
|
|
8729
|
+
const squadsDir = findSquadsDir();
|
|
8730
|
+
if (!squadsDir) {
|
|
8731
|
+
writeLine(` ${icons.error} ${colors.red}No .agents/squads directory found${RESET}`);
|
|
8732
|
+
writeLine();
|
|
8733
|
+
return;
|
|
8734
|
+
}
|
|
8735
|
+
let agentFilePath;
|
|
8736
|
+
let squadName;
|
|
8737
|
+
let agentName;
|
|
8738
|
+
if (agentPath.includes("/")) {
|
|
8739
|
+
const [squad, agent] = agentPath.split("/");
|
|
8740
|
+
squadName = squad;
|
|
8741
|
+
agentName = agent.replace(".md", "");
|
|
8742
|
+
agentFilePath = join18(squadsDir, squad, `${agentName}.md`);
|
|
8743
|
+
} else {
|
|
8744
|
+
agentName = agentPath.replace(".md", "");
|
|
8745
|
+
const foundPath = findAgentFile(squadsDir, agentName);
|
|
8746
|
+
if (!foundPath) {
|
|
8747
|
+
writeLine(` ${icons.error} ${colors.red}Agent not found: ${agentPath}${RESET}`);
|
|
8748
|
+
writeLine(` ${colors.dim}Use format: squad/agent (e.g., cli/code-eval)${RESET}`);
|
|
8749
|
+
writeLine();
|
|
8750
|
+
return;
|
|
8751
|
+
}
|
|
8752
|
+
agentFilePath = foundPath;
|
|
8753
|
+
squadName = basename2(dirname5(agentFilePath));
|
|
8754
|
+
}
|
|
8755
|
+
if (!existsSync17(agentFilePath)) {
|
|
8756
|
+
writeLine(` ${icons.error} ${colors.red}Agent file not found: ${agentFilePath}${RESET}`);
|
|
8757
|
+
writeLine();
|
|
8758
|
+
return;
|
|
8759
|
+
}
|
|
8760
|
+
const agentContent = readFileSync14(agentFilePath, "utf-8");
|
|
8761
|
+
const skillName = `${squadName}-${agentName}`;
|
|
8762
|
+
const outputDir = options.output || join18(dirname5(squadsDir), "skills", skillName);
|
|
8763
|
+
if (!existsSync17(outputDir)) {
|
|
8764
|
+
mkdirSync9(outputDir, { recursive: true });
|
|
8765
|
+
}
|
|
8766
|
+
const skillMd = convertAgentToSkill(agentContent, squadName, agentName);
|
|
8767
|
+
const skillMdPath = join18(outputDir, "SKILL.md");
|
|
8768
|
+
writeFileSync10(skillMdPath, skillMd);
|
|
8769
|
+
writeLine(` ${icons.success} ${colors.green}Converted:${RESET} ${agentPath}`);
|
|
8770
|
+
writeLine();
|
|
8771
|
+
writeLine(` ${colors.dim}Output:${RESET} ${outputDir}`);
|
|
8772
|
+
writeLine(` ${colors.dim}Files:${RESET}`);
|
|
8773
|
+
writeLine(` ${colors.cyan}SKILL.md${RESET}`);
|
|
8774
|
+
writeLine();
|
|
8775
|
+
writeLine(` ${colors.dim}Next steps:${RESET}`);
|
|
8776
|
+
writeLine(` 1. Review and edit ${colors.cyan}SKILL.md${RESET}`);
|
|
8777
|
+
writeLine(` 2. Add scripts or examples if needed`);
|
|
8778
|
+
writeLine(` 3. Upload: ${colors.cyan}squads skill upload ${outputDir}${RESET}`);
|
|
8779
|
+
writeLine();
|
|
8780
|
+
}
|
|
8781
|
+
function findAgentFile(squadsDir, agentName) {
|
|
8782
|
+
const { readdirSync: readdirSync9 } = __require("fs");
|
|
8783
|
+
const squads = readdirSync9(squadsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("_")).map((d) => d.name);
|
|
8784
|
+
for (const squad of squads) {
|
|
8785
|
+
const agentPath = join18(squadsDir, squad, `${agentName}.md`);
|
|
8786
|
+
if (existsSync17(agentPath)) {
|
|
8787
|
+
return agentPath;
|
|
8788
|
+
}
|
|
8789
|
+
}
|
|
8790
|
+
return null;
|
|
8791
|
+
}
|
|
8792
|
+
function convertAgentToSkill(agentContent, squadName, agentName) {
|
|
8793
|
+
const existingMeta = {};
|
|
8794
|
+
let bodyContent = agentContent;
|
|
8795
|
+
const frontmatterMatch = agentContent.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)/);
|
|
8796
|
+
if (frontmatterMatch) {
|
|
8797
|
+
const frontmatter = frontmatterMatch[1];
|
|
8798
|
+
bodyContent = frontmatterMatch[2];
|
|
8799
|
+
for (const line of frontmatter.split("\n")) {
|
|
8800
|
+
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
8801
|
+
if (match) {
|
|
8802
|
+
existingMeta[match[1]] = match[2].trim();
|
|
8803
|
+
}
|
|
8804
|
+
}
|
|
8805
|
+
}
|
|
8806
|
+
const title = existingMeta.name || bodyContent.match(/^#\s+(?:Agent:\s*)?(.+)$/m)?.[1] || `${squadName}-${agentName}`;
|
|
8807
|
+
const roleMatch = bodyContent.match(/^(?:role|description):\s*(.+)$/mi) || bodyContent.match(/^##?\s*(?:Role|Purpose|Description)\s*\n+(.+)/mi);
|
|
8808
|
+
const description = existingMeta.description || roleMatch?.[1] || `Agent from ${squadName} squad. Use when working on ${agentName}-related tasks.`;
|
|
8809
|
+
const skillMd = `---
|
|
8810
|
+
name: ${squadName}-${agentName}
|
|
8811
|
+
description: ${description}
|
|
8812
|
+
---
|
|
8813
|
+
|
|
8814
|
+
# ${title}
|
|
8815
|
+
|
|
8816
|
+
${bodyContent.trim()}
|
|
8817
|
+
`;
|
|
8818
|
+
return skillMd;
|
|
8819
|
+
}
|
|
8820
|
+
function formatBytes(bytes) {
|
|
8821
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
8822
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
8823
|
+
return `${(bytes / 1024 / 1024).toFixed(2)}MB`;
|
|
8824
|
+
}
|
|
8825
|
+
|
|
8826
|
+
// src/commands/permissions.ts
|
|
8827
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
8828
|
+
import { join as join19 } from "path";
|
|
8829
|
+
function registerPermissionsCommand(program2) {
|
|
8830
|
+
const permissions = program2.command("permissions").alias("perms").description("Manage and validate squad permissions");
|
|
8831
|
+
permissions.command("show <squad>").description("Show permission context for a squad").action(permissionsShowCommand);
|
|
8832
|
+
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);
|
|
8833
|
+
}
|
|
8834
|
+
async function permissionsShowCommand(squadName) {
|
|
8835
|
+
writeLine();
|
|
8836
|
+
writeLine(` ${gradient("Permissions")} ${colors.dim}show${RESET} ${colors.cyan}${squadName}${RESET}`);
|
|
8837
|
+
writeLine();
|
|
8838
|
+
const squadsDir = findSquadsDir();
|
|
8839
|
+
if (!squadsDir) {
|
|
8840
|
+
writeLine(` ${icons.error} ${colors.red}No .agents/squads directory found${RESET}`);
|
|
8841
|
+
writeLine();
|
|
8842
|
+
return;
|
|
8843
|
+
}
|
|
8844
|
+
const squad = loadSquad(squadName);
|
|
8845
|
+
if (!squad) {
|
|
8846
|
+
writeLine(` ${icons.error} ${colors.red}Squad "${squadName}" not found${RESET}`);
|
|
8847
|
+
writeLine();
|
|
8848
|
+
return;
|
|
8849
|
+
}
|
|
8850
|
+
const squadFilePath = join19(squadsDir, squadName, "SQUAD.md");
|
|
8851
|
+
const squadContent = readFileSync15(squadFilePath, "utf-8");
|
|
8852
|
+
const context2 = buildContextFromSquad(squadName, squadContent);
|
|
8853
|
+
const defaults = getDefaultContext(squadName);
|
|
8854
|
+
const isDefault = JSON.stringify(context2.permissions) === JSON.stringify(defaults.permissions);
|
|
8855
|
+
writeLine(` ${bold}Squad:${RESET} ${squadName}`);
|
|
8856
|
+
if (squad.mission) {
|
|
8857
|
+
writeLine(` ${colors.dim}${squad.mission}${RESET}`);
|
|
8858
|
+
}
|
|
8859
|
+
writeLine();
|
|
8860
|
+
if (isDefault) {
|
|
8861
|
+
writeLine(` ${icons.warning} ${colors.yellow}Using default (permissive) permissions${RESET}`);
|
|
8862
|
+
writeLine(` ${colors.dim}Add a permissions block to SQUAD.md to restrict access.${RESET}`);
|
|
8863
|
+
writeLine();
|
|
8864
|
+
}
|
|
8865
|
+
writeLine(` ${bold}Mode:${RESET} ${formatMode(context2.permissions.mode)}`);
|
|
8866
|
+
writeLine();
|
|
8867
|
+
writeLine(` ${bold}Bash Commands:${RESET}`);
|
|
8868
|
+
if (context2.permissions.bash.includes("*")) {
|
|
8869
|
+
writeLine(` ${colors.dim}All commands allowed (*)${RESET}`);
|
|
8870
|
+
} else {
|
|
8871
|
+
for (const cmd of context2.permissions.bash) {
|
|
8872
|
+
writeLine(` ${icons.active} ${cmd}`);
|
|
8873
|
+
}
|
|
8874
|
+
}
|
|
8875
|
+
writeLine();
|
|
8876
|
+
writeLine(` ${bold}Write Paths:${RESET}`);
|
|
8877
|
+
if (context2.permissions.write.includes("**") || context2.permissions.write.includes("*")) {
|
|
8878
|
+
writeLine(` ${colors.dim}All paths writable (**)${RESET}`);
|
|
8879
|
+
} else {
|
|
8880
|
+
for (const path3 of context2.permissions.write) {
|
|
8881
|
+
writeLine(` ${icons.active} ${path3}`);
|
|
8882
|
+
}
|
|
8883
|
+
}
|
|
8884
|
+
writeLine();
|
|
8885
|
+
writeLine(` ${bold}Read Paths:${RESET}`);
|
|
8886
|
+
if (context2.permissions.read.includes("**") || context2.permissions.read.includes("*")) {
|
|
8887
|
+
writeLine(` ${colors.dim}All paths readable (**)${RESET}`);
|
|
8888
|
+
} else {
|
|
8889
|
+
for (const path3 of context2.permissions.read) {
|
|
8890
|
+
writeLine(` ${icons.active} ${path3}`);
|
|
8891
|
+
}
|
|
8892
|
+
}
|
|
8893
|
+
writeLine();
|
|
8894
|
+
writeLine(` ${bold}MCP Servers:${RESET}`);
|
|
8895
|
+
if (context2.permissions.mcp.allow.includes("*")) {
|
|
8896
|
+
writeLine(` ${colors.dim}All servers allowed (*)${RESET}`);
|
|
8897
|
+
} else {
|
|
8898
|
+
writeLine(` ${colors.green}Allow:${RESET}`);
|
|
8899
|
+
for (const server of context2.permissions.mcp.allow) {
|
|
8900
|
+
writeLine(` ${icons.success} ${server}`);
|
|
8901
|
+
}
|
|
8902
|
+
}
|
|
8903
|
+
if (context2.permissions.mcp.deny.length > 0) {
|
|
8904
|
+
writeLine(` ${colors.red}Deny:${RESET}`);
|
|
8905
|
+
for (const server of context2.permissions.mcp.deny) {
|
|
8906
|
+
writeLine(` ${icons.error} ${server}`);
|
|
8907
|
+
}
|
|
8908
|
+
}
|
|
8909
|
+
writeLine();
|
|
8910
|
+
if (isDefault) {
|
|
8911
|
+
writeLine(` ${bold}Add permissions to SQUAD.md:${RESET}`);
|
|
8912
|
+
writeLine();
|
|
8913
|
+
writeLine(` ${colors.dim}\`\`\`yaml${RESET}`);
|
|
8914
|
+
writeLine(` ${colors.dim}permissions:${RESET}`);
|
|
8915
|
+
writeLine(` ${colors.dim} mode: warn # or strict, audit${RESET}`);
|
|
8916
|
+
writeLine(` ${colors.dim} bash: [npm, git, gh]${RESET}`);
|
|
8917
|
+
writeLine(` ${colors.dim} write: [agents-squads-web/**]${RESET}`);
|
|
8918
|
+
writeLine(` ${colors.dim} read: [hq/.agents/**]${RESET}`);
|
|
8919
|
+
writeLine(` ${colors.dim} mcp:${RESET}`);
|
|
8920
|
+
writeLine(` ${colors.dim} allow: [chrome-devtools]${RESET}`);
|
|
8921
|
+
writeLine(` ${colors.dim} deny: [supabase]${RESET}`);
|
|
8922
|
+
writeLine(` ${colors.dim}\`\`\`${RESET}`);
|
|
8923
|
+
writeLine();
|
|
8924
|
+
}
|
|
8925
|
+
}
|
|
8926
|
+
async function permissionsCheckCommand(squadName, options) {
|
|
8927
|
+
writeLine();
|
|
8928
|
+
writeLine(` ${gradient("Permissions")} ${colors.dim}check${RESET} ${colors.cyan}${squadName}${RESET}`);
|
|
8929
|
+
if (options.agent) {
|
|
8930
|
+
writeLine(` ${colors.dim}Agent: ${options.agent}${RESET}`);
|
|
8931
|
+
}
|
|
8932
|
+
writeLine();
|
|
8933
|
+
const squadsDir = findSquadsDir();
|
|
8934
|
+
if (!squadsDir) {
|
|
8935
|
+
writeLine(` ${icons.error} ${colors.red}No .agents/squads directory found${RESET}`);
|
|
8936
|
+
writeLine();
|
|
8937
|
+
return;
|
|
8938
|
+
}
|
|
8939
|
+
const squad = loadSquad(squadName);
|
|
8940
|
+
if (!squad) {
|
|
8941
|
+
writeLine(` ${icons.error} ${colors.red}Squad "${squadName}" not found${RESET}`);
|
|
8942
|
+
writeLine();
|
|
8943
|
+
return;
|
|
8944
|
+
}
|
|
8945
|
+
const squadFilePath = join19(squadsDir, squadName, "SQUAD.md");
|
|
8946
|
+
const squadContent = readFileSync15(squadFilePath, "utf-8");
|
|
8947
|
+
const context2 = buildContextFromSquad(squadName, squadContent, options.agent);
|
|
8948
|
+
const request = {
|
|
8949
|
+
mcpServers: options.mcp,
|
|
8950
|
+
bashCommands: options.bash,
|
|
8951
|
+
writePaths: options.write,
|
|
8952
|
+
readPaths: options.read
|
|
8953
|
+
};
|
|
8954
|
+
if (!options.mcp && !options.bash && !options.write && !options.read) {
|
|
8955
|
+
writeLine(` ${colors.yellow}No resources specified to check.${RESET}`);
|
|
8956
|
+
writeLine();
|
|
8957
|
+
writeLine(` ${colors.dim}Examples:${RESET}`);
|
|
8958
|
+
writeLine(` ${colors.cyan}squads perms check ${squadName} --mcp chrome-devtools firecrawl${RESET}`);
|
|
8959
|
+
writeLine(` ${colors.cyan}squads perms check ${squadName} --bash npm git docker${RESET}`);
|
|
8960
|
+
writeLine(` ${colors.cyan}squads perms check ${squadName} --write src/** --read config/**${RESET}`);
|
|
8961
|
+
writeLine();
|
|
8962
|
+
return;
|
|
8963
|
+
}
|
|
8964
|
+
const result = validateExecution(context2, request);
|
|
8965
|
+
if (result.violations.length === 0) {
|
|
8966
|
+
writeLine(` ${icons.success} ${colors.green}All permissions valid${RESET}`);
|
|
8967
|
+
writeLine();
|
|
8968
|
+
if (options.mcp) {
|
|
8969
|
+
writeLine(` ${colors.dim}MCP:${RESET} ${options.mcp.join(", ")}`);
|
|
8970
|
+
}
|
|
8971
|
+
if (options.bash) {
|
|
8972
|
+
writeLine(` ${colors.dim}Bash:${RESET} ${options.bash.join(", ")}`);
|
|
8973
|
+
}
|
|
8974
|
+
if (options.write) {
|
|
8975
|
+
writeLine(` ${colors.dim}Write:${RESET} ${options.write.join(", ")}`);
|
|
8976
|
+
}
|
|
8977
|
+
if (options.read) {
|
|
8978
|
+
writeLine(` ${colors.dim}Read:${RESET} ${options.read.join(", ")}`);
|
|
8979
|
+
}
|
|
8980
|
+
writeLine();
|
|
8981
|
+
} else {
|
|
8982
|
+
const violationLines = formatViolations(result);
|
|
8983
|
+
for (const line of violationLines) {
|
|
8984
|
+
writeLine(` ${line}`);
|
|
8985
|
+
}
|
|
8986
|
+
writeLine();
|
|
8987
|
+
if (!result.allowed) {
|
|
8988
|
+
process.exit(1);
|
|
8989
|
+
}
|
|
8990
|
+
}
|
|
8991
|
+
}
|
|
8992
|
+
function formatMode(mode) {
|
|
8993
|
+
switch (mode) {
|
|
8994
|
+
case "strict":
|
|
8995
|
+
return `${colors.red}strict${RESET} ${colors.dim}(blocks on violation)${RESET}`;
|
|
8996
|
+
case "warn":
|
|
8997
|
+
return `${colors.yellow}warn${RESET} ${colors.dim}(logs and continues)${RESET}`;
|
|
8998
|
+
case "audit":
|
|
8999
|
+
return `${colors.cyan}audit${RESET} ${colors.dim}(logs to trail)${RESET}`;
|
|
9000
|
+
default:
|
|
9001
|
+
return mode;
|
|
9002
|
+
}
|
|
9003
|
+
}
|
|
9004
|
+
|
|
9005
|
+
// src/commands/context.ts
|
|
9006
|
+
async function contextShowCommand(squadName, options = {}) {
|
|
9007
|
+
await track(Events.CLI_CONTEXT, { squad: squadName });
|
|
9008
|
+
const squadsDir = findSquadsDir();
|
|
9009
|
+
if (!squadsDir) {
|
|
9010
|
+
writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
|
|
9011
|
+
writeLine(`${colors.dim}Run \`squads init\` to create one.${RESET}`);
|
|
9012
|
+
process.exit(1);
|
|
9013
|
+
}
|
|
9014
|
+
const squad = loadSquad(squadName);
|
|
9015
|
+
if (!squad) {
|
|
9016
|
+
writeLine(`${colors.red}Squad "${squadName}" not found.${RESET}`);
|
|
9017
|
+
process.exit(1);
|
|
9018
|
+
}
|
|
9019
|
+
if (options.json) {
|
|
9020
|
+
console.log(JSON.stringify({
|
|
9021
|
+
name: squad.name,
|
|
9022
|
+
mission: squad.mission,
|
|
9023
|
+
repo: squad.repo,
|
|
9024
|
+
stack: squad.stack,
|
|
9025
|
+
effort: squad.effort,
|
|
9026
|
+
context: squad.context
|
|
9027
|
+
}, null, 2));
|
|
9028
|
+
return;
|
|
9029
|
+
}
|
|
9030
|
+
writeLine();
|
|
9031
|
+
writeLine(` ${gradient("squads")} ${colors.dim}context${RESET} ${colors.cyan}${squad.name}${RESET}`);
|
|
9032
|
+
const ctx = squad.context;
|
|
9033
|
+
if (!ctx) {
|
|
9034
|
+
writeLine();
|
|
9035
|
+
writeLine(` ${colors.yellow}No context defined in SQUAD.md frontmatter${RESET}`);
|
|
9036
|
+
writeLine();
|
|
9037
|
+
writeLine(` ${colors.dim}Add a frontmatter block to ${squad.name}/SQUAD.md:${RESET}`);
|
|
9038
|
+
writeLine();
|
|
9039
|
+
writeLine(` ${colors.dim}---${RESET}`);
|
|
9040
|
+
writeLine(` ${colors.dim}name: ${squad.name}${RESET}`);
|
|
9041
|
+
writeLine(` ${colors.dim}context:${RESET}`);
|
|
9042
|
+
writeLine(` ${colors.dim} mcp: [chrome-devtools]${RESET}`);
|
|
9043
|
+
writeLine(` ${colors.dim} model: { default: sonnet }${RESET}`);
|
|
9044
|
+
writeLine(` ${colors.dim} budget: { daily: 10 }${RESET}`);
|
|
9045
|
+
writeLine(` ${colors.dim}---${RESET}`);
|
|
9046
|
+
writeLine();
|
|
9047
|
+
return;
|
|
9048
|
+
}
|
|
9049
|
+
const tableWidth = 54;
|
|
9050
|
+
writeLine();
|
|
9051
|
+
writeLine(` ${colors.purple}${box.horizontal.repeat(tableWidth)}${RESET}`);
|
|
9052
|
+
if (ctx.mcp && ctx.mcp.length > 0) {
|
|
9053
|
+
writeLine(` ${bold}MCP${RESET} ${colors.cyan}${ctx.mcp.join(", ")}${RESET}`);
|
|
9054
|
+
} else {
|
|
9055
|
+
writeLine(` ${bold}MCP${RESET} ${colors.dim}none${RESET}`);
|
|
9056
|
+
}
|
|
9057
|
+
if (ctx.skills && ctx.skills.length > 0) {
|
|
9058
|
+
writeLine(` ${bold}Skills${RESET} ${colors.cyan}${ctx.skills.join(", ")}${RESET}`);
|
|
9059
|
+
}
|
|
9060
|
+
if (ctx.memory?.load && ctx.memory.load.length > 0) {
|
|
9061
|
+
writeLine(` ${bold}Memory${RESET} ${colors.cyan}${ctx.memory.load.join(", ")}${RESET}`);
|
|
9062
|
+
}
|
|
9063
|
+
if (ctx.model) {
|
|
9064
|
+
const modelParts = [];
|
|
9065
|
+
if (ctx.model.default) modelParts.push(`${colors.white}${ctx.model.default}${RESET} ${colors.dim}(default)${RESET}`);
|
|
9066
|
+
if (ctx.model.expensive) modelParts.push(`${colors.yellow}${ctx.model.expensive}${RESET} ${colors.dim}(expensive)${RESET}`);
|
|
9067
|
+
if (ctx.model.cheap) modelParts.push(`${colors.green}${ctx.model.cheap}${RESET} ${colors.dim}(cheap)${RESET}`);
|
|
9068
|
+
writeLine(` ${bold}Model${RESET} ${modelParts.join(", ")}`);
|
|
9069
|
+
}
|
|
9070
|
+
if (ctx.budget) {
|
|
9071
|
+
const budgetParts = [];
|
|
9072
|
+
if (ctx.budget.daily) budgetParts.push(`$${ctx.budget.daily}/day`);
|
|
9073
|
+
if (ctx.budget.weekly) budgetParts.push(`$${ctx.budget.weekly}/week`);
|
|
9074
|
+
if (ctx.budget.perExecution) budgetParts.push(`$${ctx.budget.perExecution}/run`);
|
|
9075
|
+
writeLine(` ${bold}Budget${RESET} ${colors.green}${budgetParts.join(", ")}${RESET}`);
|
|
9076
|
+
}
|
|
9077
|
+
if (squad.effort) {
|
|
9078
|
+
const effortColor = squad.effort === "high" ? colors.red : squad.effort === "medium" ? colors.yellow : colors.green;
|
|
9079
|
+
writeLine(` ${bold}Effort${RESET} ${effortColor}${squad.effort}${RESET}`);
|
|
9080
|
+
}
|
|
9081
|
+
if (squad.repo) {
|
|
9082
|
+
writeLine(` ${bold}Repo${RESET} ${colors.dim}${squad.repo}${RESET}`);
|
|
9083
|
+
}
|
|
9084
|
+
if (squad.stack) {
|
|
9085
|
+
writeLine(` ${bold}Stack${RESET} ${colors.dim}${squad.stack}${RESET}`);
|
|
9086
|
+
}
|
|
9087
|
+
writeLine(` ${colors.purple}${box.horizontal.repeat(tableWidth)}${RESET}`);
|
|
9088
|
+
writeLine();
|
|
9089
|
+
writeLine(` ${colors.dim}$${RESET} squads context show ${colors.cyan}${squad.name}${RESET} --json ${colors.dim}JSON output${RESET}`);
|
|
9090
|
+
writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} ${colors.dim}Run with this context${RESET}`);
|
|
9091
|
+
writeLine();
|
|
9092
|
+
}
|
|
9093
|
+
async function contextListCommand(options = {}) {
|
|
9094
|
+
await track(Events.CLI_CONTEXT, { action: "list" });
|
|
9095
|
+
const squadsDir = findSquadsDir();
|
|
9096
|
+
if (!squadsDir) {
|
|
9097
|
+
writeLine(`${colors.red}No .agents/squads directory found${RESET}`);
|
|
9098
|
+
process.exit(1);
|
|
9099
|
+
}
|
|
9100
|
+
const squads = listSquads(squadsDir);
|
|
9101
|
+
if (options.json) {
|
|
9102
|
+
const contexts = {};
|
|
9103
|
+
for (const name of squads) {
|
|
9104
|
+
const squad = loadSquad(name);
|
|
9105
|
+
if (squad) {
|
|
9106
|
+
contexts[name] = squad.context;
|
|
9107
|
+
}
|
|
9108
|
+
}
|
|
9109
|
+
console.log(JSON.stringify(contexts, null, 2));
|
|
9110
|
+
return;
|
|
9111
|
+
}
|
|
9112
|
+
writeLine();
|
|
9113
|
+
writeLine(` ${gradient("squads")} ${colors.dim}context list${RESET}`);
|
|
9114
|
+
writeLine();
|
|
9115
|
+
const w = { name: 14, mcp: 24, model: 12, budget: 12 };
|
|
9116
|
+
const tableWidth = w.name + w.mcp + w.model + w.budget + 6;
|
|
9117
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
9118
|
+
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}`;
|
|
9119
|
+
writeLine(header);
|
|
9120
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
9121
|
+
for (const name of squads) {
|
|
9122
|
+
const squad = loadSquad(name);
|
|
9123
|
+
const ctx = squad?.context;
|
|
9124
|
+
const mcpStr = ctx?.mcp?.slice(0, 2).join(", ") || `${colors.dim}\u2014${RESET}`;
|
|
9125
|
+
const modelStr = ctx?.model?.default || `${colors.dim}\u2014${RESET}`;
|
|
9126
|
+
const budgetStr = ctx?.budget?.daily ? `$${ctx.budget.daily}/d` : `${colors.dim}\u2014${RESET}`;
|
|
9127
|
+
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}`;
|
|
9128
|
+
writeLine(row);
|
|
9129
|
+
}
|
|
9130
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
9131
|
+
writeLine();
|
|
9132
|
+
}
|
|
9133
|
+
|
|
9134
|
+
// src/commands/cost.ts
|
|
9135
|
+
function getBudgetStatus(squadName, spent, dailyBudget, weeklyBudget) {
|
|
9136
|
+
let status = "no-budget";
|
|
9137
|
+
let dailyPercent = null;
|
|
9138
|
+
let weeklyPercent = null;
|
|
9139
|
+
if (dailyBudget !== null) {
|
|
9140
|
+
dailyPercent = spent / dailyBudget * 100;
|
|
9141
|
+
if (dailyPercent >= 100) {
|
|
9142
|
+
status = "over";
|
|
9143
|
+
} else if (dailyPercent >= 80) {
|
|
9144
|
+
status = "warning";
|
|
9145
|
+
} else {
|
|
9146
|
+
status = "ok";
|
|
9147
|
+
}
|
|
9148
|
+
}
|
|
9149
|
+
if (weeklyBudget !== null) {
|
|
9150
|
+
weeklyPercent = spent / weeklyBudget * 100;
|
|
9151
|
+
}
|
|
9152
|
+
return {
|
|
9153
|
+
squad: squadName,
|
|
9154
|
+
spent,
|
|
9155
|
+
dailyBudget,
|
|
9156
|
+
weeklyBudget,
|
|
9157
|
+
dailyPercent,
|
|
9158
|
+
weeklyPercent,
|
|
9159
|
+
status
|
|
9160
|
+
};
|
|
9161
|
+
}
|
|
9162
|
+
async function costCommand(options = {}) {
|
|
9163
|
+
await track(Events.CLI_COST, { squad: options.squad || "all" });
|
|
9164
|
+
const stats = await fetchBridgeStats();
|
|
9165
|
+
const plan = detectPlan();
|
|
9166
|
+
if (options.json) {
|
|
9167
|
+
const result = buildJsonOutput(stats, plan, options.squad);
|
|
9168
|
+
console.log(JSON.stringify(result, null, 2));
|
|
9169
|
+
return;
|
|
9170
|
+
}
|
|
9171
|
+
writeLine();
|
|
9172
|
+
writeLine(` ${gradient("squads")} ${colors.dim}cost${RESET}${options.squad ? ` ${colors.cyan}${options.squad}${RESET}` : ""}`);
|
|
9173
|
+
writeLine();
|
|
9174
|
+
if (!stats) {
|
|
9175
|
+
writeLine(` ${colors.yellow}\u26A0 Bridge unavailable${RESET}`);
|
|
9176
|
+
writeLine(` ${colors.dim}Run \`squads stack up\` to start infrastructure${RESET}`);
|
|
9177
|
+
writeLine();
|
|
9178
|
+
return;
|
|
9179
|
+
}
|
|
9180
|
+
const todaySection = ` ${bold}Today${RESET}`;
|
|
9181
|
+
writeLine(todaySection);
|
|
9182
|
+
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`);
|
|
9183
|
+
if (stats.byModel && stats.byModel.length > 0) {
|
|
9184
|
+
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)}`);
|
|
9185
|
+
if (modelParts.length > 0) {
|
|
9186
|
+
writeLine(` ${colors.dim}Models:${RESET} ${modelParts.join(" \xB7 ")}`);
|
|
9187
|
+
}
|
|
9188
|
+
}
|
|
9189
|
+
writeLine();
|
|
9190
|
+
if (stats.week) {
|
|
9191
|
+
writeLine(` ${bold}Week${RESET}`);
|
|
9192
|
+
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`);
|
|
9193
|
+
writeLine();
|
|
9194
|
+
}
|
|
9195
|
+
writeLine(` ${bold}Budget${RESET} ${colors.dim}(daily)${RESET}`);
|
|
9196
|
+
const budgetBar = createBudgetBar(stats.budget.usedPct);
|
|
9197
|
+
const budgetColor = stats.budget.usedPct >= 100 ? colors.red : stats.budget.usedPct >= 80 ? colors.yellow : colors.green;
|
|
9198
|
+
writeLine(` ${budgetBar} ${budgetColor}$${stats.budget.used.toFixed(0)}${RESET}/${colors.dim}$${stats.budget.daily}${RESET} (${stats.budget.usedPct.toFixed(0)}%)`);
|
|
9199
|
+
writeLine();
|
|
9200
|
+
if (stats.bySquad && stats.bySquad.length > 0 && !options.squad) {
|
|
9201
|
+
writeLine(` ${bold}By Squad${RESET}`);
|
|
9202
|
+
writeLine();
|
|
9203
|
+
const squadsDir = findSquadsDir();
|
|
9204
|
+
const squadBudgets = /* @__PURE__ */ new Map();
|
|
9205
|
+
if (squadsDir) {
|
|
9206
|
+
for (const name of listSquads(squadsDir)) {
|
|
9207
|
+
const squad = loadSquad(name);
|
|
9208
|
+
if (squad?.context?.budget) {
|
|
9209
|
+
squadBudgets.set(name, {
|
|
9210
|
+
daily: squad.context.budget.daily || null,
|
|
9211
|
+
weekly: squad.context.budget.weekly || null
|
|
9212
|
+
});
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9215
|
+
}
|
|
9216
|
+
const tableWidth = 52;
|
|
9217
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
9218
|
+
const w = { name: 14, spent: 10, budget: 12, status: 14 };
|
|
9219
|
+
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}`;
|
|
9220
|
+
writeLine(header);
|
|
9221
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
9222
|
+
for (const sq of stats.bySquad.sort((a, b) => b.costUsd - a.costUsd)) {
|
|
9223
|
+
const budgetInfo = squadBudgets.get(sq.squad);
|
|
9224
|
+
const dailyBudget = budgetInfo?.daily || null;
|
|
9225
|
+
const spentStr = `$${sq.costUsd.toFixed(2)}`;
|
|
9226
|
+
const budgetStr = dailyBudget ? `$${dailyBudget}/d` : `${colors.dim}\u2014${RESET}`;
|
|
9227
|
+
let statusStr;
|
|
9228
|
+
if (dailyBudget) {
|
|
9229
|
+
const pct = sq.costUsd / dailyBudget * 100;
|
|
9230
|
+
if (pct >= 100) {
|
|
9231
|
+
statusStr = `${colors.red}\u25CF OVER${RESET}`;
|
|
9232
|
+
} else if (pct >= 80) {
|
|
9233
|
+
statusStr = `${colors.yellow}\u25CF ${pct.toFixed(0)}%${RESET}`;
|
|
9234
|
+
} else {
|
|
9235
|
+
statusStr = `${colors.green}\u2713 ${pct.toFixed(0)}%${RESET}`;
|
|
9236
|
+
}
|
|
9237
|
+
} else {
|
|
9238
|
+
statusStr = `${colors.dim}no budget${RESET}`;
|
|
9239
|
+
}
|
|
9240
|
+
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}`;
|
|
9241
|
+
writeLine(row);
|
|
9242
|
+
}
|
|
9243
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
9244
|
+
writeLine();
|
|
9245
|
+
}
|
|
9246
|
+
if (options.squad) {
|
|
9247
|
+
const squadStats = stats.bySquad?.find((s) => s.squad === options.squad);
|
|
9248
|
+
const squad = loadSquad(options.squad);
|
|
9249
|
+
if (squadStats) {
|
|
9250
|
+
writeLine(` ${bold}Squad: ${options.squad}${RESET}`);
|
|
9251
|
+
writeLine(` Spent today: ${colors.cyan}$${squadStats.costUsd.toFixed(2)}${RESET}`);
|
|
9252
|
+
writeLine(` Calls: ${squadStats.generations.toLocaleString()}`);
|
|
9253
|
+
if (squad?.context?.budget) {
|
|
9254
|
+
const daily = squad.context.budget.daily;
|
|
9255
|
+
const weekly = squad.context.budget.weekly;
|
|
9256
|
+
if (daily) {
|
|
9257
|
+
const pct = squadStats.costUsd / daily * 100;
|
|
9258
|
+
const statusIcon = pct >= 100 ? `${colors.red}\u25CF${RESET}` : pct >= 80 ? `${colors.yellow}\u25CF${RESET}` : `${colors.green}\u2713${RESET}`;
|
|
9259
|
+
writeLine(` Daily budget: ${statusIcon} $${squadStats.costUsd.toFixed(2)}/$${daily} (${pct.toFixed(0)}%)`);
|
|
9260
|
+
}
|
|
9261
|
+
if (weekly) {
|
|
9262
|
+
writeLine(` Weekly budget: $${weekly}/week`);
|
|
9263
|
+
}
|
|
9264
|
+
} else {
|
|
9265
|
+
writeLine(` ${colors.dim}No budget defined in SQUAD.md frontmatter${RESET}`);
|
|
9266
|
+
}
|
|
9267
|
+
} else {
|
|
9268
|
+
writeLine(` ${colors.dim}No cost data for squad "${options.squad}"${RESET}`);
|
|
9269
|
+
}
|
|
9270
|
+
writeLine();
|
|
9271
|
+
}
|
|
9272
|
+
writeLine(` ${colors.dim}Plan: ${plan.plan} (${plan.reason})${RESET}`);
|
|
9273
|
+
writeLine();
|
|
9274
|
+
}
|
|
9275
|
+
async function budgetCheckCommand(squadName, options = {}) {
|
|
9276
|
+
await track(Events.CLI_COST, { action: "budget-check", squad: squadName });
|
|
9277
|
+
const stats = await fetchBridgeStats();
|
|
9278
|
+
const squad = loadSquad(squadName);
|
|
9279
|
+
if (!stats) {
|
|
9280
|
+
if (options.json) {
|
|
9281
|
+
console.log(JSON.stringify({ error: "Bridge unavailable" }));
|
|
9282
|
+
} else {
|
|
9283
|
+
writeLine(` ${colors.yellow}\u26A0 Bridge unavailable${RESET}`);
|
|
9284
|
+
}
|
|
9285
|
+
return;
|
|
9286
|
+
}
|
|
9287
|
+
const squadStats = stats.bySquad?.find((s) => s.squad === squadName);
|
|
9288
|
+
const spent = squadStats?.costUsd || 0;
|
|
9289
|
+
const dailyBudget = squad?.context?.budget?.daily || null;
|
|
9290
|
+
const weeklyBudget = squad?.context?.budget?.weekly || null;
|
|
9291
|
+
const status = getBudgetStatus(squadName, spent, dailyBudget, weeklyBudget);
|
|
9292
|
+
if (options.json) {
|
|
9293
|
+
console.log(JSON.stringify(status, null, 2));
|
|
9294
|
+
return;
|
|
9295
|
+
}
|
|
9296
|
+
writeLine();
|
|
9297
|
+
if (status.status === "no-budget") {
|
|
9298
|
+
writeLine(` ${colors.dim}\u25CB${RESET} ${colors.cyan}${squadName}${RESET}: No budget defined`);
|
|
9299
|
+
writeLine(` ${colors.dim}Add budget to SQUAD.md frontmatter:${RESET}`);
|
|
9300
|
+
writeLine(` ${colors.dim} context:${RESET}`);
|
|
9301
|
+
writeLine(` ${colors.dim} budget: { daily: 50 }${RESET}`);
|
|
9302
|
+
} else if (status.status === "over") {
|
|
9303
|
+
writeLine(` ${colors.red}\u25CF${RESET} ${colors.cyan}${squadName}${RESET}: ${colors.red}OVER BUDGET${RESET}`);
|
|
9304
|
+
writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);
|
|
9305
|
+
} else if (status.status === "warning") {
|
|
9306
|
+
writeLine(` ${colors.yellow}\u25CF${RESET} ${colors.cyan}${squadName}${RESET}: ${colors.yellow}Approaching limit${RESET}`);
|
|
9307
|
+
writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);
|
|
9308
|
+
} else {
|
|
9309
|
+
writeLine(` ${colors.green}\u2713${RESET} ${colors.cyan}${squadName}${RESET}: OK to proceed`);
|
|
9310
|
+
writeLine(` $${spent.toFixed(2)}/$${dailyBudget} daily (${status.dailyPercent?.toFixed(0)}%)`);
|
|
9311
|
+
}
|
|
9312
|
+
writeLine();
|
|
9313
|
+
}
|
|
9314
|
+
function buildJsonOutput(stats, plan, squadFilter) {
|
|
9315
|
+
if (!stats) {
|
|
9316
|
+
return { error: "Bridge unavailable", plan };
|
|
9317
|
+
}
|
|
9318
|
+
const squadsDir = findSquadsDir();
|
|
9319
|
+
const squadBudgets = {};
|
|
9320
|
+
if (squadsDir) {
|
|
9321
|
+
for (const name of listSquads(squadsDir)) {
|
|
9322
|
+
const squad = loadSquad(name);
|
|
9323
|
+
if (squad?.context?.budget) {
|
|
9324
|
+
squadBudgets[name] = {
|
|
9325
|
+
daily: squad.context.budget.daily || null,
|
|
9326
|
+
weekly: squad.context.budget.weekly || null
|
|
9327
|
+
};
|
|
9328
|
+
}
|
|
9329
|
+
}
|
|
9330
|
+
}
|
|
9331
|
+
const bySquadWithBudget = stats.bySquad?.map((sq) => ({
|
|
9332
|
+
...sq,
|
|
9333
|
+
budget: squadBudgets[sq.squad] || null,
|
|
9334
|
+
budgetStatus: squadBudgets[sq.squad]?.daily ? getBudgetStatus(sq.squad, sq.costUsd, squadBudgets[sq.squad].daily, squadBudgets[sq.squad].weekly) : null
|
|
9335
|
+
}));
|
|
9336
|
+
if (squadFilter) {
|
|
9337
|
+
const filtered = bySquadWithBudget?.find((s) => s.squad === squadFilter);
|
|
9338
|
+
return {
|
|
9339
|
+
squad: squadFilter,
|
|
9340
|
+
...filtered,
|
|
9341
|
+
plan
|
|
9342
|
+
};
|
|
9343
|
+
}
|
|
9344
|
+
return {
|
|
9345
|
+
today: stats.today,
|
|
9346
|
+
week: stats.week,
|
|
9347
|
+
budget: stats.budget,
|
|
9348
|
+
bySquad: bySquadWithBudget,
|
|
9349
|
+
byModel: stats.byModel,
|
|
9350
|
+
plan,
|
|
9351
|
+
source: stats.source
|
|
9352
|
+
};
|
|
9353
|
+
}
|
|
9354
|
+
function shortModelName(model) {
|
|
9355
|
+
if (model.includes("opus")) return "opus";
|
|
9356
|
+
if (model.includes("sonnet")) return "sonnet";
|
|
9357
|
+
if (model.includes("haiku")) return "haiku";
|
|
9358
|
+
return model.split("-")[0];
|
|
9359
|
+
}
|
|
9360
|
+
function formatTokens(tokens) {
|
|
9361
|
+
if (tokens >= 1e6) {
|
|
9362
|
+
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
9363
|
+
}
|
|
9364
|
+
if (tokens >= 1e3) {
|
|
9365
|
+
return `${(tokens / 1e3).toFixed(0)}k`;
|
|
9366
|
+
}
|
|
9367
|
+
return tokens.toString();
|
|
9368
|
+
}
|
|
9369
|
+
function createBudgetBar(percent, width = 10) {
|
|
9370
|
+
const filled = Math.min(Math.round(percent / 100 * width), width);
|
|
9371
|
+
const empty = width - filled;
|
|
9372
|
+
const filledChar = "\u2501";
|
|
9373
|
+
const emptyChar = "\u2501";
|
|
9374
|
+
const color = percent >= 100 ? colors.red : percent >= 80 ? colors.yellow : colors.green;
|
|
9375
|
+
return `${color}${filledChar.repeat(filled)}${colors.dim}${emptyChar.repeat(empty)}${RESET}`;
|
|
9376
|
+
}
|
|
9377
|
+
|
|
9378
|
+
// src/lib/executions.ts
|
|
9379
|
+
import { readFileSync as readFileSync16, existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
|
|
9380
|
+
import { join as join20 } from "path";
|
|
9381
|
+
function parseExecutionEntry(content, squad, agent) {
|
|
9382
|
+
const idMatch = content.match(/<!-- exec:(\S+) -->/);
|
|
9383
|
+
if (!idMatch) return null;
|
|
9384
|
+
const id = idMatch[1];
|
|
9385
|
+
const headerMatch = content.match(/\*\*([^*]+)\*\* \| Status: (\w+)/);
|
|
9386
|
+
if (!headerMatch) return null;
|
|
9387
|
+
const startTime = headerMatch[1].trim();
|
|
9388
|
+
const status = headerMatch[2];
|
|
9389
|
+
const triggerMatch = content.match(/- Trigger: (\w+)/);
|
|
9390
|
+
const taskTypeMatch = content.match(/- Task Type: (\w+)/);
|
|
9391
|
+
const completedMatch = content.match(/- Completed: ([^\n]+)/);
|
|
9392
|
+
const durationMatch = content.match(/- Duration: ([^\n]+)/);
|
|
9393
|
+
const outcomeMatch = content.match(/- Outcome: ([^\n]+)/);
|
|
9394
|
+
const errorMatch = content.match(/- Error: ([^\n]+)/);
|
|
9395
|
+
let durationMs;
|
|
9396
|
+
if (durationMatch) {
|
|
9397
|
+
const durationStr = durationMatch[1].trim();
|
|
9398
|
+
const secMatch = durationStr.match(/^([\d.]+)s$/);
|
|
9399
|
+
if (secMatch) {
|
|
9400
|
+
durationMs = parseFloat(secMatch[1]) * 1e3;
|
|
9401
|
+
}
|
|
9402
|
+
}
|
|
9403
|
+
return {
|
|
9404
|
+
id,
|
|
9405
|
+
squad,
|
|
9406
|
+
agent,
|
|
9407
|
+
startTime,
|
|
9408
|
+
endTime: completedMatch?.[1]?.trim(),
|
|
9409
|
+
durationMs,
|
|
9410
|
+
status,
|
|
9411
|
+
trigger: triggerMatch?.[1] || "manual",
|
|
9412
|
+
taskType: taskTypeMatch?.[1] || "execution",
|
|
9413
|
+
outcome: outcomeMatch?.[1]?.trim(),
|
|
9414
|
+
error: errorMatch?.[1]?.trim()
|
|
9415
|
+
};
|
|
9416
|
+
}
|
|
9417
|
+
function parseExecutionLog(filePath, squad, agent) {
|
|
9418
|
+
if (!existsSync18(filePath)) return [];
|
|
9419
|
+
const content = readFileSync16(filePath, "utf-8");
|
|
9420
|
+
const executions = [];
|
|
9421
|
+
const entries = content.split(/\n---\n/);
|
|
9422
|
+
for (const entry of entries) {
|
|
9423
|
+
if (!entry.includes("<!-- exec:")) continue;
|
|
9424
|
+
const execution = parseExecutionEntry(entry, squad, agent);
|
|
9425
|
+
if (execution) {
|
|
9426
|
+
executions.push(execution);
|
|
9427
|
+
}
|
|
9428
|
+
}
|
|
9429
|
+
for (const entry of entries) {
|
|
9430
|
+
if (entry.includes("<!-- exec:")) continue;
|
|
9431
|
+
const headerMatch = entry.match(/\*\*([^*]+)\*\* \| Status: (\w+)/);
|
|
9432
|
+
if (!headerMatch) continue;
|
|
9433
|
+
const startTime = headerMatch[1].trim();
|
|
9434
|
+
const status = headerMatch[2];
|
|
9435
|
+
const legacyId = `legacy_${startTime.replace(/[^a-z0-9]/gi, "")}`;
|
|
9436
|
+
if (executions.some((e) => e.startTime === startTime)) continue;
|
|
9437
|
+
executions.push({
|
|
9438
|
+
id: legacyId,
|
|
9439
|
+
squad,
|
|
9440
|
+
agent,
|
|
9441
|
+
startTime,
|
|
9442
|
+
status,
|
|
9443
|
+
trigger: "manual",
|
|
9444
|
+
taskType: "execution"
|
|
9445
|
+
});
|
|
9446
|
+
}
|
|
9447
|
+
return executions;
|
|
9448
|
+
}
|
|
9449
|
+
function listExecutions(options = {}) {
|
|
9450
|
+
const memoryDir = findMemoryDir();
|
|
9451
|
+
if (!memoryDir) return [];
|
|
9452
|
+
const executions = [];
|
|
9453
|
+
const { squad: filterSquad, agent: filterAgent, status: filterStatus, limit, since } = options;
|
|
9454
|
+
const squads = readdirSync8(memoryDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
9455
|
+
for (const squad of squads) {
|
|
9456
|
+
if (filterSquad && squad !== filterSquad) continue;
|
|
9457
|
+
const squadPath = join20(memoryDir, squad);
|
|
9458
|
+
const agents = readdirSync8(squadPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
9459
|
+
for (const agent of agents) {
|
|
9460
|
+
if (filterAgent && agent !== filterAgent) continue;
|
|
9461
|
+
const logPath = join20(squadPath, agent, "executions.md");
|
|
9462
|
+
const agentExecutions = parseExecutionLog(logPath, squad, agent);
|
|
9463
|
+
executions.push(...agentExecutions);
|
|
9464
|
+
}
|
|
9465
|
+
}
|
|
9466
|
+
let filtered = filterStatus ? executions.filter((e) => e.status === filterStatus) : executions;
|
|
9467
|
+
if (since) {
|
|
9468
|
+
const sinceMs = since.getTime();
|
|
9469
|
+
filtered = filtered.filter((e) => {
|
|
9470
|
+
const execDate = new Date(e.startTime).getTime();
|
|
9471
|
+
return !isNaN(execDate) && execDate >= sinceMs;
|
|
9472
|
+
});
|
|
9473
|
+
}
|
|
9474
|
+
filtered.sort((a, b) => {
|
|
9475
|
+
const aTime = new Date(a.startTime).getTime();
|
|
9476
|
+
const bTime = new Date(b.startTime).getTime();
|
|
9477
|
+
if (isNaN(aTime) || isNaN(bTime)) return 0;
|
|
9478
|
+
return bTime - aTime;
|
|
9479
|
+
});
|
|
9480
|
+
if (limit && limit > 0) {
|
|
9481
|
+
filtered = filtered.slice(0, limit);
|
|
9482
|
+
}
|
|
9483
|
+
return filtered;
|
|
9484
|
+
}
|
|
9485
|
+
function getExecutionStats(options = {}) {
|
|
9486
|
+
const executions = listExecutions(options);
|
|
9487
|
+
const running = executions.filter((e) => e.status === "running").length;
|
|
9488
|
+
const completed = executions.filter((e) => e.status === "completed").length;
|
|
9489
|
+
const failed = executions.filter((e) => e.status === "failed").length;
|
|
9490
|
+
const durations = executions.filter((e) => e.status === "completed" && e.durationMs).map((e) => e.durationMs);
|
|
9491
|
+
const avgDurationMs = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : null;
|
|
9492
|
+
const bySquad = {};
|
|
9493
|
+
for (const e of executions) {
|
|
9494
|
+
bySquad[e.squad] = (bySquad[e.squad] || 0) + 1;
|
|
9495
|
+
}
|
|
9496
|
+
const byAgent = {};
|
|
9497
|
+
for (const e of executions) {
|
|
9498
|
+
const key = `${e.squad}/${e.agent}`;
|
|
9499
|
+
byAgent[key] = (byAgent[key] || 0) + 1;
|
|
9500
|
+
}
|
|
9501
|
+
return {
|
|
9502
|
+
total: executions.length,
|
|
9503
|
+
running,
|
|
9504
|
+
completed,
|
|
9505
|
+
failed,
|
|
9506
|
+
avgDurationMs,
|
|
9507
|
+
bySquad,
|
|
9508
|
+
byAgent
|
|
9509
|
+
};
|
|
9510
|
+
}
|
|
9511
|
+
function formatDuration2(ms) {
|
|
9512
|
+
if (!ms) return "\u2014";
|
|
9513
|
+
if (ms < 1e3) {
|
|
9514
|
+
return `${ms}ms`;
|
|
9515
|
+
}
|
|
9516
|
+
if (ms < 6e4) {
|
|
9517
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
9518
|
+
}
|
|
9519
|
+
if (ms < 36e5) {
|
|
9520
|
+
const mins2 = Math.floor(ms / 6e4);
|
|
9521
|
+
const secs = Math.round(ms % 6e4 / 1e3);
|
|
9522
|
+
return `${mins2}m ${secs}s`;
|
|
9523
|
+
}
|
|
9524
|
+
const hours = Math.floor(ms / 36e5);
|
|
9525
|
+
const mins = Math.round(ms % 36e5 / 6e4);
|
|
9526
|
+
return `${hours}h ${mins}m`;
|
|
9527
|
+
}
|
|
9528
|
+
function formatRelativeTime(isoTime) {
|
|
9529
|
+
const date = new Date(isoTime);
|
|
9530
|
+
if (isNaN(date.getTime())) return isoTime;
|
|
9531
|
+
const now = Date.now();
|
|
9532
|
+
const diff = now - date.getTime();
|
|
9533
|
+
if (diff < 6e4) return "just now";
|
|
9534
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
9535
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
|
|
9536
|
+
if (diff < 6048e5) return `${Math.floor(diff / 864e5)}d ago`;
|
|
9537
|
+
return date.toLocaleDateString();
|
|
9538
|
+
}
|
|
9539
|
+
|
|
9540
|
+
// src/commands/exec.ts
|
|
9541
|
+
async function execListCommand(options = {}) {
|
|
9542
|
+
await track(Events.CLI_EXEC, { action: "list", squad: options.squad });
|
|
9543
|
+
const listOptions = {
|
|
9544
|
+
squad: options.squad,
|
|
9545
|
+
agent: options.agent,
|
|
9546
|
+
limit: options.limit || 20
|
|
9547
|
+
};
|
|
9548
|
+
if (options.status) {
|
|
9549
|
+
listOptions.status = options.status;
|
|
9550
|
+
}
|
|
9551
|
+
const executions = listExecutions(listOptions);
|
|
9552
|
+
if (options.json) {
|
|
9553
|
+
console.log(JSON.stringify(executions, null, 2));
|
|
9554
|
+
return;
|
|
9555
|
+
}
|
|
9556
|
+
writeLine();
|
|
9557
|
+
writeLine(` ${gradient("squads")} ${colors.dim}exec list${RESET}${options.squad ? ` ${colors.cyan}${options.squad}${RESET}` : ""}`);
|
|
9558
|
+
writeLine();
|
|
9559
|
+
if (executions.length === 0) {
|
|
9560
|
+
writeLine(` ${colors.dim}No executions found${RESET}`);
|
|
9561
|
+
writeLine();
|
|
9562
|
+
writeLine(` ${colors.dim}Executions are logged when running agents:${RESET}`);
|
|
9563
|
+
writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}<squad>${RESET} --execute`);
|
|
9564
|
+
writeLine();
|
|
9565
|
+
return;
|
|
9566
|
+
}
|
|
9567
|
+
const w = { agent: 22, status: 12, duration: 10, time: 14, id: 18 };
|
|
9568
|
+
const tableWidth = w.agent + w.status + w.duration + w.time + w.id + 8;
|
|
9569
|
+
writeLine(` ${colors.purple}${box.topLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.topRight}${RESET}`);
|
|
9570
|
+
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}`;
|
|
9571
|
+
writeLine(header);
|
|
9572
|
+
writeLine(` ${colors.purple}${box.teeRight}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.teeLeft}${RESET}`);
|
|
9573
|
+
for (const exec3 of executions) {
|
|
9574
|
+
const agentName = `${exec3.squad}/${exec3.agent}`;
|
|
9575
|
+
const truncatedAgent = agentName.length > w.agent - 1 ? agentName.slice(0, w.agent - 4) + "..." : agentName;
|
|
9576
|
+
const statusIcon = exec3.status === "running" ? icons.running : exec3.status === "completed" ? icons.success : icons.error;
|
|
9577
|
+
const statusColor = exec3.status === "running" ? colors.yellow : exec3.status === "completed" ? colors.green : colors.red;
|
|
9578
|
+
const statusStr = `${statusColor}${statusIcon} ${exec3.status}${RESET}`;
|
|
9579
|
+
const durationStr = formatDuration2(exec3.durationMs);
|
|
9580
|
+
const timeStr = formatRelativeTime(exec3.startTime);
|
|
9581
|
+
const shortId = exec3.id.slice(0, 16);
|
|
9582
|
+
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}`;
|
|
9583
|
+
writeLine(row);
|
|
9584
|
+
}
|
|
9585
|
+
writeLine(` ${colors.purple}${box.bottomLeft}${colors.dim}${box.horizontal.repeat(tableWidth)}${colors.purple}${box.bottomRight}${RESET}`);
|
|
9586
|
+
writeLine();
|
|
9587
|
+
const stats = getExecutionStats(listOptions);
|
|
9588
|
+
const parts = [];
|
|
9589
|
+
if (stats.running > 0) parts.push(`${colors.yellow}${stats.running} running${RESET}`);
|
|
9590
|
+
if (stats.completed > 0) parts.push(`${colors.green}${stats.completed} completed${RESET}`);
|
|
9591
|
+
if (stats.failed > 0) parts.push(`${colors.red}${stats.failed} failed${RESET}`);
|
|
9592
|
+
if (parts.length > 0) {
|
|
9593
|
+
writeLine(` ${parts.join(` ${colors.dim}|${RESET} `)}`);
|
|
9594
|
+
if (stats.avgDurationMs) {
|
|
9595
|
+
writeLine(` ${colors.dim}Avg duration: ${formatDuration2(stats.avgDurationMs)}${RESET}`);
|
|
9596
|
+
}
|
|
9597
|
+
writeLine();
|
|
9598
|
+
}
|
|
9599
|
+
writeLine(` ${colors.dim}$${RESET} squads exec show ${colors.cyan}<id>${RESET} ${colors.dim}Execution details${RESET}`);
|
|
9600
|
+
writeLine(` ${colors.dim}$${RESET} squads exec --json ${colors.dim}JSON output${RESET}`);
|
|
9601
|
+
writeLine();
|
|
9602
|
+
}
|
|
9603
|
+
async function execShowCommand(executionId, options = {}) {
|
|
9604
|
+
await track(Events.CLI_EXEC, { action: "show", id: executionId });
|
|
9605
|
+
const executions = listExecutions();
|
|
9606
|
+
const matches = executions.filter(
|
|
9607
|
+
(e) => e.id === executionId || e.id.startsWith(executionId)
|
|
9608
|
+
);
|
|
9609
|
+
if (matches.length === 0) {
|
|
9610
|
+
writeLine();
|
|
9611
|
+
writeLine(` ${colors.red}Execution not found: ${executionId}${RESET}`);
|
|
9612
|
+
writeLine();
|
|
9613
|
+
writeLine(` ${colors.dim}List recent executions:${RESET}`);
|
|
9614
|
+
writeLine(` ${colors.dim}$${RESET} squads exec list`);
|
|
9615
|
+
writeLine();
|
|
9616
|
+
return;
|
|
9617
|
+
}
|
|
9618
|
+
if (matches.length > 1) {
|
|
9619
|
+
writeLine();
|
|
9620
|
+
writeLine(` ${colors.yellow}Multiple matches for "${executionId}":${RESET}`);
|
|
9621
|
+
for (const m of matches.slice(0, 5)) {
|
|
9622
|
+
writeLine(` ${colors.dim}${m.id}${RESET} - ${m.squad}/${m.agent}`);
|
|
9623
|
+
}
|
|
9624
|
+
writeLine();
|
|
9625
|
+
writeLine(` ${colors.dim}Provide a more specific ID${RESET}`);
|
|
9626
|
+
writeLine();
|
|
9627
|
+
return;
|
|
9628
|
+
}
|
|
9629
|
+
const exec3 = matches[0];
|
|
9630
|
+
if (options.json) {
|
|
9631
|
+
console.log(JSON.stringify(exec3, null, 2));
|
|
9632
|
+
return;
|
|
9633
|
+
}
|
|
9634
|
+
writeLine();
|
|
9635
|
+
writeLine(` ${gradient("squads")} ${colors.dim}exec show${RESET}`);
|
|
9636
|
+
writeLine();
|
|
9637
|
+
writeLine(` ${bold}${exec3.squad}/${exec3.agent}${RESET}`);
|
|
9638
|
+
writeLine(` ${colors.dim}${exec3.id}${RESET}`);
|
|
9639
|
+
writeLine();
|
|
9640
|
+
const statusIcon = exec3.status === "running" ? icons.running : exec3.status === "completed" ? icons.success : icons.error;
|
|
9641
|
+
const statusColor = exec3.status === "running" ? colors.yellow : exec3.status === "completed" ? colors.green : colors.red;
|
|
9642
|
+
writeLine(` ${bold}Status${RESET} ${statusColor}${statusIcon} ${exec3.status}${RESET}`);
|
|
9643
|
+
writeLine(` ${bold}Task Type${RESET} ${exec3.taskType}`);
|
|
9644
|
+
writeLine(` ${bold}Trigger${RESET} ${exec3.trigger}`);
|
|
9645
|
+
writeLine();
|
|
9646
|
+
writeLine(` ${bold}Started${RESET} ${exec3.startTime}`);
|
|
9647
|
+
if (exec3.endTime) {
|
|
9648
|
+
writeLine(` ${bold}Completed${RESET} ${exec3.endTime}`);
|
|
9649
|
+
}
|
|
9650
|
+
if (exec3.durationMs) {
|
|
9651
|
+
writeLine(` ${bold}Duration${RESET} ${formatDuration2(exec3.durationMs)}`);
|
|
9652
|
+
}
|
|
9653
|
+
writeLine();
|
|
9654
|
+
if (exec3.outcome) {
|
|
9655
|
+
writeLine(` ${bold}Outcome${RESET}`);
|
|
9656
|
+
writeLine(` ${colors.dim}${exec3.outcome}${RESET}`);
|
|
9657
|
+
writeLine();
|
|
9658
|
+
}
|
|
9659
|
+
if (exec3.error) {
|
|
9660
|
+
writeLine(` ${bold}Error${RESET}`);
|
|
9661
|
+
writeLine(` ${colors.red}${exec3.error}${RESET}`);
|
|
9662
|
+
writeLine();
|
|
9663
|
+
}
|
|
9664
|
+
writeLine(` ${colors.dim}$${RESET} squads memory show ${colors.cyan}${exec3.squad}${RESET} ${colors.dim}Squad memory${RESET}`);
|
|
9665
|
+
writeLine(` ${colors.dim}$${RESET} squads exec list --squad ${colors.cyan}${exec3.squad}${RESET} ${colors.dim}Squad history${RESET}`);
|
|
9666
|
+
writeLine();
|
|
9667
|
+
}
|
|
9668
|
+
async function execStatsCommand(options = {}) {
|
|
9669
|
+
await track(Events.CLI_EXEC, { action: "stats", squad: options.squad });
|
|
9670
|
+
const listOptions = {
|
|
9671
|
+
squad: options.squad
|
|
9672
|
+
};
|
|
9673
|
+
const stats = getExecutionStats(listOptions);
|
|
9674
|
+
if (options.json) {
|
|
9675
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
9676
|
+
return;
|
|
9677
|
+
}
|
|
9678
|
+
writeLine();
|
|
9679
|
+
writeLine(` ${gradient("squads")} ${colors.dim}exec stats${RESET}${options.squad ? ` ${colors.cyan}${options.squad}${RESET}` : ""}`);
|
|
9680
|
+
writeLine();
|
|
9681
|
+
writeLine(` ${bold}Total${RESET} ${stats.total}`);
|
|
9682
|
+
writeLine(` ${colors.yellow}Running${RESET} ${stats.running}`);
|
|
9683
|
+
writeLine(` ${colors.green}Completed${RESET} ${stats.completed}`);
|
|
9684
|
+
writeLine(` ${colors.red}Failed${RESET} ${stats.failed}`);
|
|
9685
|
+
writeLine();
|
|
9686
|
+
if (stats.avgDurationMs) {
|
|
9687
|
+
writeLine(` ${bold}Avg Duration${RESET} ${formatDuration2(stats.avgDurationMs)}`);
|
|
9688
|
+
writeLine();
|
|
9689
|
+
}
|
|
9690
|
+
const squadEntries = Object.entries(stats.bySquad).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
9691
|
+
if (squadEntries.length > 0) {
|
|
9692
|
+
writeLine(` ${bold}By Squad${RESET}`);
|
|
9693
|
+
for (const [squad, count] of squadEntries) {
|
|
9694
|
+
writeLine(` ${colors.cyan}${squad}${RESET}: ${count}`);
|
|
9695
|
+
}
|
|
9696
|
+
writeLine();
|
|
9697
|
+
}
|
|
9698
|
+
const agentEntries = Object.entries(stats.byAgent).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
9699
|
+
if (agentEntries.length > 0) {
|
|
9700
|
+
writeLine(` ${bold}Top Agents${RESET}`);
|
|
9701
|
+
for (const [agent, count] of agentEntries) {
|
|
9702
|
+
writeLine(` ${colors.cyan}${agent}${RESET}: ${count}`);
|
|
9703
|
+
}
|
|
9704
|
+
writeLine();
|
|
9705
|
+
}
|
|
9706
|
+
}
|
|
9707
|
+
|
|
9708
|
+
// src/commands/tonight.ts
|
|
9709
|
+
import ora7 from "ora";
|
|
7964
9710
|
import fs2 from "fs/promises";
|
|
7965
|
-
import path2, { dirname as
|
|
9711
|
+
import path2, { dirname as dirname6 } from "path";
|
|
7966
9712
|
import { execSync as execSync14, spawn as spawn7 } from "child_process";
|
|
7967
9713
|
function getProjectRoot2() {
|
|
7968
9714
|
const squadsDir = findSquadsDir();
|
|
7969
9715
|
if (squadsDir) {
|
|
7970
|
-
return
|
|
9716
|
+
return dirname6(dirname6(squadsDir));
|
|
7971
9717
|
}
|
|
7972
9718
|
return process.cwd();
|
|
7973
9719
|
}
|
|
@@ -8107,7 +9853,7 @@ async function tonightCommand(targets, options) {
|
|
|
8107
9853
|
totalCost: await getCurrentCost(),
|
|
8108
9854
|
stopped: false
|
|
8109
9855
|
};
|
|
8110
|
-
const spinner =
|
|
9856
|
+
const spinner = ora7("Launching agents...").start();
|
|
8111
9857
|
for (const target of targets) {
|
|
8112
9858
|
const sessionId = `${target.replace("/", "-")}-${Date.now()}`;
|
|
8113
9859
|
const logFile = path2.join(logDir, `${sessionId}.log`);
|
|
@@ -8295,12 +10041,12 @@ process.stderr.on("error", (err) => {
|
|
|
8295
10041
|
throw err;
|
|
8296
10042
|
});
|
|
8297
10043
|
var envPaths = [
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
10044
|
+
join21(process.cwd(), ".env"),
|
|
10045
|
+
join21(process.cwd(), "..", "hq", ".env"),
|
|
10046
|
+
join21(homedir5(), "agents-squads", "hq", ".env")
|
|
8301
10047
|
];
|
|
8302
10048
|
for (const envPath of envPaths) {
|
|
8303
|
-
if (
|
|
10049
|
+
if (existsSync19(envPath)) {
|
|
8304
10050
|
config({ path: envPath, quiet: true });
|
|
8305
10051
|
break;
|
|
8306
10052
|
}
|
|
@@ -8320,10 +10066,20 @@ program.name("squads").description("A CLI for humans and agents").version(versio
|
|
|
8320
10066
|
throw err;
|
|
8321
10067
|
});
|
|
8322
10068
|
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) }));
|
|
10069
|
+
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
10070
|
program.command("list").description("List agents and squads").option("-s, --squads", "List squads only").option("-a, --agents", "List agents only").action(listCommand);
|
|
8325
10071
|
program.command("status [squad]").description("Show squad status and state").option("-v, --verbose", "Show detailed status").action(statusCommand);
|
|
8326
10072
|
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 }));
|
|
10073
|
+
var context = program.command("context").description("View and manage squad execution context");
|
|
10074
|
+
context.command("show <squad>").description("Show context for a squad (MCP, skills, model, budget)").option("--json", "Output as JSON").action(contextShowCommand);
|
|
10075
|
+
context.command("list").description("List context for all squads").option("--json", "Output as JSON").action(contextListCommand);
|
|
10076
|
+
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);
|
|
10077
|
+
program.command("budget").description("Check budget status for a squad").argument("<squad>", "Squad to check").option("--json", "Output as JSON").action(budgetCheckCommand);
|
|
10078
|
+
var exec2 = program.command("exec").description("View execution history and statistics");
|
|
10079
|
+
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) }));
|
|
10080
|
+
exec2.command("show <id>").description("Show execution details").option("--json", "Output as JSON").action(execShowCommand);
|
|
10081
|
+
exec2.command("stats").description("Show execution statistics").option("-s, --squad <squad>", "Filter by squad").option("--json", "Output as JSON").action(execStatsCommand);
|
|
10082
|
+
exec2.action((options) => execListCommand(options));
|
|
8327
10083
|
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
10084
|
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
10085
|
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 +10133,11 @@ sessions.command("history").description("Show session history and statistics").o
|
|
|
8377
10133
|
json: options.json
|
|
8378
10134
|
}));
|
|
8379
10135
|
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-
|
|
10136
|
+
const { buildCurrentSessionSummary } = await import("./sessions-SEITSWEV.js");
|
|
8381
10137
|
let data;
|
|
8382
10138
|
if (options.file) {
|
|
8383
|
-
const { readFileSync:
|
|
8384
|
-
data = JSON.parse(
|
|
10139
|
+
const { readFileSync: readFileSync17 } = await import("fs");
|
|
10140
|
+
data = JSON.parse(readFileSync17(options.file, "utf-8"));
|
|
8385
10141
|
} else if (options.data) {
|
|
8386
10142
|
data = JSON.parse(options.data);
|
|
8387
10143
|
} else if (!process.stdin.isTTY) {
|
|
@@ -8414,6 +10170,8 @@ stack.command("down").description("Stop Docker containers").action(stackDownComm
|
|
|
8414
10170
|
stack.command("health").description("Comprehensive health check with diagnostics").option("-v, --verbose", "Show logs for unhealthy services").action((options) => stackHealthCommand(options.verbose));
|
|
8415
10171
|
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
10172
|
registerTriggerCommand(program);
|
|
10173
|
+
registerSkillCommand(program);
|
|
10174
|
+
registerPermissionsCommand(program);
|
|
8417
10175
|
var tonight = program.command("tonight").description("Run agents autonomously overnight with safety limits");
|
|
8418
10176
|
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
10177
|
costCap: parseFloat(options.costCap),
|