squads-cli 0.4.5 → 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",
@@ -544,6 +553,29 @@ async function confirm(question, defaultYes = true) {
544
553
  });
545
554
  });
546
555
  }
556
+ async function promptEmail() {
557
+ const rl = createInterface({
558
+ input: process.stdin,
559
+ output: process.stdout
560
+ });
561
+ return new Promise((resolve) => {
562
+ rl.question(` ${chalk.dim("\u{1F4EC} Get product updates? (optional, Enter to skip):")} `, (answer) => {
563
+ rl.close();
564
+ const email = answer.trim();
565
+ if (!email) {
566
+ resolve(null);
567
+ return;
568
+ }
569
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
570
+ if (emailRegex.test(email)) {
571
+ resolve(email);
572
+ } else {
573
+ console.log(chalk.dim(" Invalid email, skipping..."));
574
+ resolve(null);
575
+ }
576
+ });
577
+ });
578
+ }
547
579
  function commandExists(cmd) {
548
580
  try {
549
581
  execSync2(`which ${cmd}`, { stdio: "ignore" });
@@ -1101,6 +1133,15 @@ squads goal list # View goals
1101
1133
  console.log(chalk.dim(" \u2022 .claude/settings.json - Claude Code hooks"));
1102
1134
  console.log(chalk.dim(" \u2022 CLAUDE.md - Agent instructions"));
1103
1135
  console.log();
1136
+ const email = await promptEmail();
1137
+ if (email) {
1138
+ await track(Events.CLI_INIT, {
1139
+ event: "email_capture",
1140
+ email
1141
+ });
1142
+ console.log(chalk.dim(` \u2713 We'll send updates to ${email}`));
1143
+ console.log();
1144
+ }
1104
1145
  console.log(chalk.bold(" \u{1F449} Try it now:"));
1105
1146
  console.log();
1106
1147
  console.log(` ${chalk.yellow.bold("squads status")}`);
@@ -1121,6 +1162,7 @@ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync
1121
1162
  // src/lib/squad-parser.ts
1122
1163
  import { readFileSync as readFileSync2, existsSync as existsSync3, readdirSync, writeFileSync as writeFileSync2 } from "fs";
1123
1164
  import { join as join3, basename } from "path";
1165
+ import matter from "gray-matter";
1124
1166
  function findSquadsDir() {
1125
1167
  let dir = process.cwd();
1126
1168
  for (let i = 0; i < 5; i++) {
@@ -1183,17 +1225,24 @@ function listAgents(squadsDir, squadName) {
1183
1225
  return agents;
1184
1226
  }
1185
1227
  function parseSquadFile(filePath) {
1186
- const content = readFileSync2(filePath, "utf-8");
1187
- 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");
1188
1232
  const squad = {
1189
- name: basename(filePath).replace(".md", ""),
1190
- mission: "",
1233
+ name: fm.name || basename(filePath).replace(".md", ""),
1234
+ mission: fm.mission || "",
1191
1235
  agents: [],
1192
1236
  pipelines: [],
1193
1237
  triggers: { scheduled: [], event: [], manual: [] },
1194
1238
  dependencies: [],
1195
1239
  outputPath: "",
1196
- goals: []
1240
+ goals: [],
1241
+ // Apply frontmatter fields
1242
+ effort: fm.effort,
1243
+ context: fm.context,
1244
+ repo: fm.repo,
1245
+ stack: fm.stack
1197
1246
  };
1198
1247
  let currentSection = "";
1199
1248
  let inTable = false;
@@ -1213,6 +1262,10 @@ function parseSquadFile(filePath) {
1213
1262
  squad.mission = line.trim();
1214
1263
  }
1215
1264
  }
1265
+ const effortMatch = line.match(/^effort:\s*(high|medium|low)/i);
1266
+ if (effortMatch && !squad.effort) {
1267
+ squad.effort = effortMatch[1].toLowerCase();
1268
+ }
1216
1269
  if (currentSection.includes("agent") || currentSection.includes("orchestrator") || currentSection.includes("evaluator") || currentSection.includes("builder") || currentSection.includes("priority")) {
1217
1270
  if (line.includes("|") && line.includes("Agent")) {
1218
1271
  inTable = true;
@@ -1225,12 +1278,16 @@ function parseSquadFile(filePath) {
1225
1278
  const roleIdx = tableHeaders.findIndex((h) => h === "role");
1226
1279
  const triggerIdx = tableHeaders.findIndex((h) => h === "trigger");
1227
1280
  const statusIdx = tableHeaders.findIndex((h) => h === "status");
1281
+ const effortIdx = tableHeaders.findIndex((h) => h === "effort");
1228
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;
1229
1285
  squad.agents.push({
1230
1286
  name: cells[agentIdx],
1231
1287
  role: roleIdx >= 0 ? cells[roleIdx] : "",
1232
1288
  trigger: triggerIdx >= 0 ? cells[triggerIdx] : "manual",
1233
- status: statusIdx >= 0 ? cells[statusIdx] : "active"
1289
+ status: statusIdx >= 0 ? cells[statusIdx] : "active",
1290
+ effort
1234
1291
  });
1235
1292
  }
1236
1293
  }
@@ -1377,7 +1434,280 @@ function updateGoalInSquad(squadName, goalIndex, updates) {
1377
1434
  return false;
1378
1435
  }
1379
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
+
1380
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
+ }
1381
1711
  function ensureProjectTrusted(projectPath) {
1382
1712
  const configPath = join4(process.env.HOME || "", ".claude.json");
1383
1713
  if (!existsSync4(configPath)) {
@@ -1419,34 +1749,75 @@ function logExecution(record) {
1419
1749
  }
1420
1750
  let content = "";
1421
1751
  if (existsSync4(logPath)) {
1422
- content = readFileSync3(logPath, "utf-8");
1752
+ content = readFileSync3(logPath, "utf-8").trimEnd();
1423
1753
  } else {
1424
- content = `# ${record.squadName}/${record.agentName} - Execution Log
1425
-
1426
- `;
1754
+ content = `# ${record.squadName}/${record.agentName} - Execution Log`;
1427
1755
  }
1428
1756
  const entry = `
1757
+
1429
1758
  ---
1759
+ <!-- exec:${record.executionId} -->
1430
1760
  **${record.startTime}** | Status: ${record.status}
1431
- ${record.endTime ? `Completed: ${record.endTime}` : ""}
1432
- ${record.outcome ? `Outcome: ${record.outcome}` : ""}
1761
+ - ID: \`${record.executionId}\`
1762
+ - Trigger: ${record.trigger || "manual"}
1763
+ - Task Type: ${record.taskType || "execution"}
1433
1764
  `;
1434
1765
  writeFileSync3(logPath, content + entry);
1435
1766
  }
1436
- function updateExecutionStatus(squadName, agentName, status, outcome) {
1767
+ function updateExecutionStatus(squadName, agentName, executionId, status, details) {
1437
1768
  const logPath = getExecutionLogPath(squadName, agentName);
1438
1769
  if (!logPath || !existsSync4(logPath)) return;
1439
1770
  let content = readFileSync3(logPath, "utf-8");
1440
1771
  const endTime = (/* @__PURE__ */ new Date()).toISOString();
1441
- content = content.replace(
1442
- /Status: running\n$/,
1443
- `Status: ${status}
1444
- Completed: ${endTime}
1445
- ${outcome ? `Outcome: ${outcome}
1446
- ` : ""}`
1447
- );
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);
1448
1791
  writeFileSync3(logPath, content);
1449
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
+ }
1450
1821
  async function runCommand(target, options) {
1451
1822
  const squadsDir = findSquadsDir();
1452
1823
  if (!squadsDir) {
@@ -1476,6 +1847,9 @@ async function runCommand(target, options) {
1476
1847
  }
1477
1848
  async function runSquad(squad, squadsDir, options) {
1478
1849
  if (!squad) return;
1850
+ if (!options.effort && squad.effort) {
1851
+ options.effort = squad.effort;
1852
+ }
1479
1853
  const startTime = (/* @__PURE__ */ new Date()).toISOString();
1480
1854
  writeLine();
1481
1855
  writeLine(` ${gradient("squads")} ${colors.dim}run${RESET} ${colors.cyan}${squad.name}${RESET}`);
@@ -1665,7 +2039,7 @@ Begin by assessing pending work, then delegate to agents via Task tool.`;
1665
2039
  writeLine(` ${gradient("Launching")} lead session${options.foreground ? " (foreground)" : ""}...`);
1666
2040
  writeLine();
1667
2041
  try {
1668
- 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");
1669
2043
  if (options.foreground) {
1670
2044
  writeLine();
1671
2045
  writeLine(` ${icons.success} Lead session completed`);
@@ -1686,7 +2060,10 @@ Begin by assessing pending work, then delegate to agents via Task tool.`;
1686
2060
  }
1687
2061
  async function runAgent(agentName, agentPath, squadName, options) {
1688
2062
  const spinner = ora2(`Running agent: ${agentName}`).start();
1689
- 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);
1690
2067
  const definition = loadAgentDefinition(agentPath);
1691
2068
  if (options.dryRun) {
1692
2069
  spinner.info(`[DRY RUN] Would run ${agentName}`);
@@ -1696,11 +2073,40 @@ async function runAgent(agentName, agentPath, squadName, options) {
1696
2073
  }
1697
2074
  return;
1698
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
+ }
1699
2102
  logExecution({
1700
2103
  squadName,
1701
2104
  agentName,
2105
+ executionId,
1702
2106
  startTime,
1703
- status: "running"
2107
+ status: "running",
2108
+ trigger: options.trigger || "manual",
2109
+ taskType
1704
2110
  });
1705
2111
  const timeoutMins = options.timeout || 30;
1706
2112
  const prompt2 = `Execute the ${agentName} agent from squad ${squadName}.
@@ -1732,7 +2138,7 @@ CRITICAL: When you have completed your tasks OR reached the time limit:
1732
2138
  if (options.execute && claudeAvailable) {
1733
2139
  spinner.text = options.foreground ? `Running ${agentName} in foreground...` : `Launching ${agentName} as background task...`;
1734
2140
  try {
1735
- 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");
1736
2142
  if (options.foreground) {
1737
2143
  spinner.succeed(`Agent ${agentName} completed`);
1738
2144
  } else {
@@ -1744,7 +2150,10 @@ CRITICAL: When you have completed your tasks OR reached the time limit:
1744
2150
  }
1745
2151
  } catch (error) {
1746
2152
  spinner.fail(`Agent ${agentName} failed to launch`);
1747
- updateExecutionStatus(squadName, agentName, "failed", String(error));
2153
+ updateExecutionStatus(squadName, agentName, executionId, "failed", {
2154
+ error: String(error),
2155
+ durationMs: Date.now() - startMs
2156
+ });
1748
2157
  writeLine(` ${colors.red}${String(error)}${RESET}`);
1749
2158
  }
1750
2159
  } else {
@@ -1770,28 +2179,45 @@ async function checkClaudeCliAvailable() {
1770
2179
  check.on("error", () => resolve(false));
1771
2180
  });
1772
2181
  }
1773
- async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foreground, useApi) {
1774
- const userConfigPath = join4(process.env.HOME || "", ".claude.json");
2182
+ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foreground, useApi, effort, skills, trigger = "manual") {
1775
2183
  const projectRoot = getProjectRoot();
1776
2184
  ensureProjectTrusted(projectRoot);
1777
2185
  const squadMatch = prompt2.match(/squad (\w+)/);
1778
2186
  const agentMatch = prompt2.match(/(\w+) agent/);
1779
2187
  const squadName = process.env.SQUADS_SQUAD || squadMatch?.[1] || "unknown";
1780
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
+ };
1781
2197
  const { ANTHROPIC_API_KEY: _apiKey, ...envWithoutApiKey } = process.env;
1782
2198
  const spawnEnv = useApi ? process.env : envWithoutApiKey;
1783
2199
  const escapedPrompt = prompt2.replace(/'/g, "'\\''");
2200
+ await registerContextWithBridge(execContext);
1784
2201
  if (foreground) {
1785
2202
  if (verbose) {
1786
2203
  writeLine(` ${colors.dim}Project: ${projectRoot}${RESET}`);
1787
2204
  writeLine(` ${colors.dim}Mode: foreground${RESET}`);
1788
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
+ }
1789
2215
  }
1790
2216
  return new Promise((resolve, reject) => {
1791
2217
  const claude = spawn2("claude", [
1792
2218
  "--dangerously-skip-permissions",
1793
2219
  "--mcp-config",
1794
- userConfigPath,
2220
+ mcpConfigPath,
1795
2221
  "--",
1796
2222
  prompt2
1797
2223
  ], {
@@ -1799,8 +2225,17 @@ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foregro
1799
2225
  cwd: projectRoot,
1800
2226
  env: {
1801
2227
  ...spawnEnv,
1802
- SQUADS_SQUAD: squadName,
1803
- 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(",") }
1804
2239
  }
1805
2240
  });
1806
2241
  claude.on("close", (code) => {
@@ -1819,9 +2254,19 @@ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foregro
1819
2254
  if (verbose) {
1820
2255
  writeLine(` ${colors.dim}Project: ${projectRoot}${RESET}`);
1821
2256
  writeLine(` ${colors.dim}Session: ${sessionName}${RESET}`);
2257
+ writeLine(` ${colors.dim}MCP config: ${mcpConfigPath}${RESET}`);
1822
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
+ }
1823
2268
  }
1824
- 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`;
1825
2270
  const tmux = spawn2("tmux", [
1826
2271
  "new-session",
1827
2272
  "-d",
@@ -1841,8 +2286,17 @@ async function executeWithClaude(prompt2, verbose, _timeoutMinutes = 30, foregro
1841
2286
  detached: true,
1842
2287
  env: {
1843
2288
  ...spawnEnv,
1844
- SQUADS_SQUAD: squadName,
1845
- 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(",") }
1846
2300
  }
1847
2301
  });
1848
2302
  tmux.unref();
@@ -4673,9 +5127,9 @@ function getPool() {
4673
5127
  async function isDatabaseAvailable() {
4674
5128
  try {
4675
5129
  const pool2 = getPool();
4676
- const client = await pool2.connect();
4677
- await client.query("SELECT 1");
4678
- client.release();
5130
+ const client2 = await pool2.connect();
5131
+ await client2.query("SELECT 1");
5132
+ client2.release();
4679
5133
  return true;
4680
5134
  } catch (err) {
4681
5135
  if (process.env.DEBUG) {
@@ -4687,8 +5141,8 @@ async function isDatabaseAvailable() {
4687
5141
  async function saveDashboardSnapshot(snapshot) {
4688
5142
  try {
4689
5143
  const pool2 = getPool();
4690
- const client = await pool2.connect();
4691
- const result = await client.query(`
5144
+ const client2 = await pool2.connect();
5145
+ const result = await client2.query(`
4692
5146
  INSERT INTO squads.dashboard_snapshots (
4693
5147
  total_squads, total_commits, total_prs_merged, total_issues_closed, total_issues_open,
4694
5148
  goal_progress_pct, cost_usd, daily_budget_usd, input_tokens, output_tokens,
@@ -4717,7 +5171,7 @@ async function saveDashboardSnapshot(snapshot) {
4717
5171
  JSON.stringify(snapshot.authorsData),
4718
5172
  JSON.stringify(snapshot.reposData)
4719
5173
  ]);
4720
- client.release();
5174
+ client2.release();
4721
5175
  return result.rows[0]?.id ?? null;
4722
5176
  } catch (err) {
4723
5177
  if (process.env.DEBUG) {
@@ -4728,8 +5182,8 @@ async function saveDashboardSnapshot(snapshot) {
4728
5182
  }
4729
5183
  async function getDashboardHistory(limit = 30) {
4730
5184
  try {
4731
- const client = await getPool().connect();
4732
- const result = await client.query(`
5185
+ const client2 = await getPool().connect();
5186
+ const result = await client2.query(`
4733
5187
  SELECT
4734
5188
  total_squads, total_commits, total_prs_merged, total_issues_closed, total_issues_open,
4735
5189
  goal_progress_pct, cost_usd, daily_budget_usd, input_tokens, output_tokens,
@@ -4739,7 +5193,7 @@ async function getDashboardHistory(limit = 30) {
4739
5193
  ORDER BY captured_at DESC
4740
5194
  LIMIT $1
4741
5195
  `, [limit]);
4742
- client.release();
5196
+ client2.release();
4743
5197
  return result.rows.map((row) => ({
4744
5198
  totalSquads: row.total_squads,
4745
5199
  totalCommits: row.total_commits,
@@ -4969,16 +5423,50 @@ async function dashboardCommand(options = {}) {
4969
5423
  renderAcquisitionCached(cache);
4970
5424
  renderHistoricalTrendsCached(cache);
4971
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
+ }
4972
5436
  const allActiveGoals = squadData.flatMap(
4973
5437
  (s) => s.goals.filter((g) => !g.completed).map((g) => ({ squad: s.name, goal: g }))
4974
5438
  );
4975
5439
  if (allActiveGoals.length > 0) {
4976
- 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}`);
4977
5464
  writeLine();
4978
- const maxGoals = 6;
4979
- 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)) {
4980
5467
  const hasProgress = goal2.progress && goal2.progress.length > 0;
4981
- const icon = hasProgress ? icons.progress : icons.empty;
5468
+ const isActive = activeSquads2.has(squad);
5469
+ const icon = isActive ? icons.active : hasProgress ? icons.progress : icons.empty;
4982
5470
  writeLine(` ${icon} ${colors.dim}${squad}${RESET} ${truncate(goal2.description, 48)}`);
4983
5471
  if (hasProgress) {
4984
5472
  writeLine(` ${colors.dim}\u2514${RESET} ${colors.green}${truncate(goal2.progress, 52)}${RESET}`);
@@ -6579,12 +7067,12 @@ function formatDuration(ms) {
6579
7067
  }
6580
7068
  function groupByDate(executions) {
6581
7069
  const groups = /* @__PURE__ */ new Map();
6582
- for (const exec2 of executions) {
6583
- const dateKey = exec2.startedAt.toISOString().split("T")[0];
7070
+ for (const exec3 of executions) {
7071
+ const dateKey = exec3.startedAt.toISOString().split("T")[0];
6584
7072
  if (!groups.has(dateKey)) {
6585
7073
  groups.set(dateKey, []);
6586
7074
  }
6587
- groups.get(dateKey).push(exec2);
7075
+ groups.get(dateKey).push(exec3);
6588
7076
  }
6589
7077
  return groups;
6590
7078
  }
@@ -6615,13 +7103,13 @@ async function historyCommand(options = {}) {
6615
7103
  ]);
6616
7104
  const seenIds = /* @__PURE__ */ new Set();
6617
7105
  const allExecutions = [];
6618
- for (const exec2 of bridgeExecs) {
6619
- seenIds.add(exec2.id);
6620
- allExecutions.push(exec2);
7106
+ for (const exec3 of bridgeExecs) {
7107
+ seenIds.add(exec3.id);
7108
+ allExecutions.push(exec3);
6621
7109
  }
6622
- for (const exec2 of localExecs) {
6623
- if (!seenIds.has(exec2.id)) {
6624
- allExecutions.push(exec2);
7110
+ for (const exec3 of localExecs) {
7111
+ if (!seenIds.has(exec3.id)) {
7112
+ allExecutions.push(exec3);
6625
7113
  }
6626
7114
  }
6627
7115
  allExecutions.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
@@ -6645,14 +7133,14 @@ async function historyCommand(options = {}) {
6645
7133
  writeLine(` ${colors.purple}\u250C${"\u2500".repeat(60)}\u2510${RESET}`);
6646
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}`);
6647
7135
  writeLine(` ${colors.purple}\u251C${"\u2500".repeat(60)}\u2524${RESET}`);
6648
- for (const exec2 of execs) {
6649
- const time = exec2.startedAt.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
6650
- const squadName = truncate(exec2.squad, 11);
6651
- const agentName = truncate(exec2.agent, 14);
6652
- 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);
6653
7141
  let statusIcon;
6654
7142
  let statusColor;
6655
- switch (exec2.status) {
7143
+ switch (exec3.status) {
6656
7144
  case "success":
6657
7145
  statusIcon = icons.success;
6658
7146
  statusColor = colors.green;
@@ -6670,14 +7158,14 @@ async function historyCommand(options = {}) {
6670
7158
  statusColor = colors.dim;
6671
7159
  }
6672
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}`);
6673
- if (verbose && (exec2.cost || exec2.tokens)) {
6674
- const costStr = exec2.cost ? `$${exec2.cost.toFixed(2)}` : "";
6675
- 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` : "";
6676
7164
  const details = [costStr, tokenStr].filter(Boolean).join(" \u2502 ");
6677
7165
  writeLine(` ${colors.purple}\u2502${RESET} ${colors.dim}\u2514 ${details}${RESET}${" ".repeat(Math.max(0, 45 - details.length))}${colors.purple}\u2502${RESET}`);
6678
7166
  }
6679
- if (exec2.error) {
6680
- 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}`);
6681
7169
  }
6682
7170
  }
6683
7171
  writeLine(` ${colors.purple}\u2514${"\u2500".repeat(60)}\u2518${RESET}`);
@@ -7927,15 +8415,1305 @@ function registerTriggerCommand(program2) {
7927
8415
  });
7928
8416
  }
7929
8417
 
7930
- // src/commands/tonight.ts
8418
+ // src/commands/skill.ts
7931
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";
7932
9710
  import fs2 from "fs/promises";
7933
- import path2, { dirname as dirname5 } from "path";
9711
+ import path2, { dirname as dirname6 } from "path";
7934
9712
  import { execSync as execSync14, spawn as spawn7 } from "child_process";
7935
9713
  function getProjectRoot2() {
7936
9714
  const squadsDir = findSquadsDir();
7937
9715
  if (squadsDir) {
7938
- return dirname5(dirname5(squadsDir));
9716
+ return dirname6(dirname6(squadsDir));
7939
9717
  }
7940
9718
  return process.cwd();
7941
9719
  }
@@ -8075,7 +9853,7 @@ async function tonightCommand(targets, options) {
8075
9853
  totalCost: await getCurrentCost(),
8076
9854
  stopped: false
8077
9855
  };
8078
- const spinner = ora6("Launching agents...").start();
9856
+ const spinner = ora7("Launching agents...").start();
8079
9857
  for (const target of targets) {
8080
9858
  const sessionId = `${target.replace("/", "-")}-${Date.now()}`;
8081
9859
  const logFile = path2.join(logDir, `${sessionId}.log`);
@@ -8263,12 +10041,12 @@ process.stderr.on("error", (err) => {
8263
10041
  throw err;
8264
10042
  });
8265
10043
  var envPaths = [
8266
- join17(process.cwd(), ".env"),
8267
- join17(process.cwd(), "..", "hq", ".env"),
8268
- join17(homedir5(), "agents-squads", "hq", ".env")
10044
+ join21(process.cwd(), ".env"),
10045
+ join21(process.cwd(), "..", "hq", ".env"),
10046
+ join21(homedir5(), "agents-squads", "hq", ".env")
8269
10047
  ];
8270
10048
  for (const envPath of envPaths) {
8271
- if (existsSync17(envPath)) {
10049
+ if (existsSync19(envPath)) {
8272
10050
  config({ path: envPath, quiet: true });
8273
10051
  break;
8274
10052
  }
@@ -8288,10 +10066,20 @@ program.name("squads").description("A CLI for humans and agents").version(versio
8288
10066
  throw err;
8289
10067
  });
8290
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);
8291
- 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) }));
8292
10070
  program.command("list").description("List agents and squads").option("-s, --squads", "List squads only").option("-a, --agents", "List agents only").action(listCommand);
8293
10071
  program.command("status [squad]").description("Show squad status and state").option("-v, --verbose", "Show detailed status").action(statusCommand);
8294
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));
8295
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);
8296
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);
8297
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);
@@ -8345,11 +10133,11 @@ sessions.command("history").description("Show session history and statistics").o
8345
10133
  json: options.json
8346
10134
  }));
8347
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) => {
8348
- const { buildCurrentSessionSummary } = await import("./sessions-JCQ34BEU.js");
10136
+ const { buildCurrentSessionSummary } = await import("./sessions-SEITSWEV.js");
8349
10137
  let data;
8350
10138
  if (options.file) {
8351
- const { readFileSync: readFileSync13 } = await import("fs");
8352
- data = JSON.parse(readFileSync13(options.file, "utf-8"));
10139
+ const { readFileSync: readFileSync17 } = await import("fs");
10140
+ data = JSON.parse(readFileSync17(options.file, "utf-8"));
8353
10141
  } else if (options.data) {
8354
10142
  data = JSON.parse(options.data);
8355
10143
  } else if (!process.stdin.isTTY) {
@@ -8382,6 +10170,8 @@ stack.command("down").description("Stop Docker containers").action(stackDownComm
8382
10170
  stack.command("health").description("Comprehensive health check with diagnostics").option("-v, --verbose", "Show logs for unhealthy services").action((options) => stackHealthCommand(options.verbose));
8383
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)));
8384
10172
  registerTriggerCommand(program);
10173
+ registerSkillCommand(program);
10174
+ registerPermissionsCommand(program);
8385
10175
  var tonight = program.command("tonight").description("Run agents autonomously overnight with safety limits");
8386
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, {
8387
10177
  costCap: parseFloat(options.costCap),