squads-cli 0.4.6 → 0.4.8

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