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/dist/cli.js CHANGED
@@ -28,15 +28,15 @@ import {
28
28
  truncate,
29
29
  updateHeartbeat,
30
30
  writeLine
31
- } from "./chunk-G63RBKDH.js";
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 existsSync17 } from "fs";
39
- import { join as join17 } from "path";
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 [hash, author, date] = line.split("|");
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 content = readFileSync2(filePath, "utf-8");
1219
- const lines = content.split("\n");
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
- ${record.endTime ? `Completed: ${record.endTime}` : ""}
1464
- ${record.outcome ? `Outcome: ${record.outcome}` : ""}
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, outcome) {
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
- content = content.replace(
1474
- /Status: running\n$/,
1475
- `Status: ${status}
1476
- Completed: ${endTime}
1477
- ${outcome ? `Outcome: ${outcome}
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 startTime = (/* @__PURE__ */ new Date()).toISOString();
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", String(error));
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
- userConfigPath,
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
- SQUADS_SQUAD: squadName,
1835
- SQUADS_AGENT: agentName
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 '${userConfigPath}' -- '${escapedPrompt}'; tmux kill-session -t ${sessionName} 2>/dev/null`;
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
- SQUADS_SQUAD: squadName,
1877
- SQUADS_AGENT: agentName
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 client = await pool2.connect();
4709
- await client.query("SELECT 1");
4710
- client.release();
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 client = await pool2.connect();
4723
- const result = await client.query(`
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
- client.release();
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 client = await getPool().connect();
4764
- const result = await client.query(`
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
- client.release();
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
- writeLine(` ${bold}Goals${RESET}`);
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 = 6;
5011
- for (const { squad, goal: goal2 } of allActiveGoals.slice(0, maxGoals)) {
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 icon = hasProgress ? icons.progress : icons.empty;
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 exec2 of executions) {
6615
- const dateKey = exec2.startedAt.toISOString().split("T")[0];
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(exec2);
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 exec2 of bridgeExecs) {
6651
- seenIds.add(exec2.id);
6652
- allExecutions.push(exec2);
7106
+ for (const exec3 of bridgeExecs) {
7107
+ seenIds.add(exec3.id);
7108
+ allExecutions.push(exec3);
6653
7109
  }
6654
- for (const exec2 of localExecs) {
6655
- if (!seenIds.has(exec2.id)) {
6656
- allExecutions.push(exec2);
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 exec2 of execs) {
6681
- const time = exec2.startedAt.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
6682
- const squadName = truncate(exec2.squad, 11);
6683
- const agentName = truncate(exec2.agent, 14);
6684
- const duration = formatDuration(exec2.durationMs);
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 (exec2.status) {
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 && (exec2.cost || exec2.tokens)) {
6706
- const costStr = exec2.cost ? `$${exec2.cost.toFixed(2)}` : "";
6707
- const tokenStr = exec2.tokens ? `${exec2.tokens.toLocaleString()} tokens` : "";
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 (exec2.error) {
6712
- writeLine(` ${colors.purple}\u2502${RESET} ${colors.red}\u2514 ${truncate(exec2.error, 45)}${RESET}${" ".repeat(Math.max(0, 45 - exec2.error.length))}${colors.purple}\u2502${RESET}`);
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/tonight.ts
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 dirname5 } from "path";
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 dirname5(dirname5(squadsDir));
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 = ora6("Launching agents...").start();
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
- join17(process.cwd(), ".env"),
8299
- join17(process.cwd(), "..", "hq", ".env"),
8300
- join17(homedir5(), "agents-squads", "hq", ".env")
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 (existsSync17(envPath)) {
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-JCQ34BEU.js");
10136
+ const { buildCurrentSessionSummary } = await import("./sessions-SEITSWEV.js");
8381
10137
  let data;
8382
10138
  if (options.file) {
8383
- const { readFileSync: readFileSync13 } = await import("fs");
8384
- data = JSON.parse(readFileSync13(options.file, "utf-8"));
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),