squadrant 0.14.3 → 0.15.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
@@ -168,11 +168,16 @@ var init_project_config = __esm({
168
168
  });
169
169
 
170
170
  // packages/shared/dist/effort.js
171
- function resolveEffort(config) {
171
+ function resolveEffort(config, projectName, projectConfigRoot) {
172
+ if (projectName) {
173
+ const override = loadProjectOverride(projectName, projectConfigRoot);
174
+ return override.effort ?? config.defaults.effort ?? "balance";
175
+ }
172
176
  return config.defaults.effort ?? "balance";
173
177
  }
174
178
  var init_effort = __esm({
175
179
  "packages/shared/dist/effort.js"() {
180
+ init_project_config();
176
181
  }
177
182
  });
178
183
 
@@ -182,6 +187,12 @@ var init_runtime = __esm({
182
187
  }
183
188
  });
184
189
 
190
+ // packages/shared/dist/types/liveness.js
191
+ var init_liveness = __esm({
192
+ "packages/shared/dist/types/liveness.js"() {
193
+ }
194
+ });
195
+
185
196
  // packages/shared/dist/types/control.js
186
197
  var TERMINAL_STATES;
187
198
  var init_control = __esm({
@@ -1040,6 +1051,7 @@ var init_dist = __esm({
1040
1051
  init_project_config();
1041
1052
  init_effort();
1042
1053
  init_runtime();
1054
+ init_liveness();
1043
1055
  init_control();
1044
1056
  init_projection();
1045
1057
  init_workspaces();
@@ -1202,7 +1214,7 @@ function healCmdFor(c) {
1202
1214
  return null;
1203
1215
  }
1204
1216
  var CREW_STALE_MS, CREW_GONE_MS;
1205
- var init_liveness = __esm({
1217
+ var init_liveness2 = __esm({
1206
1218
  "packages/core/dist/liveness.js"() {
1207
1219
  CREW_STALE_MS = 5 * 6e4;
1208
1220
  CREW_GONE_MS = 30 * 6e4;
@@ -1422,15 +1434,24 @@ var init_gate = __esm({
1422
1434
  }
1423
1435
  });
1424
1436
 
1437
+ // packages/core/dist/daemon/liveness-registry.js
1438
+ import { writeFileSync as writeFileSync6, readFileSync as readFileSync7, renameSync as renameSync2 } from "fs";
1439
+ var init_liveness_registry = __esm({
1440
+ "packages/core/dist/daemon/liveness-registry.js"() {
1441
+ init_liveness2();
1442
+ }
1443
+ });
1444
+
1425
1445
  // packages/core/dist/daemon/context.js
1426
1446
  import { homedir as homedir5 } from "os";
1427
1447
  import { join as join8 } from "path";
1428
1448
  import { spawn as realSpawn } from "child_process";
1429
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5 } from "fs";
1449
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
1430
1450
  var init_context = __esm({
1431
1451
  "packages/core/dist/daemon/context.js"() {
1432
1452
  init_store();
1433
1453
  init_dist();
1454
+ init_liveness_registry();
1434
1455
  }
1435
1456
  });
1436
1457
 
@@ -1489,6 +1510,7 @@ var init_delivery_loop = __esm({
1489
1510
  init_captain_delivery();
1490
1511
  init_dist();
1491
1512
  init_interactive_probe();
1513
+ init_liveness2();
1492
1514
  }
1493
1515
  });
1494
1516
 
@@ -1508,7 +1530,7 @@ var init_server = __esm({
1508
1530
  // packages/core/dist/daemon/snapshot-gather.js
1509
1531
  import { fileURLToPath as fileURLToPath2 } from "url";
1510
1532
  import { join as join9 } from "path";
1511
- import { statSync as statSync2, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
1533
+ import { statSync as statSync2, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
1512
1534
  var SELF_PATH;
1513
1535
  var init_snapshot_gather = __esm({
1514
1536
  "packages/core/dist/daemon/snapshot-gather.js"() {
@@ -1528,7 +1550,7 @@ var init_start = __esm({
1528
1550
  init_gates();
1529
1551
  init_server();
1530
1552
  init_mailbox();
1531
- init_liveness();
1553
+ init_liveness2();
1532
1554
  init_dist();
1533
1555
  init_snapshot_gather();
1534
1556
  SNAPSHOT_LOG_WINDOW_MS = 60 * 60 * 1e3;
@@ -2165,7 +2187,7 @@ async function isCaptainAlive(project, sockPath = DEFAULT_SOCK_PATH2) {
2165
2187
  try {
2166
2188
  const health = await sendRequest(sockPath, { kind: "health", project }, 5e3);
2167
2189
  const captain = health?.find((h) => h.kind === "captain" && h.project === project);
2168
- return captain != null && captain.state !== "gone" && captain.state !== "unknown";
2190
+ return captain?.state === "alive";
2169
2191
  } catch {
2170
2192
  return false;
2171
2193
  }
@@ -2787,7 +2809,7 @@ var init_dist2 = __esm({
2787
2809
  init_mailbox();
2788
2810
  init_protocol();
2789
2811
  init_state_machine();
2790
- init_liveness();
2812
+ init_liveness2();
2791
2813
  init_watchdog();
2792
2814
  init_store();
2793
2815
  init_snapshot();
@@ -3661,11 +3683,81 @@ var init_events_bridge = __esm({
3661
3683
  }
3662
3684
  });
3663
3685
 
3686
+ // packages/workspaces/dist/cmux-daemon/store-fingerprint.js
3687
+ function roleFromTemplate(args) {
3688
+ const i = args?.indexOf("--append-system-prompt-file") ?? -1;
3689
+ const tmpl = i >= 0 && args ? (args[i + 1] ?? "").split("/").pop() ?? "" : "";
3690
+ if (tmpl.startsWith("captain"))
3691
+ return "captain";
3692
+ if (tmpl.startsWith("crew"))
3693
+ return "crew";
3694
+ if (tmpl.startsWith("command"))
3695
+ return "command";
3696
+ return "unknown";
3697
+ }
3698
+ function projectFromCwd(cwd, projects) {
3699
+ for (const [name, p] of Object.entries(projects)) {
3700
+ const projPath = resolveHome(p.path);
3701
+ if (cwd === projPath || cwd.startsWith(`${projPath}/`))
3702
+ return name;
3703
+ }
3704
+ return void 0;
3705
+ }
3706
+ function parseStoreRecords(fileContent, projects) {
3707
+ let parsed;
3708
+ try {
3709
+ parsed = JSON.parse(fileContent);
3710
+ } catch (e) {
3711
+ throw new Error(`parseStoreRecords: invalid JSON: ${e.message}`);
3712
+ }
3713
+ const out = [];
3714
+ for (const s of Object.values(parsed.sessions ?? {})) {
3715
+ const cwd = s.cwd ?? s.launchCommand?.workingDirectory ?? "";
3716
+ const project = projectFromCwd(cwd, projects);
3717
+ if (!project || !s.sessionId)
3718
+ continue;
3719
+ out.push({
3720
+ role: roleFromTemplate(s.launchCommand?.arguments),
3721
+ project,
3722
+ pid: typeof s.pid === "number" ? s.pid : null,
3723
+ sessionId: s.sessionId,
3724
+ present: true,
3725
+ isRestorable: s.isRestorable
3726
+ });
3727
+ }
3728
+ return out;
3729
+ }
3730
+ function readLivenessSnapshot(files, readFile6, projects) {
3731
+ const out = [];
3732
+ let successes = 0;
3733
+ for (const f of files) {
3734
+ try {
3735
+ out.push(...parseStoreRecords(readFile6(f), projects));
3736
+ successes++;
3737
+ } catch {
3738
+ }
3739
+ }
3740
+ if (files.length > 0 && successes === 0) {
3741
+ throw new Error(`readLivenessSnapshot: all ${files.length} store file(s) unreadable/corrupt this tick`);
3742
+ }
3743
+ return out;
3744
+ }
3745
+ var init_store_fingerprint = __esm({
3746
+ "packages/workspaces/dist/cmux-daemon/store-fingerprint.js"() {
3747
+ init_dist();
3748
+ }
3749
+ });
3750
+
3664
3751
  // packages/workspaces/dist/cmux-daemon/daemon-cmux.js
3752
+ import { readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
3753
+ import { join as join13 } from "path";
3754
+ import { homedir as homedir8 } from "os";
3665
3755
  var DaemonCmux;
3666
3756
  var init_daemon_cmux = __esm({
3667
3757
  "packages/workspaces/dist/cmux-daemon/daemon-cmux.js"() {
3668
3758
  init_dist2();
3759
+ init_dist();
3760
+ init_store_fingerprint();
3669
3761
  DaemonCmux = class {
3670
3762
  driver;
3671
3763
  constructor(driver) {
@@ -3716,14 +3808,30 @@ var init_daemon_cmux = __esm({
3716
3808
  return false;
3717
3809
  }
3718
3810
  }
3811
+ /**
3812
+ * Ground-truth liveness from cmux's own hook-sessions store (§5.4).
3813
+ * THROWS (does not return []) when the dir can't be listed, or every store
3814
+ * file failed to read/parse — see the class doc above.
3815
+ */
3816
+ async liveness() {
3817
+ const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join13(homedir8(), ".cmuxterm");
3818
+ const projects = loadConfig().projects;
3819
+ let files;
3820
+ try {
3821
+ files = readdirSync3(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
3822
+ } catch (e) {
3823
+ throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
3824
+ }
3825
+ return readLivenessSnapshot(files, (f) => readFileSync9(join13(dir, f), "utf-8"), projects);
3826
+ }
3719
3827
  };
3720
3828
  }
3721
3829
  });
3722
3830
 
3723
3831
  // packages/workspaces/dist/cmux-daemon/cmux-store-source.js
3724
- import { join as join13 } from "path";
3725
- import { homedir as homedir8 } from "os";
3726
- import { watch, readdirSync as readdirSync3, readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
3832
+ import { join as join14 } from "path";
3833
+ import { homedir as homedir9 } from "os";
3834
+ import { watch, readdirSync as readdirSync4, readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
3727
3835
  function parseLifecycleState(s) {
3728
3836
  if (s === "running" || s === "idle" || s === "needsInput" || s === "unknown") {
3729
3837
  return s;
@@ -3740,14 +3848,14 @@ function defaultIsPidAlive(pid) {
3740
3848
  }
3741
3849
  function defaultListFiles(dir) {
3742
3850
  try {
3743
- return readdirSync3(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
3851
+ return readdirSync4(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
3744
3852
  } catch {
3745
3853
  return [];
3746
3854
  }
3747
3855
  }
3748
3856
  function defaultReadFile(path29) {
3749
3857
  try {
3750
- return readFileSync8(path29, "utf-8");
3858
+ return readFileSync10(path29, "utf-8");
3751
3859
  } catch {
3752
3860
  return void 0;
3753
3861
  }
@@ -3783,7 +3891,7 @@ var init_cmux_store_source = __esm({
3783
3891
  active = false;
3784
3892
  lastError = null;
3785
3893
  constructor(opts = {}) {
3786
- this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join13(homedir8(), ".cmuxterm");
3894
+ this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join14(homedir9(), ".cmuxterm");
3787
3895
  this.debounceMs = opts.debounceMs ?? 50;
3788
3896
  this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
3789
3897
  this.listFiles = opts.listFiles ?? defaultListFiles;
@@ -3845,7 +3953,7 @@ var init_cmux_store_source = __esm({
3845
3953
  }
3846
3954
  scanFile(filename) {
3847
3955
  const deps = this.deps;
3848
- const filePath = join13(this.stateDir, filename);
3956
+ const filePath = join14(this.stateDir, filename);
3849
3957
  const lockPath = `${filePath}.lock`;
3850
3958
  if (this.fileExists(lockPath)) {
3851
3959
  this.log(`cmux-store: skipping ${filename} (locked)`);
@@ -3899,11 +4007,11 @@ var init_cmux_store_source = __esm({
3899
4007
  });
3900
4008
 
3901
4009
  // packages/workspaces/dist/native-hooks/native-hook-source.js
3902
- import { join as join14 } from "path";
3903
- import { homedir as homedir9 } from "os";
3904
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
4010
+ import { join as join15 } from "path";
4011
+ import { homedir as homedir10 } from "os";
4012
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
3905
4013
  function installClaudeHooks(opts = {}) {
3906
- const settingsPath = opts.settingsPath ?? join14(homedir9(), ".claude", "settings.json");
4014
+ const settingsPath = opts.settingsPath ?? join15(homedir10(), ".claude", "settings.json");
3907
4015
  const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
3908
4016
  const readFile6 = opts.readFile ?? defaultReadFile2;
3909
4017
  const writeFile5 = opts.writeFile ?? defaultWriteFile;
@@ -3977,14 +4085,14 @@ function extractDetail(sub, payload) {
3977
4085
  }
3978
4086
  function defaultReadFile2(path29) {
3979
4087
  try {
3980
- return readFileSync9(path29, "utf-8");
4088
+ return readFileSync11(path29, "utf-8");
3981
4089
  } catch {
3982
4090
  return void 0;
3983
4091
  }
3984
4092
  }
3985
4093
  function defaultWriteFile(path29, content) {
3986
4094
  mkdirSync6(path29.replace(/\/[^/]+$/, ""), { recursive: true });
3987
- writeFileSync7(path29, content, "utf-8");
4095
+ writeFileSync8(path29, content, "utf-8");
3988
4096
  }
3989
4097
  var CLAUDE_HOOK_EVENTS, DEFAULT_HOOK_CMD, NativeHookSource;
3990
4098
  var init_native_hook_source = __esm({
@@ -5313,11 +5421,11 @@ var init_codex_app_server_source = __esm({
5313
5421
 
5314
5422
  // packages/agents/dist/codex/config.js
5315
5423
  import { readFile as readFile5 } from "fs/promises";
5316
- import { homedir as homedir11 } from "os";
5317
- import { join as join16 } from "path";
5424
+ import { homedir as homedir12 } from "os";
5425
+ import { join as join17 } from "path";
5318
5426
  async function resolveCodexModel() {
5319
- const home = process.env["CODEX_HOME"] ?? join16(homedir11(), ".codex");
5320
- const configPath = join16(home, "config.toml");
5427
+ const home = process.env["CODEX_HOME"] ?? join17(homedir12(), ".codex");
5428
+ const configPath = join17(home, "config.toml");
5321
5429
  let text;
5322
5430
  try {
5323
5431
  text = await readFile5(configPath, "utf8");
@@ -5838,9 +5946,9 @@ var init_sse_bridge = __esm({
5838
5946
 
5839
5947
  // packages/agents/dist/interactive/claude.js
5840
5948
  import { execSync as execSync7 } from "child_process";
5841
- import { readFileSync as readFileSync10 } from "fs";
5842
- import { homedir as homedir12 } from "os";
5843
- import { join as join17 } from "path";
5949
+ import { readFileSync as readFileSync12 } from "fs";
5950
+ import { homedir as homedir13 } from "os";
5951
+ import { join as join18 } from "path";
5844
5952
  function probeClaudeSettingsFlag() {
5845
5953
  try {
5846
5954
  const help = execSync7("claude --help", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
@@ -5891,11 +5999,11 @@ function deriveTranscriptPath(sessionId, cwd) {
5891
5999
  if (!sessionId || !cwd)
5892
6000
  return null;
5893
6001
  const escaped = cwd.replace(/[^a-zA-Z0-9]/g, "-");
5894
- return join17(homedir12(), ".claude", "projects", escaped, `${sessionId}.jsonl`);
6002
+ return join18(homedir13(), ".claude", "projects", escaped, `${sessionId}.jsonl`);
5895
6003
  }
5896
6004
  function readLastAssistantText(transcriptPath) {
5897
6005
  try {
5898
- const raw = readFileSync10(transcriptPath, "utf-8");
6006
+ const raw = readFileSync12(transcriptPath, "utf-8");
5899
6007
  const lines = raw.split(/\r?\n/);
5900
6008
  for (let i = lines.length - 1; i >= 0; i--) {
5901
6009
  const line = lines[i].trim();
@@ -6362,10 +6470,10 @@ var init_dist4 = __esm({
6362
6470
  init_dist();
6363
6471
  init_dist2();
6364
6472
  import { Command as Command31 } from "commander";
6365
- import { readFileSync as readFileSync13, existsSync as existsSync12, writeFileSync as writeFileSync10 } from "fs";
6473
+ import { readFileSync as readFileSync15, existsSync as existsSync12, writeFileSync as writeFileSync11 } from "fs";
6366
6474
  import { fileURLToPath as fileURLToPath6 } from "url";
6367
- import { dirname as dirname7, join as join26 } from "path";
6368
- import { homedir as homedir18 } from "os";
6475
+ import { dirname as dirname8, join as join27 } from "path";
6476
+ import { homedir as homedir20 } from "os";
6369
6477
 
6370
6478
  // packages/cli/src/commands/doctor.ts
6371
6479
  init_dist();
@@ -6382,10 +6490,10 @@ import chalk3 from "chalk";
6382
6490
  // packages/cli/src/commands/health-view.ts
6383
6491
  init_dist2();
6384
6492
  init_dist2();
6385
- import { homedir as homedir10 } from "os";
6386
- import { join as join15 } from "path";
6493
+ import { homedir as homedir11 } from "os";
6494
+ import { join as join16 } from "path";
6387
6495
  import chalk2 from "chalk";
6388
- var SOCK = join15(homedir10(), ".config", "squadrant", "squadrant.sock");
6496
+ var SOCK = join16(homedir11(), ".config", "squadrant", "squadrant.sock");
6389
6497
  async function queryHealth(project) {
6390
6498
  try {
6391
6499
  const reply = await sendRequest(SOCK, { kind: "health", project });
@@ -6715,13 +6823,161 @@ ${passed === total ? chalk3.green("All checks passed") : chalk3.yellow(`${passed
6715
6823
  init_dist();
6716
6824
  init_dist3();
6717
6825
  init_dist();
6718
- init_dist4();
6719
6826
  import { Command as Command2 } from "commander";
6720
6827
  import fs16 from "fs";
6721
6828
  import path18 from "path";
6722
6829
  import os9 from "os";
6723
6830
  import readline from "readline";
6724
6831
  import chalk4 from "chalk";
6832
+
6833
+ // packages/cli/src/lib/per-crew-settings.ts
6834
+ init_dist4();
6835
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
6836
+ import { dirname as dirname5, join as join19 } from "path";
6837
+ import { homedir as homedir14 } from "os";
6838
+ var CREW_PERMISSION_ALLOWLIST = [
6839
+ // git — read + safe mutations (reset/clean/config intentionally excluded)
6840
+ "Bash(git status:*)",
6841
+ "Bash(git add:*)",
6842
+ "Bash(git commit:*)",
6843
+ "Bash(git push:*)",
6844
+ "Bash(git pull:*)",
6845
+ "Bash(git fetch:*)",
6846
+ "Bash(git checkout:*)",
6847
+ "Bash(git branch:*)",
6848
+ "Bash(git diff:*)",
6849
+ "Bash(git log:*)",
6850
+ "Bash(git stash:*)",
6851
+ "Bash(git restore:*)",
6852
+ // git — read-only queries (no-mutation subcommands)
6853
+ "Bash(git show:*)",
6854
+ "Bash(git rev-parse:*)",
6855
+ "Bash(git remote -v:*)",
6856
+ "Bash(git stash list:*)",
6857
+ // npm / npx — installs + script/test/build runners
6858
+ "Bash(npm install:*)",
6859
+ "Bash(npm ci:*)",
6860
+ "Bash(npm run:*)",
6861
+ "Bash(npm test:*)",
6862
+ "Bash(npm ls:*)",
6863
+ "Bash(npm view:*)",
6864
+ "Bash(npx vitest:*)",
6865
+ "Bash(npx tsc:*)",
6866
+ // direct runners
6867
+ "Bash(node:*)",
6868
+ "Bash(node --version:*)",
6869
+ "Bash(vitest:*)",
6870
+ "Bash(tsc:*)",
6871
+ // pnpm
6872
+ "Bash(pnpm ls:*)",
6873
+ // process / system read — non-interactive, read-only
6874
+ "Bash(ps:*)",
6875
+ "Bash(pgrep:*)",
6876
+ "Bash(lsof:*)",
6877
+ "Bash(top -l:*)",
6878
+ "Bash(uptime:*)",
6879
+ "Bash(whoami:*)",
6880
+ "Bash(uname:*)",
6881
+ "Bash(date:*)",
6882
+ "Bash(env:*)",
6883
+ "Bash(sysctl:*)",
6884
+ // file read / navigation
6885
+ "Bash(ls:*)",
6886
+ "Bash(cat:*)",
6887
+ "Bash(head:*)",
6888
+ "Bash(tail:*)",
6889
+ "Bash(wc:*)",
6890
+ "Bash(grep:*)",
6891
+ "Bash(rg:*)",
6892
+ "Bash(find:*)",
6893
+ "Bash(pwd:*)",
6894
+ "Bash(stat:*)",
6895
+ "Bash(file:*)",
6896
+ "Bash(realpath:*)",
6897
+ "Bash(basename:*)",
6898
+ "Bash(dirname:*)",
6899
+ "Bash(du:*)",
6900
+ "Bash(df:*)",
6901
+ "Bash(tree:*)",
6902
+ // read-only text processing
6903
+ "Bash(sort:*)",
6904
+ "Bash(uniq:*)",
6905
+ "Bash(cut:*)",
6906
+ "Bash(tr:*)",
6907
+ "Bash(jq:*)",
6908
+ "Bash(diff:*)",
6909
+ "Bash(column:*)",
6910
+ // harmless utilities
6911
+ "Bash(echo:*)",
6912
+ "Bash(which:*)",
6913
+ "Bash(true:*)",
6914
+ "Bash(sleep:*)",
6915
+ "Bash(printf:*)"
6916
+ ];
6917
+ function healStaleCockpitRefs(raw) {
6918
+ return raw.replaceAll("cockpit crew _hook", "squadrant crew _hook").replaceAll(".config/cockpit", ".config/squadrant").replaceAll("cockpit-hub", "squadrant-hub");
6919
+ }
6920
+ function mergeCrewPermissions(settings) {
6921
+ const next = structuredClone(settings ?? {});
6922
+ const permissions = next.permissions ??= {};
6923
+ const existing = Array.isArray(permissions.allow) ? permissions.allow : [];
6924
+ permissions.allow = [.../* @__PURE__ */ new Set([...existing, ...CREW_PERMISSION_ALLOWLIST])];
6925
+ return next;
6926
+ }
6927
+ function writePerCrewSettingsLocal(o) {
6928
+ const dir = join19(o.projectCwd, ".claude");
6929
+ mkdirSync7(dir, { recursive: true });
6930
+ const file = join19(dir, "settings.local.json");
6931
+ let existing = {};
6932
+ try {
6933
+ const raw = healStaleCockpitRefs(readFileSync13(file, "utf-8"));
6934
+ existing = JSON.parse(raw);
6935
+ } catch {
6936
+ }
6937
+ const withHooks = mergeClaudeHooks(existing, o.hookCmd ?? "squadrant crew _hook");
6938
+ const merged = mergeCrewPermissions(withHooks);
6939
+ writeFileSync9(file, JSON.stringify(merged, null, 2));
6940
+ return file;
6941
+ }
6942
+ var DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH = join19(homedir14(), ".config", "opencode", "opencode.json");
6943
+ function ensureGlobalOpencodeConfig(configPath = DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH) {
6944
+ mkdirSync7(dirname5(configPath), { recursive: true });
6945
+ const defaultConfig = {
6946
+ $schema: "https://opencode.ai/config.json",
6947
+ model: "anthropic/claude-sonnet-4-5"
6948
+ };
6949
+ try {
6950
+ writeFileSync9(configPath, JSON.stringify(defaultConfig, null, 2) + "\n", { flag: "wx" });
6951
+ return configPath;
6952
+ } catch (err) {
6953
+ if (err.code === "EEXIST") return null;
6954
+ throw err;
6955
+ }
6956
+ }
6957
+ function writePerCrewOpencodeConfig(o) {
6958
+ const dir = join19(o.stateRoot, o.project, o.taskId);
6959
+ mkdirSync7(dir, { recursive: true });
6960
+ const file = join19(dir, "opencode.json");
6961
+ const config = {
6962
+ permission: {
6963
+ read: "allow",
6964
+ edit: "allow",
6965
+ glob: "allow",
6966
+ grep: "allow",
6967
+ bash: o.gateBash ? "ask" : "allow",
6968
+ webfetch: "allow",
6969
+ websearch: "allow",
6970
+ task: "allow",
6971
+ lsp: "allow",
6972
+ external_directory: { "**": "allow" }
6973
+ }
6974
+ };
6975
+ writeFileSync9(file, JSON.stringify(config, null, 2));
6976
+ return file;
6977
+ }
6978
+
6979
+ // packages/cli/src/commands/init.ts
6980
+ init_dist4();
6725
6981
  function findPackageRoot() {
6726
6982
  let dir = path18.dirname(new URL(import.meta.url).pathname);
6727
6983
  while (dir !== "/") {
@@ -6771,6 +7027,9 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
6771
7027
  console.log(chalk4.cyan(" /plugin marketplace add superpowers"));
6772
7028
  console.log(chalk4.cyan(" /plugin marketplace add thedotmack/claude-mem"));
6773
7029
  console.log(chalk4.cyan(" /plugin marketplace add context7"));
7030
+ console.log(chalk4.dim("\n Using opencode crews? Install the CLI:"));
7031
+ console.log(chalk4.cyan(" npm install -g opencode-ai"));
7032
+ console.log(chalk4.dim(` (squadrant provisions a default model in ${DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH} if it's missing)`));
6774
7033
  console.log(chalk4.bold("\n 4/5 Register first project"));
6775
7034
  console.log(chalk4.cyan(" squadrant projects add <name> <path>"));
6776
7035
  console.log(chalk4.bold("\n 5/5 Telegram (optional)"));
@@ -6866,6 +7125,15 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
6866
7125
  console.log(chalk4.cyan(" /plugin marketplace add thedotmack/claude-mem"));
6867
7126
  console.log(chalk4.cyan(" /plugin marketplace add context7\n"));
6868
7127
  console.log(chalk4.dim(" Squadrant never auto-installs plugins \u2014 open Claude Code and run the commands above."));
7128
+ console.log(chalk4.bold("\n Using opencode crews?"));
7129
+ console.log(chalk4.cyan(" npm install -g opencode-ai"));
7130
+ const opencodeConfigWritten = ensureGlobalOpencodeConfig();
7131
+ if (opencodeConfigWritten) {
7132
+ console.log(chalk4.green(` \u2714 Default model config created at ${opencodeConfigWritten}`));
7133
+ } else {
7134
+ console.log(chalk4.dim(` - ${DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH} already exists (unchanged)`));
7135
+ }
7136
+ console.log(chalk4.dim(" Per-crew config deep-merges with this file \u2014 edit it to change opencode's default model/plugins/mcp."));
6869
7137
  stepHeader(4, 5, "Register first project");
6870
7138
  const projectPath = await promptLine(
6871
7139
  chalk4.cyan(" Absolute path to your first project (Enter to skip): ")
@@ -7220,9 +7488,9 @@ init_dist4();
7220
7488
  import { Command as Command8 } from "commander";
7221
7489
  import { createConnection as createConnection3 } from "net";
7222
7490
  import { randomUUID as randomUUID4 } from "crypto";
7223
- import { homedir as homedir14 } from "os";
7224
- import { join as join19 } from "path";
7225
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
7491
+ import { homedir as homedir16 } from "os";
7492
+ import { join as join21 } from "path";
7493
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "fs";
7226
7494
 
7227
7495
  // packages/cli/src/commands/crew-output.ts
7228
7496
  function tailLines(text, maxLines = 40, maxBytes = 4096) {
@@ -7280,11 +7548,11 @@ init_dist2();
7280
7548
  import { Command as Command6 } from "commander";
7281
7549
  import chalk8 from "chalk";
7282
7550
  import { createConnection as createConnection2 } from "net";
7283
- import { homedir as homedir13 } from "os";
7284
- import { join as join18 } from "path";
7551
+ import { homedir as homedir15 } from "os";
7552
+ import { join as join20 } from "path";
7285
7553
  import { createInterface } from "readline";
7286
7554
  function socketPath() {
7287
- return process.env.SQUADRANTD_SOCK ?? join18(homedir13(), ".config", "squadrant", "squadrant.sock");
7555
+ return process.env.SQUADRANTD_SOCK ?? join20(homedir15(), ".config", "squadrant", "squadrant.sock");
7288
7556
  }
7289
7557
  function rule(width, ch = "\u2500") {
7290
7558
  return ch.repeat(Math.max(0, width));
@@ -7520,7 +7788,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
7520
7788
  });
7521
7789
 
7522
7790
  // packages/cli/src/commands/crew-control.ts
7523
- var SOCK2 = join19(homedir14(), ".config", "squadrant", "squadrant.sock");
7791
+ var SOCK2 = join21(homedir16(), ".config", "squadrant", "squadrant.sock");
7524
7792
  var CODEX_FIRST_TURN_DELAY_MS = 1500;
7525
7793
  async function sendCodexFirstTurn(taskId, text) {
7526
7794
  await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
@@ -7625,10 +7893,10 @@ function buildSignalRequest(signal, o) {
7625
7893
  return { kind: "event", project, event };
7626
7894
  }
7627
7895
  function defaultWriteResult(id, payload) {
7628
- const dir = join19(homedir14(), ".config", "squadrant", "state", "_results");
7629
- mkdirSync7(dir, { recursive: true });
7630
- const file = join19(dir, `${id}.txt`);
7631
- writeFileSync8(file, payload);
7896
+ const dir = join21(homedir16(), ".config", "squadrant", "state", "_results");
7897
+ mkdirSync8(dir, { recursive: true });
7898
+ const file = join21(dir, `${id}.txt`);
7899
+ writeFileSync10(file, payload);
7632
7900
  return file;
7633
7901
  }
7634
7902
  function addControlPlaneCrewCommands(crew) {
@@ -7734,136 +8002,6 @@ function addControlPlaneCrewCommands(crew) {
7734
8002
  var crewControlCommand = new Command8("crew").description("Dispatch and track crew via the squadrant control plane");
7735
8003
  addControlPlaneCrewCommands(crewControlCommand);
7736
8004
 
7737
- // packages/cli/src/lib/per-crew-settings.ts
7738
- init_dist4();
7739
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
7740
- import { join as join20 } from "path";
7741
- var CREW_PERMISSION_ALLOWLIST = [
7742
- // git — read + safe mutations (reset/clean/config intentionally excluded)
7743
- "Bash(git status:*)",
7744
- "Bash(git add:*)",
7745
- "Bash(git commit:*)",
7746
- "Bash(git push:*)",
7747
- "Bash(git pull:*)",
7748
- "Bash(git fetch:*)",
7749
- "Bash(git checkout:*)",
7750
- "Bash(git branch:*)",
7751
- "Bash(git diff:*)",
7752
- "Bash(git log:*)",
7753
- "Bash(git stash:*)",
7754
- "Bash(git restore:*)",
7755
- // git — read-only queries (no-mutation subcommands)
7756
- "Bash(git show:*)",
7757
- "Bash(git rev-parse:*)",
7758
- "Bash(git remote -v:*)",
7759
- "Bash(git stash list:*)",
7760
- // npm / npx — installs + script/test/build runners
7761
- "Bash(npm install:*)",
7762
- "Bash(npm ci:*)",
7763
- "Bash(npm run:*)",
7764
- "Bash(npm test:*)",
7765
- "Bash(npm ls:*)",
7766
- "Bash(npm view:*)",
7767
- "Bash(npx vitest:*)",
7768
- "Bash(npx tsc:*)",
7769
- // direct runners
7770
- "Bash(node:*)",
7771
- "Bash(node --version:*)",
7772
- "Bash(vitest:*)",
7773
- "Bash(tsc:*)",
7774
- // pnpm
7775
- "Bash(pnpm ls:*)",
7776
- // process / system read — non-interactive, read-only
7777
- "Bash(ps:*)",
7778
- "Bash(pgrep:*)",
7779
- "Bash(lsof:*)",
7780
- "Bash(top -l:*)",
7781
- "Bash(uptime:*)",
7782
- "Bash(whoami:*)",
7783
- "Bash(uname:*)",
7784
- "Bash(date:*)",
7785
- "Bash(env:*)",
7786
- "Bash(sysctl:*)",
7787
- // file read / navigation
7788
- "Bash(ls:*)",
7789
- "Bash(cat:*)",
7790
- "Bash(head:*)",
7791
- "Bash(tail:*)",
7792
- "Bash(wc:*)",
7793
- "Bash(grep:*)",
7794
- "Bash(rg:*)",
7795
- "Bash(find:*)",
7796
- "Bash(pwd:*)",
7797
- "Bash(stat:*)",
7798
- "Bash(file:*)",
7799
- "Bash(realpath:*)",
7800
- "Bash(basename:*)",
7801
- "Bash(dirname:*)",
7802
- "Bash(du:*)",
7803
- "Bash(df:*)",
7804
- "Bash(tree:*)",
7805
- // read-only text processing
7806
- "Bash(sort:*)",
7807
- "Bash(uniq:*)",
7808
- "Bash(cut:*)",
7809
- "Bash(tr:*)",
7810
- "Bash(jq:*)",
7811
- "Bash(diff:*)",
7812
- "Bash(column:*)",
7813
- // harmless utilities
7814
- "Bash(echo:*)",
7815
- "Bash(which:*)",
7816
- "Bash(true:*)",
7817
- "Bash(sleep:*)",
7818
- "Bash(printf:*)"
7819
- ];
7820
- function healStaleCockpitRefs(raw) {
7821
- return raw.replaceAll("cockpit crew _hook", "squadrant crew _hook").replaceAll(".config/cockpit", ".config/squadrant").replaceAll("cockpit-hub", "squadrant-hub");
7822
- }
7823
- function mergeCrewPermissions(settings) {
7824
- const next = structuredClone(settings ?? {});
7825
- const permissions = next.permissions ??= {};
7826
- const existing = Array.isArray(permissions.allow) ? permissions.allow : [];
7827
- permissions.allow = [.../* @__PURE__ */ new Set([...existing, ...CREW_PERMISSION_ALLOWLIST])];
7828
- return next;
7829
- }
7830
- function writePerCrewSettingsLocal(o) {
7831
- const dir = join20(o.projectCwd, ".claude");
7832
- mkdirSync8(dir, { recursive: true });
7833
- const file = join20(dir, "settings.local.json");
7834
- let existing = {};
7835
- try {
7836
- const raw = healStaleCockpitRefs(readFileSync11(file, "utf-8"));
7837
- existing = JSON.parse(raw);
7838
- } catch {
7839
- }
7840
- const withHooks = mergeClaudeHooks(existing, o.hookCmd ?? "squadrant crew _hook");
7841
- const merged = mergeCrewPermissions(withHooks);
7842
- writeFileSync9(file, JSON.stringify(merged, null, 2));
7843
- return file;
7844
- }
7845
- function writePerCrewOpencodeConfig(o) {
7846
- const dir = join20(o.stateRoot, o.project, o.taskId);
7847
- mkdirSync8(dir, { recursive: true });
7848
- const file = join20(dir, "opencode.json");
7849
- const config = {
7850
- permission: {
7851
- read: "allow",
7852
- edit: "allow",
7853
- glob: "allow",
7854
- grep: "allow",
7855
- bash: o.gateBash ? "ask" : "allow",
7856
- webfetch: "allow",
7857
- websearch: "allow",
7858
- task: "allow",
7859
- lsp: "allow",
7860
- external_directory: { "**": "allow" }
7861
- }
7862
- };
7863
- writeFileSync9(file, JSON.stringify(config, null, 2));
7864
- return file;
7865
- }
7866
-
7867
8005
  // packages/cli/src/commands/crew.ts
7868
8006
  async function runCrewSpawn2(input) {
7869
8007
  const config = loadConfig();
@@ -8160,8 +8298,8 @@ init_dist();
8160
8298
  init_dist3();
8161
8299
  import { Command as Command11 } from "commander";
8162
8300
  import { execSync as execSync10 } from "child_process";
8163
- import { homedir as homedir16 } from "os";
8164
- import { join as join22 } from "path";
8301
+ import { homedir as homedir18 } from "os";
8302
+ import { join as join23 } from "path";
8165
8303
  import chalk12 from "chalk";
8166
8304
 
8167
8305
  // packages/web/dist/read-status.js
@@ -8174,6 +8312,11 @@ function deriveState(tasks) {
8174
8312
  return "busy";
8175
8313
  return "idle";
8176
8314
  }
8315
+ function deriveRowState(tasks, captainState) {
8316
+ if (captainState === "gone" || captainState === "stopped" || captainState === "unknown")
8317
+ return "offline";
8318
+ return deriveState(tasks);
8319
+ }
8177
8320
  function buildExcerpt(tasks) {
8178
8321
  const working = tasks.filter((t) => t.state === "working").length;
8179
8322
  const blocked = tasks.filter((t) => t.state === "blocked" || t.state === "awaiting-input").length;
@@ -8197,13 +8340,25 @@ async function readAllStatuses(deps) {
8197
8340
  const result = await deps.call({ kind: "list", project });
8198
8341
  return result;
8199
8342
  });
8343
+ const captainStateByProject = /* @__PURE__ */ new Map();
8344
+ if (deps.call) {
8345
+ try {
8346
+ const health = await deps.call({ kind: "health" });
8347
+ for (const h of health ?? []) {
8348
+ if (h.kind === "captain")
8349
+ captainStateByProject.set(h.project, h.state);
8350
+ }
8351
+ } catch {
8352
+ }
8353
+ }
8200
8354
  const rows = [];
8201
8355
  for (const [name, project] of Object.entries(deps.config.projects)) {
8202
8356
  try {
8203
8357
  const tasks = await listTasks(name);
8358
+ const captainState = captainStateByProject.get(name) ?? "unknown";
8204
8359
  rows.push({
8205
8360
  project: name,
8206
- state: deriveState(tasks),
8361
+ state: deriveRowState(tasks, captainState),
8207
8362
  lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
8208
8363
  captainWorkspace: project.captainName,
8209
8364
  excerpt: buildExcerpt(tasks)
@@ -8357,9 +8512,9 @@ function mergeSnapshot(daemon, external, now) {
8357
8512
  // packages/web/dist/probes.js
8358
8513
  init_dist();
8359
8514
  init_dist();
8360
- import { join as join21 } from "path";
8361
- import { homedir as homedir15 } from "os";
8362
- import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
8515
+ import { join as join22 } from "path";
8516
+ import { homedir as homedir17 } from "os";
8517
+ import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
8363
8518
  import { execFile as execFile3 } from "child_process";
8364
8519
  var DEFAULT_TIMEOUT_MS = 2e3;
8365
8520
  var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
@@ -8395,7 +8550,7 @@ function vaultProbe(run, dir) {
8395
8550
  return { state: "unknown", detail: "no vault configured" };
8396
8551
  if (!run.pathExists(dir))
8397
8552
  return { state: "gone", detail: "vault directory missing" };
8398
- if (!run.pathExists(join21(dir, ".obsidian")))
8553
+ if (!run.pathExists(join22(dir, ".obsidian")))
8399
8554
  return { state: "gone", detail: "no .obsidian/ (not a vault)" };
8400
8555
  return { state: "alive" };
8401
8556
  } catch {
@@ -8463,13 +8618,13 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
8463
8618
  const sessions = probeSessions(run);
8464
8619
  return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
8465
8620
  }
8466
- var SESSIONS_PATH = join21(homedir15(), ".config", "squadrant", "sessions.json");
8621
+ var SESSIONS_PATH = join22(homedir17(), ".config", "squadrant", "sessions.json");
8467
8622
  function onPath(cli) {
8468
8623
  const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
8469
- return dirs.some((d) => existsSync11(join21(d, cli)));
8624
+ return dirs.some((d) => existsSync11(join22(d, cli)));
8470
8625
  }
8471
8626
  function readSessionsHashes() {
8472
- const raw = JSON.parse(readFileSync12(SESSIONS_PATH, "utf-8"));
8627
+ const raw = JSON.parse(readFileSync14(SESSIONS_PATH, "utf-8"));
8473
8628
  const hashes = Object.values(raw.workspaces ?? {}).map((w) => w.templateHash).filter((h) => typeof h === "string" && h.length > 0);
8474
8629
  return [...new Set(hashes)];
8475
8630
  }
@@ -8678,6 +8833,16 @@ function renderOverview(snap, col) {
8678
8833
  out.push(`</section>`);
8679
8834
  return out.join("");
8680
8835
  }
8836
+ function filterBar() {
8837
+ return [
8838
+ `<div class="filter-bar" role="group" aria-label="Filter projects by status">`,
8839
+ `<button class="filter-btn on" data-filter="all" aria-pressed="true">All</button>`,
8840
+ `<button class="filter-btn" data-filter="alive" aria-pressed="false">Nominal</button>`,
8841
+ `<button class="filter-btn" data-filter="caution" aria-pressed="false">Caution</button>`,
8842
+ `<button class="filter-btn" data-filter="offline" aria-pressed="false">Offline</button>`,
8843
+ `</div>`
8844
+ ].join("");
8845
+ }
8681
8846
  function componentRow(c, now) {
8682
8847
  const detail = c.detail ? esc(c.detail) : "";
8683
8848
  const row = `<tr><td>${statePill(c.state)}</td><td class="mono">${esc(c.kind)}</td><td class="mono">${esc(c.ref)}</td><td class="mono dim">${esc(ageText(c.lastSeenMs, now))}</td><td class="dim">${detail}</td></tr>`;
@@ -8702,6 +8867,7 @@ function renderProjects(snap, now) {
8702
8867
  out.push(`<div class="empty">Telemetry link lost \u2014 per-project data is served by the daemon. Start it to restore: <code>squadrant heal daemon</code></div></section>`);
8703
8868
  return out.join("");
8704
8869
  }
8870
+ out.push(filterBar());
8705
8871
  const d = snap.daemon;
8706
8872
  const projects = /* @__PURE__ */ new Set([...d.tier1.map((c) => c.project), ...d.tier2.projects.map((p) => p.project)]);
8707
8873
  if (projects.size === 0)
@@ -8718,8 +8884,10 @@ function renderProjects(snap, now) {
8718
8884
  deferralState
8719
8885
  ];
8720
8886
  const rollup = worst(rollupStates);
8887
+ const captainComp = comps.find((c) => c.kind === "captain");
8888
+ const captainState = captainComp?.state ?? "unknown";
8721
8889
  out.push(`<article class="card" data-rollup="${rollup}">`);
8722
- out.push(`<header class="card-head"><span class="card-title">${esc(project)}</span>${statePill(rollup)}</header>`);
8890
+ out.push(`<header class="card-head"><span class="card-title">${esc(project)}</span><div class="card-badges"><span class="captain-badge">captain ${statePill(captainState)}</span>${statePill(rollup)}</div></header>`);
8723
8891
  if (comps.length) {
8724
8892
  out.push(`<table class="grid"><thead><tr><th>status</th><th>kind</th><th>ref</th><th>last seen</th><th>detail</th></tr></thead><tbody>`, ...comps.map((c) => componentRow(c, now)), `</tbody></table>`);
8725
8893
  } else {
@@ -8800,7 +8968,131 @@ function renderEnv(ext) {
8800
8968
  out.push(`</section>`);
8801
8969
  return out.join("");
8802
8970
  }
8971
+ var ATTN_ORDER = { stale: 0, gone: 1, alive: 2, unknown: 3, stopped: 4 };
8972
+ var STATE_LABEL = { alive: "running", stale: "blocked", gone: "errored", unknown: "idle", stopped: "offline" };
8973
+ var STATE_CLS = { alive: "s-alive", stale: "s-stale", gone: "s-gone", unknown: "s-unknown", stopped: "s-stopped" };
8974
+ var STATE_ORDER = ["alive", "stale", "gone", "unknown", "stopped"];
8975
+ var TASK_ICON = { done: "\u2713", working: "\u25B6", blocked: "\u25CF", failed: "\u2715", cancelled: "\u2299" };
8976
+ function renderLiveGrid(snap, now) {
8977
+ const out = [`<section class="panel" data-panel="live" role="tabpanel" aria-label="Live">`];
8978
+ if (snap.daemon === "unreachable") {
8979
+ out.push(`<div class="empty">Telemetry link lost \u2014 per-project data is served by the daemon. Start it to restore: <code>squadrant heal daemon</code></div></section>`);
8980
+ return out.join("");
8981
+ }
8982
+ const d = snap.daemon;
8983
+ const projects = /* @__PURE__ */ new Set([...d.tier1.map((c) => c.project), ...d.tier2.projects.map((p) => p.project)]);
8984
+ if (projects.size === 0) {
8985
+ out.push(`<div class="empty">No projects registered yet.</div>`);
8986
+ } else {
8987
+ const rows = [];
8988
+ const stateCounts = {};
8989
+ for (const s of STATE_ORDER)
8990
+ stateCounts[s] = 0;
8991
+ for (const project of projects) {
8992
+ const comps = d.tier1.filter((c) => c.project === project);
8993
+ const dp = d.tier2.projects.find((p) => p.project === project);
8994
+ const captainStopped = comps.some((c) => c.kind === "captain" && c.state === "stopped");
8995
+ const deferralState = !captainStopped && dp && dp.deferral.stuck ? "gone" : !captainStopped && dp && dp.deferral.maxDeferCount > 0 ? "stale" : "alive";
8996
+ const rollupStates = [
8997
+ ...comps.map((c) => c.state),
8998
+ !captainStopped && dp && dp.delivery.behind > 0 ? "stale" : "alive",
8999
+ dp && dp.store.corruptCount > 0 ? "gone" : "alive",
9000
+ deferralState
9001
+ ];
9002
+ const rollup = worst(rollupStates);
9003
+ const captainComp = comps.find((c) => c.kind === "captain");
9004
+ const captainState = captainComp?.state ?? "unknown";
9005
+ const crews = comps.filter((c) => c.kind === "crew");
9006
+ let detail = "";
9007
+ if (crews.length > 0) {
9008
+ const alive = crews.filter((c) => c.state === "alive");
9009
+ const other = crews.filter((c) => c.state !== "alive");
9010
+ const parts = [];
9011
+ if (alive.length > 0)
9012
+ parts.push(`${alive.length} working \u2014 ${alive.map((c) => c.ref).join("; ")}`);
9013
+ for (const c of other)
9014
+ parts.push(`${c.state} \u2014 ${c.ref}`);
9015
+ detail = parts.join(", ");
9016
+ } else if (captainState === "alive") {
9017
+ detail = "captain idle";
9018
+ }
9019
+ rows.push({ name: project, rollup, captainState, detail, crews, tasks: dp?.store.byState ?? {} });
9020
+ stateCounts[rollup]++;
9021
+ }
9022
+ const headerLabels = STATE_ORDER.map((s) => `${STATE_LABEL[s]}`);
9023
+ out.push(`<div class="live-header" data-live-header="">`);
9024
+ out.push(headerLabels.map((l) => {
9025
+ const raw = Object.entries(STATE_LABEL).find(([, v]) => v === l)[0];
9026
+ const c = stateCounts[raw];
9027
+ const cls = c === 0 ? "zero" : "";
9028
+ return `<span class="live-stat ${cls}"><span class="pdot ${STATE_CLS[raw]}"></span>${c} ${l}</span>`;
9029
+ }).join(` <span class="live-sep">\xB7</span> `));
9030
+ out.push(`</div>`);
9031
+ rows.sort((a, b) => {
9032
+ const d2 = (ATTN_ORDER[a.rollup] ?? 99) - (ATTN_ORDER[b.rollup] ?? 99);
9033
+ return d2 !== 0 ? d2 : a.name.localeCompare(b.name);
9034
+ });
9035
+ out.push(`<div class="live-controls">`);
9036
+ out.push(`<input type="text" class="live-search" placeholder="Search projects or crews\u2026" data-live-search="">`);
9037
+ out.push(`<div class="filter-bar" role="group" aria-label="Filter live rows by status">`);
9038
+ for (const fb of [["all", "All"], ["alive", "Nominal"], ["caution", "Caution"], ["offline", "Offline"]]) {
9039
+ out.push(`<button class="filter-btn${fb[0] === "all" ? " on" : ""}" data-filter="${fb[0]}" data-live-filter="" aria-pressed="${fb[0] === "all" ? "true" : "false"}">${fb[1]}</button>`);
9040
+ }
9041
+ out.push(`</div></div>`);
9042
+ out.push(`<table class="live-grid" data-live-table=""><thead><tr class="lh-row">`);
9043
+ out.push(`<th class="lh-dot"></th><th class="lh-name" data-sort="name">Project</th><th class="lh-state" data-sort="attention">Status</th><th class="lh-detail">Activity</th><th class="lh-lastseen" data-sort="activity">Last Seen</th><th class="lh-tasks">Tasks</th>`);
9044
+ out.push(`</tr></thead><tbody>`);
9045
+ for (const row of rows) {
9046
+ let maxAge = -1;
9047
+ let maxSeenMs = null;
9048
+ for (const c of row.crews) {
9049
+ if (c.lastSeenMs != null) {
9050
+ const age = now - c.lastSeenMs;
9051
+ if (age > maxAge) {
9052
+ maxAge = age;
9053
+ maxSeenMs = c.lastSeenMs;
9054
+ }
9055
+ }
9056
+ }
9057
+ const crewAgeStr = maxSeenMs == null ? "\u2014" : ageText(maxSeenMs, now);
9058
+ const crewAgeQuiet = maxSeenMs != null && maxAge > CREW_STALE_MS;
9059
+ const tParts = [];
9060
+ for (const [st, n] of Object.entries(row.tasks)) {
9061
+ const icon = TASK_ICON[st] ?? st[0];
9062
+ tParts.push(`<span class="tk">${n}${esc(icon)}</span>`);
9063
+ }
9064
+ out.push(`<tr class="live-row" data-rollup="${row.rollup}" data-project="${esc(row.name)}" data-max-age="${maxAge}">`);
9065
+ out.push(`<td class="live-dot"><span class="pdot s-${row.rollup}"></span></td>`);
9066
+ out.push(`<td class="live-name">${esc(row.name)}</td>`);
9067
+ out.push(`<td class="live-state">${statePill(row.captainState)}</td>`);
9068
+ out.push(`<td class="live-detail dim">${esc(row.detail)}</td>`);
9069
+ out.push(`<td class="live-lastseen${crewAgeQuiet ? " quiet" : ""}">${crewAgeStr}</td>`);
9070
+ out.push(`<td class="live-tasks">${tParts.join("") || '<span class="dim">\u2014</span>'}</td>`);
9071
+ out.push(`</tr>`);
9072
+ out.push(`<tr class="live-expand" data-project="${esc(row.name)}" hidden>`);
9073
+ out.push(`<td colspan="6"><div class="live-crew-list">`);
9074
+ out.push(`<div class="live-crew-head"><span class="lc-name">Crew</span><span class="lc-state">Status</span><span class="lc-lastseen">Last Seen</span><span class="lc-detail">Detail</span></div>`);
9075
+ for (const c of row.crews) {
9076
+ const cs = ageText(c.lastSeenMs, now);
9077
+ out.push(`<div class="live-crew-row"><span class="lc-name">${esc(c.ref)}</span><span class="lc-state">${statePill(c.state)}</span><span class="lc-lastseen">${cs}</span><span class="lc-detail dim">${c.detail ? esc(c.detail) : ""}</span></div>`);
9078
+ }
9079
+ if (row.crews.length === 0) {
9080
+ out.push(`<div class="live-crew-row dim"><span class="lc-name" colspan="4">no live crews</span></div>`);
9081
+ }
9082
+ out.push(`</div><div class="live-crew-meta">`);
9083
+ out.push(`<span class="lc-meta-item">pid: \u2014 (incoming)</span>`);
9084
+ out.push(`<span class="lc-meta-item">uptime: \u2014 (incoming)</span>`);
9085
+ out.push(`<span class="lc-meta-item">agent: \u2014 (incoming)</span>`);
9086
+ out.push(`<span class="lc-meta-item">model: \u2014 (incoming)</span>`);
9087
+ out.push(`</div></td></tr>`);
9088
+ }
9089
+ out.push(`</tbody></table>`);
9090
+ }
9091
+ out.push(`</section>`);
9092
+ return out.join("");
9093
+ }
8803
9094
  var TABS = [
9095
+ ["live", "Live"],
8804
9096
  ["overview", "Overview"],
8805
9097
  ["projects", "Projects"],
8806
9098
  ["daemon", "Daemon"],
@@ -8848,6 +9140,7 @@ function renderContent(snap) {
8848
9140
  }
8849
9141
  out.push(tabNav());
8850
9142
  out.push(`<div class="panels">`);
9143
+ out.push(renderLiveGrid(snap, col.now));
8851
9144
  out.push(renderOverview(snap, col));
8852
9145
  out.push(renderProjects(snap, col.now));
8853
9146
  out.push(renderDaemon(snap));
@@ -8972,6 +9265,77 @@ body{margin:0;min-height:100vh;background:radial-gradient(1200px 760px at 50% -2
8972
9265
  .spark-area{fill:var(--hud);opacity:.1}
8973
9266
  .spark-dot{fill:var(--hud)}
8974
9267
 
9268
+ /* Live controls (search + filter bar) */
9269
+ .live-controls{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin:4px 0 10px}
9270
+ .live-search{flex:1;min-width:140px;max-width:300px;padding:5px 10px;border:1px solid var(--bezel);border-radius:8px;background:var(--panel);color:var(--ink);font:12px/1.4 ui-monospace,monospace;outline:none;transition:border-color .15s ease}
9271
+ .live-search:focus{border-color:var(--hud);box-shadow:0 0 0 2px rgba(11,111,194,.15)}
9272
+ .live-search::placeholder{color:var(--ink-dim);opacity:.7}
9273
+
9274
+ /* Live grid (mission control) */
9275
+ .live-grid{width:100%;border-collapse:collapse;font-size:12px;table-layout:fixed}
9276
+ .live-grid td,.live-grid th{padding:5px 6px;vertical-align:middle;white-space:nowrap}
9277
+ .live-grid th{padding:6px 6px 8px;text-align:left;font-family:system-ui,sans-serif;text-transform:uppercase;letter-spacing:.1em;font-size:9px;color:var(--ink-dim);border-bottom:1px solid var(--bezel);font-weight:600;cursor:default}
9278
+ .live-grid th:hover{color:var(--ink)}
9279
+ .live-row td{border-bottom:1px solid var(--track)}
9280
+ .live-row:last-child td{border-bottom:0}
9281
+ .lh-dot{width:22px;text-align:center}
9282
+ .lh-name{width:auto;min-width:14%}
9283
+ .lh-state{width:88px;text-align:right;padding-right:12px!important}
9284
+ .lh-detail{width:auto}
9285
+ .lh-lastseen{width:105px}
9286
+ .lh-tasks{width:90px;text-align:right}
9287
+ .live-dot{width:22px;text-align:center;padding-right:0!important}
9288
+ .live-dot .pdot{display:inline-block;vertical-align:middle}
9289
+ .live-name{font-weight:600}
9290
+ .live-state{width:88px;text-align:right;padding-right:12px!important}
9291
+ .live-state .pill{font-size:10px;padding:1px 7px}
9292
+ .live-detail{overflow:hidden;text-overflow:ellipsis;color:var(--ink-dim)}
9293
+ .live-lastseen{width:105px;font-size:11px;color:var(--ink-dim)}
9294
+ .live-lastseen.quiet{color:var(--warn);font-weight:600}
9295
+ .live-tasks{width:90px;text-align:right;font-size:11px}
9296
+ .live-tasks .tk{padding:0 3px}
9297
+ .live-tasks .tk:first-child{padding-left:0}
9298
+ .live-row.expanded td{border-bottom-color:transparent}
9299
+ .live-expand td{padding:0 6px 10px;background:var(--panel-2);border-bottom:1px solid var(--track)}
9300
+ .live-expand td:first-child{border-radius:0 0 0 8px}
9301
+ .live-expand td:last-child{border-radius:0 0 8px 0}
9302
+ .live-crew-list{display:table;width:100%;border-collapse:collapse;font-size:12px}
9303
+ .live-crew-head,.live-crew-row{display:table-row}
9304
+ .live-crew-head>span,.live-crew-row>span{display:table-cell;padding:4px 8px;white-space:nowrap;vertical-align:middle}
9305
+ .live-crew-head>span{font-family:system-ui,sans-serif;text-transform:uppercase;letter-spacing:.1em;font-size:9px;color:var(--ink-dim);font-weight:600;border-bottom:1px solid var(--bezel)}
9306
+ .live-crew-row>span{border-bottom:1px solid var(--track)}
9307
+ .live-crew-row:last-child>span{border-bottom:0}
9308
+ .lc-name{font-weight:600}
9309
+ .lc-state{width:80px}
9310
+ .lc-state .pill{font-size:10px;padding:1px 7px}
9311
+ .lc-lastseen{width:90px;color:var(--ink-dim);font-size:11px}
9312
+ .lc-detail{color:var(--ink-dim);font-size:11px}
9313
+ .live-crew-meta{display:flex;gap:14px;padding:6px 8px 0;font-size:11px;color:var(--ink-dim);border-top:1px solid var(--track);margin-top:4px;padding-top:6px}
9314
+ .lc-meta-item{white-space:nowrap}
9315
+ .live-row.expanded+.live-expand td{background:var(--panel-2)}
9316
+ .live-row[data-rollup="gone"] td.live-dot .pdot{background:var(--crit);box-shadow:0 0 6px var(--crit)}
9317
+ .live-row[data-rollup="stale"] td.live-dot .pdot{background:var(--warn);box-shadow:0 0 6px var(--warn)}
9318
+ .live-row[data-rollup="stopped"] td.live-dot .pdot{background:var(--stopped)}
9319
+ .live-row[data-rollup="alive"] td.live-dot .pdot{background:var(--ok);box-shadow:0 0 6px var(--ok)}
9320
+ .live-row[data-rollup="unknown"] td.live-dot .pdot{background:var(--unk)}
9321
+ .live-row:hover{background:var(--panel-2)}
9322
+ .live-header{display:flex;gap:8px 16px;flex-wrap:wrap;padding:6px 0 10px;font-size:12px;cursor:default;user-select:none}
9323
+ .live-stat{display:flex;align-items:center;gap:5px;font-weight:600}
9324
+ .live-stat .pdot{width:6px;height:6px}
9325
+ .live-stat.zero{opacity:.35}
9326
+ .live-sep{color:var(--ink-dim);opacity:.4}
9327
+
9328
+ /* Filter bar */
9329
+ .filter-bar{display:flex;gap:6px;margin:0 0 14px;flex-wrap:wrap}
9330
+ .filter-btn{appearance:none;background:var(--panel-2);border:1px solid var(--bezel);border-radius:999px;
9331
+ padding:4px 13px;cursor:pointer;font-family:system-ui,sans-serif;text-transform:uppercase;letter-spacing:.1em;
9332
+ font-size:10px;font-weight:600;color:var(--ink-dim);transition:all .15s ease}
9333
+ .filter-btn:hover{background:var(--panel);border-color:var(--ink-dim);color:var(--ink)}
9334
+ .filter-btn.on{background:var(--hud);border-color:var(--hud);color:#fff;box-shadow:0 1px 3px rgba(11,111,194,.3)}
9335
+ .card-badges{display:flex;align-items:center;gap:8px;flex:none}
9336
+ .captain-badge{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--ink-dim);letter-spacing:.04em}
9337
+ .captain-badge .pill{font-size:10px;padding:1px 7px}
9338
+
8975
9339
  /* Cards / tables */
8976
9340
  .card{border:1px solid var(--bezel);border-radius:12px;background:var(--panel);box-shadow:var(--shadow);padding:14px 16px;margin-bottom:14px;border-left:3px solid var(--bezel)}
8977
9341
  .card[data-rollup="gone"]{border-left-color:var(--crit)}
@@ -9034,7 +9398,7 @@ body{margin:0;min-height:100vh;background:radial-gradient(1200px 760px at 50% -2
9034
9398
  `.trim();
9035
9399
  var CLIENT_JS = `
9036
9400
  (function(){
9037
- var activeTab='overview';var hist={};var prev={};
9401
+ var activeTab='live';var hist={};var prev={};var currentFilter='all';var currentSort='attention';var sortAsc=true;var currentSearch='';var expandedProjects={};
9038
9402
  function pushHist(k,v,t){var a=hist[k]||(hist[k]=[]);if(a.length&&a[a.length-1].t===t)return;a.push({t:t,v:v});if(a.length>48)a.shift();}
9039
9403
  function ingest(){var el=document.getElementById('squadrant-metrics');if(!el)return;var m;try{m=JSON.parse(el.textContent);}catch(e){return;}pushHist('errors',m.errors,m.t);pushHist('behind',m.behind,m.t);pushHist('crewAge',Math.round(m.crewAgeMs/1000),m.t);pushHist('defers',m.maxDeferCount,m.t);}
9040
9404
  function sparkSVG(a){var W=100,H=28,p=2;if(!a.length)return'';var vs=a.map(function(x){return x.v;});var mx=Math.max.apply(null,vs),mn=Math.min.apply(null,vs);if(mx===mn)mx=mn+1;var n=a.length;var pts=a.map(function(x,i){var px=n>1?p+i/(n-1)*(W-2*p):W/2;var py=H-p-(x.v-mn)/(mx-mn)*(H-2*p);return[px,py];});var d=pts.map(function(pt,i){return(i?'L':'M')+pt[0].toFixed(1)+' '+pt[1].toFixed(1);}).join(' ');var last=pts[pts.length-1];var area=d+' L'+last[0].toFixed(1)+' '+H+' L'+pts[0][0].toFixed(1)+' '+H+' Z';return'<path class="spark-area" d="'+area+'"/><path class="spark-line" d="'+d+'"/><circle class="spark-dot" cx="'+last[0].toFixed(1)+'" cy="'+last[1].toFixed(1)+'" r="2"/>';}
@@ -9042,9 +9406,15 @@ var CLIENT_JS = `
9042
9406
  function applyTab(){var ts=document.querySelectorAll('[data-tab]');for(var i=0;i<ts.length;i++){var on=ts[i].getAttribute('data-tab')===activeTab;ts[i].setAttribute('aria-selected',on?'true':'false');ts[i].classList.toggle('on',on);}var ps=document.querySelectorAll('[data-panel]');for(var j=0;j<ps.length;j++){var on2=ps[j].getAttribute('data-panel')===activeTab;ps[j].hidden=!on2;}}
9043
9407
  function ease(k){return k<.5?2*k*k:1-Math.pow(-2*k+2,2)/2;}
9044
9408
  function countUps(){var ns=document.querySelectorAll('[data-countup]');for(var i=0;i<ns.length;i++){(function(el){var key=el.getAttribute('data-countup');var to=parseFloat(el.getAttribute('data-value'))||0;var from=prev[key]!=null?prev[key]:0;prev[key]=to;if(from===to){el.textContent=to;return;}var s=null;function step(ts){if(s===null)s=ts;var k=Math.min(1,(ts-s)/600);el.textContent=Math.round(from+(to-from)*ease(k));if(k<1)requestAnimationFrame(step);}requestAnimationFrame(step);})(ns[i]);}}
9045
- function refresh(){ingest();applyTab();drawSparks();countUps();}
9046
- document.addEventListener('click',function(e){var t=e.target&&e.target.closest?e.target.closest('[data-tab]'):null;if(t){activeTab=t.getAttribute('data-tab');applyTab();var c=document.getElementById('content');if(c){c.classList.remove('swap');void c.offsetWidth;c.classList.add('swap');}}});
9047
- document.addEventListener('keydown',function(e){var ae=document.activeElement;if((e.key==='ArrowRight'||e.key==='ArrowLeft')&&ae&&ae.getAttribute&&ae.getAttribute('data-tab')){var o=['overview','projects','daemon','environment'];var i=o.indexOf(activeTab);i=(i+(e.key==='ArrowRight'?1:o.length-1))%o.length;activeTab=o[i];applyTab();var nt=document.querySelector('[data-tab="'+activeTab+'"]');if(nt)nt.focus();e.preventDefault();}});
9409
+ function hideByFilter(r,filter){if(filter==='all')return false;if(filter==='alive')return r!=='alive';if(filter==='caution')return r!=='stale';if(filter==='offline')return r!=='gone'&&r!=='stopped'&&r!=='unknown';return false;}
9410
+ function applyFilter(){document.querySelectorAll('[data-panel="projects"] .card').forEach(function(c){c.hidden=hideByFilter(c.getAttribute('data-rollup'),currentFilter);});document.querySelectorAll('[data-panel="live"] .live-row').forEach(function(c){c.hidden=hideByFilter(c.getAttribute('data-rollup'),currentFilter);});document.querySelectorAll('.filter-btn').forEach(function(x){var on=x.getAttribute('data-filter')===currentFilter;x.classList.toggle('on',on);x.setAttribute('aria-pressed',on?'true':'false');});}
9411
+ function applySearch(){var q=currentSearch.toLowerCase();document.querySelectorAll('[data-panel="live"] .live-row').forEach(function(r){if(!q){if(!hideByFilter(r.getAttribute('data-rollup'),currentFilter))r.hidden=false;return;}var pn=(r.getAttribute('data-project')||'').toLowerCase();var dd=((r.querySelector('.live-detail')||{}).textContent||'').toLowerCase();var rl=r.getAttribute('data-rollup');if(hideByFilter(rl,currentFilter)){r.hidden=true;return;}r.hidden=pn.indexOf(q)===-1&&dd.indexOf(q)===-1;});}
9412
+ function applySort(){var table=document.querySelector('[data-panel="live"] [data-live-table]');if(!table)return;var tbody=table.querySelector('tbody');if(!tbody)return;var rows=Array.prototype.slice.call(tbody.querySelectorAll('.live-row'));var dir=sortAsc?1:-1;rows.sort(function(a,b){var va,vb;if(currentSort==='name'){va=(a.getAttribute('data-project')||'').toLowerCase();vb=(b.getAttribute('data-project')||'').toLowerCase();}else if(currentSort==='attention'){var order={gone:0,stale:1,alive:2,unknown:3,stopped:4};va=order[a.getAttribute('data-rollup')]??99;vb=order[b.getAttribute('data-rollup')]??99;if(va===vb){va=(a.getAttribute('data-project')||'').toLowerCase();vb=(b.getAttribute('data-project')||'').toLowerCase();return va<vb?-1:va>vb?1:0;}}else if(currentSort==='activity'){va=parseInt(a.getAttribute('data-max-age'))||0;vb=parseInt(b.getAttribute('data-max-age'))||0;}return va<vb?-dir:va>vb?dir:0;});rows.forEach(function(r){tbody.appendChild(r);});}
9413
+ function applyExpand(){document.querySelectorAll('[data-panel="live"] .live-row').forEach(function(r){var pn=r.getAttribute('data-project');var er=document.querySelector('[data-panel="live"] .live-expand[data-project="'+pn+'"]');if(er){var show=!!expandedProjects[pn];er.hidden=!show;r.classList.toggle('expanded',show);}});}
9414
+ function refresh(){ingest();applyTab();drawSparks();countUps();applyFilter();applySearch();applySort();applyExpand();}
9415
+ document.addEventListener('click',function(e){var t=e.target&&e.target.closest?e.target.closest('[data-tab]'):null;if(t){activeTab=t.getAttribute('data-tab');applyTab();var c=document.getElementById('content');if(c){c.classList.remove('swap');void c.offsetWidth;c.classList.add('swap');}}var fb=e.target&&e.target.closest?e.target.closest('[data-filter]'):null;if(fb){currentFilter=fb.getAttribute('data-filter');applyFilter();applySearch();}var sh=e.target&&e.target.closest?e.target.closest('[data-sort]'):null;if(sh){var key=sh.getAttribute('data-sort');if(key===currentSort)sortAsc=!sortAsc;else{currentSort=key;sortAsc=true;}applySort();}var lr=e.target&&e.target.closest?e.target.closest('[data-panel="live"] .live-row'):null;if(lr&&!e.target.closest('[data-sort]')){var pn=lr.getAttribute('data-project');var er=document.querySelector('[data-panel="live"] .live-expand[data-project="'+pn+'"]');if(er){var show=er.hidden;er.hidden=!show;lr.classList.toggle('expanded',!!show);expandedProjects[pn]=!!show;}}});
9416
+ document.addEventListener('keydown',function(e){var ae=document.activeElement;if((e.key==='ArrowRight'||e.key==='ArrowLeft')&&ae&&ae.getAttribute&&ae.getAttribute('data-tab')){var o=['live','overview','projects','daemon','environment'];var i=o.indexOf(activeTab);i=(i+(e.key==='ArrowRight'?1:o.length-1))%o.length;activeTab=o[i];applyTab();var nt=document.querySelector('[data-tab="'+activeTab+'"]');if(nt)nt.focus();e.preventDefault();}});
9417
+ document.addEventListener('input',function(e){var sb=e.target&&e.target.closest?e.target.closest('[data-live-search]'):null;if(sb){currentSearch=sb.value;applySearch();}});
9048
9418
  var led=document.getElementById('led');var conn=document.getElementById('conn');var updated=document.getElementById('updated');
9049
9419
  function tickAge(){if(!generatedAt)return;var s=Math.max(0,Math.round((Date.now()-generatedAt)/1000));updated.textContent='updated '+s+'s ago';}
9050
9420
  setInterval(tickAge,1000);
@@ -9159,7 +9529,7 @@ async function startWebServer(opts) {
9159
9529
 
9160
9530
  // packages/cli/src/commands/dashboard.ts
9161
9531
  init_dist();
9162
- var SOCK3 = join22(homedir16(), ".config", "squadrant", "squadrant.sock");
9532
+ var SOCK3 = join23(homedir18(), ".config", "squadrant", "squadrant.sock");
9163
9533
  function detectCurrentWorkspace2() {
9164
9534
  const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
9165
9535
  const match = out.match(/workspace:\d+/);
@@ -9327,8 +9697,8 @@ init_dist2();
9327
9697
  var CMUX_APP = "/Applications/cmux.app";
9328
9698
  var TEMPLATES_DIR4 = path23.join(os12.homedir(), ".config", "squadrant", "templates");
9329
9699
  var SESSIONS_PATH2 = path23.join(os12.homedir(), ".config", "squadrant", "sessions.json");
9330
- function ensureCmuxReady() {
9331
- if (isInsideCmux()) return;
9700
+ function ensureCmuxReady(headless) {
9701
+ if (headless || isInsideCmux()) return;
9332
9702
  console.log(chalk13.yellow("\n Not running inside cmux. Opening cmux app...\n"));
9333
9703
  execSync11(`open "${CMUX_APP}"`, { stdio: "inherit" });
9334
9704
  console.log(chalk13.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
@@ -9336,8 +9706,9 @@ function ensureCmuxReady() {
9336
9706
  }
9337
9707
  var launchCommand = new Command12("launch").description(
9338
9708
  "Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
9339
- ).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").option("--all", "Launch all captain workspaces").action(async (project, opts) => {
9709
+ ).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").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) => {
9340
9710
  const config = loadConfig();
9711
+ let hadFailure = false;
9341
9712
  const drivers = {
9342
9713
  claude: createClaudeDriver(),
9343
9714
  codex: createCodexDriver(),
@@ -9347,7 +9718,7 @@ var launchCommand = new Command12("launch").description(
9347
9718
  const registry = new CapabilityRegistry(drivers);
9348
9719
  const runtimes = new RuntimeRegistry({ cmux: createCmuxDriver() });
9349
9720
  async function launchOne(workspaceName, role, cwd, permissionMode, navigate, pinToTop = false, projectName) {
9350
- ensureCmuxReady();
9721
+ ensureCmuxReady(!!opts.headless);
9351
9722
  const roleConfig = config.defaults.roles?.[role];
9352
9723
  const agentName = roleConfig?.agent || "claude";
9353
9724
  const model = roleConfig?.model || config.defaults.models?.[role];
@@ -9387,6 +9758,7 @@ var launchCommand = new Command12("launch").description(
9387
9758
  });
9388
9759
  } catch (err) {
9389
9760
  console.error(chalk13.red(` \u2718 Failed: ${err.message}`));
9761
+ hadFailure = true;
9390
9762
  }
9391
9763
  }
9392
9764
  if (opts.all) {
@@ -9415,7 +9787,7 @@ var launchCommand = new Command12("launch").description(
9415
9787
  );
9416
9788
  process.exit(1);
9417
9789
  }
9418
- ensureCmuxReady();
9790
+ ensureCmuxReady(!!opts.headless);
9419
9791
  const sessions = loadSessions(SESSIONS_PATH2);
9420
9792
  const entries = Object.entries(config.projects).map(([name, proj]) => ({
9421
9793
  projectName: name,
@@ -9472,6 +9844,7 @@ Launching captain workspace for '${project}' (${proj.captainName})
9472
9844
  );
9473
9845
  await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, project);
9474
9846
  }
9847
+ if (hadFailure) process.exitCode = 1;
9475
9848
  });
9476
9849
 
9477
9850
  // packages/cli/src/commands/shutdown.ts
@@ -10440,7 +10813,7 @@ init_dist2();
10440
10813
  import { Command as Command22 } from "commander";
10441
10814
  import fs25 from "fs";
10442
10815
  import { fileURLToPath as fileURLToPath5 } from "url";
10443
- import { dirname as dirname5, join as join23 } from "path";
10816
+ import { dirname as dirname6, join as join24 } from "path";
10444
10817
  import chalk22 from "chalk";
10445
10818
  function runConfigCheck(opts) {
10446
10819
  const raw = JSON.parse(fs25.readFileSync(opts.configPath, "utf-8"));
@@ -10576,7 +10949,7 @@ configCommand.command("set").description("Write a config value by dotted key (e.
10576
10949
  }
10577
10950
  });
10578
10951
  function readPkgVersion2() {
10579
- const pkgPath = join23(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
10952
+ const pkgPath = join24(dirname6(fileURLToPath5(import.meta.url)), "..", "package.json");
10580
10953
  return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version;
10581
10954
  }
10582
10955
 
@@ -10816,9 +11189,9 @@ var EFFORT_MEANING = {
10816
11189
  balance: "normal routing \u2014 use default crew routing rules unchanged",
10817
11190
  low: "conserve tokens \u2014 bias crew spawns toward opencode/sonnet; reserve opus for work that genuinely needs it"
10818
11191
  };
10819
- function runEffortGet(configPath = DEFAULT_CONFIG_PATH) {
11192
+ function runEffortGet(configPath = DEFAULT_CONFIG_PATH, projectName) {
10820
11193
  const config = loadConfig(configPath);
10821
- const effort = resolveEffort(config);
11194
+ const effort = resolveEffort(config, projectName);
10822
11195
  const description = `${effort}: ${EFFORT_MEANING[effort]}`;
10823
11196
  return { effort, description };
10824
11197
  }
@@ -10853,10 +11226,11 @@ async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(
10853
11226
  }
10854
11227
  }
10855
11228
  }
10856
- var effortCommand = new Command28("effort").description("Get or set the global crew tokenomics dial (max | balance | low)").argument("[value]", "effort level to set: max | balance | low").action(async (value) => {
11229
+ var effortCommand = new Command28("effort").description("Get or set the global crew tokenomics dial (max | balance | low)").argument("[value]", "effort level to set: max | balance | low").option("--project <name>", "show resolved effort for a specific project").action(async (value, options) => {
10857
11230
  if (value === void 0) {
10858
- const { effort: effort2, description } = runEffortGet();
10859
- console.log(chalk28.bold("Current effort:"), chalk28.cyan(effort2));
11231
+ const { effort: effort2, description } = runEffortGet(void 0, options.project);
11232
+ const label = options.project ? `${options.project} project` : "global";
11233
+ console.log(chalk28.bold(`Current effort (${label}):`), chalk28.cyan(effort2));
10860
11234
  console.log(chalk28.dim(EFFORT_MEANING[effort2]));
10861
11235
  return;
10862
11236
  }
@@ -10883,12 +11257,12 @@ var effortCommand = new Command28("effort").description("Get or set the global c
10883
11257
  // packages/cli/src/commands/telegram.ts
10884
11258
  init_dist();
10885
11259
  init_dist2();
10886
- import { join as join24, dirname as dirname6 } from "path";
11260
+ import { join as join25, dirname as dirname7 } from "path";
10887
11261
  import { emitKeypressEvents } from "readline";
10888
11262
  import { Command as Command29 } from "commander";
10889
11263
  import chalk29 from "chalk";
10890
11264
  function defaultStateRoot() {
10891
- return join24(dirname6(DEFAULT_CONFIG_PATH), "state");
11265
+ return join25(dirname7(DEFAULT_CONFIG_PATH), "state");
10892
11266
  }
10893
11267
  async function questionMasked() {
10894
11268
  return new Promise((resolve3) => {
@@ -11213,9 +11587,9 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
11213
11587
  init_dist2();
11214
11588
  init_dist4();
11215
11589
  import { Command as Command30 } from "commander";
11216
- import { join as join25 } from "path";
11217
- import { homedir as homedir17 } from "os";
11218
- var SOCK4 = join25(homedir17(), ".config", "squadrant", "squadrant.sock");
11590
+ import { join as join26 } from "path";
11591
+ import { homedir as homedir19 } from "os";
11592
+ var SOCK4 = join26(homedir19(), ".config", "squadrant", "squadrant.sock");
11219
11593
  async function sendToSock(req) {
11220
11594
  await sendRequest(SOCK4, req);
11221
11595
  }
@@ -11277,21 +11651,21 @@ function hooksCommand() {
11277
11651
  init_dist();
11278
11652
  init_dist();
11279
11653
  init_dist();
11280
- var __dirname = dirname7(fileURLToPath6(import.meta.url));
11281
- var pkg = JSON.parse(readFileSync13(join26(__dirname, "..", "package.json"), "utf-8"));
11654
+ var __dirname = dirname8(fileURLToPath6(import.meta.url));
11655
+ var pkg = JSON.parse(readFileSync15(join27(__dirname, "..", "package.json"), "utf-8"));
11282
11656
  ensureRuntimeSynced({
11283
- sourceRoot: join26(__dirname, ".."),
11284
- runtimeRoot: join26(homedir18(), ".config", "squadrant")
11657
+ sourceRoot: join27(__dirname, ".."),
11658
+ runtimeRoot: join27(homedir20(), ".config", "squadrant")
11285
11659
  });
11286
11660
  if (process.argv[2] !== "config") {
11287
11661
  try {
11288
- const cfgPath = join26(homedir18(), ".config", "squadrant", "config.json");
11662
+ const cfgPath = join27(homedir20(), ".config", "squadrant", "config.json");
11289
11663
  if (existsSync12(cfgPath)) {
11290
- const cfg = JSON.parse(readFileSync13(cfgPath, "utf-8"));
11664
+ const cfg = JSON.parse(readFileSync15(cfgPath, "utf-8"));
11291
11665
  if (needsCheck(cfg, pkg.version)) {
11292
11666
  const items = detectDrift(cfg, getDefaultConfig());
11293
11667
  if (items.length === 0) {
11294
- writeFileSync10(cfgPath, JSON.stringify(withStamp(cfg, pkg.version), null, 2) + "\n");
11668
+ writeFileSync11(cfgPath, JSON.stringify(withStamp(cfg, pkg.version), null, 2) + "\n");
11295
11669
  } else {
11296
11670
  const from = cfg._squadrantVersion ?? "an earlier version";
11297
11671
  process.stderr.write(