squadrant 0.17.1 → 0.18.0

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
@@ -773,6 +773,12 @@ function ensureSpotlightExcluded(repoRoot, worktreeDir) {
773
773
  }
774
774
  }
775
775
  function resolveWorktreeBase(repoRoot, fallback = "develop") {
776
+ try {
777
+ const head = execFileSync2("git", ["-C", repoRoot, "rev-parse", "--abbrev-ref", "HEAD"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
778
+ if (head && head !== "HEAD")
779
+ return head;
780
+ } catch {
781
+ }
776
782
  try {
777
783
  const ref = execFileSync2("git", ["-C", repoRoot, "symbolic-ref", "refs/remotes/origin/HEAD"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
778
784
  const m = ref.match(/^refs\/remotes\/origin\/(.+)$/);
@@ -839,13 +845,17 @@ function installWorktreeDependencies(wt) {
839
845
  `);
840
846
  }
841
847
  }
842
- function removeWorktree(repoRoot, wtPath) {
848
+ function worktreeDirtyFiles(wtPath) {
843
849
  try {
844
- execFileSync2("git", ["-C", repoRoot, "worktree", "remove", wtPath], { stdio: "pipe" });
850
+ return execFileSync2("git", ["-C", wtPath, "status", "--porcelain", "--untracked-files=all"], { stdio: ["ignore", "pipe", "ignore"] }).toString().split("\n").map((l) => l.slice(3).trim()).filter(Boolean);
845
851
  } catch {
846
- execFileSync2("git", ["-C", repoRoot, "worktree", "remove", "--force", wtPath], { stdio: "pipe" });
852
+ return [];
847
853
  }
848
854
  }
855
+ function removeWorktree(repoRoot, wtPath, opts) {
856
+ const args = ["-C", repoRoot, "worktree", "remove", ...opts?.force ? ["--force"] : [], wtPath];
857
+ execFileSync2("git", args, { stdio: "pipe" });
858
+ }
849
859
  var init_git_worktree = __esm({
850
860
  "packages/shared/dist/lib/git-worktree.js"() {
851
861
  }
@@ -977,7 +987,7 @@ function ensureRuntimeSynced(opts) {
977
987
  var CREW_SKILLS, MANAGED_TARGETS;
978
988
  var init_runtime_sync = __esm({
979
989
  "packages/shared/dist/lib/runtime-sync.js"() {
980
- CREW_SKILLS = ["karpathy-principles"];
990
+ CREW_SKILLS = ["karpathy-principles", "takeover", "handback"];
981
991
  MANAGED_TARGETS = [
982
992
  { name: "plugin", srcRel: "plugin", mode: "tree" },
983
993
  { name: "plugin-crew", srcRel: "plugin", mode: "subset", skills: CREW_SKILLS },
@@ -1302,6 +1312,7 @@ __export(dist_exports, {
1302
1312
  saveConfig: () => saveConfig,
1303
1313
  saveProjectOverride: () => saveProjectOverride,
1304
1314
  withStamp: () => withStamp,
1315
+ worktreeDirtyFiles: () => worktreeDirtyFiles,
1305
1316
  worktreePath: () => worktreePath,
1306
1317
  writeUpdateCheckState: () => writeUpdateCheckState
1307
1318
  });
@@ -1361,7 +1372,21 @@ function nextPendingMonitor(current, ev, now) {
1361
1372
  }
1362
1373
  function reduce(rec, ev, now) {
1363
1374
  if (ev.type === "task.reopened") {
1364
- return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type };
1375
+ return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type, workingStretchStartedAt: now };
1376
+ }
1377
+ if (ev.type === "crew.takeover.started") {
1378
+ if (rec.operatorHold)
1379
+ return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
1380
+ return {
1381
+ ...rec,
1382
+ operatorHold: { since: now, ...ev.note !== void 0 ? { note: ev.note } : {} },
1383
+ lastHeartbeat: now,
1384
+ lastEvent: ev.type
1385
+ };
1386
+ }
1387
+ if (ev.type === "crew.takeover.ended") {
1388
+ const { operatorHold: _dropped, ...rest } = rec;
1389
+ return { ...rest, lastHeartbeat: now, lastEvent: ev.type };
1365
1390
  }
1366
1391
  if (TERMINAL_STATES.has(rec.state))
1367
1392
  return rec;
@@ -1377,8 +1402,9 @@ function reduce(rec, ev, now) {
1377
1402
  // resuming after a blocked→reply clears the question
1378
1403
  pendingTool: void 0,
1379
1404
  // #354: a new turn closes any prior tool window
1380
- pendingMonitor: void 0
1405
+ pendingMonitor: void 0,
1381
1406
  // #594a: same reset — a new turn moots any prior watch
1407
+ workingStretchStartedAt: now
1382
1408
  };
1383
1409
  case "task.progress": {
1384
1410
  const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
@@ -1549,6 +1575,8 @@ function firePush(deps, project, prev, next, event, lastCaptainTurnAt) {
1549
1575
  return;
1550
1576
  if (!ATTENTION_STATES.has(next.state))
1551
1577
  return;
1578
+ if (next.operatorHold)
1579
+ return;
1552
1580
  if (next.state === "awaiting-input" && lastCaptainTurnAt != null && deps.now() - lastCaptainTurnAt <= IDLE_DEBOUNCE_MS) {
1553
1581
  return;
1554
1582
  }
@@ -1757,9 +1785,32 @@ function createDaemon(deps) {
1757
1785
  store.delete(r.project, r.id);
1758
1786
  continue;
1759
1787
  }
1788
+ if (r.operatorHold) {
1789
+ const threshold = (deps.takeoverNudgeHours ?? 6) * 36e5;
1790
+ const holdAge = t - r.operatorHold.since;
1791
+ if (holdAge > threshold) {
1792
+ const timeSinceLastNudge = t - (r.operatorHold.lastNudgeAt ?? 0);
1793
+ if (timeSinceLastNudge > threshold) {
1794
+ const hrs = Math.round(holdAge / 36e5);
1795
+ const tag = crewTag(r);
1796
+ const message = `CREW HELD-LONG ${tag} \u2014 held ${hrs}h. Ask the operator whether it is still in use. Do not release it yourself.`;
1797
+ store.put({ ...r, operatorHold: { ...r.operatorHold, lastNudgeAt: t } });
1798
+ if (deps.notify) {
1799
+ try {
1800
+ const p = deps.notify({ project: r.project, message, record: r, event: { type: "task.progress", id: r.id } });
1801
+ if (p && typeof p.catch === "function")
1802
+ p.catch(() => {
1803
+ });
1804
+ } catch {
1805
+ }
1806
+ }
1807
+ }
1808
+ }
1809
+ }
1760
1810
  if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {
1761
1811
  const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
1762
- if (t - r.createdAt > ceiling) {
1812
+ const refTime = r.workingStretchStartedAt ?? r.createdAt;
1813
+ if (t - refTime > ceiling) {
1763
1814
  const prevState = r.state;
1764
1815
  const tag = crewTag(r);
1765
1816
  const hrs = Math.round(ceiling / 36e5);
@@ -1913,8 +1964,11 @@ var init_reduce = __esm({
1913
1964
  "task.reconcile-failed",
1914
1965
  "task.cancelled",
1915
1966
  "task.session.ended",
1916
- "task.first-turn.confirmed"
1967
+ "task.first-turn.confirmed",
1917
1968
  // #466: delivery confirmation
1969
+ "crew.takeover.started",
1970
+ "crew.takeover.ended"
1971
+ // #649: operator takeover
1918
1972
  ]);
1919
1973
  }
1920
1974
  });
@@ -3198,7 +3252,9 @@ function buildContext(opts) {
3198
3252
  const sockPath = opts.sockPath ?? join9(homedir6(), ".config", "squadrant", "squadrant.sock");
3199
3253
  const store = createStore(stateRoot);
3200
3254
  const bootedAt = Date.now();
3201
- const taskTimeoutMs = loadConfig().defaults.taskTimeoutMs;
3255
+ const config = loadConfig();
3256
+ const taskTimeoutMs = config.defaults.taskTimeoutMs;
3257
+ const takeoverNudgeHours = config.defaults.takeoverNudgeHours;
3202
3258
  const isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
3203
3259
  const spawn2 = opts.spawn ?? realSpawn;
3204
3260
  const resultsDir = join9(stateRoot, "_results");
@@ -3218,6 +3274,7 @@ function buildContext(opts) {
3218
3274
  bootedAt,
3219
3275
  lastSweepAt: { value: null },
3220
3276
  taskTimeoutMs,
3277
+ takeoverNudgeHours,
3221
3278
  isPidAlive,
3222
3279
  spawn: spawn2,
3223
3280
  resultsDir,
@@ -4068,6 +4125,7 @@ function startDaemon(ctx, opts, pkgVersion) {
4068
4125
  isPidAlive,
4069
4126
  notify,
4070
4127
  taskTimeoutMs,
4128
+ takeoverNudgeHours: ctx.takeoverNudgeHours,
4071
4129
  isSurfaceAlive: surfaceProbe,
4072
4130
  resendFirstTurn: ctx.resendFirstTurn,
4073
4131
  launchHeadless: opts.launchHeadless,
@@ -6008,12 +6066,17 @@ async function runCrewSpawn(input, config, deps) {
6008
6066
  }
6009
6067
  }
6010
6068
  const name = input.name ?? nextAutoName(existingTitles, input.project);
6069
+ let base = "";
6070
+ if (!input.shared) {
6071
+ base = resolveWorktreeBase(proj.path);
6072
+ deps.onBaseResolved?.(base);
6073
+ }
6011
6074
  const spawnCwd = !input.shared ? addWorktree({
6012
6075
  repoRoot: proj.path,
6013
6076
  worktreeDir: config.defaults.worktreeDir ?? ".worktrees",
6014
6077
  project: input.project,
6015
6078
  name,
6016
- base: resolveWorktreeBase(proj.path)
6079
+ base
6017
6080
  }) : proj.path;
6018
6081
  let firstTurnTask = input.task;
6019
6082
  if (input.taskFile && input.taskFile !== "-" && !input.shared) {
@@ -6181,7 +6244,7 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
6181
6244
  function pickMostRecentTask(tasks) {
6182
6245
  return tasks.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
6183
6246
  }
6184
- async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
6247
+ async function runCrewSend(project, name, message, runtime, workspaceId, deps, opts) {
6185
6248
  const crew = await findCrewPane(runtime, workspaceId, project, name);
6186
6249
  if (!crew) {
6187
6250
  throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
@@ -6190,9 +6253,17 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
6190
6253
  if (deps.isBlockedByModal && await deps.isBlockedByModal(crew)) {
6191
6254
  throw new Error(blockedByModalMessage());
6192
6255
  }
6256
+ let task;
6193
6257
  try {
6194
6258
  const matches = (await deps.listTasks(project)).filter((t) => t.name === name);
6195
- const task = matches.length > 0 ? pickMostRecentTask(matches) : void 0;
6259
+ task = matches.length > 0 ? pickMostRecentTask(matches) : void 0;
6260
+ } catch {
6261
+ }
6262
+ if (task && task.operatorHold && !opts?.force) {
6263
+ const heldForMin = Math.round((Date.now() - task.operatorHold.since) / 6e4);
6264
+ throw new Error(`Crew '${name}' is under operator takeover (held ${heldForMin}m${task.operatorHold.note ? `: ${task.operatorHold.note}` : ""}). The operator is working in that tab \u2014 sending a message disrupts their conversation. Ask them to run 'squadrant crew handback ${project} ${name}', or pass --force if they told you to.`);
6265
+ }
6266
+ try {
6196
6267
  if (task) {
6197
6268
  if (TERMINAL_STATES.has(task.state)) {
6198
6269
  await deps.emitEvent(project, { type: "task.reopened", id: task.id });
@@ -6218,23 +6289,60 @@ async function runCrewRead(project, name, runtime, workspaceId) {
6218
6289
  }
6219
6290
  return runtime.readPaneScreen(crew);
6220
6291
  }
6221
- async function runCrewClose(project, name, runtime, workspaceId, deps) {
6292
+ function buildRecoveryHint(sessId, provider, worktreeCwd) {
6293
+ if (!sessId || !worktreeCwd)
6294
+ return "";
6295
+ if (provider === "claude") {
6296
+ const escaped = worktreeCwd.replace(/[^a-zA-Z0-9]/g, "-");
6297
+ const transcriptPath = path11.join(os5.homedir(), ".claude", "projects", escaped, `${sessId}.jsonl`);
6298
+ return `
6299
+ transcript: ${transcriptPath}
6300
+ resume: claude --resume ${sessId} (run from the worktree path above)
6301
+ `;
6302
+ }
6303
+ return "";
6304
+ }
6305
+ async function runCrewClose(project, name, runtime, workspaceId, deps, opts) {
6222
6306
  const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
6223
6307
  const projRoot = loadConfig().projects[project]?.path;
6224
- let taskId;
6225
- let worktreeCwd;
6308
+ let matches = [];
6226
6309
  try {
6227
- let matches = (await deps.listTasks(project)).filter((t) => t.name === name);
6310
+ matches = (await deps.listTasks(project)).filter((t) => t.name === name);
6228
6311
  for (let attempt = 0; attempt < CLOSE_LOOKUP_RETRIES && matches.length === 0; attempt++) {
6229
6312
  await sleep3(CLOSE_LOOKUP_RETRY_DELAY_MS);
6230
6313
  matches = (await deps.listTasks(project)).filter((t) => t.name === name);
6231
6314
  }
6232
- if (matches.length > 0) {
6233
- const primary = pickMostRecentTask(matches);
6234
- taskId = primary.id;
6235
- if (primary.cwd && projRoot && primary.cwd !== projRoot) {
6236
- worktreeCwd = primary.cwd;
6237
- }
6315
+ } catch {
6316
+ }
6317
+ let taskId;
6318
+ let worktreeCwd;
6319
+ let sessId;
6320
+ let provider;
6321
+ if (matches.length > 0) {
6322
+ const primary = pickMostRecentTask(matches);
6323
+ taskId = primary.id;
6324
+ sessId = primary.sessionId;
6325
+ provider = primary.provider;
6326
+ if (primary.cwd && projRoot && primary.cwd !== projRoot) {
6327
+ worktreeCwd = primary.cwd;
6328
+ }
6329
+ if (primary.operatorHold && !opts?.force) {
6330
+ const heldForMin = Math.round((Date.now() - primary.operatorHold.since) / 6e4);
6331
+ throw new Error(`Crew '${name}' is under operator takeover (held ${heldForMin}m${primary.operatorHold.note ? `: ${primary.operatorHold.note}` : ""}). The operator is working in that tab \u2014 closing it kills their session and prunes the worktree. Ask them to run 'squadrant crew handback ${project} ${name}', or pass --force if they told you to.`);
6332
+ }
6333
+ }
6334
+ if (worktreeCwd && projRoot) {
6335
+ const dirty = worktreeDirtyFiles(worktreeCwd);
6336
+ if (dirty.length > 0 && !opts?.force) {
6337
+ const transcriptStr = buildRecoveryHint(sessId, provider, worktreeCwd);
6338
+ throw new Error(`Worktree '${worktreeCwd}' has uncommitted files:
6339
+ ${dirty.map((f) => ` ${f}`).join("\n")}
6340
+ Why are they uncommitted? Commit them, or pass --force to destroy them.
6341
+ ${transcriptStr}`);
6342
+ }
6343
+ }
6344
+ if (matches.length > 0) {
6345
+ try {
6238
6346
  for (const task of matches) {
6239
6347
  if (!TERMINAL_STATES.has(task.state)) {
6240
6348
  await deps.emitEvent(project, { type: "task.cancelled", id: task.id, reason: "closed by captain" });
@@ -6243,8 +6351,8 @@ async function runCrewClose(project, name, runtime, workspaceId, deps) {
6243
6351
  await deps.closeCodexThread(task.id);
6244
6352
  }
6245
6353
  }
6354
+ } catch {
6246
6355
  }
6247
- } catch {
6248
6356
  }
6249
6357
  const crew = await findCrewPane(runtime, workspaceId, project, name);
6250
6358
  if (crew) {
@@ -6257,12 +6365,16 @@ async function runCrewClose(project, name, runtime, workspaceId, deps) {
6257
6365
  }
6258
6366
  if (worktreeCwd && projRoot) {
6259
6367
  try {
6260
- removeWorktree(projRoot, worktreeCwd);
6368
+ removeWorktree(projRoot, worktreeCwd, opts);
6261
6369
  } catch (e) {
6262
6370
  process.stderr.write(`(worktree remove failed: ${e.message})
6263
6371
  `);
6264
6372
  }
6265
6373
  }
6374
+ const hint = buildRecoveryHint(sessId, provider, worktreeCwd);
6375
+ if (hint) {
6376
+ process.stdout.write(hint);
6377
+ }
6266
6378
  }
6267
6379
  async function runCrewList(project, runtime, workspaceId) {
6268
6380
  const crews = await listCrewPanes(runtime, workspaceId, project);
@@ -10269,8 +10381,8 @@ var require_daemon_exports = {};
10269
10381
  __export(require_daemon_exports, {
10270
10382
  requireDaemon: () => requireDaemon
10271
10383
  });
10272
- import { join as join25 } from "path";
10273
- import { homedir as homedir20 } from "os";
10384
+ import { join as join22 } from "path";
10385
+ import { homedir as homedir17 } from "os";
10274
10386
  async function requireDaemon(sockPath = DEFAULT_SOCK_PATH3) {
10275
10387
  const isLive = await isDaemonSocketLive(sockPath);
10276
10388
  if (!isLive) {
@@ -10281,7 +10393,168 @@ var DEFAULT_SOCK_PATH3;
10281
10393
  var init_require_daemon = __esm({
10282
10394
  "packages/cli/src/lib/require-daemon.ts"() {
10283
10395
  init_dist2();
10284
- DEFAULT_SOCK_PATH3 = join25(homedir20(), ".config", "squadrant", "squadrant.sock");
10396
+ DEFAULT_SOCK_PATH3 = join22(homedir17(), ".config", "squadrant", "squadrant.sock");
10397
+ }
10398
+ });
10399
+
10400
+ // packages/cli/src/commands/runtime.ts
10401
+ var runtime_exports = {};
10402
+ __export(runtime_exports, {
10403
+ buildRegistry: () => buildRegistry,
10404
+ needRef: () => needRef,
10405
+ resolveTarget: () => resolveTarget,
10406
+ runRuntimeSend: () => runRuntimeSend,
10407
+ runtimeCommand: () => runtimeCommand
10408
+ });
10409
+ import { Command as Command8 } from "commander";
10410
+ import chalk9 from "chalk";
10411
+ function buildRegistry() {
10412
+ return new RuntimeRegistry({
10413
+ cmux: createCmuxDriver()
10414
+ });
10415
+ }
10416
+ function resolveTarget(registry, config, target, useCommand) {
10417
+ if (useCommand) {
10418
+ return {
10419
+ driver: registry.global(config),
10420
+ workspaceName: config.commandName
10421
+ };
10422
+ }
10423
+ if (!target) {
10424
+ throw new Error("Missing target: pass a project name or use --command");
10425
+ }
10426
+ const proj = config.projects[target];
10427
+ if (!proj) {
10428
+ throw new Error(`Project '${target}' not found. Run 'squadrant projects list'.`);
10429
+ }
10430
+ return {
10431
+ driver: registry.forProject(target, config),
10432
+ workspaceName: proj.captainName
10433
+ };
10434
+ }
10435
+ async function needRef(resolved) {
10436
+ const ref = await resolved.driver.status(resolved.workspaceName);
10437
+ if (!ref) {
10438
+ throw new Error(`Workspace '${resolved.workspaceName}' is not running`);
10439
+ }
10440
+ return ref.id;
10441
+ }
10442
+ async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
10443
+ const config = loadConfig();
10444
+ const registry = buildRegistry();
10445
+ if (opts.command && arg2 !== void 0) {
10446
+ throw new Error("With --command, pass only the message (not a project name)");
10447
+ }
10448
+ const target = opts.command ? void 0 : arg1;
10449
+ const message = opts.command ? arg1 : arg2;
10450
+ if (!message) throw new Error("Message is required");
10451
+ const { requireDaemon: requireDaemon2 } = await Promise.resolve().then(() => (init_require_daemon(), require_daemon_exports));
10452
+ const { appendCaptainMessage: appendCaptainMessage2, waitForCaptainDelivery: waitForCaptainDelivery2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
10453
+ await requireDaemon2();
10454
+ const resolved = resolveTarget(registry, config, target, !!opts.command);
10455
+ await needRef(resolved);
10456
+ const finalProject = opts.command ? config.commandName : target;
10457
+ const { join: join31, dirname: dirname10 } = await import("path");
10458
+ const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
10459
+ const stateRoot = join31(dirname10(DEFAULT_CONFIG_PATH2), "state");
10460
+ const seq = await appendCaptainMessage2({
10461
+ stateRoot,
10462
+ project: finalProject,
10463
+ text: message,
10464
+ source: "cli"
10465
+ });
10466
+ const timeoutMs = confirmOpts?.timeoutMs ?? SEND_CONFIRM_TIMEOUT_MS;
10467
+ const delivered = await waitForCaptainDelivery2({
10468
+ stateRoot,
10469
+ project: finalProject,
10470
+ seq,
10471
+ timeoutMs,
10472
+ pollMs: confirmOpts?.pollMs ?? SEND_CONFIRM_POLL_MS
10473
+ });
10474
+ if (!delivered) {
10475
+ throw new Error(
10476
+ `Message queued for '${finalProject}' (seq=${seq}) but delivery was not confirmed within ${Math.round(timeoutMs / 1e3)}s. It may still be pending \u2014 check with 'squadrant runtime read-screen ${finalProject}${opts.command ? " --command" : ""}'.`
10477
+ );
10478
+ }
10479
+ }
10480
+ var runtimeCommand, SEND_CONFIRM_TIMEOUT_MS, SEND_CONFIRM_POLL_MS;
10481
+ var init_runtime2 = __esm({
10482
+ "packages/cli/src/commands/runtime.ts"() {
10483
+ init_dist();
10484
+ init_dist3();
10485
+ runtimeCommand = new Command8("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
10486
+ 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) => {
10487
+ const config = loadConfig();
10488
+ const registry = buildRegistry();
10489
+ try {
10490
+ const resolved = resolveTarget(registry, config, target, !!opts.command);
10491
+ const ref = await resolved.driver.status(resolved.workspaceName);
10492
+ if (ref) {
10493
+ console.log("running");
10494
+ process.exit(0);
10495
+ } else {
10496
+ console.log("stopped");
10497
+ process.exit(1);
10498
+ }
10499
+ } catch (err) {
10500
+ console.error(chalk9.red(err.message));
10501
+ process.exit(2);
10502
+ }
10503
+ });
10504
+ SEND_CONFIRM_TIMEOUT_MS = 15e3;
10505
+ SEND_CONFIRM_POLL_MS = 500;
10506
+ 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) => {
10507
+ try {
10508
+ await runRuntimeSend(arg1, arg2, opts);
10509
+ console.log(chalk9.green("\u2714 Delivered (confirmed)"));
10510
+ } catch (err) {
10511
+ console.error(chalk9.red(err.message));
10512
+ process.exit(1);
10513
+ }
10514
+ });
10515
+ runtimeCommand.command("list").description("List all workspaces from the global runtime").option("-j, --json", "Output as JSON").action(async (opts) => {
10516
+ const config = loadConfig();
10517
+ const registry = buildRegistry();
10518
+ const driver = registry.global(config);
10519
+ const refs = await driver.list();
10520
+ if (opts.json) {
10521
+ console.log(JSON.stringify(refs, null, 2));
10522
+ } else {
10523
+ for (const r of refs) {
10524
+ console.log(`${r.id} ${r.name} ${r.status}`);
10525
+ }
10526
+ }
10527
+ });
10528
+ runtimeCommand.command("read-screen").description("Print a terminal snapshot of a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
10529
+ const config = loadConfig();
10530
+ const registry = buildRegistry();
10531
+ try {
10532
+ const resolved = resolveTarget(registry, config, target, !!opts.command);
10533
+ const ref = await needRef(resolved);
10534
+ const screen = await resolved.driver.readScreen(ref);
10535
+ process.stdout.write(screen);
10536
+ } catch (err) {
10537
+ console.error(chalk9.red(err.message));
10538
+ process.exit(1);
10539
+ }
10540
+ });
10541
+ runtimeCommand.command("stop").description("Stop a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
10542
+ const config = loadConfig();
10543
+ const registry = buildRegistry();
10544
+ try {
10545
+ const resolved = resolveTarget(registry, config, target, !!opts.command);
10546
+ const ref = await resolved.driver.status(resolved.workspaceName);
10547
+ if (!ref) {
10548
+ console.log(chalk9.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
10549
+ return;
10550
+ }
10551
+ await resolved.driver.stop(ref.id);
10552
+ console.log(chalk9.green(`\u2714 Stopped ${resolved.workspaceName}`));
10553
+ } catch (err) {
10554
+ console.error(chalk9.red(err.message));
10555
+ process.exit(1);
10556
+ }
10557
+ });
10285
10558
  }
10286
10559
  });
10287
10560
 
@@ -11264,20 +11537,20 @@ init_dist();
11264
11537
  init_dist3();
11265
11538
  init_dist4();
11266
11539
  init_dist2();
11267
- import { Command as Command9 } from "commander";
11268
- import chalk9 from "chalk";
11540
+ import { Command as Command10 } from "commander";
11541
+ import chalk10 from "chalk";
11269
11542
 
11270
11543
  // packages/cli/src/commands/crew-control.ts
11271
11544
  init_dist2();
11272
11545
  init_dist2();
11273
11546
  init_dist();
11274
11547
  init_dist4();
11275
- import { Command as Command8 } from "commander";
11548
+ import { Command as Command9 } from "commander";
11276
11549
  import { createConnection as createConnection3 } from "net";
11277
11550
  import { randomUUID as randomUUID4 } from "crypto";
11278
11551
  import { execFileSync as execFileSync6 } from "child_process";
11279
- import { homedir as homedir17 } from "os";
11280
- import { join as join22 } from "path";
11552
+ import { homedir as homedir18 } from "os";
11553
+ import { join as join23 } from "path";
11281
11554
  import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync11 } from "fs";
11282
11555
 
11283
11556
  // packages/cli/src/commands/crew-output.ts
@@ -11328,7 +11601,26 @@ function formatCompactTasks(records, opts) {
11328
11601
  if (opts.compact === false) {
11329
11602
  return JSON.stringify(records, null, 2);
11330
11603
  }
11331
- return records.map(formatTaskLine).join("\n");
11604
+ const active = records.filter((r) => !r.operatorHold);
11605
+ const held = records.filter((r) => r.operatorHold);
11606
+ let out = "";
11607
+ if (active.length > 0) {
11608
+ if (held.length > 0) out += `active (${active.length}):
11609
+ `;
11610
+ out += active.map((r) => (held.length > 0 ? " " : "") + formatTaskLine(r)).join("\n");
11611
+ }
11612
+ if (held.length > 0) {
11613
+ if (out) out += "\n";
11614
+ out += `HELD BY OPERATOR (${held.length}) \u2014 not counted toward maxCrew:
11615
+ `;
11616
+ out += held.map((r) => {
11617
+ const m = Math.round((Date.now() - r.operatorHold.since) / 6e4);
11618
+ const hm = m >= 60 ? `${Math.floor(m / 60)}h${m % 60}m` : `${m}m`;
11619
+ const note = r.operatorHold.note ? ` \xB7 "${r.operatorHold.note}"` : "";
11620
+ return ` ${formatTaskLine(r)} \xB7 held ${hm}${note}`;
11621
+ }).join("\n");
11622
+ }
11623
+ return out;
11332
11624
  }
11333
11625
 
11334
11626
  // packages/cli/src/commands/crew-attach.ts
@@ -11576,7 +11868,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
11576
11868
  });
11577
11869
 
11578
11870
  // packages/cli/src/commands/crew-control.ts
11579
- var SOCK2 = join22(homedir17(), ".config", "squadrant", "squadrant.sock");
11871
+ var SOCK2 = join23(homedir18(), ".config", "squadrant", "squadrant.sock");
11580
11872
  var CODEX_FIRST_TURN_DELAY_MS = 1500;
11581
11873
  async function sendCodexFirstTurn(taskId, text) {
11582
11874
  await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
@@ -11640,6 +11932,40 @@ function buildGateResolveRequest(o) {
11640
11932
  payload: { text: o.message, ...decision ? { decision } : {} }
11641
11933
  };
11642
11934
  }
11935
+ async function runCrewTakeover(mode, opts, deps) {
11936
+ const taskId = opts.taskId ?? process.env.SQUADRANT_CREW_TASK_ID;
11937
+ let project = opts.project ?? process.env.SQUADRANT_CREW_PROJECT;
11938
+ let target;
11939
+ if (taskId) {
11940
+ if (!project) {
11941
+ throw new Error("not running under a crew (SQUADRANT_CREW_PROJECT unset)");
11942
+ }
11943
+ const tasks = await deps.listTasks(project);
11944
+ target = tasks.find((t) => t.id === taskId);
11945
+ if (!target) throw new Error(`Task '${taskId}' not found for project '${project}'`);
11946
+ } else if (project && opts.crew) {
11947
+ const tasks = await deps.listTasks(project);
11948
+ target = resolveApproveTarget(tasks, opts.crew) || void 0;
11949
+ if (!target) throw new Error(`Crew '${opts.crew}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
11950
+ } else {
11951
+ throw new Error("must provide either <project> <crew> or --task-id");
11952
+ }
11953
+ const nameDisp = project && opts.crew ? `${project}/${opts.crew}` : target.name ? `${target.project}/${target.name}` : `${target.project}/${target.id}`;
11954
+ if (mode === "start") {
11955
+ const event = { type: "crew.takeover.started", id: target.id, ...opts.note !== void 0 ? { note: opts.note } : {} };
11956
+ await deps.emitEvent(target.project, event);
11957
+ const msg = `CREW TAKEOVER [${nameDisp}] \u2014 operator is driving this tab. Do not send, do not close, do not act on its signals until handback.`;
11958
+ await deps.runtimeSend(target.project, msg);
11959
+ deps.printSuccess(msg);
11960
+ } else {
11961
+ const event = { type: "crew.takeover.ended", id: target.id };
11962
+ await deps.emitEvent(target.project, event);
11963
+ const msg = `CREW HANDBACK [${nameDisp}] \u2014 operator returned control. State: ${target.state}. Run 'squadrant crew read ${target.project} ${target.name || target.id}' before acting; the tab has history you did not see.`;
11964
+ await deps.runtimeSend(target.project, msg);
11965
+ deps.printSuccess(msg);
11966
+ }
11967
+ return target;
11968
+ }
11643
11969
  async function squadrantdCall(req) {
11644
11970
  try {
11645
11971
  return await sendRequest(SOCK2, req);
@@ -11683,9 +12009,9 @@ function buildSignalRequest(signal, o) {
11683
12009
  return { kind: "event", project, event };
11684
12010
  }
11685
12011
  function defaultWriteResult(id, payload) {
11686
- const dir = join22(homedir17(), ".config", "squadrant", "state", "_results");
12012
+ const dir = join23(homedir18(), ".config", "squadrant", "state", "_results");
11687
12013
  mkdirSync9(dir, { recursive: true });
11688
- const file = join22(dir, `${id}.txt`);
12014
+ const file = join23(dir, `${id}.txt`);
11689
12015
  writeFileSync11(file, payload);
11690
12016
  return file;
11691
12017
  }
@@ -11865,6 +12191,60 @@ function addControlPlaneCrewCommands(crew) {
11865
12191
  `);
11866
12192
  } catch (e) {
11867
12193
  process.stderr.write(`${e.message}
12194
+ `);
12195
+ process.exit(1);
12196
+ }
12197
+ });
12198
+ crew.command("takeover [project] [crewName]").description("Take over a crew's tab explicitly, suppressing captain action until handback (#649)").option("--task-id <id>", "Explicit task id (overrides SQUADRANT_CREW_TASK_ID env)").option("--note <note>", "Optional note explaining the takeover").action(async (project, crewName, opts) => {
12199
+ try {
12200
+ await runCrewTakeover("start", { project, crew: crewName, taskId: opts.taskId, note: opts.note }, {
12201
+ listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
12202
+ emitEvent: async (p, event) => {
12203
+ await squadrantdCall({ kind: "event", project: p, event });
12204
+ },
12205
+ runtimeSend: async (p, msg) => {
12206
+ const { runRuntimeSend: runRuntimeSend2 } = await Promise.resolve().then(() => (init_runtime2(), runtime_exports));
12207
+ await runRuntimeSend2(p, msg, {});
12208
+ },
12209
+ printError: (msg) => {
12210
+ process.stderr.write(`${msg}
12211
+ `);
12212
+ },
12213
+ printSuccess: (msg) => {
12214
+ process.stdout.write(`\u2714 ${msg}
12215
+ `);
12216
+ }
12217
+ });
12218
+ process.exit(0);
12219
+ } catch (e) {
12220
+ process.stderr.write(`${e.message}
12221
+ `);
12222
+ process.exit(1);
12223
+ }
12224
+ });
12225
+ crew.command("handback [project] [crewName]").description("Return control of a held crew tab to the captain (#649)").option("--task-id <id>", "Explicit task id (overrides SQUADRANT_CREW_TASK_ID env)").action(async (project, crewName, opts) => {
12226
+ try {
12227
+ await runCrewTakeover("end", { project, crew: crewName, taskId: opts.taskId }, {
12228
+ listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
12229
+ emitEvent: async (p, event) => {
12230
+ await squadrantdCall({ kind: "event", project: p, event });
12231
+ },
12232
+ runtimeSend: async (p, msg) => {
12233
+ const { runRuntimeSend: runRuntimeSend2 } = await Promise.resolve().then(() => (init_runtime2(), runtime_exports));
12234
+ await runRuntimeSend2(p, msg, {});
12235
+ },
12236
+ printError: (msg) => {
12237
+ process.stderr.write(`${msg}
12238
+ `);
12239
+ },
12240
+ printSuccess: (msg) => {
12241
+ process.stdout.write(`\u2714 ${msg}
12242
+ `);
12243
+ }
12244
+ });
12245
+ process.exit(0);
12246
+ } catch (e) {
12247
+ process.stderr.write(`${e.message}
11868
12248
  `);
11869
12249
  process.exit(1);
11870
12250
  }
@@ -11872,7 +12252,7 @@ function addControlPlaneCrewCommands(crew) {
11872
12252
  crew.addCommand(crewAttachCommand);
11873
12253
  crew.addCommand(crewChatCommand);
11874
12254
  }
11875
- var crewControlCommand = new Command8("crew").description("Dispatch and track crew via the squadrant control plane");
12255
+ var crewControlCommand = new Command9("crew").description("Dispatch and track crew via the squadrant control plane");
11876
12256
  addControlPlaneCrewCommands(crewControlCommand);
11877
12257
 
11878
12258
  // packages/cli/src/commands/crew.ts
@@ -11904,13 +12284,14 @@ async function runCrewSpawn2(input) {
11904
12284
  await squadrantdCall({ kind: "event", project: p, event });
11905
12285
  },
11906
12286
  onRouted: (route) => console.log(
11907
- chalk9.dim(
12287
+ chalk10.dim(
11908
12288
  `routed: tier=${route.tier} \u2192 ${route.agent}${route.model ? `/${route.model}` : ""} (rule: "${route.matchedRule}")`
11909
12289
  )
11910
- )
12290
+ ),
12291
+ onBaseResolved: (base) => console.log(chalk10.dim(`base: ${base}`))
11911
12292
  });
11912
12293
  }
11913
- async function runCrewSend2(project, name, message) {
12294
+ async function runCrewSend2(project, name, message, opts) {
11914
12295
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
11915
12296
  return runCrewSend(project, name, message, runtime, workspaceId, {
11916
12297
  listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
@@ -11922,13 +12303,13 @@ async function runCrewSend2(project, name, message) {
11922
12303
  sendToPane: (pane, msg) => confirmedSendToPane(runtime, pane, msg),
11923
12304
  // #516: side-effect-free modal precheck, run before any daemon-state emit.
11924
12305
  isBlockedByModal: (pane) => paneHasOpenModal(runtime, pane)
11925
- });
12306
+ }, opts);
11926
12307
  }
11927
12308
  async function runCrewRead2(project, name) {
11928
12309
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
11929
12310
  return runCrewRead(project, name, runtime, workspaceId);
11930
12311
  }
11931
- async function runCrewClose2(project, name) {
12312
+ async function runCrewClose2(project, name, opts) {
11932
12313
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
11933
12314
  return runCrewClose(project, name, runtime, workspaceId, {
11934
12315
  listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
@@ -11938,13 +12319,13 @@ async function runCrewClose2(project, name) {
11938
12319
  closeCodexThread: async (taskId) => {
11939
12320
  await squadrantdCall({ kind: "codex-close", taskId });
11940
12321
  }
11941
- });
12322
+ }, opts);
11942
12323
  }
11943
12324
  async function runCrewList2(project) {
11944
12325
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
11945
12326
  return runCrewList(project, runtime, workspaceId);
11946
12327
  }
11947
- var crewCommand = new Command9("crew").description(
12328
+ var crewCommand = new Command10("crew").description(
11948
12329
  "Spawn and manage interactive crew sessions next to the project's captain"
11949
12330
  );
11950
12331
  crewCommand.command("spawn").description(
@@ -11970,9 +12351,9 @@ crewCommand.command("spawn").description(
11970
12351
  // into the isolated worktree root for relative-path access.
11971
12352
  ...opts.taskFile && opts.taskFile !== "-" ? { taskFile: opts.taskFile } : {}
11972
12353
  });
11973
- console.log(chalk9.green(`\u2714 Crew '${pane.title}' spawned (${pane.surfaceId})`));
12354
+ console.log(chalk10.green(`\u2714 Crew '${pane.title}' spawned (${pane.surfaceId})`));
11974
12355
  } catch (err) {
11975
- console.error(chalk9.red(err.message));
12356
+ console.error(chalk10.red(err.message));
11976
12357
  process.exit(1);
11977
12358
  }
11978
12359
  }
@@ -11981,24 +12362,48 @@ crewCommand.command("list").description("List live crew sessions for a project")
11981
12362
  try {
11982
12363
  const crews = await runCrewList2(project);
11983
12364
  if (crews.length === 0) {
11984
- console.log(chalk9.yellow(`No live crew sessions for ${project}.`));
12365
+ console.log(chalk10.yellow(`No live crew sessions for ${project}.`));
11985
12366
  return;
11986
12367
  }
12368
+ const tasks = await squadrantdCall({ kind: "list", project }).catch(() => []);
12369
+ const active = [];
12370
+ const held = [];
11987
12371
  for (const c of crews) {
11988
- console.log(` ${c.name} (${c.surfaceId})`);
12372
+ const task = tasks.find((t2) => t2.name === c.name);
12373
+ const t = task ? resolveApproveTarget(tasks, c.name) : void 0;
12374
+ if (t?.operatorHold) {
12375
+ held.push({ c, t });
12376
+ } else {
12377
+ active.push({ c, t });
12378
+ }
12379
+ }
12380
+ if (active.length > 0) {
12381
+ console.log(`active (${active.length}):`);
12382
+ for (const { c, t } of active) {
12383
+ console.log(` ${c.name.padEnd(10)} ${t ? t.state : "unknown"} (${c.surfaceId})`);
12384
+ }
12385
+ }
12386
+ if (held.length > 0) {
12387
+ console.log(`HELD BY OPERATOR (${held.length}) \u2014 not counted toward maxCrew:`);
12388
+ for (const { c, t } of held) {
12389
+ const m = Math.round((Date.now() - t.operatorHold.since) / 6e4);
12390
+ const hm = m >= 60 ? `${Math.floor(m / 60)}h${m % 60}m` : `${m}m`;
12391
+ const note = t.operatorHold.note ? ` \xB7 "${t.operatorHold.note}"` : "";
12392
+ console.log(` ${c.name.padEnd(10)} ${t.state} \xB7 held ${hm}${note} (${c.surfaceId})`);
12393
+ }
11989
12394
  }
11990
12395
  } catch (err) {
11991
- console.error(chalk9.red(err.message));
12396
+ console.error(chalk10.red(err.message));
11992
12397
  process.exit(1);
11993
12398
  }
11994
12399
  });
11995
- crewCommand.command("send").description("Send a follow-up message to an existing crew session").argument("<project>", "Project name").argument("<name>", "Crew name (e.g. crew-1)").argument("[message]", "Message to send (omit with --message-file)").option("--message-file <path>", "Read message from file instead of positional arg ('-' for stdin)").action(async (project, name, message, opts) => {
12400
+ crewCommand.command("send").description("Send a follow-up message to an existing crew session").argument("<project>", "Project name").argument("<name>", "Crew name (e.g. crew-1)").argument("[message]", "Message to send (omit with --message-file)").option("--message-file <path>", "Read message from file instead of positional arg ('-' for stdin)").option("--force", "override an operator takeover (only when the operator told you to)", false).action(async (project, name, message, opts) => {
11996
12401
  try {
11997
12402
  const resolvedMessage = await resolveTextInput({ positional: message, filePath: opts.messageFile, label: "message" });
11998
- await runCrewSend2(project, name, resolvedMessage);
11999
- console.log(chalk9.green(`\u2714 Sent to ${project}:${name}`));
12000
- } catch (err) {
12001
- console.error(chalk9.red(err.message));
12403
+ await runCrewSend2(project, name, resolvedMessage, opts);
12404
+ console.log(chalk10.green(`\u2714 Sent to ${project}:${name}`));
12405
+ } catch (e) {
12406
+ console.error(chalk10.red(e.message));
12002
12407
  process.exit(1);
12003
12408
  }
12004
12409
  });
@@ -12008,16 +12413,16 @@ crewCommand.command("read").description("Read the current screen of a crew sessi
12008
12413
  const out = opts.full ? screen : tailLines(screen, Number(opts.lines ?? 40));
12009
12414
  console.log(out);
12010
12415
  } catch (err) {
12011
- console.error(chalk9.red(err.message));
12416
+ console.error(chalk10.red(err.message));
12012
12417
  process.exit(1);
12013
12418
  }
12014
12419
  });
12015
- crewCommand.command("close").description("Shutdown a crew session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Crew name").action(async (project, name) => {
12420
+ crewCommand.command("close").description("Shutdown a crew session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Crew name").option("--force", "override an operator takeover (only when the operator told you to)", false).action(async (project, name, opts) => {
12016
12421
  try {
12017
- await runCrewClose2(project, name);
12018
- console.log(chalk9.green(`\u2714 Closed ${project}:${name}`));
12422
+ await runCrewClose2(project, name, opts);
12423
+ console.log(chalk10.green(`\u2714 Closed ${project}:${name}`));
12019
12424
  } catch (err) {
12020
- console.error(chalk9.red(err.message));
12425
+ console.error(chalk10.red(err.message));
12021
12426
  process.exit(1);
12022
12427
  }
12023
12428
  });
@@ -12025,8 +12430,8 @@ crewCommand.command("close").description("Shutdown a crew session (closes its ta
12025
12430
  // packages/cli/src/commands/diff.ts
12026
12431
  init_dist();
12027
12432
  init_dist3();
12028
- import { Command as Command10 } from "commander";
12029
- import chalk10 from "chalk";
12433
+ import { Command as Command11 } from "commander";
12434
+ import chalk11 from "chalk";
12030
12435
  import { execFileSync as execFileSync7 } from "child_process";
12031
12436
  import readline2 from "readline";
12032
12437
  function resolveDiffTarget(tasks, crew, projectPath) {
@@ -12143,7 +12548,7 @@ async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
12143
12548
  const label = sources.length > 1 ? "staged or unstaged" : sources[0];
12144
12549
  console.log(`No ${label} changes on ${branchLabel}.`);
12145
12550
  } else {
12146
- console.log(chalk10.dim(`Opened ${opened} working-tree diff(s) (${sources.join(", ")}) for ${branchLabel}.`));
12551
+ console.log(chalk11.dim(`Opened ${opened} working-tree diff(s) (${sources.join(", ")}) for ${branchLabel}.`));
12147
12552
  }
12148
12553
  return;
12149
12554
  }
@@ -12166,7 +12571,7 @@ async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
12166
12571
  lastTurn: opts.lastTurn,
12167
12572
  source: "branch"
12168
12573
  });
12169
- console.log(chalk10.dim(`Opened ${branchLabel} vs ${base} in cmux diff.`));
12574
+ console.log(chalk11.dim(`Opened ${branchLabel} vs ${base} in cmux diff.`));
12170
12575
  }
12171
12576
  async function runDiff(project, crewArg, opts) {
12172
12577
  const config = loadConfig();
@@ -12187,7 +12592,7 @@ async function runDiff(project, crewArg, opts) {
12187
12592
  return;
12188
12593
  }
12189
12594
  await runtime.showPatch({ workspaceId, patch, title, layout: opts.layout, focus: opts.focus });
12190
- console.log(chalk10.dim(`Opened ${title} in cmux diff.`));
12595
+ console.log(chalk11.dim(`Opened ${title} in cmux diff.`));
12191
12596
  return;
12192
12597
  }
12193
12598
  let crew;
@@ -12213,7 +12618,7 @@ async function runDiff(project, crewArg, opts) {
12213
12618
  }
12214
12619
  await openCrewDiff(project, proj, crew, opts, runtime, workspaceId);
12215
12620
  }
12216
- 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);
12621
+ var diffCommand = new Command11("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);
12217
12622
 
12218
12623
  // packages/cli/src/commands/side.ts
12219
12624
  init_dist();
@@ -12222,11 +12627,11 @@ init_dist4();
12222
12627
  init_dist3();
12223
12628
  init_dist();
12224
12629
  init_dist2();
12225
- import { Command as Command11 } from "commander";
12630
+ import { Command as Command12 } from "commander";
12226
12631
  import fs20 from "fs";
12227
12632
  import path22 from "path";
12228
12633
  import os12 from "os";
12229
- import chalk11 from "chalk";
12634
+ import chalk12 from "chalk";
12230
12635
  var TEMPLATES_DIR3 = path22.join(os12.homedir(), ".config", "squadrant", "templates");
12231
12636
  async function runSideSpawn2(input) {
12232
12637
  const config = loadConfig();
@@ -12288,7 +12693,7 @@ async function runSideClose2(project, name) {
12288
12693
  config.defaults.worktreeDir ?? ".worktrees"
12289
12694
  );
12290
12695
  }
12291
- var sideCommand = new Command11("side").description(
12696
+ var sideCommand = new Command12("side").description(
12292
12697
  "Spawn and manage side-sessions (research/debug) \u2014 fresh-context tabs off the daemon lifecycle"
12293
12698
  );
12294
12699
  sideCommand.command("spawn").description(
@@ -12313,9 +12718,9 @@ sideCommand.command("spawn").description(
12313
12718
  direction: opts.direction,
12314
12719
  agent: opts.agent
12315
12720
  });
12316
- console.log(chalk11.green(`\u2714 Side session '${pane.title}' spawned (${pane.surfaceId})`));
12721
+ console.log(chalk12.green(`\u2714 Side session '${pane.title}' spawned (${pane.surfaceId})`));
12317
12722
  } catch (err) {
12318
- console.error(chalk11.red(err.message));
12723
+ console.error(chalk12.red(err.message));
12319
12724
  process.exit(1);
12320
12725
  }
12321
12726
  }
@@ -12324,14 +12729,14 @@ sideCommand.command("list").description("List live side-sessions for a project")
12324
12729
  try {
12325
12730
  const sessions = await runSideList2(project);
12326
12731
  if (sessions.length === 0) {
12327
- console.log(chalk11.yellow(`No live side-sessions for ${project}.`));
12732
+ console.log(chalk12.yellow(`No live side-sessions for ${project}.`));
12328
12733
  return;
12329
12734
  }
12330
12735
  for (const s of sessions) {
12331
12736
  console.log(` ${s.name} (${s.surfaceId})`);
12332
12737
  }
12333
12738
  } catch (err) {
12334
- console.error(chalk11.red(err.message));
12739
+ console.error(chalk12.red(err.message));
12335
12740
  process.exit(1);
12336
12741
  }
12337
12742
  });
@@ -12344,9 +12749,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
12344
12749
  label: "message"
12345
12750
  });
12346
12751
  await runSideSend2(project, name, resolvedMessage);
12347
- console.log(chalk11.green(`\u2714 Sent to ${project}:${name}`));
12752
+ console.log(chalk12.green(`\u2714 Sent to ${project}:${name}`));
12348
12753
  } catch (err) {
12349
- console.error(chalk11.red(err.message));
12754
+ console.error(chalk12.red(err.message));
12350
12755
  process.exit(1);
12351
12756
  }
12352
12757
  }
@@ -12354,9 +12759,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
12354
12759
  sideCommand.command("close").description("Close a side-session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Session name").action(async (project, name) => {
12355
12760
  try {
12356
12761
  await runSideClose2(project, name);
12357
- console.log(chalk11.green(`\u2714 Closed ${project}:${name}`));
12762
+ console.log(chalk12.green(`\u2714 Closed ${project}:${name}`));
12358
12763
  } catch (err) {
12359
- console.error(chalk11.red(err.message));
12764
+ console.error(chalk12.red(err.message));
12360
12765
  process.exit(1);
12361
12766
  }
12362
12767
  });
@@ -12364,11 +12769,11 @@ sideCommand.command("close").description("Close a side-session (closes its tab)"
12364
12769
  // packages/cli/src/commands/dashboard.ts
12365
12770
  init_dist();
12366
12771
  init_dist3();
12367
- import { Command as Command12 } from "commander";
12772
+ import { Command as Command13 } from "commander";
12368
12773
  import { execSync as execSync10 } from "child_process";
12369
- import { homedir as homedir19 } from "os";
12370
- import { join as join24 } from "path";
12371
- import chalk13 from "chalk";
12774
+ import { homedir as homedir20 } from "os";
12775
+ import { join as join25 } from "path";
12776
+ import chalk14 from "chalk";
12372
12777
 
12373
12778
  // packages/web/dist/read-status.js
12374
12779
  function deriveState(tasks) {
@@ -12445,14 +12850,14 @@ async function readAllStatuses(deps) {
12445
12850
  }
12446
12851
 
12447
12852
  // packages/web/dist/render.js
12448
- import chalk12 from "chalk";
12853
+ import chalk13 from "chalk";
12449
12854
  var ICON = {
12450
- idle: chalk12.green,
12451
- busy: chalk12.cyan,
12452
- blocked: chalk12.yellow,
12453
- errored: chalk12.red,
12454
- offline: chalk12.dim,
12455
- unknown: chalk12.gray
12855
+ idle: chalk13.green,
12856
+ busy: chalk13.cyan,
12857
+ blocked: chalk13.yellow,
12858
+ errored: chalk13.red,
12859
+ offline: chalk13.dim,
12860
+ unknown: chalk13.gray
12456
12861
  };
12457
12862
  var ICON_CHAR = {
12458
12863
  idle: "\u25CF",
@@ -12495,10 +12900,10 @@ function renderDashboard(rows, opts) {
12495
12900
  const width = opts.width ?? 100;
12496
12901
  const lines = [];
12497
12902
  lines.push("");
12498
- lines.push(" " + chalk12.bold("\u{1F4CA} Squadrant Dashboard") + " " + chalk12.dim(opts.now));
12903
+ lines.push(" " + chalk13.bold("\u{1F4CA} Squadrant Dashboard") + " " + chalk13.dim(opts.now));
12499
12904
  lines.push("");
12500
12905
  if (rows.length === 0) {
12501
- lines.push(" " + chalk12.yellow("No projects registered. Add one with: squadrant projects add <name> <path>"));
12906
+ lines.push(" " + chalk13.yellow("No projects registered. Add one with: squadrant projects add <name> <path>"));
12502
12907
  lines.push("");
12503
12908
  return lines.join("\n");
12504
12909
  }
@@ -12509,14 +12914,14 @@ function renderDashboard(rows, opts) {
12509
12914
  const excerptW = Math.max(20, width - FIXED);
12510
12915
  for (const r of rows) {
12511
12916
  const icon = ICON[r.state](ICON_CHAR[r.state]);
12512
- const name = chalk12.cyan(pad(r.project, NAME_W));
12917
+ const name = chalk13.cyan(pad(r.project, NAME_W));
12513
12918
  const state = ICON[r.state](pad(r.state, STATE_W));
12514
12919
  const age = pad(formatAge(r.lastChecked, opts.now), AGE_W);
12515
- const excerpt = chalk12.dim(truncate(firstLine(r.excerpt), excerptW));
12920
+ const excerpt = chalk13.dim(truncate(firstLine(r.excerpt), excerptW));
12516
12921
  lines.push(` ${icon} ${name} ${state} ${age} \u2502 ${excerpt}`);
12517
12922
  }
12518
12923
  lines.push("");
12519
- lines.push(chalk12.dim(" Refreshes every 10s \xB7 Ctrl+C to exit"));
12924
+ lines.push(chalk13.dim(" Refreshes every 10s \xB7 Ctrl+C to exit"));
12520
12925
  lines.push("");
12521
12926
  return lines.join("\n");
12522
12927
  }
@@ -12580,8 +12985,8 @@ function mergeSnapshot(daemon, external, now) {
12580
12985
  // packages/web/dist/probes.js
12581
12986
  init_dist();
12582
12987
  init_dist();
12583
- import { join as join23 } from "path";
12584
- import { homedir as homedir18 } from "os";
12988
+ import { join as join24 } from "path";
12989
+ import { homedir as homedir19 } from "os";
12585
12990
  import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
12586
12991
  import { execFile as execFile4 } from "child_process";
12587
12992
  var DEFAULT_TIMEOUT_MS = 2e3;
@@ -12618,7 +13023,7 @@ function vaultProbe(run, dir) {
12618
13023
  return { state: "unknown", detail: "no vault configured" };
12619
13024
  if (!run.pathExists(dir))
12620
13025
  return { state: "gone", detail: "vault directory missing" };
12621
- if (!run.pathExists(join23(dir, ".obsidian")))
13026
+ if (!run.pathExists(join24(dir, ".obsidian")))
12622
13027
  return { state: "gone", detail: "no .obsidian/ (not a vault)" };
12623
13028
  return { state: "alive" };
12624
13029
  } catch {
@@ -12686,10 +13091,10 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
12686
13091
  const sessions = probeSessions(run);
12687
13092
  return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
12688
13093
  }
12689
- var SESSIONS_PATH = join23(homedir18(), ".config", "squadrant", "sessions.json");
13094
+ var SESSIONS_PATH = join24(homedir19(), ".config", "squadrant", "sessions.json");
12690
13095
  function onPath(cli) {
12691
13096
  const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
12692
- return dirs.some((d) => existsSync12(join23(d, cli)));
13097
+ return dirs.some((d) => existsSync12(join24(d, cli)));
12693
13098
  }
12694
13099
  function readSessionsHashes() {
12695
13100
  const raw = JSON.parse(readFileSync15(SESSIONS_PATH, "utf-8"));
@@ -13597,7 +14002,7 @@ async function startWebServer(opts) {
13597
14002
 
13598
14003
  // packages/cli/src/commands/dashboard.ts
13599
14004
  init_dist();
13600
- var SOCK3 = join24(homedir19(), ".config", "squadrant", "squadrant.sock");
14005
+ var SOCK3 = join25(homedir20(), ".config", "squadrant", "squadrant.sock");
13601
14006
  function detectCurrentWorkspace2() {
13602
14007
  const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
13603
14008
  const match = out.match(/workspace:\d+/);
@@ -13639,10 +14044,10 @@ async function runDashboardWeb(input) {
13639
14044
  sockPath: SOCK3,
13640
14045
  runners: defaultProbeRunners()
13641
14046
  });
13642
- console.log(chalk13.green(`\u2714 Squadrant system dashboard \u2192 http://127.0.0.1:${handle.port}`));
13643
- console.log(chalk13.dim(` polling the daemon every ${input.interval}s \xB7 localhost only \xB7 read-only \xB7 Ctrl-C to stop`));
14047
+ console.log(chalk14.green(`\u2714 Squadrant system dashboard \u2192 http://127.0.0.1:${handle.port}`));
14048
+ console.log(chalk14.dim(` polling the daemon every ${input.interval}s \xB7 localhost only \xB7 read-only \xB7 Ctrl-C to stop`));
13644
14049
  }
13645
- 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) => {
14050
+ var dashboardCommand = new Command13("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) => {
13646
14051
  try {
13647
14052
  if (opts.web) {
13648
14053
  await runDashboardWeb({ port: opts.port, interval: opts.interval ?? 5 });
@@ -13650,12 +14055,12 @@ var dashboardCommand = new Command12("dashboard").description("Live status grid
13650
14055
  }
13651
14056
  if (opts.pane) {
13652
14057
  const pane = await runDashboardPane({ direction: opts.direction, interval: opts.interval ?? 10 });
13653
- console.log(chalk13.green(`\u2714 Dashboard pane opened in ${pane.workspaceId} ${pane.surfaceId}`));
14058
+ console.log(chalk14.green(`\u2714 Dashboard pane opened in ${pane.workspaceId} ${pane.surfaceId}`));
13654
14059
  return;
13655
14060
  }
13656
14061
  await runDashboardOnce();
13657
14062
  } catch (err) {
13658
- console.error(chalk13.red(err.message));
14063
+ console.error(chalk14.red(err.message));
13659
14064
  process.exit(1);
13660
14065
  }
13661
14066
  });
@@ -13666,12 +14071,12 @@ dashboardCommand.command("sync-hub").description("Mirror each spoke status.md in
13666
14071
  return;
13667
14072
  }
13668
14073
  if (results.length === 0) {
13669
- console.log(chalk13.dim("\n No mirrors written (no projects with usable status.md, or hubVault unset).\n"));
14074
+ console.log(chalk14.dim("\n No mirrors written (no projects with usable status.md, or hubVault unset).\n"));
13670
14075
  return;
13671
14076
  }
13672
- console.log(chalk13.bold("\n \u{1F4CA} Hub mirror sync\n"));
14077
+ console.log(chalk14.bold("\n \u{1F4CA} Hub mirror sync\n"));
13673
14078
  for (const r of results) {
13674
- console.log(` ${chalk13.green("\u2714")} ${chalk13.cyan(r.project.padEnd(16))} \u2192 ${chalk13.dim(r.hubPath)}`);
14079
+ console.log(` ${chalk14.green("\u2714")} ${chalk14.cyan(r.project.padEnd(16))} \u2192 ${chalk14.dim(r.hubPath)}`);
13675
14080
  }
13676
14081
  console.log("");
13677
14082
  });
@@ -13681,12 +14086,12 @@ init_dist();
13681
14086
  init_dist4();
13682
14087
  init_dist3();
13683
14088
  init_dist2();
13684
- import { Command as Command13 } from "commander";
14089
+ import { Command as Command14 } from "commander";
13685
14090
  import { execSync as execSync11 } from "child_process";
13686
14091
  import fs22 from "fs";
13687
14092
  import path24 from "path";
13688
14093
  import os13 from "os";
13689
- import chalk14 from "chalk";
14094
+ import chalk15 from "chalk";
13690
14095
 
13691
14096
  // packages/cli/src/commands/launch-interactive.ts
13692
14097
  import checkbox, { Separator } from "@inquirer/checkbox";
@@ -13767,16 +14172,16 @@ var TEMPLATES_DIR4 = path24.join(os13.homedir(), ".config", "squadrant", "templa
13767
14172
  var SESSIONS_PATH2 = path24.join(os13.homedir(), ".config", "squadrant", "sessions.json");
13768
14173
  function ensureCmuxReady(headless) {
13769
14174
  if (headless || isInsideCmux()) return;
13770
- console.log(chalk14.yellow("\n Not running inside cmux. Opening cmux app...\n"));
14175
+ console.log(chalk15.yellow("\n Not running inside cmux. Opening cmux app...\n"));
13771
14176
  execSync11(`open "${CMUX_APP}"`, { stdio: "inherit" });
13772
- console.log(chalk14.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
14177
+ console.log(chalk15.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
13773
14178
  process.exit(0);
13774
14179
  }
13775
- var launchCommand = new Command13("launch").description(
14180
+ var launchCommand = new Command14("launch").description(
13776
14181
  "Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
13777
14182
  ).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) => {
13778
14183
  if (opts.fresh && opts.keep) {
13779
- console.error(chalk14.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
14184
+ console.error(chalk15.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
13780
14185
  process.exit(1);
13781
14186
  }
13782
14187
  const config = loadConfig();
@@ -13824,29 +14229,29 @@ var launchCommand = new Command13("launch").description(
13824
14229
  return null;
13825
14230
  }
13826
14231
  },
13827
- onFreshReason: (reason) => console.log(chalk14.cyan(` \u21BB ${reason}`)),
13828
- onStoppingStale: (name) => console.log(chalk14.yellow(` Closing stale workspace '${name}' for fresh start`)),
13829
- onAlreadyExists: (name) => console.log(chalk14.yellow(` Workspace '${name}' already exists \u2014 switching to it`)),
13830
- onCreated: (name) => console.log(chalk14.green(` \u2714 Workspace '${name}' created`))
14232
+ onFreshReason: (reason) => console.log(chalk15.cyan(` \u21BB ${reason}`)),
14233
+ onStoppingStale: (name) => console.log(chalk15.yellow(` Closing stale workspace '${name}' for fresh start`)),
14234
+ onAlreadyExists: (name) => console.log(chalk15.yellow(` Workspace '${name}' already exists \u2014 switching to it`)),
14235
+ onCreated: (name) => console.log(chalk15.green(` \u2714 Workspace '${name}' created`))
13831
14236
  });
13832
14237
  } catch (err) {
13833
- console.error(chalk14.red(` \u2718 Failed: ${err.message}`));
14238
+ console.error(chalk15.red(` \u2718 Failed: ${err.message}`));
13834
14239
  hadFailure = true;
13835
14240
  }
13836
14241
  }
13837
14242
  if (opts.all) {
13838
14243
  const hubPath = resolveHome(config.hubVault);
13839
14244
  fs22.mkdirSync(hubPath, { recursive: true });
13840
- console.log(chalk14.bold("\nLaunching all captain workspaces\n"));
14245
+ console.log(chalk15.bold("\nLaunching all captain workspaces\n"));
13841
14246
  for (const [name, proj] of Object.entries(config.projects)) {
13842
14247
  const projPath = resolveHome(proj.path);
13843
14248
  const spokePath = resolveHome(proj.spokeVault);
13844
14249
  if (!fs22.existsSync(spokePath)) {
13845
14250
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
13846
14251
  await ensureSpokeLayout(spokeDriver);
13847
- console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
14252
+ console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13848
14253
  }
13849
- console.log(chalk14.bold(`
14254
+ console.log(chalk15.bold(`
13850
14255
  Captain: ${proj.captainName} (${name})`));
13851
14256
  await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
13852
14257
  }
@@ -13854,7 +14259,7 @@ var launchCommand = new Command13("launch").description(
13854
14259
  } else if (!project) {
13855
14260
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
13856
14261
  console.error(
13857
- chalk14.red(
14262
+ chalk15.red(
13858
14263
  "\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"
13859
14264
  )
13860
14265
  );
@@ -13869,10 +14274,10 @@ var launchCommand = new Command13("launch").description(
13869
14274
  }));
13870
14275
  const selected = await selectCaptainsInteractive(entries);
13871
14276
  if (selected.length === 0) {
13872
- console.log(chalk14.yellow("\n No captains selected.\n"));
14277
+ console.log(chalk15.yellow("\n No captains selected.\n"));
13873
14278
  return;
13874
14279
  }
13875
- console.log(chalk14.bold(`
14280
+ console.log(chalk15.bold(`
13876
14281
  Launching ${selected.length} captain workspace(s) in parallel
13877
14282
  `));
13878
14283
  await Promise.all(selected.map(async (name) => {
@@ -13882,9 +14287,9 @@ Launching ${selected.length} captain workspace(s) in parallel
13882
14287
  if (!fs22.existsSync(spokePath)) {
13883
14288
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
13884
14289
  await ensureSpokeLayout(spokeDriver);
13885
- console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
14290
+ console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13886
14291
  }
13887
- console.log(chalk14.bold(`
14292
+ console.log(chalk15.bold(`
13888
14293
  Captain: ${proj.captainName} (${name})`));
13889
14294
  await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
13890
14295
  }));
@@ -13892,7 +14297,7 @@ Launching ${selected.length} captain workspace(s) in parallel
13892
14297
  } else {
13893
14298
  if (!config.projects[project]) {
13894
14299
  console.error(
13895
- chalk14.red(
14300
+ chalk15.red(
13896
14301
  `
13897
14302
  \u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
13898
14303
  `
@@ -13906,10 +14311,10 @@ Launching ${selected.length} captain workspace(s) in parallel
13906
14311
  if (!fs22.existsSync(spokePath)) {
13907
14312
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
13908
14313
  await ensureSpokeLayout(spokeDriver);
13909
- console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
14314
+ console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13910
14315
  }
13911
14316
  console.log(
13912
- chalk14.bold(
14317
+ chalk15.bold(
13913
14318
  `
13914
14319
  Launching captain workspace for '${project}' (${proj.captainName})
13915
14320
  `
@@ -13923,8 +14328,8 @@ Launching captain workspace for '${project}' (${proj.captainName})
13923
14328
  // packages/cli/src/commands/shutdown.ts
13924
14329
  init_dist();
13925
14330
  init_dist3();
13926
- import { Command as Command14 } from "commander";
13927
- import chalk15 from "chalk";
14331
+ import { Command as Command15 } from "commander";
14332
+ import chalk16 from "chalk";
13928
14333
  init_dist();
13929
14334
  function nameVariants(name) {
13930
14335
  const stripped = name.replace(/^⚓\s+/, "").trim();
@@ -13937,23 +14342,23 @@ async function closeMatching(runtime, variants, label) {
13937
14342
  const failed = [];
13938
14343
  if (matches.length === 0) {
13939
14344
  console.log(
13940
- chalk15.yellow(` \u26A0 Workspace '${label}' not found \u2014 already closed?`)
14345
+ chalk16.yellow(` \u26A0 Workspace '${label}' not found \u2014 already closed?`)
13941
14346
  );
13942
14347
  return { closed, failed };
13943
14348
  }
13944
14349
  for (const ws of matches) {
13945
14350
  try {
13946
14351
  await runtime.stop(ws.id);
13947
- console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
14352
+ console.log(chalk16.green(` \u2714 Closed: ${ws.name}`));
13948
14353
  closed.push(ws.name);
13949
14354
  } catch {
13950
- console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
14355
+ console.log(chalk16.red(` \u2718 Failed to close: ${ws.name}`));
13951
14356
  failed.push(ws.name);
13952
14357
  }
13953
14358
  }
13954
14359
  return { closed, failed };
13955
14360
  }
13956
- var shutdownCommand = new Command14("shutdown").description(
14361
+ var shutdownCommand = new Command15("shutdown").description(
13957
14362
  "Shutdown command + all captain workspaces (no args) or one captain workspace"
13958
14363
  ).argument("[project]", "Project name to shut down captain for").action(async (project) => {
13959
14364
  const config = loadConfig();
@@ -13969,11 +14374,11 @@ var shutdownCommand = new Command14("shutdown").description(
13969
14374
  const allVariants = /* @__PURE__ */ new Set([...captainVariants, ...commandVariants]);
13970
14375
  const squadrantWorkspaces = workspaces.filter((w) => allVariants.has(w.name));
13971
14376
  if (squadrantWorkspaces.length === 0) {
13972
- console.log(chalk15.yellow("\nNo squadrant workspaces found to close.\n"));
14377
+ console.log(chalk16.yellow("\nNo squadrant workspaces found to close.\n"));
13973
14378
  return;
13974
14379
  }
13975
14380
  console.log(
13976
- chalk15.bold(
14381
+ chalk16.bold(
13977
14382
  `
13978
14383
  Shutting down ${squadrantWorkspaces.length} workspace(s)...
13979
14384
  `
@@ -13993,9 +14398,9 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
13993
14398
  for (const ws of squadrantWorkspaces) {
13994
14399
  try {
13995
14400
  await globalRuntime.stop(ws.id);
13996
- console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
14401
+ console.log(chalk16.green(` \u2714 Closed: ${ws.name}`));
13997
14402
  } catch {
13998
- console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
14403
+ console.log(chalk16.red(` \u2718 Failed to close: ${ws.name}`));
13999
14404
  }
14000
14405
  }
14001
14406
  console.log("");
@@ -14003,7 +14408,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
14003
14408
  }
14004
14409
  if (!config.projects[project]) {
14005
14410
  console.error(
14006
- chalk15.red(
14411
+ chalk16.red(
14007
14412
  `
14008
14413
  \u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
14009
14414
  `
@@ -14014,7 +14419,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
14014
14419
  const captainName = config.projects[project].captainName;
14015
14420
  const runtime = runtimes.forProject(project, config);
14016
14421
  console.log(
14017
- chalk15.bold(`
14422
+ chalk16.bold(`
14018
14423
  Shutting down captain workspace for '${project}'...
14019
14424
  `)
14020
14425
  );
@@ -14038,13 +14443,13 @@ Shutting down captain workspace for '${project}'...
14038
14443
 
14039
14444
  // packages/cli/src/commands/feedback.ts
14040
14445
  init_dist();
14041
- import { Command as Command15 } from "commander";
14446
+ import { Command as Command16 } from "commander";
14042
14447
  import fs23 from "fs";
14043
14448
  import os14 from "os";
14044
14449
  import path25 from "path";
14045
14450
  import { fileURLToPath as fileURLToPath3 } from "url";
14046
14451
  import { execSync as execSync12 } from "child_process";
14047
- import chalk16 from "chalk";
14452
+ import chalk17 from "chalk";
14048
14453
  var REPO_URL = "https://github.com/tu11aa/squadrant";
14049
14454
  function readPkgVersion() {
14050
14455
  try {
@@ -14092,21 +14497,21 @@ function buildIssueUrl(metrics, squadrantVersion) {
14092
14497
  });
14093
14498
  return `${REPO_URL}/issues/new?${params.toString()}`;
14094
14499
  }
14095
- var feedbackCommand = new Command15("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
14500
+ var feedbackCommand = new Command16("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
14096
14501
  const config = loadConfig();
14097
14502
  const metricsPath = config.metrics?.path || path25.join(os14.homedir(), ".config", "squadrant", "metrics.json");
14098
14503
  const metrics = readMetrics(metricsPath);
14099
14504
  const version = readStamp(config) ?? readPkgVersion();
14100
14505
  const issueUrl = buildIssueUrl(metrics, version);
14101
- console.log(chalk16.bold("\nOpening feedback issue in browser...\n"));
14102
- console.log(chalk16.dim(` URL: ${issueUrl.substring(0, 80)}...
14506
+ console.log(chalk17.bold("\nOpening feedback issue in browser...\n"));
14507
+ console.log(chalk17.dim(` URL: ${issueUrl.substring(0, 80)}...
14103
14508
  `));
14104
14509
  try {
14105
14510
  execSync12(`open "${issueUrl}"`, { stdio: "ignore" });
14106
- console.log(chalk16.green(" \u2714 Browser opened\n"));
14511
+ console.log(chalk17.green(" \u2714 Browser opened\n"));
14107
14512
  } catch {
14108
- console.log(chalk16.yellow(" \u26A0 Could not open browser automatically."));
14109
- console.log(` Open manually: ${chalk16.cyan(issueUrl)}
14513
+ console.log(chalk17.yellow(" \u26A0 Could not open browser automatically."));
14514
+ console.log(` Open manually: ${chalk17.cyan(issueUrl)}
14110
14515
  `);
14111
14516
  }
14112
14517
  });
@@ -14115,8 +14520,8 @@ var feedbackCommand = new Command15("feedback").description("Open a pre-filled G
14115
14520
  init_dist();
14116
14521
  init_dist();
14117
14522
  init_dist3();
14118
- import { Command as Command16 } from "commander";
14119
- import chalk17 from "chalk";
14523
+ import { Command as Command17 } from "commander";
14524
+ import chalk18 from "chalk";
14120
14525
  function getDateStr(yesterday) {
14121
14526
  return iso(daysAgo(yesterday ? 1 : 0));
14122
14527
  }
@@ -14135,7 +14540,7 @@ function formatStandup(standups, dateStr, raw) {
14135
14540
  const lines = [];
14136
14541
  const header = `Standup \u2014 ${dateStr}`;
14137
14542
  if (!raw) {
14138
- lines.push(chalk17.bold(`
14543
+ lines.push(chalk18.bold(`
14139
14544
  ${header}
14140
14545
  `));
14141
14546
  } else {
@@ -14145,12 +14550,12 @@ ${header}
14145
14550
  let hasBlockers = false;
14146
14551
  for (const s of standups) {
14147
14552
  if (!raw) {
14148
- lines.push(chalk17.cyan.bold(`## ${s.name}`));
14553
+ lines.push(chalk18.cyan.bold(`## ${s.name}`));
14149
14554
  } else {
14150
14555
  lines.push(`## ${s.name}`);
14151
14556
  }
14152
14557
  if (s.gitCommits.length > 0) {
14153
- lines.push(!raw ? chalk17.green("Done:") : "**Done:**");
14558
+ lines.push(!raw ? chalk18.green("Done:") : "**Done:**");
14154
14559
  for (const commit of s.gitCommits) {
14155
14560
  lines.push(` - ${commit}`);
14156
14561
  }
@@ -14162,7 +14567,7 @@ ${header}
14162
14567
  if (match) {
14163
14568
  const items = match[1].trim().split("\n").filter((l) => l.trim().startsWith("-"));
14164
14569
  if (items.length > 0 && section === "Tomorrow") {
14165
- lines.push(!raw ? chalk17.blue("Next:") : "**Next:**");
14570
+ lines.push(!raw ? chalk18.blue("Next:") : "**Next:**");
14166
14571
  for (const item of items) lines.push(` ${item.trim()}`);
14167
14572
  }
14168
14573
  }
@@ -14170,19 +14575,19 @@ ${header}
14170
14575
  }
14171
14576
  if (s.blockers.length > 0) {
14172
14577
  hasBlockers = true;
14173
- lines.push(!raw ? chalk17.red("Blocked:") : "**Blocked:**");
14578
+ lines.push(!raw ? chalk18.red("Blocked:") : "**Blocked:**");
14174
14579
  for (const b of s.blockers) {
14175
14580
  lines.push(` - ${b}`);
14176
14581
  }
14177
14582
  }
14178
14583
  if (s.gitCommits.length === 0 && !s.dailyLog) {
14179
- lines.push(!raw ? chalk17.dim(" (no activity)") : " (no activity)");
14584
+ lines.push(!raw ? chalk18.dim(" (no activity)") : " (no activity)");
14180
14585
  }
14181
14586
  lines.push("");
14182
14587
  }
14183
14588
  const totalCommits = standups.reduce((sum, s) => sum + s.gitCommits.length, 0);
14184
14589
  if (!raw) {
14185
- lines.push(chalk17.dim(`--- ${totalCommits} commits${hasBlockers ? ", HAS BLOCKERS" : ""} (task tracking: no data source \u2014 #630) ---
14590
+ lines.push(chalk18.dim(`--- ${totalCommits} commits${hasBlockers ? ", HAS BLOCKERS" : ""} (task tracking: no data source \u2014 #630) ---
14186
14591
  `));
14187
14592
  } else {
14188
14593
  lines.push(`---
@@ -14191,12 +14596,12 @@ ${header}
14191
14596
  }
14192
14597
  return lines.join("\n");
14193
14598
  }
14194
- 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) => {
14599
+ var standupCommand = new Command17("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) => {
14195
14600
  const config = loadConfig();
14196
14601
  const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
14197
14602
  const projects = Object.entries(config.projects);
14198
14603
  if (projects.length === 0) {
14199
- console.log(chalk17.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
14604
+ console.log(chalk18.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
14200
14605
  return;
14201
14606
  }
14202
14607
  const dateStr = getDateStr(!!opts.yesterday);
@@ -14205,7 +14610,7 @@ var standupCommand = new Command16("standup").description("Generate daily standu
14205
14610
  if (opts.project) {
14206
14611
  const match = projects.find(([name]) => name === opts.project);
14207
14612
  if (!match) {
14208
- console.error(chalk17.red(`Project "${opts.project}" not found.`));
14613
+ console.error(chalk18.red(`Project "${opts.project}" not found.`));
14209
14614
  process.exit(1);
14210
14615
  }
14211
14616
  targets = [match];
@@ -14223,8 +14628,8 @@ var standupCommand = new Command16("standup").description("Generate daily standu
14223
14628
  init_dist();
14224
14629
  init_dist();
14225
14630
  init_dist3();
14226
- import { Command as Command17 } from "commander";
14227
- import chalk18 from "chalk";
14631
+ import { Command as Command18 } from "commander";
14632
+ import chalk19 from "chalk";
14228
14633
  function dedupe(items) {
14229
14634
  const seen = /* @__PURE__ */ new Set();
14230
14635
  const out = [];
@@ -14280,7 +14685,7 @@ function formatRetro(retros, fromStr, toStr, raw) {
14280
14685
  const lines = [];
14281
14686
  const header = `Retro \u2014 ${fromStr} \u2192 ${toStr}`;
14282
14687
  lines.push(raw ? `# ${header}
14283
- ` : chalk18.bold(`
14688
+ ` : chalk19.bold(`
14284
14689
  ${header}
14285
14690
  `));
14286
14691
  let totalCommits = 0;
@@ -14290,39 +14695,39 @@ ${header}
14290
14695
  totalCommits += r.commits.length;
14291
14696
  totalPRs += r.mergedPRs.length;
14292
14697
  totalShipped += r.shipped.length;
14293
- lines.push(raw ? `## ${r.name}` : chalk18.cyan.bold(`## ${r.name}`));
14294
- renderList(lines, r.shipped, raw, "Shipped", chalk18.green);
14698
+ lines.push(raw ? `## ${r.name}` : chalk19.cyan.bold(`## ${r.name}`));
14699
+ renderList(lines, r.shipped, raw, "Shipped", chalk19.green);
14295
14700
  if (r.mergedPRs.length > 0) {
14296
- lines.push(raw ? `**PRs merged:**` : chalk18.green("PRs merged:"));
14701
+ lines.push(raw ? `**PRs merged:**` : chalk19.green("PRs merged:"));
14297
14702
  for (const pr of r.mergedPRs) lines.push(` - ${pr}`);
14298
14703
  }
14299
- renderList(lines, r.inProgress, raw, "In Progress", chalk18.yellow);
14300
- renderList(lines, r.blocked, raw, "Blocked", chalk18.red);
14301
- renderList(lines, r.decisions, raw, "Key Decisions", chalk18.magenta);
14704
+ renderList(lines, r.inProgress, raw, "In Progress", chalk19.yellow);
14705
+ renderList(lines, r.blocked, raw, "Blocked", chalk19.red);
14706
+ renderList(lines, r.decisions, raw, "Key Decisions", chalk19.magenta);
14302
14707
  const metricBits = [
14303
14708
  `${r.commits.length} commits`,
14304
14709
  `${r.mergedPRs.length} PRs merged`,
14305
14710
  `${r.shipped.length} shipped`
14306
14711
  ];
14307
- lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` : chalk18.dim(` ${metricBits.join(" \xB7 ")}`));
14712
+ lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` : chalk19.dim(` ${metricBits.join(" \xB7 ")}`));
14308
14713
  if (r.shipped.length === 0 && r.commits.length === 0 && r.mergedPRs.length === 0 && r.inProgress.length === 0 && r.blocked.length === 0) {
14309
- lines.push(raw ? "_(no activity in this window)_" : chalk18.dim(" (no activity in this window)"));
14714
+ lines.push(raw ? "_(no activity in this window)_" : chalk19.dim(" (no activity in this window)"));
14310
14715
  }
14311
14716
  lines.push("");
14312
14717
  }
14313
14718
  const summary = `${totalShipped} items shipped \xB7 ${totalCommits} commits \xB7 ${totalPRs} PRs merged`;
14314
14719
  lines.push(raw ? `---
14315
14720
  *${summary}*
14316
- ` : chalk18.dim(`--- ${summary} ---
14721
+ ` : chalk19.dim(`--- ${summary} ---
14317
14722
  `));
14318
14723
  return lines.join("\n");
14319
14724
  }
14320
- 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) => {
14725
+ var retroCommand = new Command18("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) => {
14321
14726
  const config = loadConfig();
14322
14727
  const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
14323
14728
  const projects = Object.entries(config.projects);
14324
14729
  if (projects.length === 0) {
14325
- console.log(chalk18.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
14730
+ console.log(chalk19.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
14326
14731
  return;
14327
14732
  }
14328
14733
  let windowDays = 7;
@@ -14339,7 +14744,7 @@ var retroCommand = new Command17("retro").description("Generate a retro (weekly/
14339
14744
  if (opts.project) {
14340
14745
  const match = projects.find(([name]) => name === opts.project);
14341
14746
  if (!match) {
14342
- console.error(chalk18.red(`Project "${opts.project}" not found.`));
14747
+ console.error(chalk19.red(`Project "${opts.project}" not found.`));
14343
14748
  process.exit(1);
14344
14749
  }
14345
14750
  targets = [match];
@@ -14352,153 +14757,8 @@ var retroCommand = new Command17("retro").description("Generate a retro (weekly/
14352
14757
  console.log(formatRetro(retros, fromStr, toStr, raw));
14353
14758
  });
14354
14759
 
14355
- // packages/cli/src/commands/runtime.ts
14356
- init_dist();
14357
- init_dist3();
14358
- import { Command as Command18 } from "commander";
14359
- import chalk19 from "chalk";
14360
- function buildRegistry() {
14361
- return new RuntimeRegistry({
14362
- cmux: createCmuxDriver()
14363
- });
14364
- }
14365
- function resolveTarget(registry, config, target, useCommand) {
14366
- if (useCommand) {
14367
- return {
14368
- driver: registry.global(config),
14369
- workspaceName: config.commandName
14370
- };
14371
- }
14372
- if (!target) {
14373
- throw new Error("Missing target: pass a project name or use --command");
14374
- }
14375
- const proj = config.projects[target];
14376
- if (!proj) {
14377
- throw new Error(`Project '${target}' not found. Run 'squadrant projects list'.`);
14378
- }
14379
- return {
14380
- driver: registry.forProject(target, config),
14381
- workspaceName: proj.captainName
14382
- };
14383
- }
14384
- async function needRef(resolved) {
14385
- const ref = await resolved.driver.status(resolved.workspaceName);
14386
- if (!ref) {
14387
- throw new Error(`Workspace '${resolved.workspaceName}' is not running`);
14388
- }
14389
- return ref.id;
14390
- }
14391
- var runtimeCommand = new Command18("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
14392
- 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) => {
14393
- const config = loadConfig();
14394
- const registry = buildRegistry();
14395
- try {
14396
- const resolved = resolveTarget(registry, config, target, !!opts.command);
14397
- const ref = await resolved.driver.status(resolved.workspaceName);
14398
- if (ref) {
14399
- console.log("running");
14400
- process.exit(0);
14401
- } else {
14402
- console.log("stopped");
14403
- process.exit(1);
14404
- }
14405
- } catch (err) {
14406
- console.error(chalk19.red(err.message));
14407
- process.exit(2);
14408
- }
14409
- });
14410
- var SEND_CONFIRM_TIMEOUT_MS = 15e3;
14411
- var SEND_CONFIRM_POLL_MS = 500;
14412
- async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
14413
- const config = loadConfig();
14414
- const registry = buildRegistry();
14415
- if (opts.command && arg2 !== void 0) {
14416
- throw new Error("With --command, pass only the message (not a project name)");
14417
- }
14418
- const target = opts.command ? void 0 : arg1;
14419
- const message = opts.command ? arg1 : arg2;
14420
- if (!message) throw new Error("Message is required");
14421
- const { requireDaemon: requireDaemon2 } = await Promise.resolve().then(() => (init_require_daemon(), require_daemon_exports));
14422
- const { appendCaptainMessage: appendCaptainMessage2, waitForCaptainDelivery: waitForCaptainDelivery2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
14423
- await requireDaemon2();
14424
- const resolved = resolveTarget(registry, config, target, !!opts.command);
14425
- await needRef(resolved);
14426
- const finalProject = opts.command ? config.commandName : target;
14427
- const { join: join31, dirname: dirname10 } = await import("path");
14428
- const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
14429
- const stateRoot = join31(dirname10(DEFAULT_CONFIG_PATH2), "state");
14430
- const seq = await appendCaptainMessage2({
14431
- stateRoot,
14432
- project: finalProject,
14433
- text: message,
14434
- source: "cli"
14435
- });
14436
- const timeoutMs = confirmOpts?.timeoutMs ?? SEND_CONFIRM_TIMEOUT_MS;
14437
- const delivered = await waitForCaptainDelivery2({
14438
- stateRoot,
14439
- project: finalProject,
14440
- seq,
14441
- timeoutMs,
14442
- pollMs: confirmOpts?.pollMs ?? SEND_CONFIRM_POLL_MS
14443
- });
14444
- if (!delivered) {
14445
- throw new Error(
14446
- `Message queued for '${finalProject}' (seq=${seq}) but delivery was not confirmed within ${Math.round(timeoutMs / 1e3)}s. It may still be pending \u2014 check with 'squadrant runtime read-screen ${finalProject}${opts.command ? " --command" : ""}'.`
14447
- );
14448
- }
14449
- }
14450
- 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) => {
14451
- try {
14452
- await runRuntimeSend(arg1, arg2, opts);
14453
- console.log(chalk19.green("\u2714 Delivered (confirmed)"));
14454
- } catch (err) {
14455
- console.error(chalk19.red(err.message));
14456
- process.exit(1);
14457
- }
14458
- });
14459
- runtimeCommand.command("list").description("List all workspaces from the global runtime").option("-j, --json", "Output as JSON").action(async (opts) => {
14460
- const config = loadConfig();
14461
- const registry = buildRegistry();
14462
- const driver = registry.global(config);
14463
- const refs = await driver.list();
14464
- if (opts.json) {
14465
- console.log(JSON.stringify(refs, null, 2));
14466
- } else {
14467
- for (const r of refs) {
14468
- console.log(`${r.id} ${r.name} ${r.status}`);
14469
- }
14470
- }
14471
- });
14472
- runtimeCommand.command("read-screen").description("Print a terminal snapshot of a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
14473
- const config = loadConfig();
14474
- const registry = buildRegistry();
14475
- try {
14476
- const resolved = resolveTarget(registry, config, target, !!opts.command);
14477
- const ref = await needRef(resolved);
14478
- const screen = await resolved.driver.readScreen(ref);
14479
- process.stdout.write(screen);
14480
- } catch (err) {
14481
- console.error(chalk19.red(err.message));
14482
- process.exit(1);
14483
- }
14484
- });
14485
- runtimeCommand.command("stop").description("Stop a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
14486
- const config = loadConfig();
14487
- const registry = buildRegistry();
14488
- try {
14489
- const resolved = resolveTarget(registry, config, target, !!opts.command);
14490
- const ref = await resolved.driver.status(resolved.workspaceName);
14491
- if (!ref) {
14492
- console.log(chalk19.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
14493
- return;
14494
- }
14495
- await resolved.driver.stop(ref.id);
14496
- console.log(chalk19.green(`\u2714 Stopped ${resolved.workspaceName}`));
14497
- } catch (err) {
14498
- console.error(chalk19.red(err.message));
14499
- process.exit(1);
14500
- }
14501
- });
14760
+ // packages/cli/src/index.ts
14761
+ init_runtime2();
14502
14762
 
14503
14763
  // packages/cli/src/commands/workspace.ts
14504
14764
  init_dist();
@@ -15149,10 +15409,11 @@ var groupCommand = new Command26("group").description("Cross-project intra-group
15149
15409
  // packages/cli/src/commands/ping.ts
15150
15410
  init_dist();
15151
15411
  init_dist2();
15412
+ init_runtime2();
15413
+ init_require_daemon();
15152
15414
  import { join as join27, dirname as dirname7 } from "path";
15153
15415
  import { Command as Command27 } from "commander";
15154
15416
  import chalk27 from "chalk";
15155
- init_require_daemon();
15156
15417
  async function runPing(project, message) {
15157
15418
  const config = loadConfig();
15158
15419
  const registry = buildRegistry();
@@ -16395,7 +16656,7 @@ function gatherOpenPRs(runner, projectPath) {
16395
16656
  }
16396
16657
  }
16397
16658
  function gatherLiveCrews(tasks) {
16398
- return tasks.filter((t) => !TERMINAL_STATES.has(t.state)).map((t) => ({ name: t.name ?? t.id, state: t.state, task: t.task, question: t.question }));
16659
+ return tasks.filter((t) => !TERMINAL_STATES.has(t.state)).map((t) => ({ name: t.name ?? t.id, state: t.state, task: t.task, question: t.question, operatorHold: t.operatorHold }));
16399
16660
  }
16400
16661
  function ghAheadOfBase(runner, projectPath, nameWithOwner, base, branch) {
16401
16662
  return tryInt(