squadrant 0.16.4 → 0.16.6

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/index.js CHANGED
@@ -1313,6 +1313,9 @@ function stampAttempt(rec, patch, now) {
1313
1313
  attempts.push(last);
1314
1314
  return { ...rec, attempts };
1315
1315
  }
1316
+ function isStickyAttention(state) {
1317
+ return state === "blocked" || state === "review";
1318
+ }
1316
1319
  function nextPendingTool(current, ev, now) {
1317
1320
  if (ev.note === "agent.hook.PreToolUse")
1318
1321
  return { name: ev.tool ?? "tool", since: now };
@@ -1320,6 +1323,11 @@ function nextPendingTool(current, ev, now) {
1320
1323
  return void 0;
1321
1324
  return current;
1322
1325
  }
1326
+ function nextPendingMonitor(current, ev, now) {
1327
+ if (ev.note === "agent.hook.PreToolUse" && ev.tool === "Monitor")
1328
+ return { since: now };
1329
+ return current;
1330
+ }
1323
1331
  function reduce(rec, ev, now) {
1324
1332
  if (ev.type === "task.reopened") {
1325
1333
  return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type };
@@ -1336,20 +1344,23 @@ function reduce(rec, ev, now) {
1336
1344
  sessionId: ev.sessionId ?? rec.sessionId,
1337
1345
  question: void 0,
1338
1346
  // resuming after a blocked→reply clears the question
1339
- pendingTool: void 0
1347
+ pendingTool: void 0,
1340
1348
  // #354: a new turn closes any prior tool window
1349
+ pendingMonitor: void 0
1350
+ // #594a: same reset — a new turn moots any prior watch
1341
1351
  };
1342
1352
  case "task.progress": {
1343
1353
  const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
1344
- if (rec.state === "blocked")
1345
- return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool };
1346
- const b = { ...base, pendingTool };
1354
+ const pendingMonitor = nextPendingMonitor(rec.pendingMonitor, ev, now);
1355
+ if (isStickyAttention(rec.state))
1356
+ return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool, pendingMonitor };
1357
+ const b = { ...base, pendingTool, pendingMonitor };
1347
1358
  if (rec.state === "awaiting-input" || rec.state === "stalled")
1348
1359
  return { ...stampAttempt(b, {}, now), state: "working" };
1349
1360
  return stampAttempt(b, {}, now);
1350
1361
  }
1351
1362
  case "heartbeat":
1352
- if (rec.state === "blocked")
1363
+ if (isStickyAttention(rec.state))
1353
1364
  return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
1354
1365
  if (rec.state === "awaiting-input")
1355
1366
  return { ...base, state: "working" };
@@ -1357,8 +1368,13 @@ function reduce(rec, ev, now) {
1357
1368
  case "task.blocked":
1358
1369
  if (rec.state === "blocked")
1359
1370
  return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
1360
- return { ...base, state: "blocked", question: ev.question, pendingTool: void 0 };
1371
+ return { ...base, state: "blocked", question: ev.question, pendingTool: void 0, pendingMonitor: void 0 };
1372
+ case "task.review":
1373
+ return { ...base, state: "review", reviewNote: ev.message, pendingTool: void 0, pendingMonitor: void 0 };
1361
1374
  case "task.done":
1375
+ if (rec.state === "review" && ev.source !== "approve") {
1376
+ return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
1377
+ }
1362
1378
  return { ...base, state: "done", resultRef: ev.resultRef, parseWarning: ev.parseWarning };
1363
1379
  case "task.failed":
1364
1380
  return { ...base, state: "failed", error: ev.error, exitCode: ev.exitCode };
@@ -1369,19 +1385,19 @@ function reduce(rec, ev, now) {
1369
1385
  case "task.session":
1370
1386
  return stampAttempt(base, { resumeRef: ev.resumeRef }, now);
1371
1387
  case "task.turn.started":
1372
- return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0 };
1388
+ return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0, pendingMonitor: void 0 };
1373
1389
  case "task.turn.completed":
1374
- if (rec.state === "blocked")
1390
+ if (isStickyAttention(rec.state))
1375
1391
  return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
1376
- if (rec.pendingTool)
1392
+ if (rec.pendingTool || rec.pendingMonitor)
1377
1393
  return stampAttempt(base, {}, now);
1378
- return { ...stampAttempt(base, {}, now), state: "awaiting-input", pendingTool: void 0 };
1394
+ return { ...stampAttempt(base, {}, now), state: "awaiting-input", pendingTool: void 0, pendingMonitor: void 0 };
1379
1395
  case "task.delta":
1380
1396
  return stampAttempt(base, {}, now);
1381
1397
  // heartbeat-only
1382
1398
  case "task.input.requested":
1383
1399
  case "task.approval.requested":
1384
- return { ...stampAttempt(base, {}, now), state: "blocked", question: ev.question, pendingTool: void 0 };
1400
+ return { ...stampAttempt(base, {}, now), state: "blocked", question: ev.question, pendingTool: void 0, pendingMonitor: void 0 };
1385
1401
  case "task.reattached":
1386
1402
  return stampAttempt(base, {}, now);
1387
1403
  case "task.first-turn.confirmed":
@@ -1406,15 +1422,21 @@ var init_state_machine = __esm({
1406
1422
  });
1407
1423
 
1408
1424
  // packages/core/dist/watchdog.js
1409
- function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS) {
1425
+ function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS, monitorStallMs = MONITOR_STALL_BUDGET_MS) {
1410
1426
  if (rec.state !== "working")
1411
1427
  return null;
1412
1428
  if (rec.mode === "interactive") {
1413
- if (!rec.pendingTool)
1414
- return null;
1415
- if (now - rec.pendingTool.since <= toolStallMs)
1416
- return null;
1417
- return { ...rec, state: "stalled", lastEvent: "watchdog.tool-stall" };
1429
+ if (rec.pendingTool) {
1430
+ if (now - rec.pendingTool.since <= toolStallMs)
1431
+ return null;
1432
+ return { ...rec, state: "stalled", lastEvent: "watchdog.tool-stall" };
1433
+ }
1434
+ if (rec.pendingMonitor) {
1435
+ if (now - rec.pendingMonitor.since <= monitorStallMs)
1436
+ return null;
1437
+ return { ...rec, state: "stalled", lastEvent: "watchdog.monitor-stall" };
1438
+ }
1439
+ return null;
1418
1440
  }
1419
1441
  const liveness = rec.attempts.at(-1)?.lastHeartbeatAt ?? rec.lastHeartbeat;
1420
1442
  if (now - liveness <= rec.heartbeatBudgetMs)
@@ -1424,12 +1446,13 @@ function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS) {
1424
1446
  function recoverStall(rec, now) {
1425
1447
  if (rec.state !== "stalled")
1426
1448
  return null;
1427
- return { ...rec, state: "working", lastHeartbeat: now, lastEvent: "watchdog.recover", pendingTool: void 0 };
1449
+ return { ...rec, state: "working", lastHeartbeat: now, lastEvent: "watchdog.recover", pendingTool: void 0, pendingMonitor: void 0 };
1428
1450
  }
1429
- var TOOL_STALL_BUDGET_MS;
1451
+ var TOOL_STALL_BUDGET_MS, MONITOR_STALL_BUDGET_MS;
1430
1452
  var init_watchdog = __esm({
1431
1453
  "packages/core/dist/watchdog.js"() {
1432
1454
  TOOL_STALL_BUDGET_MS = 10 * 60 * 1e3;
1455
+ MONITOR_STALL_BUDGET_MS = 60 * 60 * 1e3;
1433
1456
  }
1434
1457
  });
1435
1458
 
@@ -1454,6 +1477,10 @@ function formatMessage(rec, event) {
1454
1477
  }
1455
1478
  case "blocked":
1456
1479
  return `CREW BLOCKED ${tag}: ${(rec.question ?? "(no question)").trim()}`;
1480
+ case "review": {
1481
+ const note = (rec.reviewNote ?? "").trim();
1482
+ return `CREW REVIEW ${tag}: ${note || "ready for review"} \u2014 run 'squadrant diff ${rec.project} ${rec.name ?? rec.id}' then 'squadrant crew approve' or send feedback.`;
1483
+ }
1457
1484
  case "failed":
1458
1485
  return `CREW FAILED ${tag}: ${(rec.error ?? "(no error)").trim()}`;
1459
1486
  case "stalled": {
@@ -1699,7 +1726,7 @@ function createDaemon(deps) {
1699
1726
  store.delete(r.project, r.id);
1700
1727
  continue;
1701
1728
  }
1702
- if (!TERMINAL_STATES.has(r.state)) {
1729
+ if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {
1703
1730
  const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
1704
1731
  if (t - r.createdAt > ceiling) {
1705
1732
  const prevState = r.state;
@@ -1744,7 +1771,7 @@ function createDaemon(deps) {
1744
1771
  const idle = evaluateStall(r, t);
1745
1772
  if (idle) {
1746
1773
  store.put(idle);
1747
- const synthEvent = idle.pendingTool ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: idle.pendingTool.name, elapsedMs: t - idle.pendingTool.since } : { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs };
1774
+ const synthEvent = idle.pendingTool ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: idle.pendingTool.name, elapsedMs: t - idle.pendingTool.since } : idle.pendingMonitor ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: "Monitor", elapsedMs: t - idle.pendingMonitor.since } : { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs };
1748
1775
  firePush(deps, r.project, r.state, idle, synthEvent, lastCaptainTurnAt.get(r.id));
1749
1776
  continue;
1750
1777
  }
@@ -1829,14 +1856,15 @@ var init_reduce = __esm({
1829
1856
  DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1e3;
1830
1857
  TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
1831
1858
  TERMINAL_RECORD_KEEP_PER_PROJECT = 20;
1832
- ATTENTION_STATES = /* @__PURE__ */ new Set(["done", "blocked", "failed", "stalled", "awaiting-input"]);
1833
- REAPABLE_SURFACE_STATES = /* @__PURE__ */ new Set(["working", "stalled", "awaiting-input", "blocked"]);
1859
+ ATTENTION_STATES = /* @__PURE__ */ new Set(["done", "blocked", "review", "failed", "stalled", "awaiting-input"]);
1860
+ REAPABLE_SURFACE_STATES = /* @__PURE__ */ new Set(["working", "stalled", "awaiting-input", "blocked", "review"]);
1834
1861
  IDLE_DEBOUNCE_MS = 12e3;
1835
1862
  KNOWN_EVENT_TYPES = /* @__PURE__ */ new Set([
1836
1863
  "task.started",
1837
1864
  "task.progress",
1838
1865
  "heartbeat",
1839
1866
  "task.blocked",
1867
+ "task.review",
1840
1868
  "task.done",
1841
1869
  "task.failed",
1842
1870
  "task.session",
@@ -3306,9 +3334,11 @@ var init_defer_delivery = __esm({
3306
3334
  "packages/core/dist/delivery/defer-delivery.js"() {
3307
3335
  DeferDelivery = class extends Error {
3308
3336
  draft;
3309
- constructor(draft = null) {
3337
+ reason;
3338
+ constructor(draft = null, reason = "draft") {
3310
3339
  super("deferred: captain composing");
3311
3340
  this.draft = draft;
3341
+ this.reason = reason;
3312
3342
  this.name = "DeferDelivery";
3313
3343
  }
3314
3344
  };
@@ -3332,6 +3362,7 @@ var init_captain_delivery = __esm({
3332
3362
  deferCounts = /* @__PURE__ */ new Map();
3333
3363
  lastContent = /* @__PURE__ */ new Map();
3334
3364
  stableCounts = /* @__PURE__ */ new Map();
3365
+ lastReason = /* @__PURE__ */ new Map();
3335
3366
  constructor(opts) {
3336
3367
  this.opts = opts;
3337
3368
  }
@@ -3354,29 +3385,40 @@ var init_captain_delivery = __esm({
3354
3385
  this.deferCounts.delete(seq);
3355
3386
  this.stableCounts.delete(seq);
3356
3387
  this.lastContent.delete(seq);
3388
+ this.lastReason.delete(seq);
3357
3389
  return { delivered: true };
3358
3390
  } catch (e) {
3359
3391
  if (e instanceof DeferDelivery) {
3360
3392
  this.deferCounts.set(seq, deferCount + 1);
3361
3393
  const content = e.draft;
3394
+ let stableCount;
3362
3395
  if (content && content === this.lastContent.get(seq)) {
3363
- this.stableCounts.set(seq, (this.stableCounts.get(seq) ?? 0) + 1);
3396
+ stableCount = (this.stableCounts.get(seq) ?? 0) + 1;
3397
+ this.stableCounts.set(seq, stableCount);
3364
3398
  } else {
3399
+ stableCount = 0;
3365
3400
  this.stableCounts.set(seq, 0);
3366
3401
  }
3367
3402
  this.lastContent.set(seq, content);
3368
- return { deferred: true };
3403
+ const reason = e.reason !== "draft" ? e.reason : stableCount >= this.opts.stableProbePolls ? "stable" : "draft";
3404
+ this.lastReason.set(seq, reason);
3405
+ return { deferred: true, reason };
3369
3406
  }
3370
- return { deferred: true };
3407
+ this.lastReason.set(seq, "unknown");
3408
+ return { deferred: true, reason: "unknown" };
3371
3409
  }
3372
3410
  }
3373
3411
  /** Read-only. Never mutates — safe to poll from the snapshot assembler every tick. */
3374
3412
  stats() {
3375
3413
  let maxDeferCount = 0;
3376
- for (const c of this.deferCounts.values())
3377
- if (c > maxDeferCount)
3414
+ let reason;
3415
+ for (const [seq, c] of this.deferCounts) {
3416
+ if (c > maxDeferCount) {
3378
3417
  maxDeferCount = c;
3379
- return { maxDeferCount, stuck: maxDeferCount >= this.opts.maxDefers };
3418
+ reason = this.lastReason.get(seq);
3419
+ }
3420
+ }
3421
+ return { maxDeferCount, stuck: maxDeferCount >= this.opts.maxDefers, reason };
3380
3422
  }
3381
3423
  };
3382
3424
  }
@@ -3476,6 +3518,10 @@ function createDelivery(ctx, daemonCmux) {
3476
3518
  const notifyFault = ctx.notifyFault ?? (() => {
3477
3519
  });
3478
3520
  const defaultNotify = async (args) => {
3521
+ const fresh = store.get(args.project, args.record.id);
3522
+ if (fresh && TERMINAL_STATES.has(fresh.state) && fresh.state !== args.record.state) {
3523
+ return;
3524
+ }
3479
3525
  try {
3480
3526
  await appendToMailbox({
3481
3527
  stateRoot,
@@ -3565,16 +3611,19 @@ function createDelivery(ctx, daemonCmux) {
3565
3611
  log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=delivered`);
3566
3612
  await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
3567
3613
  } else {
3568
- log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred`);
3614
+ const { maxDeferCount } = d.stats();
3615
+ if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {
3616
+ log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);
3617
+ }
3569
3618
  break;
3570
3619
  }
3571
3620
  }
3572
3621
  const stuck = d.stats().stuck;
3573
3622
  if (stuck && !stuckNotified.has(project)) {
3574
3623
  stuckNotified.add(project);
3575
- const { maxDeferCount } = d.stats();
3576
- log(`delivery stuck project=${project} deferCount=${maxDeferCount}`);
3577
- const text = `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
3624
+ const { maxDeferCount, reason } = d.stats();
3625
+ log(`delivery stuck project=${project} deferCount=${maxDeferCount} reason=${reason ?? "unknown"}`);
3626
+ const text = reason === "modal" ? `\u26A0\uFE0F DELIVERY STUCK: a modal question is open in your captain pane and has blocked pending notification(s) for ${maxDeferCount}+ retries. This keeps retrying safely and will deliver automatically once you answer or dismiss it.` : `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
3578
3627
  appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`delivery stuck alert failed project=${project}: ${e.message}`));
3579
3628
  Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
3580
3629
  telegramBridge?.pushRaw(project, text);
@@ -4309,8 +4358,12 @@ var init_commands = __esm({
4309
4358
  build: (a) => a[0] ? ok("crews", ["crew", "list", a[0]]) : usage("crews", "usage: /crews <project>")
4310
4359
  },
4311
4360
  launch: {
4361
+ // --headless (#586, same reason as #520 on the boot-if-down path): runCommand
4362
+ // execs this argv from the daemon, which has no CMUX_WORKSPACE_ID and no
4363
+ // terminal — a plain `launch` would open the cmux GUI app and exit 0 before
4364
+ // the workspace is ever launched.
4312
4365
  usage: "/launch <project>",
4313
- build: (a) => a[0] ? ok("launch", ["launch", a[0]]) : usage("launch", "usage: /launch <project>")
4366
+ build: (a) => a[0] ? ok("launch", ["launch", a[0], "--headless"]) : usage("launch", "usage: /launch <project>")
4314
4367
  },
4315
4368
  effort: {
4316
4369
  usage: "/effort [max|balance|low]",
@@ -4484,6 +4537,9 @@ ${ev.message}` : "");
4484
4537
  case "task.blocked":
4485
4538
  return `\u{1F6A7} [${project}] CREW BLOCKED \xB7 ${ev.id}
4486
4539
  ${ev.question}`;
4540
+ case "task.review":
4541
+ return `\u{1F440} [${project}] CREW REVIEW \xB7 ${ev.id}` + (ev.message ? `
4542
+ ${ev.message}` : "");
4487
4543
  case "task.idle":
4488
4544
  return `\u{1F4A4} [${project}] CREW IDLE \xB7 ${ev.id}`;
4489
4545
  case "task.failed":
@@ -4716,6 +4772,7 @@ var init_tiers = __esm({
4716
4772
  ALERTS = /* @__PURE__ */ new Set([
4717
4773
  ...DONE_ONLY,
4718
4774
  "task.blocked",
4775
+ "task.review",
4719
4776
  "task.approval.requested",
4720
4777
  "task.input.requested",
4721
4778
  "task.timeout"
@@ -5932,7 +5989,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
5932
5989
  if (task) {
5933
5990
  if (TERMINAL_STATES.has(task.state)) {
5934
5991
  await deps.emitEvent(project, { type: "task.reopened", id: task.id });
5935
- } else if (task.state === "blocked" || task.state === "awaiting-input") {
5992
+ } else if (task.state === "blocked" || task.state === "awaiting-input" || task.state === "review") {
5936
5993
  await deps.emitEvent(project, { type: "task.started", id: task.id });
5937
5994
  }
5938
5995
  }
@@ -6058,6 +6115,7 @@ __export(dist_exports2, {
6058
6115
  GROUP_DISPATCH_WARMUP_TIMEOUT_MS: () => GROUP_DISPATCH_WARMUP_TIMEOUT_MS,
6059
6116
  IDLE_DEBOUNCE_MS: () => IDLE_DEBOUNCE_MS,
6060
6117
  LABEL: () => LABEL,
6118
+ MONITOR_STALL_BUDGET_MS: () => MONITOR_STALL_BUDGET_MS,
6061
6119
  PROBE_QUIET_MS: () => PROBE_QUIET_MS,
6062
6120
  PROTOCOL_VERSION: () => PROTOCOL_VERSION,
6063
6121
  STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
@@ -6127,6 +6185,7 @@ __export(dist_exports2, {
6127
6185
  isDaemonSocketLive: () => isDaemonSocketLive,
6128
6186
  isNotifyActive: () => isNotifyActive,
6129
6187
  isSideTitle: () => isSideTitle,
6188
+ isStickyAttention: () => isStickyAttention,
6130
6189
  isTurnAccepted: () => isTurnAccepted,
6131
6190
  kickstartArgv: () => kickstartArgv,
6132
6191
  launchOneWorkspace: () => launchOneWorkspace,
@@ -6277,6 +6336,18 @@ function cmux(args) {
6277
6336
  );
6278
6337
  });
6279
6338
  }
6339
+ function cmuxStdin(args, input) {
6340
+ return new Promise((resolve3, reject) => {
6341
+ const child = execFile2(resolveCmuxBin(), args, { encoding: "utf-8", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: "1" } }, (err, stdout) => {
6342
+ if (err) {
6343
+ reject(err.code === "ETIMEDOUT" ? new CmuxTimeoutError(args.join(" ")) : err);
6344
+ return;
6345
+ }
6346
+ resolve3(stdout.trim());
6347
+ });
6348
+ child.stdin.end(input);
6349
+ });
6350
+ }
6280
6351
  function parseList(output) {
6281
6352
  let parsed;
6282
6353
  try {
@@ -6633,9 +6704,9 @@ function createCmuxDriver() {
6633
6704
  }
6634
6705
  const draft = parseDraftFromScreen(screen);
6635
6706
  if (draft === null)
6636
- throw new DeferDelivery(null);
6707
+ throw new DeferDelivery(null, "no-box");
6637
6708
  if (hasModalOptionList(screen))
6638
- throw new DeferDelivery(null);
6709
+ throw new DeferDelivery(null, "modal");
6639
6710
  if (draft === "") {
6640
6711
  await deliver();
6641
6712
  return;
@@ -6676,6 +6747,37 @@ function createCmuxDriver() {
6676
6747
  }
6677
6748
  throw new DeferDelivery(draft);
6678
6749
  },
6750
+ async showDiff(opts) {
6751
+ const source = opts.source ?? "branch";
6752
+ const args = ["diff"];
6753
+ if (source === "staged") {
6754
+ args.push("--staged");
6755
+ } else if (source === "unstaged") {
6756
+ args.push("--unstaged");
6757
+ } else {
6758
+ args.push("--branch", "--base", opts.base);
6759
+ if (opts.lastTurn)
6760
+ args.push("--last-turn");
6761
+ }
6762
+ args.push("--cwd", opts.cwd, "--workspace", opts.workspaceId, "--layout", opts.layout ?? "split");
6763
+ if (opts.title)
6764
+ args.push("--title", opts.title);
6765
+ if (opts.focus === false)
6766
+ args.push("--no-focus");
6767
+ else
6768
+ args.push("--focus", "true");
6769
+ await cmux(args);
6770
+ },
6771
+ async showPatch(opts) {
6772
+ const args = ["diff", "-", "--workspace", opts.workspaceId, "--layout", opts.layout ?? "split"];
6773
+ if (opts.title)
6774
+ args.push("--title", opts.title);
6775
+ if (opts.focus === false)
6776
+ args.push("--no-focus");
6777
+ else
6778
+ args.push("--focus", "true");
6779
+ await cmuxStdin(args, opts.patch);
6780
+ },
6679
6781
  async listSurfaces(workspaceId) {
6680
6782
  let output;
6681
6783
  try {
@@ -7429,6 +7531,7 @@ function installClaudeHooks(opts = {}) {
7429
7531
  });
7430
7532
  let settings = {};
7431
7533
  const raw = readFile6(settingsPath);
7534
+ const hadExistingSettings = raw !== void 0;
7432
7535
  if (raw) {
7433
7536
  try {
7434
7537
  settings = JSON.parse(raw);
@@ -7441,6 +7544,7 @@ function installClaudeHooks(opts = {}) {
7441
7544
  }
7442
7545
  const hooks = settings.hooks;
7443
7546
  let changed = false;
7547
+ const repaired = [];
7444
7548
  for (const [eventName, sub, matcher] of CLAUDE_HOOK_EVENTS) {
7445
7549
  if (!Array.isArray(hooks[eventName])) {
7446
7550
  hooks[eventName] = [];
@@ -7452,6 +7556,26 @@ function installClaudeHooks(opts = {}) {
7452
7556
  if (!alreadyPresent) {
7453
7557
  entries.push({ matcher: hookMatcher, hooks: [{ type: "command", command, timeout: 10 }] });
7454
7558
  changed = true;
7559
+ repaired.push(`${eventName}/${sub}`);
7560
+ }
7561
+ }
7562
+ if (repaired.length > 0 && hadExistingSettings) {
7563
+ log(`native-hook: repaired ${repaired.length} missing squadrant hook(s) in ${settingsPath} [${repaired.join(", ")}] \u2014 WARNING: blocked-signalling or lifecycle tracking may have been broken until this run`);
7564
+ }
7565
+ if (opts.claudeEnv && Object.keys(opts.claudeEnv).length > 0) {
7566
+ if (typeof settings.env !== "object" || settings.env === null || Array.isArray(settings.env)) {
7567
+ settings.env = {};
7568
+ }
7569
+ const env = settings.env;
7570
+ for (const [key, value] of Object.entries(opts.claudeEnv)) {
7571
+ if (key in env) {
7572
+ if (env[key] !== value) {
7573
+ log(`native-hook: claudeEnv key '${key}' already set to '${String(env[key])}' in ${settingsPath} \u2014 not overwriting with '${value}'`);
7574
+ }
7575
+ continue;
7576
+ }
7577
+ env[key] = value;
7578
+ changed = true;
7455
7579
  }
7456
7580
  }
7457
7581
  if (changed) {
@@ -7526,9 +7650,9 @@ var init_native_hook_source = __esm({
7526
7650
  cache = /* @__PURE__ */ new Map();
7527
7651
  active = false;
7528
7652
  constructor(opts = {}) {
7529
- this.hookInstall = opts.hookInstall ?? {};
7530
7653
  this.log = opts.log ?? (() => {
7531
7654
  });
7655
+ this.hookInstall = { log: this.log, ...opts.hookInstall };
7532
7656
  }
7533
7657
  start(deps) {
7534
7658
  this.deps = deps;
@@ -9944,7 +10068,7 @@ var init_require_daemon = __esm({
9944
10068
  // packages/cli/src/index.ts
9945
10069
  init_dist();
9946
10070
  init_dist2();
9947
- import { Command as Command31 } from "commander";
10071
+ import { Command as Command32 } from "commander";
9948
10072
  import { readFileSync as readFileSync15, existsSync as existsSync12, writeFileSync as writeFileSync11 } from "fs";
9949
10073
  import { fileURLToPath as fileURLToPath6 } from "url";
9950
10074
  import { dirname as dirname9, join as join29 } from "path";
@@ -10975,6 +11099,7 @@ init_dist4();
10975
11099
  import { Command as Command8 } from "commander";
10976
11100
  import { createConnection as createConnection3 } from "net";
10977
11101
  import { randomUUID as randomUUID4 } from "crypto";
11102
+ import { execFileSync as execFileSync6 } from "child_process";
10978
11103
  import { homedir as homedir16 } from "os";
10979
11104
  import { join as join21 } from "path";
10980
11105
  import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "fs";
@@ -11374,6 +11499,8 @@ function buildSignalRequest(signal, o) {
11374
11499
  };
11375
11500
  } else if (signal === "blocked") {
11376
11501
  event = { type: "task.blocked", id: taskId, reason: "crew signaled blocked", question: o.question ?? "" };
11502
+ } else if (signal === "review") {
11503
+ event = { type: "task.review", id: taskId, ...o.message !== void 0 ? { message: o.message } : {} };
11377
11504
  } else {
11378
11505
  event = { type: "task.failed", id: taskId, error: o.error ?? "crew signaled failed" };
11379
11506
  }
@@ -11402,6 +11529,63 @@ async function runCrewSignal(signal, o, deps) {
11402
11529
  const req = buildSignalRequest(signal, { ...o, writeResult: o.writeResult ?? defaultWriteResult });
11403
11530
  await deps.call(req);
11404
11531
  }
11532
+ function resolveApproveTarget(tasks, crew) {
11533
+ const matches = tasks.filter((t) => t.name === crew);
11534
+ if (matches.length === 0) return null;
11535
+ return matches.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
11536
+ }
11537
+ function defaultPushBranch(cwd, branch) {
11538
+ execFileSync6("git", ["-C", cwd, "push", "-u", "origin", branch], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
11539
+ }
11540
+ function defaultCreatePr(cwd, o) {
11541
+ return execFileSync6(
11542
+ "gh",
11543
+ ["pr", "create", "--base", o.base, "--head", o.branch, "--title", o.title, "--body", o.body],
11544
+ { cwd, encoding: "utf-8" }
11545
+ ).trim();
11546
+ }
11547
+ function defaultGetCommitSubject(cwd) {
11548
+ try {
11549
+ return execFileSync6("git", ["-C", cwd, "log", "-1", "--format=%s"], {
11550
+ encoding: "utf-8",
11551
+ stdio: ["ignore", "pipe", "pipe"]
11552
+ }).trim();
11553
+ } catch {
11554
+ return "";
11555
+ }
11556
+ }
11557
+ async function runCrewApprove(project, crew, deps) {
11558
+ const config = loadConfig();
11559
+ const proj = config.projects[project];
11560
+ if (!proj) throw new Error(`Project '${project}' not found. Run 'squadrant projects list'.`);
11561
+ const tasks = await deps.call({ kind: "list", project });
11562
+ const task = resolveApproveTarget(tasks, crew);
11563
+ if (!task) throw new Error(`Crew '${crew}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
11564
+ if (task.state !== "review") {
11565
+ throw new Error(
11566
+ `Crew '${crew}' is not awaiting review (state=${task.state}). Only a crew that signaled 'review' can be approved.`
11567
+ );
11568
+ }
11569
+ const cwd = task.cwd ?? proj.path;
11570
+ const base = resolveWorktreeBase(proj.path);
11571
+ const branch = crewBranch(crew);
11572
+ const body = (task.reviewNote ?? task.task ?? "").trim();
11573
+ const getCommitSubject = deps.getCommitSubject ?? defaultGetCommitSubject;
11574
+ const commitSubject = getCommitSubject(cwd).trim();
11575
+ const reviewNoteFirstLine = (task.reviewNote ?? "").split(/\r?\n/)[0].trim();
11576
+ const taskFirstLine = (task.task ?? branch).split(/\r?\n/)[0].trim();
11577
+ const title = (commitSubject || reviewNoteFirstLine || taskFirstLine).slice(0, 100);
11578
+ const pushBranch = deps.pushBranch ?? defaultPushBranch;
11579
+ const createPr = deps.createPr ?? defaultCreatePr;
11580
+ pushBranch(cwd, branch);
11581
+ const prUrl = createPr(cwd, { base, branch, title, body });
11582
+ await deps.call({
11583
+ kind: "event",
11584
+ project,
11585
+ event: { type: "task.done", id: task.id, resultRef: "", message: `Approved \u2014 PR opened: ${prUrl}`, source: "approve" }
11586
+ });
11587
+ return prUrl;
11588
+ }
11405
11589
  function addControlPlaneCrewCommands(crew) {
11406
11590
  crew.command("dispatch <project> <task>").description("Dispatch a crew task via the control-plane daemon").requiredOption("--provider <p>", "claude|opencode|codex (gemini: experimental, headless not supported)").option("--mode <m>", "headless|interactive", "interactive").option("--cwd <dir>", "working dir for the crew (project/worktree); required for codex to edit code").action(async (project, task, opts) => {
11407
11591
  const req = buildDispatchRequest({ project, task, provider: opts.provider, mode: opts.mode, cwd: opts.cwd });
@@ -11476,9 +11660,9 @@ function addControlPlaneCrewCommands(crew) {
11476
11660
  }
11477
11661
  process.exit(0);
11478
11662
  });
11479
- crew.command("signal <state>").description("Emit explicit terminal signal from a crew session: done|blocked|failed (reads SQUADRANT_CREW_* env, or --task-id/--project for codex)").option("--message <m>", "Summary written to resultRef (done)").option("--question <q>", "Question to surface to captain (blocked)").option("--error <e>", "Error message (failed)").option("--task-id <id>", "Explicit task id (codex; overrides SQUADRANT_CREW_TASK_ID env)").option("--project <p>", "Explicit project (codex; overrides SQUADRANT_CREW_PROJECT env)").action(async (state, opts) => {
11480
- if (state !== "done" && state !== "blocked" && state !== "failed") {
11481
- process.stderr.write(`unknown signal '${state}' (expected: done|blocked|failed)
11663
+ crew.command("signal <state>").description("Emit explicit terminal/review signal from a crew session: done|blocked|failed|review (reads SQUADRANT_CREW_* env, or --task-id/--project for codex)").option("--message <m>", "Summary written to resultRef (done), or review summary (review)").option("--question <q>", "Question to surface to captain (blocked)").option("--error <e>", "Error message (failed)").option("--task-id <id>", "Explicit task id (codex; overrides SQUADRANT_CREW_TASK_ID env)").option("--project <p>", "Explicit project (codex; overrides SQUADRANT_CREW_PROJECT env)").action(async (state, opts) => {
11664
+ if (state !== "done" && state !== "blocked" && state !== "failed" && state !== "review") {
11665
+ process.stderr.write(`unknown signal '${state}' (expected: done|blocked|failed|review)
11482
11666
  `);
11483
11667
  process.exit(2);
11484
11668
  }
@@ -11494,6 +11678,17 @@ function addControlPlaneCrewCommands(crew) {
11494
11678
  process.exit(0);
11495
11679
  } catch (e) {
11496
11680
  process.stderr.write(`${e.message}
11681
+ `);
11682
+ process.exit(1);
11683
+ }
11684
+ });
11685
+ crew.command("approve <project> <crew>").description("Approve a crew's reviewed work: push crew/<name> + open a PR, then terminalize DONE (#599)").action(async (project, crewName) => {
11686
+ try {
11687
+ const prUrl = await runCrewApprove(project, crewName, { call: squadrantdCall });
11688
+ process.stdout.write(`\u2714 Approved ${crewBranch(crewName)} \u2014 pushed + PR opened: ${prUrl}
11689
+ `);
11690
+ } catch (e) {
11691
+ process.stderr.write(`${e.message}
11497
11692
  `);
11498
11693
  process.exit(1);
11499
11694
  }
@@ -11651,6 +11846,199 @@ crewCommand.command("close").description("Shutdown a crew session (closes its ta
11651
11846
  }
11652
11847
  });
11653
11848
 
11849
+ // packages/cli/src/commands/diff.ts
11850
+ init_dist();
11851
+ init_dist3();
11852
+ import { Command as Command10 } from "commander";
11853
+ import chalk10 from "chalk";
11854
+ import { execFileSync as execFileSync7 } from "child_process";
11855
+ import readline2 from "readline";
11856
+ function resolveDiffTarget(tasks, crew, projectPath) {
11857
+ const matches = tasks.filter((t) => t.name === crew);
11858
+ if (matches.length === 0) return null;
11859
+ const task = matches.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
11860
+ const cwd = task.cwd ?? projectPath;
11861
+ return { cwd, isShared: cwd === projectPath };
11862
+ }
11863
+ function resolveDiffSources(opts) {
11864
+ if (opts.working) return ["unstaged", "staged"];
11865
+ if (opts.staged) return ["staged"];
11866
+ if (opts.unstaged) return ["unstaged"];
11867
+ return [];
11868
+ }
11869
+ function resolveDiffMode(crew, opts) {
11870
+ const requestedModes = [crew !== void 0, opts.pr !== void 0, opts.base !== void 0 || opts.head !== void 0 || opts.against !== void 0];
11871
+ if (requestedModes.filter(Boolean).length > 1) {
11872
+ throw new Error(
11873
+ "squadrant diff: a crew argument, --pr, and --base/--head/--against are mutually exclusive."
11874
+ );
11875
+ }
11876
+ if (opts.against !== void 0 && (opts.base !== void 0 || opts.head !== void 0)) {
11877
+ throw new Error("squadrant diff: --against cannot be combined with --base/--head.");
11878
+ }
11879
+ if (crew !== void 0) return { mode: "crew", crew };
11880
+ if (opts.pr !== void 0) return { mode: "pr", pr: opts.pr };
11881
+ if (opts.against !== void 0) return { mode: "refs", base: opts.against, head: "HEAD" };
11882
+ if (opts.base !== void 0 || opts.head !== void 0) {
11883
+ if (opts.base === void 0 || opts.head === void 0) {
11884
+ throw new Error(
11885
+ "squadrant diff: --base and --head must be used together (or use --against <ref> to diff against HEAD)."
11886
+ );
11887
+ }
11888
+ return { mode: "refs", base: opts.base, head: opts.head };
11889
+ }
11890
+ return { mode: "pick" };
11891
+ }
11892
+ function buildCrewDiffStats(tasks, projectPath, base, getStat) {
11893
+ const live = /* @__PURE__ */ new Map();
11894
+ for (const t of tasks) {
11895
+ if (!t.name || TERMINAL_STATES.has(t.state)) continue;
11896
+ const prev = live.get(t.name);
11897
+ if (!prev || (t.createdAt ?? 0) > (prev.createdAt ?? 0)) live.set(t.name, t);
11898
+ }
11899
+ return [...live.values()].map((t) => {
11900
+ const cwd = t.cwd ?? projectPath;
11901
+ return { name: t.name, cwd, stat: getStat(cwd, base).trim() };
11902
+ });
11903
+ }
11904
+ function parseCrewPick(raw, stats) {
11905
+ const trimmed = raw.trim();
11906
+ const idx = Number(trimmed);
11907
+ if (Number.isInteger(idx) && idx >= 1 && idx <= stats.length) {
11908
+ return stats[idx - 1].name;
11909
+ }
11910
+ const byName = stats.find((s) => s.name === trimmed);
11911
+ if (byName) return byName.name;
11912
+ throw new Error(`Invalid selection '${raw}'. Enter a number 1-${stats.length} or a crew name.`);
11913
+ }
11914
+ function promptLine2(question) {
11915
+ return new Promise((resolve3) => {
11916
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
11917
+ rl.question(question, (answer) => {
11918
+ rl.close();
11919
+ resolve3(answer);
11920
+ });
11921
+ });
11922
+ }
11923
+ function getPrDiff(projectPath, pr) {
11924
+ try {
11925
+ return execFileSync7("gh", ["pr", "diff", pr], { cwd: projectPath, encoding: "utf-8" });
11926
+ } catch (e) {
11927
+ throw new Error(`Could not fetch PR #${pr} diff (gh pr diff failed): ${e.message}`);
11928
+ }
11929
+ }
11930
+ function getRefsDiff(projectPath, base, head) {
11931
+ try {
11932
+ return execFileSync7("git", ["-C", projectPath, "diff", `${base}...${head}`], { encoding: "utf-8" });
11933
+ } catch (e) {
11934
+ throw new Error(`Could not diff ${base}...${head}: ${e.message}`);
11935
+ }
11936
+ }
11937
+ async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
11938
+ const tasks = await squadrantdCall({ kind: "list", project });
11939
+ const target = resolveDiffTarget(tasks, crew, proj.path);
11940
+ if (!target) {
11941
+ throw new Error(`Crew '${crew}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
11942
+ }
11943
+ const base = resolveWorktreeBase(proj.path);
11944
+ const branchLabel = crewBranch(crew);
11945
+ if (!runtime.showDiff) {
11946
+ throw new Error(`Runtime '${runtime.name}' has no native diff viewer yet \u2014 Phase 1 supports cmux only.`);
11947
+ }
11948
+ const sources = resolveDiffSources(opts);
11949
+ if (sources.length > 0) {
11950
+ let opened = 0;
11951
+ for (const source of sources) {
11952
+ const statArgs = source === "staged" ? ["diff", "--stat", "--cached"] : ["diff", "--stat"];
11953
+ const stat2 = execFileSync7("git", ["-C", target.cwd, ...statArgs], { encoding: "utf-8" }).trim();
11954
+ if (!stat2) continue;
11955
+ await runtime.showDiff({
11956
+ workspaceId,
11957
+ cwd: target.cwd,
11958
+ base,
11959
+ title: `${branchLabel} \u2014 ${source}`,
11960
+ layout: opts.layout,
11961
+ focus: opts.focus,
11962
+ source
11963
+ });
11964
+ opened++;
11965
+ }
11966
+ if (opened === 0) {
11967
+ const label = sources.length > 1 ? "staged or unstaged" : sources[0];
11968
+ console.log(`No ${label} changes on ${branchLabel}.`);
11969
+ } else {
11970
+ console.log(chalk10.dim(`Opened ${opened} working-tree diff(s) (${sources.join(", ")}) for ${branchLabel}.`));
11971
+ }
11972
+ return;
11973
+ }
11974
+ const diffStat = execFileSync7(
11975
+ "git",
11976
+ ["-C", target.cwd, "diff", "--stat", `${base}...HEAD`],
11977
+ { encoding: "utf-8" }
11978
+ ).trim();
11979
+ if (!diffStat) {
11980
+ console.log(`No changes on ${branchLabel} vs ${base}.`);
11981
+ return;
11982
+ }
11983
+ await runtime.showDiff({
11984
+ workspaceId,
11985
+ cwd: target.cwd,
11986
+ base,
11987
+ title: `${branchLabel} vs ${base}`,
11988
+ layout: opts.layout,
11989
+ focus: opts.focus,
11990
+ lastTurn: opts.lastTurn,
11991
+ source: "branch"
11992
+ });
11993
+ console.log(chalk10.dim(`Opened ${branchLabel} vs ${base} in cmux diff.`));
11994
+ }
11995
+ async function runDiff(project, crewArg, opts) {
11996
+ const config = loadConfig();
11997
+ const proj = config.projects[project];
11998
+ if (!proj) {
11999
+ throw new Error(`Project '${project}' not found. Run 'squadrant projects list'.`);
12000
+ }
12001
+ const mode = resolveDiffMode(crewArg, opts);
12002
+ const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
12003
+ if (mode.mode === "pr" || mode.mode === "refs") {
12004
+ if (!runtime.showPatch) {
12005
+ throw new Error(`Runtime '${runtime.name}' has no native patch viewer yet \u2014 Phase 1 supports cmux only.`);
12006
+ }
12007
+ const title = mode.mode === "pr" ? `PR #${mode.pr}` : `${mode.base}...${mode.head}`;
12008
+ const patch = mode.mode === "pr" ? getPrDiff(proj.path, mode.pr) : getRefsDiff(proj.path, mode.base, mode.head);
12009
+ if (!patch.trim()) {
12010
+ console.log(`No changes in ${title}.`);
12011
+ return;
12012
+ }
12013
+ await runtime.showPatch({ workspaceId, patch, title, layout: opts.layout, focus: opts.focus });
12014
+ console.log(chalk10.dim(`Opened ${title} in cmux diff.`));
12015
+ return;
12016
+ }
12017
+ let crew;
12018
+ if (mode.mode === "pick") {
12019
+ const tasks = await squadrantdCall({ kind: "list", project });
12020
+ const base = resolveWorktreeBase(proj.path);
12021
+ const stats = buildCrewDiffStats(tasks, proj.path, base, (cwd, b) => {
12022
+ try {
12023
+ return execFileSync7("git", ["-C", cwd, "diff", "--stat", `${b}...HEAD`], { encoding: "utf-8" });
12024
+ } catch {
12025
+ return "";
12026
+ }
12027
+ });
12028
+ if (stats.length === 0) {
12029
+ throw new Error(`No live crews for ${project}. Run 'squadrant crew list ${project}' to check, or 'squadrant crew spawn' one.`);
12030
+ }
12031
+ console.log(`Live crews for ${project} (vs ${base}):`);
12032
+ stats.forEach((s, i) => console.log(` ${i + 1}. ${s.name} \u2014 ${s.stat || "no changes"}`));
12033
+ const raw = await promptLine2("Pick a crew to diff (number or name): ");
12034
+ crew = parseCrewPick(raw, stats);
12035
+ } else {
12036
+ crew = mode.crew;
12037
+ }
12038
+ await openCrewDiff(project, proj, crew, opts, runtime, workspaceId);
12039
+ }
12040
+ var diffCommand = new Command10("diff").description("Open a crew's branch diff, a PR, or a ref comparison in cmux's native diff viewer \u2014 no VSCode required (#596/#604)").argument("<project>", "Project name (must be registered)").argument("[crew]", "Crew name (e.g. crew-1); omit with no other flags to pick from live crews").option("--pr <n>", "Review a PR: wraps `gh pr diff <n>` (mutually exclusive with crew/--base/--head)").option("--base <ref>", "Base ref for a --head ref comparison (merge-base diff)").option("--head <ref>", "Head ref for a --base ref comparison").option("--against <ref>", "Alias: diff <ref>...HEAD (mutually exclusive with --base/--head)").option("--layout <mode>", "split (default) or unified", "split").option("--last-turn", "diff only changes since the crew's last agent turn", false).option("--no-focus", "open the diff pane without stealing focus").option("--staged", "show only staged (index) changes \u2014 VSCode's 'Staged Changes' panel (#599)", false).option("--unstaged", "show only unstaged working-tree changes \u2014 VSCode's 'Changes' panel (#599)", false).option("--working", "show both staged and unstaged changes (mid-task working-tree review, #599)", false).action(runDiff);
12041
+
11654
12042
  // packages/cli/src/commands/side.ts
11655
12043
  init_dist();
11656
12044
  init_dist3();
@@ -11658,11 +12046,11 @@ init_dist4();
11658
12046
  init_dist3();
11659
12047
  init_dist();
11660
12048
  init_dist2();
11661
- import { Command as Command10 } from "commander";
12049
+ import { Command as Command11 } from "commander";
11662
12050
  import fs20 from "fs";
11663
12051
  import path22 from "path";
11664
12052
  import os12 from "os";
11665
- import chalk10 from "chalk";
12053
+ import chalk11 from "chalk";
11666
12054
  var TEMPLATES_DIR3 = path22.join(os12.homedir(), ".config", "squadrant", "templates");
11667
12055
  async function runSideSpawn2(input) {
11668
12056
  const config = loadConfig();
@@ -11724,7 +12112,7 @@ async function runSideClose2(project, name) {
11724
12112
  config.defaults.worktreeDir ?? ".worktrees"
11725
12113
  );
11726
12114
  }
11727
- var sideCommand = new Command10("side").description(
12115
+ var sideCommand = new Command11("side").description(
11728
12116
  "Spawn and manage side-sessions (research/debug) \u2014 fresh-context tabs off the daemon lifecycle"
11729
12117
  );
11730
12118
  sideCommand.command("spawn").description(
@@ -11749,9 +12137,9 @@ sideCommand.command("spawn").description(
11749
12137
  direction: opts.direction,
11750
12138
  agent: opts.agent
11751
12139
  });
11752
- console.log(chalk10.green(`\u2714 Side session '${pane.title}' spawned (${pane.surfaceId})`));
12140
+ console.log(chalk11.green(`\u2714 Side session '${pane.title}' spawned (${pane.surfaceId})`));
11753
12141
  } catch (err) {
11754
- console.error(chalk10.red(err.message));
12142
+ console.error(chalk11.red(err.message));
11755
12143
  process.exit(1);
11756
12144
  }
11757
12145
  }
@@ -11760,14 +12148,14 @@ sideCommand.command("list").description("List live side-sessions for a project")
11760
12148
  try {
11761
12149
  const sessions = await runSideList2(project);
11762
12150
  if (sessions.length === 0) {
11763
- console.log(chalk10.yellow(`No live side-sessions for ${project}.`));
12151
+ console.log(chalk11.yellow(`No live side-sessions for ${project}.`));
11764
12152
  return;
11765
12153
  }
11766
12154
  for (const s of sessions) {
11767
12155
  console.log(` ${s.name} (${s.surfaceId})`);
11768
12156
  }
11769
12157
  } catch (err) {
11770
- console.error(chalk10.red(err.message));
12158
+ console.error(chalk11.red(err.message));
11771
12159
  process.exit(1);
11772
12160
  }
11773
12161
  });
@@ -11780,9 +12168,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
11780
12168
  label: "message"
11781
12169
  });
11782
12170
  await runSideSend2(project, name, resolvedMessage);
11783
- console.log(chalk10.green(`\u2714 Sent to ${project}:${name}`));
12171
+ console.log(chalk11.green(`\u2714 Sent to ${project}:${name}`));
11784
12172
  } catch (err) {
11785
- console.error(chalk10.red(err.message));
12173
+ console.error(chalk11.red(err.message));
11786
12174
  process.exit(1);
11787
12175
  }
11788
12176
  }
@@ -11790,9 +12178,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
11790
12178
  sideCommand.command("close").description("Close a side-session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Session name").action(async (project, name) => {
11791
12179
  try {
11792
12180
  await runSideClose2(project, name);
11793
- console.log(chalk10.green(`\u2714 Closed ${project}:${name}`));
12181
+ console.log(chalk11.green(`\u2714 Closed ${project}:${name}`));
11794
12182
  } catch (err) {
11795
- console.error(chalk10.red(err.message));
12183
+ console.error(chalk11.red(err.message));
11796
12184
  process.exit(1);
11797
12185
  }
11798
12186
  });
@@ -11800,15 +12188,15 @@ sideCommand.command("close").description("Close a side-session (closes its tab)"
11800
12188
  // packages/cli/src/commands/dashboard.ts
11801
12189
  init_dist();
11802
12190
  init_dist3();
11803
- import { Command as Command11 } from "commander";
12191
+ import { Command as Command12 } from "commander";
11804
12192
  import { execSync as execSync10 } from "child_process";
11805
12193
  import { homedir as homedir18 } from "os";
11806
12194
  import { join as join23 } from "path";
11807
- import chalk12 from "chalk";
12195
+ import chalk13 from "chalk";
11808
12196
 
11809
12197
  // packages/web/dist/read-status.js
11810
12198
  function deriveState(tasks) {
11811
- if (tasks.some((t) => t.state === "blocked" || t.state === "awaiting-input"))
12199
+ if (tasks.some((t) => t.state === "blocked" || t.state === "awaiting-input" || t.state === "review"))
11812
12200
  return "blocked";
11813
12201
  if (tasks.some((t) => t.state === "failed" || t.state === "stalled"))
11814
12202
  return "errored";
@@ -11823,14 +12211,14 @@ function deriveRowState(tasks, captainState) {
11823
12211
  }
11824
12212
  function buildExcerpt(tasks) {
11825
12213
  const working = tasks.filter((t) => t.state === "working").length;
11826
- const blocked = tasks.filter((t) => t.state === "blocked" || t.state === "awaiting-input").length;
12214
+ const blocked = tasks.filter((t) => t.state === "blocked" || t.state === "awaiting-input" || t.state === "review").length;
11827
12215
  const parts = [];
11828
12216
  if (working > 0)
11829
12217
  parts.push(`${working} working`);
11830
12218
  if (blocked > 0)
11831
12219
  parts.push(`${blocked} blocked`);
11832
12220
  const summary = parts.length > 0 ? parts.join(", ") : "idle";
11833
- const active = tasks.filter((t) => ["working", "blocked", "awaiting-input", "submitted"].includes(t.state));
12221
+ const active = tasks.filter((t) => ["working", "blocked", "awaiting-input", "review", "submitted"].includes(t.state));
11834
12222
  const titles = active.slice(0, 3).map((t) => {
11835
12223
  const firstLine2 = t.task ? t.task.split("\n")[0] : "";
11836
12224
  return t.name ?? (firstLine2 || t.id.slice(0, 8));
@@ -11881,14 +12269,14 @@ async function readAllStatuses(deps) {
11881
12269
  }
11882
12270
 
11883
12271
  // packages/web/dist/render.js
11884
- import chalk11 from "chalk";
12272
+ import chalk12 from "chalk";
11885
12273
  var ICON = {
11886
- idle: chalk11.green,
11887
- busy: chalk11.cyan,
11888
- blocked: chalk11.yellow,
11889
- errored: chalk11.red,
11890
- offline: chalk11.dim,
11891
- unknown: chalk11.gray
12274
+ idle: chalk12.green,
12275
+ busy: chalk12.cyan,
12276
+ blocked: chalk12.yellow,
12277
+ errored: chalk12.red,
12278
+ offline: chalk12.dim,
12279
+ unknown: chalk12.gray
11892
12280
  };
11893
12281
  var ICON_CHAR = {
11894
12282
  idle: "\u25CF",
@@ -11931,10 +12319,10 @@ function renderDashboard(rows, opts) {
11931
12319
  const width = opts.width ?? 100;
11932
12320
  const lines = [];
11933
12321
  lines.push("");
11934
- lines.push(" " + chalk11.bold("\u{1F4CA} Squadrant Dashboard") + " " + chalk11.dim(opts.now));
12322
+ lines.push(" " + chalk12.bold("\u{1F4CA} Squadrant Dashboard") + " " + chalk12.dim(opts.now));
11935
12323
  lines.push("");
11936
12324
  if (rows.length === 0) {
11937
- lines.push(" " + chalk11.yellow("No projects registered. Add one with: squadrant projects add <name> <path>"));
12325
+ lines.push(" " + chalk12.yellow("No projects registered. Add one with: squadrant projects add <name> <path>"));
11938
12326
  lines.push("");
11939
12327
  return lines.join("\n");
11940
12328
  }
@@ -11945,14 +12333,14 @@ function renderDashboard(rows, opts) {
11945
12333
  const excerptW = Math.max(20, width - FIXED);
11946
12334
  for (const r of rows) {
11947
12335
  const icon = ICON[r.state](ICON_CHAR[r.state]);
11948
- const name = chalk11.cyan(pad(r.project, NAME_W));
12336
+ const name = chalk12.cyan(pad(r.project, NAME_W));
11949
12337
  const state = ICON[r.state](pad(r.state, STATE_W));
11950
12338
  const age = pad(formatAge(r.lastChecked, opts.now), AGE_W);
11951
- const excerpt = chalk11.dim(truncate(firstLine(r.excerpt), excerptW));
12339
+ const excerpt = chalk12.dim(truncate(firstLine(r.excerpt), excerptW));
11952
12340
  lines.push(` ${icon} ${name} ${state} ${age} \u2502 ${excerpt}`);
11953
12341
  }
11954
12342
  lines.push("");
11955
- lines.push(chalk11.dim(" Refreshes every 10s \xB7 Ctrl+C to exit"));
12343
+ lines.push(chalk12.dim(" Refreshes every 10s \xB7 Ctrl+C to exit"));
11956
12344
  lines.push("");
11957
12345
  return lines.join("\n");
11958
12346
  }
@@ -13075,10 +13463,10 @@ async function runDashboardWeb(input) {
13075
13463
  sockPath: SOCK3,
13076
13464
  runners: defaultProbeRunners()
13077
13465
  });
13078
- console.log(chalk12.green(`\u2714 Squadrant system dashboard \u2192 http://127.0.0.1:${handle.port}`));
13079
- console.log(chalk12.dim(` polling the daemon every ${input.interval}s \xB7 localhost only \xB7 read-only \xB7 Ctrl-C to stop`));
13466
+ console.log(chalk13.green(`\u2714 Squadrant system dashboard \u2192 http://127.0.0.1:${handle.port}`));
13467
+ console.log(chalk13.dim(` polling the daemon every ${input.interval}s \xB7 localhost only \xB7 read-only \xB7 Ctrl-C to stop`));
13080
13468
  }
13081
- var dashboardCommand = new Command11("dashboard").description("Live status grid of all projects (derived from daemon task state)").option("--once", "Print one snapshot and exit (used by --pane's refresh loop)").option("--pane", "Open a refreshing sidebar pane in the current cmux workspace").option("--web", "Serve the live system-health web dashboard on 127.0.0.1 (HTTP + SSE)").option("--port <port>", "Port for --web (default 7878)", (v) => parseInt(v, 10), 7878).option("--direction <dir>", "Pane split direction (right|left|up|down)", "right").option("--interval <seconds>", "Daemon poll interval for --web (default 5); refresh interval for --pane (default 10)", (v) => parseInt(v, 10)).action(async (opts) => {
13469
+ var dashboardCommand = new Command12("dashboard").description("Live status grid of all projects (derived from daemon task state)").option("--once", "Print one snapshot and exit (used by --pane's refresh loop)").option("--pane", "Open a refreshing sidebar pane in the current cmux workspace").option("--web", "Serve the live system-health web dashboard on 127.0.0.1 (HTTP + SSE)").option("--port <port>", "Port for --web (default 7878)", (v) => parseInt(v, 10), 7878).option("--direction <dir>", "Pane split direction (right|left|up|down)", "right").option("--interval <seconds>", "Daemon poll interval for --web (default 5); refresh interval for --pane (default 10)", (v) => parseInt(v, 10)).action(async (opts) => {
13082
13470
  try {
13083
13471
  if (opts.web) {
13084
13472
  await runDashboardWeb({ port: opts.port, interval: opts.interval ?? 5 });
@@ -13086,12 +13474,12 @@ var dashboardCommand = new Command11("dashboard").description("Live status grid
13086
13474
  }
13087
13475
  if (opts.pane) {
13088
13476
  const pane = await runDashboardPane({ direction: opts.direction, interval: opts.interval ?? 10 });
13089
- console.log(chalk12.green(`\u2714 Dashboard pane opened in ${pane.workspaceId} ${pane.surfaceId}`));
13477
+ console.log(chalk13.green(`\u2714 Dashboard pane opened in ${pane.workspaceId} ${pane.surfaceId}`));
13090
13478
  return;
13091
13479
  }
13092
13480
  await runDashboardOnce();
13093
13481
  } catch (err) {
13094
- console.error(chalk12.red(err.message));
13482
+ console.error(chalk13.red(err.message));
13095
13483
  process.exit(1);
13096
13484
  }
13097
13485
  });
@@ -13102,12 +13490,12 @@ dashboardCommand.command("sync-hub").description("Mirror each spoke status.md in
13102
13490
  return;
13103
13491
  }
13104
13492
  if (results.length === 0) {
13105
- console.log(chalk12.dim("\n No mirrors written (no projects with usable status.md, or hubVault unset).\n"));
13493
+ console.log(chalk13.dim("\n No mirrors written (no projects with usable status.md, or hubVault unset).\n"));
13106
13494
  return;
13107
13495
  }
13108
- console.log(chalk12.bold("\n \u{1F4CA} Hub mirror sync\n"));
13496
+ console.log(chalk13.bold("\n \u{1F4CA} Hub mirror sync\n"));
13109
13497
  for (const r of results) {
13110
- console.log(` ${chalk12.green("\u2714")} ${chalk12.cyan(r.project.padEnd(16))} \u2192 ${chalk12.dim(r.hubPath)}`);
13498
+ console.log(` ${chalk13.green("\u2714")} ${chalk13.cyan(r.project.padEnd(16))} \u2192 ${chalk13.dim(r.hubPath)}`);
13111
13499
  }
13112
13500
  console.log("");
13113
13501
  });
@@ -13117,12 +13505,12 @@ init_dist();
13117
13505
  init_dist4();
13118
13506
  init_dist3();
13119
13507
  init_dist2();
13120
- import { Command as Command12 } from "commander";
13508
+ import { Command as Command13 } from "commander";
13121
13509
  import { execSync as execSync11 } from "child_process";
13122
13510
  import fs22 from "fs";
13123
13511
  import path24 from "path";
13124
13512
  import os13 from "os";
13125
- import chalk13 from "chalk";
13513
+ import chalk14 from "chalk";
13126
13514
 
13127
13515
  // packages/cli/src/commands/launch-interactive.ts
13128
13516
  import checkbox, { Separator } from "@inquirer/checkbox";
@@ -13203,16 +13591,16 @@ var TEMPLATES_DIR4 = path24.join(os13.homedir(), ".config", "squadrant", "templa
13203
13591
  var SESSIONS_PATH2 = path24.join(os13.homedir(), ".config", "squadrant", "sessions.json");
13204
13592
  function ensureCmuxReady(headless) {
13205
13593
  if (headless || isInsideCmux()) return;
13206
- console.log(chalk13.yellow("\n Not running inside cmux. Opening cmux app...\n"));
13594
+ console.log(chalk14.yellow("\n Not running inside cmux. Opening cmux app...\n"));
13207
13595
  execSync11(`open "${CMUX_APP}"`, { stdio: "inherit" });
13208
- console.log(chalk13.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
13596
+ console.log(chalk14.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
13209
13597
  process.exit(0);
13210
13598
  }
13211
- var launchCommand = new Command12("launch").description(
13599
+ var launchCommand = new Command13("launch").description(
13212
13600
  "Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
13213
13601
  ).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").option("--keep", "Resume the latest session even on a new day / after a template change").option("--all", "Launch all captain workspaces").option("--headless", "Skip the interactive cmux-app requirement (used by the daemon to boot captains without a terminal)").action(async (project, opts) => {
13214
13602
  if (opts.fresh && opts.keep) {
13215
- console.error(chalk13.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
13603
+ console.error(chalk14.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
13216
13604
  process.exit(1);
13217
13605
  }
13218
13606
  const config = loadConfig();
@@ -13260,29 +13648,29 @@ var launchCommand = new Command12("launch").description(
13260
13648
  return null;
13261
13649
  }
13262
13650
  },
13263
- onFreshReason: (reason) => console.log(chalk13.cyan(` \u21BB ${reason}`)),
13264
- onStoppingStale: (name) => console.log(chalk13.yellow(` Closing stale workspace '${name}' for fresh start`)),
13265
- onAlreadyExists: (name) => console.log(chalk13.yellow(` Workspace '${name}' already exists \u2014 switching to it`)),
13266
- onCreated: (name) => console.log(chalk13.green(` \u2714 Workspace '${name}' created`))
13651
+ onFreshReason: (reason) => console.log(chalk14.cyan(` \u21BB ${reason}`)),
13652
+ onStoppingStale: (name) => console.log(chalk14.yellow(` Closing stale workspace '${name}' for fresh start`)),
13653
+ onAlreadyExists: (name) => console.log(chalk14.yellow(` Workspace '${name}' already exists \u2014 switching to it`)),
13654
+ onCreated: (name) => console.log(chalk14.green(` \u2714 Workspace '${name}' created`))
13267
13655
  });
13268
13656
  } catch (err) {
13269
- console.error(chalk13.red(` \u2718 Failed: ${err.message}`));
13657
+ console.error(chalk14.red(` \u2718 Failed: ${err.message}`));
13270
13658
  hadFailure = true;
13271
13659
  }
13272
13660
  }
13273
13661
  if (opts.all) {
13274
13662
  const hubPath = resolveHome(config.hubVault);
13275
13663
  fs22.mkdirSync(hubPath, { recursive: true });
13276
- console.log(chalk13.bold("\nLaunching all captain workspaces\n"));
13664
+ console.log(chalk14.bold("\nLaunching all captain workspaces\n"));
13277
13665
  for (const [name, proj] of Object.entries(config.projects)) {
13278
13666
  const projPath = resolveHome(proj.path);
13279
13667
  const spokePath = resolveHome(proj.spokeVault);
13280
13668
  if (!fs22.existsSync(spokePath)) {
13281
13669
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
13282
13670
  await ensureSpokeLayout(spokeDriver);
13283
- console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13671
+ console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13284
13672
  }
13285
- console.log(chalk13.bold(`
13673
+ console.log(chalk14.bold(`
13286
13674
  Captain: ${proj.captainName} (${name})`));
13287
13675
  await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
13288
13676
  }
@@ -13290,7 +13678,7 @@ var launchCommand = new Command12("launch").description(
13290
13678
  } else if (!project) {
13291
13679
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
13292
13680
  console.error(
13293
- chalk13.red(
13681
+ chalk14.red(
13294
13682
  "\n \u2718 Specify a project name, or pass --all to launch every captain.\n For one-shot Command tasks, use `squadrant command --task <briefing|learnings-review|wiki-aggregate>`.\n"
13295
13683
  )
13296
13684
  );
@@ -13305,10 +13693,10 @@ var launchCommand = new Command12("launch").description(
13305
13693
  }));
13306
13694
  const selected = await selectCaptainsInteractive(entries);
13307
13695
  if (selected.length === 0) {
13308
- console.log(chalk13.yellow("\n No captains selected.\n"));
13696
+ console.log(chalk14.yellow("\n No captains selected.\n"));
13309
13697
  return;
13310
13698
  }
13311
- console.log(chalk13.bold(`
13699
+ console.log(chalk14.bold(`
13312
13700
  Launching ${selected.length} captain workspace(s) in parallel
13313
13701
  `));
13314
13702
  await Promise.all(selected.map(async (name) => {
@@ -13318,9 +13706,9 @@ Launching ${selected.length} captain workspace(s) in parallel
13318
13706
  if (!fs22.existsSync(spokePath)) {
13319
13707
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
13320
13708
  await ensureSpokeLayout(spokeDriver);
13321
- console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13709
+ console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13322
13710
  }
13323
- console.log(chalk13.bold(`
13711
+ console.log(chalk14.bold(`
13324
13712
  Captain: ${proj.captainName} (${name})`));
13325
13713
  await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
13326
13714
  }));
@@ -13328,7 +13716,7 @@ Launching ${selected.length} captain workspace(s) in parallel
13328
13716
  } else {
13329
13717
  if (!config.projects[project]) {
13330
13718
  console.error(
13331
- chalk13.red(
13719
+ chalk14.red(
13332
13720
  `
13333
13721
  \u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
13334
13722
  `
@@ -13342,10 +13730,10 @@ Launching ${selected.length} captain workspace(s) in parallel
13342
13730
  if (!fs22.existsSync(spokePath)) {
13343
13731
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
13344
13732
  await ensureSpokeLayout(spokeDriver);
13345
- console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13733
+ console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13346
13734
  }
13347
13735
  console.log(
13348
- chalk13.bold(
13736
+ chalk14.bold(
13349
13737
  `
13350
13738
  Launching captain workspace for '${project}' (${proj.captainName})
13351
13739
  `
@@ -13359,8 +13747,8 @@ Launching captain workspace for '${project}' (${proj.captainName})
13359
13747
  // packages/cli/src/commands/shutdown.ts
13360
13748
  init_dist();
13361
13749
  init_dist3();
13362
- import { Command as Command13 } from "commander";
13363
- import chalk14 from "chalk";
13750
+ import { Command as Command14 } from "commander";
13751
+ import chalk15 from "chalk";
13364
13752
  init_dist();
13365
13753
  function nameVariants(name) {
13366
13754
  const stripped = name.replace(/^⚓\s+/, "").trim();
@@ -13373,23 +13761,23 @@ async function closeMatching(runtime, variants, label) {
13373
13761
  const failed = [];
13374
13762
  if (matches.length === 0) {
13375
13763
  console.log(
13376
- chalk14.yellow(` \u26A0 Workspace '${label}' not found \u2014 already closed?`)
13764
+ chalk15.yellow(` \u26A0 Workspace '${label}' not found \u2014 already closed?`)
13377
13765
  );
13378
13766
  return { closed, failed };
13379
13767
  }
13380
13768
  for (const ws of matches) {
13381
13769
  try {
13382
13770
  await runtime.stop(ws.id);
13383
- console.log(chalk14.green(` \u2714 Closed: ${ws.name}`));
13771
+ console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
13384
13772
  closed.push(ws.name);
13385
13773
  } catch {
13386
- console.log(chalk14.red(` \u2718 Failed to close: ${ws.name}`));
13774
+ console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
13387
13775
  failed.push(ws.name);
13388
13776
  }
13389
13777
  }
13390
13778
  return { closed, failed };
13391
13779
  }
13392
- var shutdownCommand = new Command13("shutdown").description(
13780
+ var shutdownCommand = new Command14("shutdown").description(
13393
13781
  "Shutdown command + all captain workspaces (no args) or one captain workspace"
13394
13782
  ).argument("[project]", "Project name to shut down captain for").action(async (project) => {
13395
13783
  const config = loadConfig();
@@ -13405,11 +13793,11 @@ var shutdownCommand = new Command13("shutdown").description(
13405
13793
  const allVariants = /* @__PURE__ */ new Set([...captainVariants, ...commandVariants]);
13406
13794
  const squadrantWorkspaces = workspaces.filter((w) => allVariants.has(w.name));
13407
13795
  if (squadrantWorkspaces.length === 0) {
13408
- console.log(chalk14.yellow("\nNo squadrant workspaces found to close.\n"));
13796
+ console.log(chalk15.yellow("\nNo squadrant workspaces found to close.\n"));
13409
13797
  return;
13410
13798
  }
13411
13799
  console.log(
13412
- chalk14.bold(
13800
+ chalk15.bold(
13413
13801
  `
13414
13802
  Shutting down ${squadrantWorkspaces.length} workspace(s)...
13415
13803
  `
@@ -13429,9 +13817,9 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
13429
13817
  for (const ws of squadrantWorkspaces) {
13430
13818
  try {
13431
13819
  await globalRuntime.stop(ws.id);
13432
- console.log(chalk14.green(` \u2714 Closed: ${ws.name}`));
13820
+ console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
13433
13821
  } catch {
13434
- console.log(chalk14.red(` \u2718 Failed to close: ${ws.name}`));
13822
+ console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
13435
13823
  }
13436
13824
  }
13437
13825
  console.log("");
@@ -13439,7 +13827,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
13439
13827
  }
13440
13828
  if (!config.projects[project]) {
13441
13829
  console.error(
13442
- chalk14.red(
13830
+ chalk15.red(
13443
13831
  `
13444
13832
  \u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
13445
13833
  `
@@ -13450,7 +13838,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
13450
13838
  const captainName = config.projects[project].captainName;
13451
13839
  const runtime = runtimes.forProject(project, config);
13452
13840
  console.log(
13453
- chalk14.bold(`
13841
+ chalk15.bold(`
13454
13842
  Shutting down captain workspace for '${project}'...
13455
13843
  `)
13456
13844
  );
@@ -13474,13 +13862,13 @@ Shutting down captain workspace for '${project}'...
13474
13862
 
13475
13863
  // packages/cli/src/commands/feedback.ts
13476
13864
  init_dist();
13477
- import { Command as Command14 } from "commander";
13865
+ import { Command as Command15 } from "commander";
13478
13866
  import fs23 from "fs";
13479
13867
  import os14 from "os";
13480
13868
  import path25 from "path";
13481
13869
  import { fileURLToPath as fileURLToPath3 } from "url";
13482
13870
  import { execSync as execSync12 } from "child_process";
13483
- import chalk15 from "chalk";
13871
+ import chalk16 from "chalk";
13484
13872
  var REPO_URL = "https://github.com/tu11aa/squadrant";
13485
13873
  function readPkgVersion() {
13486
13874
  try {
@@ -13528,21 +13916,21 @@ function buildIssueUrl(metrics, squadrantVersion) {
13528
13916
  });
13529
13917
  return `${REPO_URL}/issues/new?${params.toString()}`;
13530
13918
  }
13531
- var feedbackCommand = new Command14("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
13919
+ var feedbackCommand = new Command15("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
13532
13920
  const config = loadConfig();
13533
13921
  const metricsPath = config.metrics?.path || path25.join(os14.homedir(), ".config", "squadrant", "metrics.json");
13534
13922
  const metrics = readMetrics(metricsPath);
13535
13923
  const version = readStamp(config) ?? readPkgVersion();
13536
13924
  const issueUrl = buildIssueUrl(metrics, version);
13537
- console.log(chalk15.bold("\nOpening feedback issue in browser...\n"));
13538
- console.log(chalk15.dim(` URL: ${issueUrl.substring(0, 80)}...
13925
+ console.log(chalk16.bold("\nOpening feedback issue in browser...\n"));
13926
+ console.log(chalk16.dim(` URL: ${issueUrl.substring(0, 80)}...
13539
13927
  `));
13540
13928
  try {
13541
13929
  execSync12(`open "${issueUrl}"`, { stdio: "ignore" });
13542
- console.log(chalk15.green(" \u2714 Browser opened\n"));
13930
+ console.log(chalk16.green(" \u2714 Browser opened\n"));
13543
13931
  } catch {
13544
- console.log(chalk15.yellow(" \u26A0 Could not open browser automatically."));
13545
- console.log(` Open manually: ${chalk15.cyan(issueUrl)}
13932
+ console.log(chalk16.yellow(" \u26A0 Could not open browser automatically."));
13933
+ console.log(` Open manually: ${chalk16.cyan(issueUrl)}
13546
13934
  `);
13547
13935
  }
13548
13936
  });
@@ -13551,10 +13939,10 @@ var feedbackCommand = new Command14("feedback").description("Open a pre-filled G
13551
13939
  init_dist();
13552
13940
  init_dist();
13553
13941
  init_dist3();
13554
- import { Command as Command15 } from "commander";
13942
+ import { Command as Command16 } from "commander";
13555
13943
  import fs24 from "fs";
13556
13944
  import path26 from "path";
13557
- import chalk16 from "chalk";
13945
+ import chalk17 from "chalk";
13558
13946
  import matter3 from "gray-matter";
13559
13947
  function getDateStr(yesterday) {
13560
13948
  return iso(daysAgo(yesterday ? 1 : 0));
@@ -13584,7 +13972,7 @@ function formatStandup(standups, dateStr, raw) {
13584
13972
  const lines = [];
13585
13973
  const header = `Standup \u2014 ${dateStr}`;
13586
13974
  if (!raw) {
13587
- lines.push(chalk16.bold(`
13975
+ lines.push(chalk17.bold(`
13588
13976
  ${header}
13589
13977
  `));
13590
13978
  } else {
@@ -13597,12 +13985,12 @@ ${header}
13597
13985
  const tasksTotal = s.status.tasks_total ?? 0;
13598
13986
  const tasksInProgress = s.status.tasks_in_progress ?? 0;
13599
13987
  if (!raw) {
13600
- lines.push(chalk16.cyan.bold(`## ${s.name}`));
13988
+ lines.push(chalk17.cyan.bold(`## ${s.name}`));
13601
13989
  } else {
13602
13990
  lines.push(`## ${s.name}`);
13603
13991
  }
13604
13992
  if (s.gitCommits.length > 0 || tasksDone > 0) {
13605
- lines.push(!raw ? chalk16.green("Done:") : "**Done:**");
13993
+ lines.push(!raw ? chalk17.green("Done:") : "**Done:**");
13606
13994
  for (const commit of s.gitCommits) {
13607
13995
  lines.push(` - ${commit}`);
13608
13996
  }
@@ -13611,7 +13999,7 @@ ${header}
13611
13999
  }
13612
14000
  }
13613
14001
  if (tasksInProgress > 0) {
13614
- lines.push(!raw ? chalk16.yellow("In Progress:") : "**In Progress:**");
14002
+ lines.push(!raw ? chalk17.yellow("In Progress:") : "**In Progress:**");
13615
14003
  lines.push(` - ${tasksInProgress} task(s) active`);
13616
14004
  }
13617
14005
  if (s.dailyLog) {
@@ -13621,7 +14009,7 @@ ${header}
13621
14009
  if (match) {
13622
14010
  const items = match[1].trim().split("\n").filter((l) => l.trim().startsWith("-"));
13623
14011
  if (items.length > 0 && section === "Tomorrow") {
13624
- lines.push(!raw ? chalk16.blue("Next:") : "**Next:**");
14012
+ lines.push(!raw ? chalk17.blue("Next:") : "**Next:**");
13625
14013
  for (const item of items) lines.push(` ${item.trim()}`);
13626
14014
  }
13627
14015
  }
@@ -13629,20 +14017,20 @@ ${header}
13629
14017
  }
13630
14018
  if (s.blockers.length > 0) {
13631
14019
  hasBlockers = true;
13632
- lines.push(!raw ? chalk16.red("Blocked:") : "**Blocked:**");
14020
+ lines.push(!raw ? chalk17.red("Blocked:") : "**Blocked:**");
13633
14021
  for (const b of s.blockers) {
13634
14022
  lines.push(` - ${b}`);
13635
14023
  }
13636
14024
  }
13637
14025
  if (s.gitCommits.length === 0 && tasksDone === 0 && !s.dailyLog) {
13638
- lines.push(!raw ? chalk16.dim(" (no activity)") : " (no activity)");
14026
+ lines.push(!raw ? chalk17.dim(" (no activity)") : " (no activity)");
13639
14027
  }
13640
14028
  lines.push("");
13641
14029
  }
13642
14030
  const totalCommits = standups.reduce((sum, s) => sum + s.gitCommits.length, 0);
13643
14031
  const totalDone = standups.reduce((sum, s) => sum + (s.status.tasks_completed ?? 0), 0);
13644
14032
  if (!raw) {
13645
- lines.push(chalk16.dim(`--- ${totalCommits} commits, ${totalDone} tasks done${hasBlockers ? ", HAS BLOCKERS" : ""} ---
14033
+ lines.push(chalk17.dim(`--- ${totalCommits} commits, ${totalDone} tasks done${hasBlockers ? ", HAS BLOCKERS" : ""} ---
13646
14034
  `));
13647
14035
  } else {
13648
14036
  lines.push(`---
@@ -13651,12 +14039,12 @@ ${header}
13651
14039
  }
13652
14040
  return lines.join("\n");
13653
14041
  }
13654
- var standupCommand = new Command15("standup").description("Generate daily standup report from spoke vault data and git logs (zero tokens)").option("-p, --project <name>", "Show standup for a specific project only").option("-a, --all", "Show all projects (default)").option("-y, --yesterday", "Show yesterday's standup instead of today").option("-r, --raw", "Output raw markdown (for pasting into Slack/chat)").action(async (opts) => {
14042
+ var standupCommand = new Command16("standup").description("Generate daily standup report from spoke vault data and git logs (zero tokens)").option("-p, --project <name>", "Show standup for a specific project only").option("-a, --all", "Show all projects (default)").option("-y, --yesterday", "Show yesterday's standup instead of today").option("-r, --raw", "Output raw markdown (for pasting into Slack/chat)").action(async (opts) => {
13655
14043
  const config = loadConfig();
13656
14044
  const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
13657
14045
  const projects = Object.entries(config.projects);
13658
14046
  if (projects.length === 0) {
13659
- console.log(chalk16.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
14047
+ console.log(chalk17.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
13660
14048
  return;
13661
14049
  }
13662
14050
  const dateStr = getDateStr(!!opts.yesterday);
@@ -13665,7 +14053,7 @@ var standupCommand = new Command15("standup").description("Generate daily standu
13665
14053
  if (opts.project) {
13666
14054
  const match = projects.find(([name]) => name === opts.project);
13667
14055
  if (!match) {
13668
- console.error(chalk16.red(`Project "${opts.project}" not found.`));
14056
+ console.error(chalk17.red(`Project "${opts.project}" not found.`));
13669
14057
  process.exit(1);
13670
14058
  }
13671
14059
  targets = [match];
@@ -13683,10 +14071,10 @@ var standupCommand = new Command15("standup").description("Generate daily standu
13683
14071
  init_dist();
13684
14072
  init_dist();
13685
14073
  init_dist3();
13686
- import { Command as Command16 } from "commander";
14074
+ import { Command as Command17 } from "commander";
13687
14075
  import fs25 from "fs";
13688
14076
  import path27 from "path";
13689
- import chalk17 from "chalk";
14077
+ import chalk18 from "chalk";
13690
14078
  import matter4 from "gray-matter";
13691
14079
  function readStatus(spokeVault) {
13692
14080
  const statusFile = path27.join(spokeVault, "status.md");
@@ -13756,7 +14144,7 @@ function formatRetro(retros, fromStr, toStr, raw) {
13756
14144
  const lines = [];
13757
14145
  const header = `Retro \u2014 ${fromStr} \u2192 ${toStr}`;
13758
14146
  lines.push(raw ? `# ${header}
13759
- ` : chalk17.bold(`
14147
+ ` : chalk18.bold(`
13760
14148
  ${header}
13761
14149
  `));
13762
14150
  let totalCommits = 0;
@@ -13766,39 +14154,39 @@ ${header}
13766
14154
  totalCommits += r.commits.length;
13767
14155
  totalPRs += r.mergedPRs.length;
13768
14156
  totalShipped += r.shipped.length;
13769
- lines.push(raw ? `## ${r.name}` : chalk17.cyan.bold(`## ${r.name}`));
13770
- renderList(lines, r.shipped, raw, "Shipped", chalk17.green);
14157
+ lines.push(raw ? `## ${r.name}` : chalk18.cyan.bold(`## ${r.name}`));
14158
+ renderList(lines, r.shipped, raw, "Shipped", chalk18.green);
13771
14159
  if (r.mergedPRs.length > 0) {
13772
- lines.push(raw ? `**PRs merged:**` : chalk17.green("PRs merged:"));
14160
+ lines.push(raw ? `**PRs merged:**` : chalk18.green("PRs merged:"));
13773
14161
  for (const pr of r.mergedPRs) lines.push(` - ${pr}`);
13774
14162
  }
13775
- renderList(lines, r.inProgress, raw, "In Progress", chalk17.yellow);
13776
- renderList(lines, r.blocked, raw, "Blocked", chalk17.red);
13777
- renderList(lines, r.decisions, raw, "Key Decisions", chalk17.magenta);
14163
+ renderList(lines, r.inProgress, raw, "In Progress", chalk18.yellow);
14164
+ renderList(lines, r.blocked, raw, "Blocked", chalk18.red);
14165
+ renderList(lines, r.decisions, raw, "Key Decisions", chalk18.magenta);
13778
14166
  const metricBits = [
13779
14167
  `${r.commits.length} commits`,
13780
14168
  `${r.mergedPRs.length} PRs merged`,
13781
14169
  `${r.shipped.length} shipped`
13782
14170
  ];
13783
- lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` : chalk17.dim(` ${metricBits.join(" \xB7 ")}`));
14171
+ lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` : chalk18.dim(` ${metricBits.join(" \xB7 ")}`));
13784
14172
  if (r.shipped.length === 0 && r.commits.length === 0 && r.mergedPRs.length === 0 && r.inProgress.length === 0 && r.blocked.length === 0) {
13785
- lines.push(raw ? "_(no activity in this window)_" : chalk17.dim(" (no activity in this window)"));
14173
+ lines.push(raw ? "_(no activity in this window)_" : chalk18.dim(" (no activity in this window)"));
13786
14174
  }
13787
14175
  lines.push("");
13788
14176
  }
13789
14177
  const summary = `${totalShipped} items shipped \xB7 ${totalCommits} commits \xB7 ${totalPRs} PRs merged`;
13790
14178
  lines.push(raw ? `---
13791
14179
  *${summary}*
13792
- ` : chalk17.dim(`--- ${summary} ---
14180
+ ` : chalk18.dim(`--- ${summary} ---
13793
14181
  `));
13794
14182
  return lines.join("\n");
13795
14183
  }
13796
- var retroCommand = new Command16("retro").description("Generate a retro (weekly/sprint summary) from daily logs and git (zero tokens)").option("-w, --week", "Trailing 7 days (default)").option("-s, --sprint [days]", "Custom window of N days (default 14 if N omitted)").option("-p, --project <name>", "Retro for a single project").option("-a, --all", "All projects (default)").option("-r, --raw", "Raw markdown output (for pasting into Slack/Obsidian)").action(async (opts) => {
14184
+ var retroCommand = new Command17("retro").description("Generate a retro (weekly/sprint summary) from daily logs and git (zero tokens)").option("-w, --week", "Trailing 7 days (default)").option("-s, --sprint [days]", "Custom window of N days (default 14 if N omitted)").option("-p, --project <name>", "Retro for a single project").option("-a, --all", "All projects (default)").option("-r, --raw", "Raw markdown output (for pasting into Slack/Obsidian)").action(async (opts) => {
13797
14185
  const config = loadConfig();
13798
14186
  const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
13799
14187
  const projects = Object.entries(config.projects);
13800
14188
  if (projects.length === 0) {
13801
- console.log(chalk17.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
14189
+ console.log(chalk18.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
13802
14190
  return;
13803
14191
  }
13804
14192
  let windowDays = 7;
@@ -13815,7 +14203,7 @@ var retroCommand = new Command16("retro").description("Generate a retro (weekly/
13815
14203
  if (opts.project) {
13816
14204
  const match = projects.find(([name]) => name === opts.project);
13817
14205
  if (!match) {
13818
- console.error(chalk17.red(`Project "${opts.project}" not found.`));
14206
+ console.error(chalk18.red(`Project "${opts.project}" not found.`));
13819
14207
  process.exit(1);
13820
14208
  }
13821
14209
  targets = [match];
@@ -13831,8 +14219,8 @@ var retroCommand = new Command16("retro").description("Generate a retro (weekly/
13831
14219
  // packages/cli/src/commands/runtime.ts
13832
14220
  init_dist();
13833
14221
  init_dist3();
13834
- import { Command as Command17 } from "commander";
13835
- import chalk18 from "chalk";
14222
+ import { Command as Command18 } from "commander";
14223
+ import chalk19 from "chalk";
13836
14224
  function buildRegistry() {
13837
14225
  return new RuntimeRegistry({
13838
14226
  cmux: createCmuxDriver()
@@ -13864,7 +14252,7 @@ async function needRef(resolved) {
13864
14252
  }
13865
14253
  return ref.id;
13866
14254
  }
13867
- var runtimeCommand = new Command17("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
14255
+ var runtimeCommand = new Command18("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
13868
14256
  runtimeCommand.command("status").description("Print 'running' or 'stopped' for a target; exit 0 if running, 1 if not").argument("[target]", "Project name").option("--command", "Target the command workspace instead of a project captain").action(async (target, opts) => {
13869
14257
  const config = loadConfig();
13870
14258
  const registry = buildRegistry();
@@ -13879,7 +14267,7 @@ runtimeCommand.command("status").description("Print 'running' or 'stopped' for a
13879
14267
  process.exit(1);
13880
14268
  }
13881
14269
  } catch (err) {
13882
- console.error(chalk18.red(err.message));
14270
+ console.error(chalk19.red(err.message));
13883
14271
  process.exit(2);
13884
14272
  }
13885
14273
  });
@@ -13926,9 +14314,9 @@ async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
13926
14314
  runtimeCommand.command("send").description("Send a message to a target workspace AND commit with Enter. With --command, the first positional is the message.").argument("<arg1>", "Project name, or the message when --command is used").argument("[arg2]", "Message (when target is a project). Omit when using --command.").option("--command", "Target the command workspace").action(async (arg1, arg2, opts) => {
13927
14315
  try {
13928
14316
  await runRuntimeSend(arg1, arg2, opts);
13929
- console.log(chalk18.green("\u2714 Delivered (confirmed)"));
14317
+ console.log(chalk19.green("\u2714 Delivered (confirmed)"));
13930
14318
  } catch (err) {
13931
- console.error(chalk18.red(err.message));
14319
+ console.error(chalk19.red(err.message));
13932
14320
  process.exit(1);
13933
14321
  }
13934
14322
  });
@@ -13954,7 +14342,7 @@ runtimeCommand.command("read-screen").description("Print a terminal snapshot of
13954
14342
  const screen = await resolved.driver.readScreen(ref);
13955
14343
  process.stdout.write(screen);
13956
14344
  } catch (err) {
13957
- console.error(chalk18.red(err.message));
14345
+ console.error(chalk19.red(err.message));
13958
14346
  process.exit(1);
13959
14347
  }
13960
14348
  });
@@ -13965,13 +14353,13 @@ runtimeCommand.command("stop").description("Stop a target workspace").argument("
13965
14353
  const resolved = resolveTarget(registry, config, target, !!opts.command);
13966
14354
  const ref = await resolved.driver.status(resolved.workspaceName);
13967
14355
  if (!ref) {
13968
- console.log(chalk18.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
14356
+ console.log(chalk19.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
13969
14357
  return;
13970
14358
  }
13971
14359
  await resolved.driver.stop(ref.id);
13972
- console.log(chalk18.green(`\u2714 Stopped ${resolved.workspaceName}`));
14360
+ console.log(chalk19.green(`\u2714 Stopped ${resolved.workspaceName}`));
13973
14361
  } catch (err) {
13974
- console.error(chalk18.red(err.message));
14362
+ console.error(chalk19.red(err.message));
13975
14363
  process.exit(1);
13976
14364
  }
13977
14365
  });
@@ -13979,8 +14367,8 @@ runtimeCommand.command("stop").description("Stop a target workspace").argument("
13979
14367
  // packages/cli/src/commands/workspace.ts
13980
14368
  init_dist();
13981
14369
  init_dist3();
13982
- import { Command as Command18 } from "commander";
13983
- import chalk19 from "chalk";
14370
+ import { Command as Command19 } from "commander";
14371
+ import chalk20 from "chalk";
13984
14372
  function buildRegistry2() {
13985
14373
  return new WorkspaceRegistry({
13986
14374
  obsidian: createObsidianDriver
@@ -13998,7 +14386,7 @@ async function readStdin() {
13998
14386
  }
13999
14387
  return Buffer.concat(chunks).toString("utf-8");
14000
14388
  }
14001
- var workspaceCommand = new Command18("workspace").description("Interact with the workspace layer (vault storage). Bridges bash scripts to the WorkspaceDriver.");
14389
+ var workspaceCommand = new Command19("workspace").description("Interact with the workspace layer (vault storage). Bridges bash scripts to the WorkspaceDriver.");
14002
14390
  function resolveTargetAndPath(arg1, arg2, useHub) {
14003
14391
  if (useHub) {
14004
14392
  if (arg2 !== void 0) {
@@ -14020,7 +14408,7 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
14020
14408
  const content = await driver.read(path30);
14021
14409
  process.stdout.write(content);
14022
14410
  } catch (err) {
14023
- console.error(chalk19.red(err.message));
14411
+ console.error(chalk20.red(err.message));
14024
14412
  process.exit(1);
14025
14413
  }
14026
14414
  });
@@ -14050,7 +14438,7 @@ workspaceCommand.command("write").description("Write content to a scope-relative
14050
14438
  const payload = rawContent === "-" ? await readStdin() : rawContent;
14051
14439
  await driver.write(path30, payload);
14052
14440
  } catch (err) {
14053
- console.error(chalk19.red(err.message));
14441
+ console.error(chalk20.red(err.message));
14054
14442
  process.exit(1);
14055
14443
  }
14056
14444
  });
@@ -14063,7 +14451,7 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
14063
14451
  const entries = await driver.list(path30);
14064
14452
  for (const entry of entries) console.log(entry);
14065
14453
  } catch (err) {
14066
- console.error(chalk19.red(err.message));
14454
+ console.error(chalk20.red(err.message));
14067
14455
  process.exit(1);
14068
14456
  }
14069
14457
  });
@@ -14076,7 +14464,7 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
14076
14464
  const ok2 = await driver.exists(path30);
14077
14465
  process.exit(ok2 ? 0 : 1);
14078
14466
  } catch (err) {
14079
- console.error(chalk19.red(err.message));
14467
+ console.error(chalk20.red(err.message));
14080
14468
  process.exit(2);
14081
14469
  }
14082
14470
  });
@@ -14088,7 +14476,7 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
14088
14476
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
14089
14477
  await driver.mkdir(path30);
14090
14478
  } catch (err) {
14091
- console.error(chalk19.red(err.message));
14479
+ console.error(chalk20.red(err.message));
14092
14480
  process.exit(1);
14093
14481
  }
14094
14482
  });
@@ -14096,8 +14484,8 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
14096
14484
  // packages/cli/src/commands/notify.ts
14097
14485
  init_dist();
14098
14486
  init_dist3();
14099
- import { Command as Command19 } from "commander";
14100
- import chalk20 from "chalk";
14487
+ import { Command as Command20 } from "commander";
14488
+ import chalk21 from "chalk";
14101
14489
  async function readStdin2() {
14102
14490
  const chunks = [];
14103
14491
  for await (const chunk of process.stdin) {
@@ -14105,7 +14493,7 @@ async function readStdin2() {
14105
14493
  }
14106
14494
  return Buffer.concat(chunks).toString("utf-8");
14107
14495
  }
14108
- var notifyCommand = new Command19("notify").description("Send a message to the user via the configured notifier").argument("<message>", "Message to send (use '-' to read from stdin)").action(async (message) => {
14496
+ var notifyCommand = new Command20("notify").description("Send a message to the user via the configured notifier").argument("<message>", "Message to send (use '-' to read from stdin)").action(async (message) => {
14109
14497
  const config = loadConfig();
14110
14498
  const registry = new NotifierRegistry({ cmux: createCmuxNotifier });
14111
14499
  try {
@@ -14113,7 +14501,7 @@ var notifyCommand = new Command19("notify").description("Send a message to the u
14113
14501
  if (!payload) throw new Error("Empty message");
14114
14502
  await registry.get(config).notify(payload);
14115
14503
  } catch (err) {
14116
- console.error(chalk20.red(err.message));
14504
+ console.error(chalk21.red(err.message));
14117
14505
  process.exit(1);
14118
14506
  }
14119
14507
  });
@@ -14123,8 +14511,8 @@ init_dist();
14123
14511
  init_dist4();
14124
14512
  init_dist3();
14125
14513
  init_dist();
14126
- import { Command as Command20 } from "commander";
14127
- import chalk21 from "chalk";
14514
+ import { Command as Command21 } from "commander";
14515
+ import chalk22 from "chalk";
14128
14516
  import fs26 from "fs";
14129
14517
  import path28 from "path";
14130
14518
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -14171,17 +14559,17 @@ async function runEmit(opts) {
14171
14559
  for (const dest of emitter.destinations(scope, projectRoot)) {
14172
14560
  const result = await emitter.emit(source, dest, { dryRun: opts.dryRun });
14173
14561
  if (opts.dryRun) {
14174
- console.log(chalk21.cyan(`[${emitter.name}] ${dest.path}`));
14562
+ console.log(chalk22.cyan(`[${emitter.name}] ${dest.path}`));
14175
14563
  console.log(result.diff ?? "(no diff)");
14176
14564
  } else if (result.written) {
14177
14565
  console.log(
14178
- chalk21.green(
14566
+ chalk22.green(
14179
14567
  `\u2714 ${emitter.name} \u2192 ${dest.path} (${result.bytesWritten} bytes)`
14180
14568
  )
14181
14569
  );
14182
14570
  emittedCount.written++;
14183
14571
  } else {
14184
- console.log(chalk21.gray(`- ${emitter.name} \u2192 ${dest.path} (skipped)`));
14572
+ console.log(chalk22.gray(`- ${emitter.name} \u2192 ${dest.path} (skipped)`));
14185
14573
  emittedCount.skipped++;
14186
14574
  }
14187
14575
  }
@@ -14204,14 +14592,14 @@ async function runEmit(opts) {
14204
14592
  `Unknown project '${projectName}'. Available: ${Object.keys(cfg.projects).join(", ") || "(none)"}`
14205
14593
  );
14206
14594
  }
14207
- console.error(chalk21.yellow(`\u26A0 unknown project: ${projectName}`));
14595
+ console.error(chalk22.yellow(`\u26A0 unknown project: ${projectName}`));
14208
14596
  continue;
14209
14597
  }
14210
14598
  const source = await readProjectLevelSource(
14211
14599
  createObsidianDriver({ root: proj.path })
14212
14600
  );
14213
14601
  if (!source) {
14214
- console.log(chalk21.gray(`- ${projectName}: no AGENTS.md, skipping`));
14602
+ console.log(chalk22.gray(`- ${projectName}: no AGENTS.md, skipping`));
14215
14603
  continue;
14216
14604
  }
14217
14605
  for (const name of targets) {
@@ -14221,21 +14609,21 @@ async function runEmit(opts) {
14221
14609
  }
14222
14610
  if (!opts.dryRun) {
14223
14611
  console.log(
14224
- chalk21.bold(
14612
+ chalk22.bold(
14225
14613
  `
14226
14614
  Projection complete \u2014 ${emittedCount.written} written, ${emittedCount.skipped} skipped.`
14227
14615
  )
14228
14616
  );
14229
14617
  }
14230
14618
  }
14231
- var projectionCommand = new Command20("projection").description(
14619
+ var projectionCommand = new Command21("projection").description(
14232
14620
  "Project squadrant instructions and skills to supported agent formats"
14233
14621
  );
14234
14622
  projectionCommand.command("emit").description("Emit projections to disk").option("--scope <scope>", "user or project", parseScope).option("--project <name>", "managed project name").option("--target <name>", "single target (cursor, codex, gemini, opencode)").option("--all", "emit user-level + every managed project").action(async (opts) => {
14235
14623
  try {
14236
14624
  await runEmit({ ...opts, dryRun: false });
14237
14625
  } catch (err) {
14238
- console.error(chalk21.red(err.message));
14626
+ console.error(chalk22.red(err.message));
14239
14627
  process.exit(1);
14240
14628
  }
14241
14629
  });
@@ -14243,7 +14631,7 @@ projectionCommand.command("diff").description("Preview changes without writing")
14243
14631
  try {
14244
14632
  await runEmit({ ...opts, dryRun: true });
14245
14633
  } catch (err) {
14246
- console.error(chalk21.red(err.message));
14634
+ console.error(chalk22.red(err.message));
14247
14635
  process.exit(1);
14248
14636
  }
14249
14637
  });
@@ -14253,7 +14641,7 @@ projectionCommand.command("list").description("List registered projection target
14253
14641
  const emitter = registry.get(name);
14254
14642
  const userDests = emitter.destinations("user").map((d) => d.path);
14255
14643
  const projectDests = emitter.destinations("project", "<project>").map((d) => d.path);
14256
- console.log(chalk21.bold(name));
14644
+ console.log(chalk22.bold(name));
14257
14645
  console.log(` user: ${userDests.join(", ") || "(none)"}`);
14258
14646
  console.log(` project: ${projectDests.join(", ") || "(none)"}`);
14259
14647
  }
@@ -14261,9 +14649,9 @@ projectionCommand.command("list").description("List registered projection target
14261
14649
 
14262
14650
  // packages/cli/src/commands/codex-chat-smoke.ts
14263
14651
  init_dist4();
14264
- import { Command as Command21 } from "commander";
14652
+ import { Command as Command22 } from "commander";
14265
14653
  import { resolve as resolve2 } from "path";
14266
- var codexChatSmokeCommand = new Command21("codex-chat-smoke").description("Phase 1 gate: prove the codex app-server JSON-RPC path works end-to-end.").option("--cwd <dir>", "working dir for the codex thread", process.cwd()).option("--model <m>", "model id (optional)").option(
14654
+ var codexChatSmokeCommand = new Command22("codex-chat-smoke").description("Phase 1 gate: prove the codex app-server JSON-RPC path works end-to-end.").option("--cwd <dir>", "working dir for the codex thread", process.cwd()).option("--model <m>", "model id (optional)").option(
14267
14655
  "--approval",
14268
14656
  "include the approval round-trip (Phase 1 PASS requires this)",
14269
14657
  false
@@ -14332,11 +14720,11 @@ init_dist();
14332
14720
  init_dist();
14333
14721
  init_dist();
14334
14722
  init_dist2();
14335
- import { Command as Command22 } from "commander";
14723
+ import { Command as Command23 } from "commander";
14336
14724
  import fs27 from "fs";
14337
14725
  import { fileURLToPath as fileURLToPath5 } from "url";
14338
14726
  import { dirname as dirname6, join as join25 } from "path";
14339
- import chalk22 from "chalk";
14727
+ import chalk23 from "chalk";
14340
14728
  function runConfigCheck(opts) {
14341
14729
  const raw = JSON.parse(fs27.readFileSync(opts.configPath, "utf-8"));
14342
14730
  const def = getDefaultConfig();
@@ -14391,14 +14779,14 @@ function runConfigSet(key, value, configPath = DEFAULT_CONFIG_PATH) {
14391
14779
  }
14392
14780
  function printRestartOutcome(outcome) {
14393
14781
  if (outcome === "skipped-not-running") {
14394
- console.log(chalk22.dim("(daemon not running \u2014 change applies on next start)"));
14782
+ console.log(chalk23.dim("(daemon not running \u2014 change applies on next start)"));
14395
14783
  } else if (outcome === "skipped-opt-out") {
14396
- console.log(chalk22.dim("(run 'squadrant heal daemon' to apply)"));
14784
+ console.log(chalk23.dim("(run 'squadrant heal daemon' to apply)"));
14397
14785
  }
14398
14786
  }
14399
14787
  function runConfigSetAction(opts) {
14400
14788
  runConfigSet(opts.key, opts.value, opts.configPath);
14401
- console.log(chalk22.green(`\u2714 set ${opts.key} = ${opts.value}`));
14789
+ console.log(chalk23.green(`\u2714 set ${opts.key} = ${opts.value}`));
14402
14790
  if (isDaemonCachedKey(opts.key)) {
14403
14791
  const doRestart = opts.doRestart ?? restartDaemonIfRunning;
14404
14792
  const outcome = doRestart({ reason: `config ${opts.key}`, noRestart: opts.noRestart });
@@ -14406,9 +14794,9 @@ function runConfigSetAction(opts) {
14406
14794
  }
14407
14795
  }
14408
14796
  var SEV_COLOR = {
14409
- info: chalk22.green,
14410
- advisory: chalk22.yellow,
14411
- warn: chalk22.red
14797
+ info: chalk23.green,
14798
+ advisory: chalk23.yellow,
14799
+ warn: chalk23.red
14412
14800
  };
14413
14801
  var KIND_GLYPH = {
14414
14802
  missing: "+",
@@ -14420,14 +14808,14 @@ function printItems(items) {
14420
14808
  for (const i of items) {
14421
14809
  const color = SEV_COLOR[i.severity] ?? ((s) => s);
14422
14810
  const detail = i.note ? ` (${i.note})` : i.suggested !== void 0 ? ` \u2192 ${JSON.stringify(i.suggested)}` : "";
14423
- console.log(" " + color(`${KIND_GLYPH[i.kind]} ${i.kind}: ${i.path}`) + chalk22.dim(detail));
14811
+ console.log(" " + color(`${KIND_GLYPH[i.kind]} ${i.kind}: ${i.path}`) + chalk23.dim(detail));
14424
14812
  }
14425
14813
  }
14426
- var configCommand = new Command22("config").description("Inspect and reconcile squadrant config");
14814
+ var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
14427
14815
  configCommand.command("check").description("Detect config drift vs the current default schema").option("--fix", "Apply the safe tier (add missing, remove deprecated)", false).option("--accept", "Stamp the current version without changing config (dismiss advisories)", false).option("--json", "Output drift items as JSON", false).action((opts) => {
14428
14816
  const pkgVersion = readPkgVersion2();
14429
14817
  if (!fs27.existsSync(DEFAULT_CONFIG_PATH)) {
14430
- console.log(chalk22.yellow("No config found \u2014 run `squadrant init` first."));
14818
+ console.log(chalk23.yellow("No config found \u2014 run `squadrant init` first."));
14431
14819
  return;
14432
14820
  }
14433
14821
  const res = runConfigCheck({ configPath: DEFAULT_CONFIG_PATH, pkgVersion, fix: opts.fix, accept: opts.accept });
@@ -14436,21 +14824,21 @@ configCommand.command("check").description("Detect config drift vs the current d
14436
14824
  return;
14437
14825
  }
14438
14826
  if (res.items.length === 0) {
14439
- console.log(chalk22.green("\u2714 Config is in sync with the current schema."));
14827
+ console.log(chalk23.green("\u2714 Config is in sync with the current schema."));
14440
14828
  return;
14441
14829
  }
14442
- console.log(chalk22.bold("\nConfig drift:\n"));
14830
+ console.log(chalk23.bold("\nConfig drift:\n"));
14443
14831
  printItems(res.items);
14444
14832
  if (opts.fix && res.applied.length) {
14445
- console.log(chalk22.green(`
14833
+ console.log(chalk23.green(`
14446
14834
  \u2714 Applied ${res.applied.length} safe item(s): ${res.applied.join(", ")}`));
14447
14835
  }
14448
14836
  const judgment = res.remaining.filter((i) => i.kind === "changed-default" || i.kind === "invalid");
14449
14837
  if (judgment.length) {
14450
- console.log(chalk22.yellow(`
14838
+ console.log(chalk23.yellow(`
14451
14839
  ${judgment.length} item(s) need review \u2014 run the config-doctor skill, or \`squadrant config check --accept\` to keep your values.`));
14452
14840
  } else if (res.stamped) {
14453
- console.log(chalk22.green("\n\u2714 Config reconciled and stamped."));
14841
+ console.log(chalk23.green("\n\u2714 Config reconciled and stamped."));
14454
14842
  }
14455
14843
  });
14456
14844
  configCommand.command("get").description("Read a config value by dotted key (e.g. defaults.effort)").argument("<key>", "dotted config key").action((key) => {
@@ -14458,7 +14846,7 @@ configCommand.command("get").description("Read a config value by dotted key (e.g
14458
14846
  const value = runConfigGet(key);
14459
14847
  console.log(typeof value === "string" ? value : JSON.stringify(value));
14460
14848
  } catch (e) {
14461
- console.error(chalk22.red(e.message));
14849
+ console.error(chalk23.red(e.message));
14462
14850
  process.exit(1);
14463
14851
  }
14464
14852
  });
@@ -14466,7 +14854,7 @@ configCommand.command("set").description("Write a config value by dotted key (e.
14466
14854
  try {
14467
14855
  runConfigSetAction({ key, value, noRestart: opts.restart === false });
14468
14856
  } catch (e) {
14469
- console.error(chalk22.red(e.message));
14857
+ console.error(chalk23.red(e.message));
14470
14858
  process.exit(1);
14471
14859
  }
14472
14860
  });
@@ -14476,8 +14864,8 @@ function readPkgVersion2() {
14476
14864
  }
14477
14865
 
14478
14866
  // packages/cli/src/commands/heal.ts
14479
- import { Command as Command23 } from "commander";
14480
- import chalk23 from "chalk";
14867
+ import { Command as Command24 } from "commander";
14868
+ import chalk24 from "chalk";
14481
14869
  init_dist2();
14482
14870
  init_dist2();
14483
14871
  function buildHealStatus(components) {
@@ -14518,15 +14906,15 @@ async function runHealStatus(opts) {
14518
14906
  return result.healthy ? 0 : 2;
14519
14907
  }
14520
14908
  if (result.healthy) {
14521
- stdout.write(chalk23.green("\u2714 all components healthy\n"));
14909
+ stdout.write(chalk24.green("\u2714 all components healthy\n"));
14522
14910
  return 0;
14523
14911
  }
14524
- stdout.write(chalk23.bold("Unhealthy components:\n\n"));
14912
+ stdout.write(chalk24.bold("Unhealthy components:\n\n"));
14525
14913
  for (const c of result.components) {
14526
14914
  if (c.healCmd) {
14527
- stdout.write(` ${chalk23.red("\u2718")} ${c.kind.padEnd(8)} ${c.ref.padEnd(16)} ${chalk23.red(c.state.padEnd(8))} ${c.project}
14915
+ stdout.write(` ${chalk24.red("\u2718")} ${c.kind.padEnd(8)} ${c.ref.padEnd(16)} ${chalk24.red(c.state.padEnd(8))} ${c.project}
14528
14916
  `);
14529
- stdout.write(` heal: ${chalk23.cyan(c.healCmd)}
14917
+ stdout.write(` heal: ${chalk24.cyan(c.healCmd)}
14530
14918
  `);
14531
14919
  }
14532
14920
  }
@@ -14537,7 +14925,7 @@ async function runHealDaemon(opts) {
14537
14925
  stdout.write("restarting squadrantd via launchd kickstart...\n");
14538
14926
  try {
14539
14927
  opts.ensureDaemon();
14540
- stdout.write(chalk23.green("\u2714 daemon kickstart complete\n"));
14928
+ stdout.write(chalk24.green("\u2714 daemon kickstart complete\n"));
14541
14929
  return 0;
14542
14930
  } catch (e) {
14543
14931
  stderr.write(`heal daemon failed: ${e.message}
@@ -14545,8 +14933,8 @@ async function runHealDaemon(opts) {
14545
14933
  return 1;
14546
14934
  }
14547
14935
  }
14548
- var healCommand = new Command23("heal").description("Targeted, idempotent remediation for squadrant components (daemon, health)").addHelpText("after", "\nDeferred: 'squadrant heal crew <id>' (re-attach) \u2014 see issue #100.").addCommand(
14549
- new Command23("status").description("Dry-run: print unhealthy components and the exact heal command for each").option("-p, --project <project>", "scope to one project").option("--json", "output machine-readable JSON (exit 0=healthy, 1=error, 2=unhealthy)").action(async (opts) => {
14936
+ var healCommand = new Command24("heal").description("Targeted, idempotent remediation for squadrant components (daemon, health)").addHelpText("after", "\nDeferred: 'squadrant heal crew <id>' (re-attach) \u2014 see issue #100.").addCommand(
14937
+ new Command24("status").description("Dry-run: print unhealthy components and the exact heal command for each").option("-p, --project <project>", "scope to one project").option("--json", "output machine-readable JSON (exit 0=healthy, 1=error, 2=unhealthy)").action(async (opts) => {
14550
14938
  const code = await runHealStatus({
14551
14939
  project: opts.project,
14552
14940
  json: opts.json ?? false,
@@ -14557,7 +14945,7 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
14557
14945
  process.exit(code);
14558
14946
  })
14559
14947
  ).addCommand(
14560
- new Command23("daemon").description("Restart squadrantd via the idempotent launchd kickstart path").action(async () => {
14948
+ new Command24("daemon").description("Restart squadrantd via the idempotent launchd kickstart path").action(async () => {
14561
14949
  const code = await runHealDaemon({
14562
14950
  ensureDaemon: () => restartDaemonIfRunning({ reason: "heal", isRunning: () => true }),
14563
14951
  stdout: process.stdout,
@@ -14568,15 +14956,15 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
14568
14956
  );
14569
14957
 
14570
14958
  // packages/cli/src/commands/group.ts
14571
- import { Command as Command25 } from "commander";
14572
- import chalk25 from "chalk";
14959
+ import { Command as Command26 } from "commander";
14960
+ import chalk26 from "chalk";
14573
14961
 
14574
14962
  // packages/cli/src/commands/dispatch.ts
14575
14963
  init_dist();
14576
14964
  init_dist2();
14577
- import { Command as Command24 } from "commander";
14965
+ import { Command as Command25 } from "commander";
14578
14966
  import { execSync as execSync13 } from "child_process";
14579
- import chalk24 from "chalk";
14967
+ import chalk25 from "chalk";
14580
14968
  async function runDispatch(toProject, task, opts) {
14581
14969
  const fromProject = resolveCurrentProject(loadConfig());
14582
14970
  if (!fromProject) {
@@ -14601,21 +14989,21 @@ async function runDispatch(toProject, task, opts) {
14601
14989
  async function dispatchAction(toProject, task, opts) {
14602
14990
  try {
14603
14991
  const result = await runDispatch(toProject, task, opts);
14604
- console.log(chalk24.green(`\u2714 Dispatched to '${toProject}' (task ${result.id.slice(0, 8)})`));
14605
- console.log(chalk24.dim(` originProject: ${result.originProject ?? "none"}`));
14606
- console.log(chalk24.dim(" You will be notified when the task settles (done/blocked/failed)."));
14992
+ console.log(chalk25.green(`\u2714 Dispatched to '${toProject}' (task ${result.id.slice(0, 8)})`));
14993
+ console.log(chalk25.dim(` originProject: ${result.originProject ?? "none"}`));
14994
+ console.log(chalk25.dim(" You will be notified when the task settles (done/blocked/failed)."));
14607
14995
  } catch (e) {
14608
- console.error(chalk24.red(`\u2718 ${e.message}`));
14996
+ console.error(chalk25.red(`\u2718 ${e.message}`));
14609
14997
  process.exit(1);
14610
14998
  }
14611
14999
  }
14612
- var dispatchCommand = new Command24("dispatch").description("Dispatch a task to any registered project (tracked, reports back on settle)").argument("<project>", "Target project name").argument("<task>", "Task description to dispatch").option("--provider <p>", "claude|opencode|codex", "claude").option("--mode <m>", "headless|interactive", "headless").option("--warmup-timeout <s>", "seconds to wait for target captain to boot (same-group only; default: 120)", (v) => parseInt(v, 10) * 1e3).action(dispatchAction);
15000
+ var dispatchCommand = new Command25("dispatch").description("Dispatch a task to any registered project (tracked, reports back on settle)").argument("<project>", "Target project name").argument("<task>", "Task description to dispatch").option("--provider <p>", "claude|opencode|codex", "claude").option("--mode <m>", "headless|interactive", "headless").option("--warmup-timeout <s>", "seconds to wait for target captain to boot (same-group only; default: 120)", (v) => parseInt(v, 10) * 1e3).action(dispatchAction);
14613
15001
 
14614
15002
  // packages/cli/src/commands/group.ts
14615
15003
  init_dist2();
14616
- var groupCommand = new Command25("group").description("Cross-project intra-group operations (Phase 1: dispatch)").addCommand(
14617
- new Command25("dispatch").description("[DEPRECATED \u2014 use 'squadrant dispatch'] Dispatch a task to a sibling project in the same group").argument("<to-project>", "Target project name").argument("<task>", "Task description to dispatch").option("--provider <p>", "claude|opencode|codex", "claude").option("--mode <m>", "headless|interactive", "headless").option("--warmup-timeout <s>", "seconds to wait for target captain to boot (default: 120)", (v) => parseInt(v, 10) * 1e3).action(async (toProject, task, opts) => {
14618
- console.error(chalk25.yellow(
15004
+ var groupCommand = new Command26("group").description("Cross-project intra-group operations (Phase 1: dispatch)").addCommand(
15005
+ new Command26("dispatch").description("[DEPRECATED \u2014 use 'squadrant dispatch'] Dispatch a task to a sibling project in the same group").argument("<to-project>", "Target project name").argument("<task>", "Task description to dispatch").option("--provider <p>", "claude|opencode|codex", "claude").option("--mode <m>", "headless|interactive", "headless").option("--warmup-timeout <s>", "seconds to wait for target captain to boot (default: 120)", (v) => parseInt(v, 10) * 1e3).action(async (toProject, task, opts) => {
15006
+ console.error(chalk26.yellow(
14619
15007
  `\u26A0 'squadrant group dispatch' is deprecated \u2014 use 'squadrant dispatch <project> "<task>"' instead.`
14620
15008
  ));
14621
15009
  await dispatchAction(toProject, task, opts);
@@ -14626,8 +15014,8 @@ var groupCommand = new Command25("group").description("Cross-project intra-group
14626
15014
  init_dist();
14627
15015
  init_dist2();
14628
15016
  import { join as join26, dirname as dirname7 } from "path";
14629
- import { Command as Command26 } from "commander";
14630
- import chalk26 from "chalk";
15017
+ import { Command as Command27 } from "commander";
15018
+ import chalk27 from "chalk";
14631
15019
  init_require_daemon();
14632
15020
  async function runPing(project, message) {
14633
15021
  const config = loadConfig();
@@ -14643,20 +15031,20 @@ async function runPing(project, message) {
14643
15031
  source: "cli"
14644
15032
  });
14645
15033
  }
14646
- var pingCommand = new Command26("ping").description("Fire-and-forget: deliver a message into a registered project's captain pane (no tracked task, no report-back)").argument("<project>", "Target project name (must be registered)").argument("<message>", "Message to deliver").action(async (project, message) => {
15034
+ var pingCommand = new Command27("ping").description("Fire-and-forget: deliver a message into a registered project's captain pane (no tracked task, no report-back)").argument("<project>", "Target project name (must be registered)").argument("<message>", "Message to deliver").action(async (project, message) => {
14647
15035
  try {
14648
15036
  await runPing(project, message);
14649
- console.log(chalk26.green(`\u2714 Pinged '${project}'`));
15037
+ console.log(chalk27.green(`\u2714 Pinged '${project}'`));
14650
15038
  } catch (err) {
14651
- console.error(chalk26.red(err.message));
15039
+ console.error(chalk27.red(err.message));
14652
15040
  process.exit(1);
14653
15041
  }
14654
15042
  });
14655
15043
 
14656
15044
  // packages/cli/src/commands/cmux.ts
14657
15045
  init_dist();
14658
- import { Command as Command27 } from "commander";
14659
- import chalk27 from "chalk";
15046
+ import { Command as Command28 } from "commander";
15047
+ import chalk28 from "chalk";
14660
15048
  async function runCmuxAutoconfig(opts) {
14661
15049
  const { json, stdout, stderr } = opts;
14662
15050
  let r;
@@ -14675,19 +15063,19 @@ async function runCmuxAutoconfig(opts) {
14675
15063
  stdout.write(`wrote cmux automation config \u2192 ${r.configPath}
14676
15064
  `);
14677
15065
  } else {
14678
- stdout.write(chalk27.dim(`cmux automation config already in place (${r.configPath})
15066
+ stdout.write(chalk28.dim(`cmux automation config already in place (${r.configPath})
14679
15067
  `));
14680
15068
  }
14681
15069
  if (r.verdict === "reachable") {
14682
- stdout.write(chalk27.green("\u2714 daemon-direct delivery is reachable \u2014 cmux control socket accepts the daemon\n"));
15070
+ stdout.write(chalk28.green("\u2714 daemon-direct delivery is reachable \u2014 cmux control socket accepts the daemon\n"));
14683
15071
  return 0;
14684
15072
  }
14685
15073
  if (r.needsRestart) {
14686
15074
  stdout.write(
14687
- chalk27.yellow("\u26A0 cmux is still on the old socket mode \u2014 restart cmux to enable daemon-direct delivery.\n")
15075
+ chalk28.yellow("\u26A0 cmux is still on the old socket mode \u2014 restart cmux to enable daemon-direct delivery.\n")
14688
15076
  );
14689
15077
  if (r.promptedThisRun) {
14690
- stdout.write(chalk27.dim(" (one-time prompt \u2014 you won't be nagged again)\n"));
15078
+ stdout.write(chalk28.dim(" (one-time prompt \u2014 you won't be nagged again)\n"));
14691
15079
  }
14692
15080
  return 2;
14693
15081
  }
@@ -14696,8 +15084,8 @@ async function runCmuxAutoconfig(opts) {
14696
15084
  );
14697
15085
  return 1;
14698
15086
  }
14699
- var cmuxCommand = new Command27("cmux").description("cmux integration helpers").addCommand(
14700
- new Command27("autoconfig").description(
15087
+ var cmuxCommand = new Command28("cmux").description("cmux integration helpers").addCommand(
15088
+ new Command28("autoconfig").description(
14701
15089
  "Write the cmux automation socket config and probe whether daemon-direct\ndelivery is reachable. Idempotent; prompts once if a cmux restart is needed."
14702
15090
  ).option("--json", "output machine-readable JSON (exit 0=reachable, 1=unknown, 2=restart-needed)").action(async (opts) => {
14703
15091
  const code = await runCmuxAutoconfig({
@@ -14714,8 +15102,8 @@ init_dist();
14714
15102
  init_dist2();
14715
15103
  import fs28 from "fs";
14716
15104
  import path29 from "path";
14717
- import { Command as Command28 } from "commander";
14718
- import chalk28 from "chalk";
15105
+ import { Command as Command29 } from "commander";
15106
+ import chalk29 from "chalk";
14719
15107
  var VALID_EFFORTS = ["max", "balance", "low"];
14720
15108
  var EFFORT_MEANING = {
14721
15109
  max: "tokens are plentiful \u2014 bias crew spawns toward claude/opus",
@@ -14780,29 +15168,29 @@ async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(
14780
15168
  }
14781
15169
  }
14782
15170
  }
14783
- var effortCommand = new Command28("effort").description("Get or set the crew tokenomics dial (max | balance | low) \u2014 global by default, or per-project with --project").argument("[value]", "effort level to set: max | balance | low").option("--project <name>", "target a specific project (get: show its resolved effort; set: write a per-project override)").action(async (value, options) => {
15171
+ var effortCommand = new Command29("effort").description("Get or set the crew tokenomics dial (max | balance | low) \u2014 global by default, or per-project with --project").argument("[value]", "effort level to set: max | balance | low").option("--project <name>", "target a specific project (get: show its resolved effort; set: write a per-project override)").action(async (value, options) => {
14784
15172
  if (value === void 0) {
14785
15173
  let result;
14786
15174
  try {
14787
15175
  result = runEffortGet(void 0, options.project);
14788
15176
  } catch (err) {
14789
- console.error(chalk28.red(err.message));
15177
+ console.error(chalk29.red(err.message));
14790
15178
  process.exit(1);
14791
15179
  }
14792
15180
  const label = options.project ? `${options.project} project` : "global";
14793
- console.log(chalk28.bold(`Current effort (${label}):`), chalk28.cyan(result.effort));
14794
- console.log(chalk28.dim(EFFORT_MEANING[result.effort]));
15181
+ console.log(chalk29.bold(`Current effort (${label}):`), chalk29.cyan(result.effort));
15182
+ console.log(chalk29.dim(EFFORT_MEANING[result.effort]));
14795
15183
  return;
14796
15184
  }
14797
15185
  try {
14798
15186
  runEffortSet(value, void 0, options.project);
14799
15187
  } catch (err) {
14800
- console.error(chalk28.red(err.message));
15188
+ console.error(chalk29.red(err.message));
14801
15189
  process.exit(1);
14802
15190
  }
14803
15191
  const effort = value;
14804
- console.log(chalk28.green(`\u2714 effort \u2192 ${effort} (${effortScopeLabel(options.project)})`));
14805
- console.log(chalk28.dim(EFFORT_MEANING[effort]));
15192
+ console.log(chalk29.green(`\u2714 effort \u2192 ${effort} (${effortScopeLabel(options.project)})`));
15193
+ console.log(chalk29.dim(EFFORT_MEANING[effort]));
14806
15194
  try {
14807
15195
  const { createCmuxDriver: createCmuxDriver2, RuntimeRegistry: RuntimeRegistry2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports3));
14808
15196
  const config = loadConfig();
@@ -14812,7 +15200,7 @@ var effortCommand = new Command28("effort").description("Get or set the crew tok
14812
15200
  const append = (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
14813
15201
  await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append, options.project);
14814
15202
  } catch {
14815
- console.log(chalk28.dim("(no running captain detected \u2014 change applies on next launch)"));
15203
+ console.log(chalk29.dim("(no running captain detected \u2014 change applies on next launch)"));
14816
15204
  }
14817
15205
  });
14818
15206
 
@@ -14821,8 +15209,8 @@ init_dist();
14821
15209
  init_dist2();
14822
15210
  import { join as join27, dirname as dirname8 } from "path";
14823
15211
  import { emitKeypressEvents } from "readline";
14824
- import { Command as Command29 } from "commander";
14825
- import chalk29 from "chalk";
15212
+ import { Command as Command30 } from "commander";
15213
+ import chalk30 from "chalk";
14826
15214
  function defaultStateRoot() {
14827
15215
  return join27(dirname8(DEFAULT_CONFIG_PATH), "state");
14828
15216
  }
@@ -14869,11 +15257,11 @@ async function questionYesNo(prompt) {
14869
15257
  });
14870
15258
  });
14871
15259
  }
14872
- var telegramCommand = new Command29("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
15260
+ var telegramCommand = new Command30("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
14873
15261
  telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
14874
15262
  const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
14875
- console.log(`token: ${tokenSet ? chalk29.green("set") : chalk29.yellow("unset")}`);
14876
- console.log(`supergroup: ${supergroupId ?? chalk29.yellow("unset")}`);
15263
+ console.log(`token: ${tokenSet ? chalk30.green("set") : chalk30.yellow("unset")}`);
15264
+ console.log(`supergroup: ${supergroupId ?? chalk30.yellow("unset")}`);
14877
15265
  if (links.length === 0) {
14878
15266
  console.log("no projects linked");
14879
15267
  return;
@@ -14883,32 +15271,32 @@ telegramCommand.command("status").description("Show Telegram config and linked p
14883
15271
  telegramCommand.command("link").argument("<project>", "project to bind to a Telegram topic").description("Create (or reuse) a forum topic for a project and bind it").action(async (project) => {
14884
15272
  const cfg = loadConfig().telegram;
14885
15273
  if (!cfg) {
14886
- console.error(chalk29.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
15274
+ console.error(chalk30.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
14887
15275
  process.exit(1);
14888
15276
  }
14889
15277
  const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
14890
15278
  if (!token) {
14891
- console.error(chalk29.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
15279
+ console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
14892
15280
  process.exit(1);
14893
15281
  }
14894
15282
  const client = createTelegramClient({ token });
14895
15283
  const { topicId, created } = await runTelegramLink({ project, cfg, client, stateRoot: defaultStateRoot() });
14896
- console.log(chalk29.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
15284
+ console.log(chalk30.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
14897
15285
  });
14898
15286
  telegramCommand.command("setup").description("Interactive wizard \u2014 bot token, validate, auto-detect supergroup, write config").option("--reset-token", "force re-entry of the bot token even if one already exists").option("--redetect", "force group re-detection even when a supergroup is already configured").option("--user-id <id>", "allowlist user-id \u2014 enables remote control on a re-run without getUpdates detection", (v) => parseInt(v, 10)).action(async (opts) => {
14899
15287
  if (!process.stdin.isTTY) {
14900
- console.error(chalk29.red("setup requires a TTY \u2014 pipe input is not supported"));
15288
+ console.error(chalk30.red("setup requires a TTY \u2014 pipe input is not supported"));
14901
15289
  process.exit(1);
14902
15290
  }
14903
15291
  console.log();
14904
- console.log(chalk29.bold("Telegram setup") + " \u2014 connect squadrant to a Telegram bot for notifications + remote control");
15292
+ console.log(chalk30.bold("Telegram setup") + " \u2014 connect squadrant to a Telegram bot for notifications + remote control");
14905
15293
  console.log();
14906
15294
  console.log("Before you start you need:");
14907
15295
  console.log(" 1. A bot token from @BotFather (send /newbot)");
14908
15296
  console.log(" 2. A forum supergroup with the bot added as an admin (Topics enabled)");
14909
15297
  console.log(" 3. Bot privacy mode set to OFF (@BotFather \u2192 /setprivacy \u2192 Disable)");
14910
15298
  console.log();
14911
- console.log(chalk29.bold("Step 1/3 \u2014 Bot token"));
15299
+ console.log(chalk30.bold("Step 1/3 \u2014 Bot token"));
14912
15300
  const existingCfg = loadConfig().telegram;
14913
15301
  const existingToken = existingCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
14914
15302
  const decision = resolveSetupToken(existingToken, { resetToken: opts.resetToken ?? false });
@@ -14920,67 +15308,67 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
14920
15308
  try {
14921
15309
  botUser = await client.getMe();
14922
15310
  token = existingToken;
14923
- console.log(chalk29.green(`Using existing bot token (@${botUser.username})`));
15311
+ console.log(chalk30.green(`Using existing bot token (@${botUser.username})`));
14924
15312
  console.log();
14925
15313
  } catch {
14926
- console.log(chalk29.yellow("Existing token is invalid \u2014 please enter a new one."));
15314
+ console.log(chalk30.yellow("Existing token is invalid \u2014 please enter a new one."));
14927
15315
  console.log("Paste your bot token then press Enter (input is hidden):");
14928
15316
  token = await questionMasked();
14929
15317
  if (!token) {
14930
- console.error(chalk29.red("token required"));
15318
+ console.error(chalk30.red("token required"));
14931
15319
  process.exit(1);
14932
15320
  }
14933
15321
  client = createTelegramClient({ token });
14934
15322
  try {
14935
15323
  botUser = await client.getMe();
14936
15324
  } catch (e) {
14937
- console.error(chalk29.red(`token rejected: ${e.message}`));
15325
+ console.error(chalk30.red(`token rejected: ${e.message}`));
14938
15326
  process.exit(1);
14939
15327
  }
14940
- console.log(chalk29.green(`Connected as @${botUser.username}`));
15328
+ console.log(chalk30.green(`Connected as @${botUser.username}`));
14941
15329
  console.log();
14942
15330
  }
14943
15331
  } else {
14944
15332
  console.log("Paste your bot token then press Enter (input is hidden):");
14945
15333
  token = await questionMasked();
14946
15334
  if (!token) {
14947
- console.error(chalk29.red("token required"));
15335
+ console.error(chalk30.red("token required"));
14948
15336
  process.exit(1);
14949
15337
  }
14950
15338
  client = createTelegramClient({ token });
14951
15339
  try {
14952
15340
  botUser = await client.getMe();
14953
15341
  } catch (e) {
14954
- console.error(chalk29.red(`token rejected: ${e.message}`));
15342
+ console.error(chalk30.red(`token rejected: ${e.message}`));
14955
15343
  process.exit(1);
14956
15344
  }
14957
- console.log(chalk29.green(`Connected as @${botUser.username}`));
15345
+ console.log(chalk30.green(`Connected as @${botUser.username}`));
14958
15346
  console.log();
14959
15347
  }
14960
- console.log(chalk29.bold("Step 2/3 \u2014 Supergroup"));
15348
+ console.log(chalk30.bold("Step 2/3 \u2014 Supergroup"));
14961
15349
  const groupDecision = resolveSetupGroup(existingCfg?.supergroupId, { redetect: opts.redetect ?? false });
14962
15350
  let supergroupId;
14963
15351
  let detectedUserId;
14964
15352
  if (groupDecision === "reuse") {
14965
15353
  supergroupId = existingCfg.supergroupId;
14966
- console.log(chalk29.green(`Using existing group: ${supergroupId}`));
15354
+ console.log(chalk30.green(`Using existing group: ${supergroupId}`));
14967
15355
  console.log();
14968
15356
  } else {
14969
15357
  console.log("Add the bot to your forum supergroup, then send any message in it.");
14970
- console.log(chalk29.dim("Waiting for a message (up to 60s)\u2026"));
15358
+ console.log(chalk30.dim("Waiting for a message (up to 60s)\u2026"));
14971
15359
  try {
14972
15360
  ({ supergroupId, userId: detectedUserId } = await detectGroupAndUser(client, { timeoutMs: 6e4 }));
14973
15361
  } catch {
14974
- console.error(chalk29.red("Timed out \u2014 no supergroup message received within 60s."));
14975
- console.error(chalk29.yellow("Check: bot is an admin in the group \xB7 privacy mode is OFF \xB7 Topics enabled"));
15362
+ console.error(chalk30.red("Timed out \u2014 no supergroup message received within 60s."));
15363
+ console.error(chalk30.yellow("Check: bot is an admin in the group \xB7 privacy mode is OFF \xB7 Topics enabled"));
14976
15364
  process.exit(1);
14977
15365
  }
14978
- console.log(chalk29.green(`Found group: ${supergroupId}`));
15366
+ console.log(chalk30.green(`Found group: ${supergroupId}`));
14979
15367
  console.log();
14980
15368
  }
14981
- console.log(chalk29.bold("Step 3/3 \u2014 Remote control + Save"));
14982
- console.log(chalk29.dim("Remote control enables auto-launching captains and the General command channel"));
14983
- console.log(chalk29.dim("from your phone \u2014 gated to your Telegram user-id only (fail-closed)."));
15369
+ console.log(chalk30.bold("Step 3/3 \u2014 Remote control + Save"));
15370
+ console.log(chalk30.dim("Remote control enables auto-launching captains and the General command channel"));
15371
+ console.log(chalk30.dim("from your phone \u2014 gated to your Telegram user-id only (fail-closed)."));
14984
15372
  const finalUserId = resolveSetupUserId(opts.userId, detectedUserId, defaultStateRoot());
14985
15373
  let users;
14986
15374
  let remoteControl;
@@ -14994,32 +15382,32 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
14994
15382
  remoteControl = true;
14995
15383
  }
14996
15384
  } else if (groupDecision === "detect") {
14997
- console.log(chalk29.yellow("Could not read your user-id from that message \u2014 skipping remote control."));
14998
- console.log(chalk29.yellow("Re-run with --user-id <id> to enable, or edit telegram.users in config manually."));
15385
+ console.log(chalk30.yellow("Could not read your user-id from that message \u2014 skipping remote control."));
15386
+ console.log(chalk30.yellow("Re-run with --user-id <id> to enable, or edit telegram.users in config manually."));
14999
15387
  printedRemoteControlState = true;
15000
15388
  } else {
15001
15389
  const existingUsers = existingCfg?.users;
15002
15390
  if (existingUsers && existingUsers.length > 0) {
15003
- console.log(chalk29.dim(`Remote control: already configured (user-id ${existingUsers[0]}). Use --user-id to update.`));
15391
+ console.log(chalk30.dim(`Remote control: already configured (user-id ${existingUsers[0]}). Use --user-id to update.`));
15004
15392
  } else {
15005
- console.log(chalk29.dim("Remote control: off. Re-run with --user-id <id> to enable."));
15393
+ console.log(chalk30.dim("Remote control: off. Re-run with --user-id <id> to enable."));
15006
15394
  }
15007
15395
  printedRemoteControlState = true;
15008
15396
  }
15009
15397
  writeTelegramConfig(DEFAULT_CONFIG_PATH, { token, supergroupId, users, remoteControl });
15010
- console.log(chalk29.green(`Wrote telegram config \u2014 token: ${maskToken(token)} group: ${supergroupId}`));
15398
+ console.log(chalk30.green(`Wrote telegram config \u2014 token: ${maskToken(token)} group: ${supergroupId}`));
15011
15399
  if (!printedRemoteControlState) {
15012
15400
  if (remoteControl) {
15013
- console.log(chalk29.green(`Remote control: ON (allowlisted user-id ${users[0]})`));
15401
+ console.log(chalk30.green(`Remote control: ON (allowlisted user-id ${users[0]})`));
15014
15402
  } else {
15015
- console.log(chalk29.dim("Remote control: off (default). Re-run with --user-id <id> to enable."));
15403
+ console.log(chalk30.dim("Remote control: off (default). Re-run with --user-id <id> to enable."));
15016
15404
  }
15017
15405
  }
15018
15406
  try {
15019
15407
  await runRegisterCommands({ client });
15020
- console.log(chalk29.dim("Registered the /command menu."));
15408
+ console.log(chalk30.dim("Registered the /command menu."));
15021
15409
  } catch (e) {
15022
- console.log(chalk29.yellow(`command-menu registration skipped: ${e.message}`));
15410
+ console.log(chalk30.yellow(`command-menu registration skipped: ${e.message}`));
15023
15411
  }
15024
15412
  const topics = loadState(defaultStateRoot()).topics;
15025
15413
  const topicEntries = Object.entries(topics);
@@ -15028,28 +15416,28 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
15028
15416
  const project = key.slice(0, key.indexOf("::"));
15029
15417
  return `${project}\u2192${id}`;
15030
15418
  }).join(", ");
15031
- console.log(chalk29.dim(`Existing topics: ${summary} (already created \u2014 not recreated)`));
15419
+ console.log(chalk30.dim(`Existing topics: ${summary} (already created \u2014 not recreated)`));
15032
15420
  } else {
15033
- console.log(chalk29.dim("No project topics yet \u2014 they're created on first delivery or via: squadrant telegram link <project>"));
15421
+ console.log(chalk30.dim("No project topics yet \u2014 they're created on first delivery or via: squadrant telegram link <project>"));
15034
15422
  }
15035
15423
  runTelegramPostSetup({});
15036
15424
  console.log();
15037
- console.log(`Next: ${chalk29.cyan("squadrant telegram link <project>")}`);
15425
+ console.log(`Next: ${chalk30.cyan("squadrant telegram link <project>")}`);
15038
15426
  });
15039
15427
  telegramCommand.command("register-commands").description("Register (or re-register) the bot's / command menu with Telegram").action(async () => {
15040
15428
  const cfg = loadConfig().telegram;
15041
15429
  if (!cfg) {
15042
- console.error(chalk29.red("telegram config absent \u2014 run: squadrant telegram setup"));
15430
+ console.error(chalk30.red("telegram config absent \u2014 run: squadrant telegram setup"));
15043
15431
  process.exit(1);
15044
15432
  }
15045
15433
  const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
15046
15434
  if (!token) {
15047
- console.error(chalk29.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
15435
+ console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
15048
15436
  process.exit(1);
15049
15437
  }
15050
15438
  const client = createTelegramClient({ token });
15051
15439
  await runRegisterCommands({ client });
15052
- console.log(chalk29.green(`registered ${BOT_COMMANDS.length} bot commands`));
15440
+ console.log(chalk30.green(`registered ${BOT_COMMANDS.length} bot commands`));
15053
15441
  });
15054
15442
  telegramCommand.command("notify").argument("[project]", "project to toggle").argument("[state]", "on | off | crew | cap").argument("[value]", "tier for crew (all|alert_only|done_only|none) or on|off for cap").option("--status", "list notification state for all projects").description("Live on|off (state), or crew <tier> / cap <on|off> preference (per-project config)").action(async (project, state, value, opts) => {
15055
15443
  const stateRoot = defaultStateRoot();
@@ -15060,7 +15448,7 @@ telegramCommand.command("notify").argument("[project]", "project to toggle").arg
15060
15448
  return;
15061
15449
  }
15062
15450
  for (const r of rows) {
15063
- console.log(` ${r.project}: ${r.active ? chalk29.green("on") : chalk29.dim("off (muted)")}`);
15451
+ console.log(` ${r.project}: ${r.active ? chalk30.green("on") : chalk30.dim("off (muted)")}`);
15064
15452
  }
15065
15453
  return;
15066
15454
  }
@@ -15069,53 +15457,53 @@ telegramCommand.command("notify").argument("[project]", "project to toggle").arg
15069
15457
  const token = tgCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
15070
15458
  if (state === "crew" || state === "cap") {
15071
15459
  if (value === void 0) {
15072
- console.error(chalk29.red(`usage: squadrant telegram notify <project> ${state} <value>`));
15460
+ console.error(chalk30.red(`usage: squadrant telegram notify <project> ${state} <value>`));
15073
15461
  process.exit(1);
15074
15462
  }
15075
15463
  const resolved2 = resolveNotify(globalNotify, loadProjectOverride(project));
15076
15464
  const before2 = { ...resolved2, active: isNotifyActive(stateRoot, project) };
15077
15465
  const res = runTelegramNotifyPref({ project, dimension: state, value });
15078
15466
  if (!res.ok) {
15079
- console.error(chalk29.red(res.message));
15467
+ console.error(chalk30.red(res.message));
15080
15468
  process.exit(1);
15081
15469
  }
15082
- console.log(chalk29.green(`${project} ${state} = ${value}`));
15470
+ console.log(chalk30.green(`${project} ${state} = ${value}`));
15083
15471
  const after2 = state === "crew" ? { ...before2, crew: value } : { ...before2, cap: value === "on" };
15084
15472
  if (tgCfg && token) {
15085
15473
  const client = createTelegramClient({ token });
15086
15474
  const sent = await runNotifyConfirmation({ project, before: before2, after: after2, cfg: tgCfg, client, stateRoot });
15087
- if (sent) console.log(chalk29.dim(`\u2192 notified ${project} topic`));
15475
+ if (sent) console.log(chalk30.dim(`\u2192 notified ${project} topic`));
15088
15476
  }
15089
15477
  return;
15090
15478
  }
15091
15479
  if (state !== "on" && state !== "off") {
15092
- console.error(chalk29.red("usage: squadrant telegram notify <project> <on|off|crew <tier>|cap <on|off>>"));
15480
+ console.error(chalk30.red("usage: squadrant telegram notify <project> <on|off|crew <tier>|cap <on|off>>"));
15093
15481
  process.exit(1);
15094
15482
  }
15095
15483
  const resolved = resolveNotify(globalNotify, loadProjectOverride(project));
15096
15484
  const before = { ...resolved, active: isNotifyActive(stateRoot, project) };
15097
15485
  const after = { ...before, active: state === "on" };
15098
15486
  runTelegramNotifySet({ project, active: state === "on", stateRoot });
15099
- console.log(chalk29.green(`${project} notifications ${state === "on" ? "ON" : "OFF"}`));
15487
+ console.log(chalk30.green(`${project} notifications ${state === "on" ? "ON" : "OFF"}`));
15100
15488
  if (tgCfg && token) {
15101
15489
  const client = createTelegramClient({ token });
15102
15490
  const sent = await runNotifyConfirmation({ project, before, after, cfg: tgCfg, client, stateRoot });
15103
- if (sent) console.log(chalk29.dim(`\u2192 notified ${project} topic`));
15491
+ if (sent) console.log(chalk30.dim(`\u2192 notified ${project} topic`));
15104
15492
  }
15105
15493
  });
15106
15494
  telegramCommand.command("send").argument("<project>", "project whose topic receives the message").argument("[message...]", "message text (omit to read from stdin)").description("Send a message to a project's linked Telegram topic").action(async (project, messageParts) => {
15107
15495
  const cfg = loadConfig().telegram;
15108
15496
  if (!cfg) {
15109
- console.error(chalk29.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
15497
+ console.error(chalk30.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
15110
15498
  process.exit(1);
15111
15499
  }
15112
15500
  const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
15113
15501
  if (!token) {
15114
- console.error(chalk29.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
15502
+ console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
15115
15503
  process.exit(1);
15116
15504
  }
15117
15505
  if (!capAllowed(project, cfg.notify)) {
15118
- console.log(chalk29.dim(`${project}: captain messages muted (cap=off) \u2014 not sent`));
15506
+ console.log(chalk30.dim(`${project}: captain messages muted (cap=off) \u2014 not sent`));
15119
15507
  return;
15120
15508
  }
15121
15509
  let message;
@@ -15128,19 +15516,19 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
15128
15516
  for await (const line of rl) lines.push(line);
15129
15517
  message = lines.join("\n").trimEnd();
15130
15518
  if (!message) {
15131
- console.error(chalk29.red("no message provided (stdin was empty)"));
15519
+ console.error(chalk30.red("no message provided (stdin was empty)"));
15132
15520
  process.exit(1);
15133
15521
  }
15134
15522
  } else {
15135
- console.error(chalk29.red("message required \u2014 pass as argument or pipe via stdin"));
15523
+ console.error(chalk30.red("message required \u2014 pass as argument or pipe via stdin"));
15136
15524
  process.exit(1);
15137
15525
  }
15138
15526
  const client = createTelegramClient({ token });
15139
15527
  try {
15140
15528
  const { chatId, topicId } = await runTelegramSend({ project, message, cfg, client, stateRoot: defaultStateRoot() });
15141
- console.log(chalk29.green(`sent to group ${chatId} topic ${topicId}`));
15529
+ console.log(chalk30.green(`sent to group ${chatId} topic ${topicId}`));
15142
15530
  } catch (e) {
15143
- console.error(chalk29.red(e.message));
15531
+ console.error(chalk30.red(e.message));
15144
15532
  process.exit(1);
15145
15533
  }
15146
15534
  });
@@ -15148,7 +15536,7 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
15148
15536
  // packages/cli/src/commands/hooks.ts
15149
15537
  init_dist2();
15150
15538
  init_dist4();
15151
- import { Command as Command30 } from "commander";
15539
+ import { Command as Command31 } from "commander";
15152
15540
  import { join as join28 } from "path";
15153
15541
  import { homedir as homedir20 } from "os";
15154
15542
  var SOCK4 = join28(homedir20(), ".config", "squadrant", "squadrant.sock");
@@ -15175,7 +15563,7 @@ function mapHookSub(sub, payload, taskId) {
15175
15563
  }
15176
15564
  }
15177
15565
  function hooksCommand() {
15178
- const hooks = new Command30("hooks").description("(internal) receive lifecycle hook events from agent processes");
15566
+ const hooks = new Command31("hooks").description("(internal) receive lifecycle hook events from agent processes");
15179
15567
  hooks.command("claude <sub>", { hidden: true }).description("internal: bridge a NativeHookSource claude hook to squadrantd").action(async (sub) => {
15180
15568
  const taskId = process.env.SQUADRANT_CREW_TASK_ID;
15181
15569
  const project = process.env.SQUADRANT_CREW_PROJECT;
@@ -15246,7 +15634,7 @@ if (process.argv[2] !== "config") {
15246
15634
  if (!process.env.SQUADRANT_DAEMON_SKIP) {
15247
15635
  ensureDaemon();
15248
15636
  }
15249
- var program = new Command31();
15637
+ var program = new Command32();
15250
15638
  program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
15251
15639
  program.addCommand(doctorCommand);
15252
15640
  program.addCommand(initCommand);
@@ -15255,6 +15643,7 @@ program.addCommand(statusCommand);
15255
15643
  addControlPlaneCrewCommands(crewCommand);
15256
15644
  program.addCommand(crewCommand);
15257
15645
  program.addCommand(sideCommand);
15646
+ program.addCommand(diffCommand);
15258
15647
  program.addCommand(commandCommand);
15259
15648
  program.addCommand(dashboardCommand);
15260
15649
  program.addCommand(launchCommand);