squadrant 0.17.1 → 0.18.1

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
@@ -9,15 +9,87 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // packages/shared/dist/config.js
12
+ // packages/shared/dist/lib/config-io.js
13
13
  import fs from "fs";
14
14
  import path from "path";
15
+ function tightenModeSync(p, expectedMode) {
16
+ try {
17
+ if (!fs.existsSync(p))
18
+ return;
19
+ const stat2 = fs.statSync(p);
20
+ const currentMode = stat2.mode & 4095;
21
+ if ((currentMode & ~expectedMode) !== 0) {
22
+ fs.chmodSync(p, expectedMode);
23
+ if (!didLogMigration) {
24
+ console.warn(`\x1B[33m\u26A0\x1B[0m squadrant: tightened config file permissions (security fix #668)`);
25
+ didLogMigration = true;
26
+ }
27
+ }
28
+ } catch {
29
+ }
30
+ }
31
+ function writeConfigFileSync(filePath, content) {
32
+ ensureDirSync(path.dirname(filePath));
33
+ fs.writeFileSync(filePath, content, { mode: 384 });
34
+ tightenModeSync(filePath, 384);
35
+ }
36
+ function ensureDirSync(dirPath) {
37
+ if (dirPath !== CONFIG_DIR && !dirPath.startsWith(CONFIG_DIR + path.sep)) {
38
+ fs.mkdirSync(dirPath, { recursive: true });
39
+ return;
40
+ }
41
+ fs.mkdirSync(dirPath, { recursive: true, mode: 448 });
42
+ let current = dirPath;
43
+ while (current === CONFIG_DIR || current.startsWith(CONFIG_DIR + path.sep)) {
44
+ tightenModeSync(current, 448);
45
+ if (current === CONFIG_DIR)
46
+ break;
47
+ const parent = path.dirname(current);
48
+ if (parent === current)
49
+ break;
50
+ current = parent;
51
+ }
52
+ }
53
+ function readConfigFileSync(filePath) {
54
+ return fs.readFileSync(filePath, "utf-8");
55
+ }
56
+ function migrateConfigPermsSync() {
57
+ if (migrationRun)
58
+ return;
59
+ migrationRun = true;
60
+ tightenModeSync(CONFIG_DIR, 448);
61
+ tightenModeSync(path.join(CONFIG_DIR, "config.json"), 384);
62
+ const projectsDir = path.join(CONFIG_DIR, "projects");
63
+ tightenModeSync(projectsDir, 448);
64
+ try {
65
+ if (fs.existsSync(projectsDir)) {
66
+ const files = fs.readdirSync(projectsDir);
67
+ for (const file of files) {
68
+ if (file.endsWith(".json")) {
69
+ tightenModeSync(path.join(projectsDir, file), 384);
70
+ }
71
+ }
72
+ }
73
+ } catch {
74
+ }
75
+ }
76
+ var didLogMigration, migrationRun;
77
+ var init_config_io = __esm({
78
+ "packages/shared/dist/lib/config-io.js"() {
79
+ init_config();
80
+ didLogMigration = false;
81
+ migrationRun = false;
82
+ }
83
+ });
84
+
85
+ // packages/shared/dist/config.js
86
+ import path2 from "path";
15
87
  import os from "os";
16
88
  import chalk from "chalk";
17
89
  function getDefaultConfig() {
18
90
  return {
19
91
  commandName: "\u{1F3DB}\uFE0F command",
20
- hubVault: path.join(os.homedir(), "squadrant-hub"),
92
+ hubVault: path2.join(os.homedir(), "squadrant-hub"),
21
93
  projects: {},
22
94
  agents: {
23
95
  claude: { cli: "claude", driver: "claude" }
@@ -61,13 +133,14 @@ function getDefaultConfig() {
61
133
  },
62
134
  metrics: {
63
135
  enabled: true,
64
- path: path.join(CONFIG_DIR, "metrics.json")
136
+ path: path2.join(CONFIG_DIR, "metrics.json")
65
137
  }
66
138
  };
67
139
  }
68
140
  function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
141
+ migrateConfigPermsSync();
69
142
  try {
70
- const raw = fs.readFileSync(configPath, "utf-8");
143
+ const raw = readConfigFileSync(configPath);
71
144
  const config = JSON.parse(raw);
72
145
  if (config.defaults.models && !config.defaults.roles) {
73
146
  const m = config.defaults.models;
@@ -92,34 +165,32 @@ function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
92
165
  }
93
166
  }
94
167
  function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
95
- const dir = path.dirname(configPath);
96
- fs.mkdirSync(dir, { recursive: true });
97
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
168
+ writeConfigFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
98
169
  }
99
170
  function resolveHome(p) {
100
171
  return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
101
172
  }
102
- var CONFIG_DIR, DEFAULT_CONFIG_PATH;
173
+ var DEFAULT_CONFIG_PATH, CONFIG_DIR;
103
174
  var init_config = __esm({
104
175
  "packages/shared/dist/config.js"() {
105
- CONFIG_DIR = path.join(os.homedir(), ".config", "squadrant");
106
- DEFAULT_CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
176
+ init_config_io();
177
+ DEFAULT_CONFIG_PATH = process.env.SQUADRANT_CONFIG || path2.join(os.homedir(), ".config", "squadrant", "config.json");
178
+ CONFIG_DIR = path2.dirname(DEFAULT_CONFIG_PATH);
107
179
  }
108
180
  });
109
181
 
110
182
  // packages/shared/dist/project-config.js
111
- import fs2 from "fs";
112
183
  import os2 from "os";
113
- import path2 from "path";
184
+ import path3 from "path";
114
185
  function defaultRoot() {
115
- return path2.join(os2.homedir(), ".config", "squadrant");
186
+ return path3.join(os2.homedir(), ".config", "squadrant");
116
187
  }
117
188
  function projectConfigPath(name, root = defaultRoot()) {
118
- return path2.join(root, "projects", `${name}.json`);
189
+ return path3.join(root, "projects", `${name}.json`);
119
190
  }
120
191
  function loadProjectOverride(name, root = defaultRoot()) {
121
192
  try {
122
- return JSON.parse(fs2.readFileSync(projectConfigPath(name, root), "utf-8"));
193
+ return JSON.parse(readConfigFileSync(projectConfigPath(name, root)));
123
194
  } catch {
124
195
  return {};
125
196
  }
@@ -136,8 +207,7 @@ function deepMerge(base, patch) {
136
207
  function saveProjectOverride(name, patch, root = defaultRoot()) {
137
208
  const merged = deepMerge(loadProjectOverride(name, root), patch);
138
209
  const file = projectConfigPath(name, root);
139
- fs2.mkdirSync(path2.dirname(file), { recursive: true });
140
- fs2.writeFileSync(file, JSON.stringify(merged, null, 2) + "\n");
210
+ writeConfigFileSync(file, JSON.stringify(merged, null, 2) + "\n");
141
211
  }
142
212
  function crewRank(tier) {
143
213
  return CREW_RANK[tier];
@@ -162,6 +232,7 @@ function resolveNotify(globalNotify, override) {
162
232
  var DEFAULT_NOTIFY, CREW_RANK;
163
233
  var init_project_config = __esm({
164
234
  "packages/shared/dist/project-config.js"() {
235
+ init_config_io();
165
236
  DEFAULT_NOTIFY = { active: false, cap: true, crew: "alert_only" };
166
237
  CREW_RANK = { none: 0, done_only: 1, alert_only: 2, all: 3 };
167
238
  }
@@ -234,22 +305,22 @@ function defaultCmuxConfigPath() {
234
305
  return join(homedir(), ".config", "cmux", "cmux.json");
235
306
  }
236
307
  function ensureSocketAutomation(opts = {}) {
237
- const path34 = opts.path ?? defaultCmuxConfigPath();
238
- if (!existsSync(path34)) {
239
- mkdirSync(dirname(path34), { recursive: true });
240
- writeFileSync(path34, MINIMAL_TEMPLATE);
241
- return { path: path34, changed: true, alreadySet: false };
308
+ const path35 = opts.path ?? defaultCmuxConfigPath();
309
+ if (!existsSync(path35)) {
310
+ mkdirSync(dirname(path35), { recursive: true });
311
+ writeFileSync(path35, MINIMAL_TEMPLATE);
312
+ return { path: path35, changed: true, alreadySet: false };
242
313
  }
243
- const text = readFileSync(path34, "utf-8");
314
+ const text = readFileSync(path35, "utf-8");
244
315
  const current = parse(text)?.automation?.socketControlMode;
245
316
  if (current === AUTOMATION_MODE) {
246
- return { path: path34, changed: false, alreadySet: true };
317
+ return { path: path35, changed: false, alreadySet: true };
247
318
  }
248
319
  const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
249
320
  formattingOptions: { insertSpaces: true, tabSize: 2 }
250
321
  });
251
- writeFileSync(path34, applyEdits(text, edits));
252
- return { path: path34, changed: true, alreadySet: false };
322
+ writeFileSync(path35, applyEdits(text, edits));
323
+ return { path: path35, changed: true, alreadySet: false };
253
324
  }
254
325
  var SOCKET_CONTROL_MODE_PATH, AUTOMATION_MODE, MINIMAL_TEMPLATE;
255
326
  var init_cmux_config = __esm({
@@ -396,15 +467,15 @@ writeFileSync(resultFile, JSON.stringify(result));
396
467
  });
397
468
 
398
469
  // packages/shared/dist/lib/cmux-autoconfig.js
399
- import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, rmSync as rmSync2 } from "fs";
470
+ import { existsSync as existsSync4, rmSync as rmSync2 } from "fs";
400
471
  import { homedir as homedir3 } from "os";
401
- import { dirname as dirname2, join as join4 } from "path";
472
+ import { join as join4 } from "path";
402
473
  function defaultStatePath() {
403
474
  return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
404
475
  }
405
- function readState(path34) {
476
+ function readState(path35) {
406
477
  try {
407
- return JSON.parse(readFileSync4(path34, "utf-8"));
478
+ return JSON.parse(readConfigFileSync(path35));
408
479
  } catch {
409
480
  return {};
410
481
  }
@@ -420,8 +491,7 @@ async function ensureCmuxAutoConfig(opts = {}) {
420
491
  if (needsRestart) {
421
492
  const already = readState(statePath2).promptedRestart === true;
422
493
  if (!already) {
423
- mkdirSync2(dirname2(statePath2), { recursive: true });
424
- writeFileSync3(statePath2, JSON.stringify({ promptedRestart: true }));
494
+ writeConfigFileSync(statePath2, JSON.stringify({ promptedRestart: true }));
425
495
  promptedThisRun = true;
426
496
  }
427
497
  } else if (verdict === "reachable") {
@@ -441,6 +511,7 @@ var init_cmux_autoconfig = __esm({
441
511
  "packages/shared/dist/lib/cmux-autoconfig.js"() {
442
512
  init_cmux_config();
443
513
  init_cmux_probe();
514
+ init_config_io();
444
515
  }
445
516
  });
446
517
 
@@ -624,8 +695,8 @@ var init_config_version = __esm({
624
695
  });
625
696
 
626
697
  // packages/shared/dist/lib/update-check.js
627
- import fs3 from "fs";
628
- import path3 from "path";
698
+ import fs2 from "fs";
699
+ import path4 from "path";
629
700
  import os3 from "os";
630
701
  import https from "https";
631
702
  function isUpdateCheckDisabled(config, env = process.env) {
@@ -648,8 +719,25 @@ function isNewerVersion(latest, current) {
648
719
  return lb > cb;
649
720
  return lc > cc;
650
721
  }
651
- function formatUpdateNotice(latest, current) {
652
- return `\u2B06 squadrant ${latest} available (you have ${current}) \u2014 npm i -g squadrant@latest`;
722
+ function detectInstallManager(modulePath = import.meta.url) {
723
+ if (modulePath.includes("Library/pnpm/") || modulePath.includes("/pnpm/"))
724
+ return "pnpm";
725
+ if (modulePath.includes("/yarn/global/node_modules/"))
726
+ return "yarn";
727
+ return "npm";
728
+ }
729
+ function upgradeCommandFor(manager) {
730
+ switch (manager) {
731
+ case "pnpm":
732
+ return "pnpm add -g squadrant@latest";
733
+ case "yarn":
734
+ return "yarn global add squadrant@latest";
735
+ case "npm":
736
+ return "npm i -g squadrant@latest";
737
+ }
738
+ }
739
+ function formatUpdateNotice(latest, current, modulePath = import.meta.url) {
740
+ return `\u2B06 squadrant ${latest} available (you have ${current}) \u2014 ${upgradeCommandFor(detectInstallManager(modulePath))}`;
653
741
  }
654
742
  async function fetchLatestVersion(requestFn = requestJson, timeoutMs = FETCH_TIMEOUT_MS) {
655
743
  const timeout = new Promise((resolve4) => {
@@ -681,15 +769,15 @@ async function checkForUpdate(opts) {
681
769
  }
682
770
  function readUpdateCheckState(statePath2 = UPDATE_CHECK_STATE_PATH) {
683
771
  try {
684
- return JSON.parse(fs3.readFileSync(statePath2, "utf-8"));
772
+ return JSON.parse(fs2.readFileSync(statePath2, "utf-8"));
685
773
  } catch {
686
774
  return void 0;
687
775
  }
688
776
  }
689
777
  function writeUpdateCheckState(state, statePath2 = UPDATE_CHECK_STATE_PATH) {
690
778
  try {
691
- fs3.mkdirSync(path3.dirname(statePath2), { recursive: true });
692
- fs3.writeFileSync(statePath2, JSON.stringify(state, null, 2) + "\n");
779
+ fs2.mkdirSync(path4.dirname(statePath2), { recursive: true });
780
+ fs2.writeFileSync(statePath2, JSON.stringify(state, null, 2) + "\n");
693
781
  } catch {
694
782
  }
695
783
  }
@@ -720,7 +808,7 @@ ${line}
720
808
  var UPDATE_CHECK_STATE_PATH, REGISTRY_URL, CHECK_INTERVAL_MS, FAILURE_RETRY_MS, FETCH_TIMEOUT_MS, requestJson;
721
809
  var init_update_check = __esm({
722
810
  "packages/shared/dist/lib/update-check.js"() {
723
- UPDATE_CHECK_STATE_PATH = path3.join(os3.homedir(), ".config", "squadrant", "update-check.json");
811
+ UPDATE_CHECK_STATE_PATH = path4.join(os3.homedir(), ".config", "squadrant", "update-check.json");
724
812
  REGISTRY_URL = "https://registry.npmjs.org/squadrant/latest";
725
813
  CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
726
814
  FAILURE_RETRY_MS = 60 * 60 * 1e3;
@@ -752,10 +840,10 @@ var init_update_check = __esm({
752
840
 
753
841
  // packages/shared/dist/lib/git-worktree.js
754
842
  import { execFileSync as execFileSync2 } from "child_process";
755
- import fs4 from "fs";
756
- import path4 from "path";
843
+ import fs3 from "fs";
844
+ import path5 from "path";
757
845
  function worktreePath(repoRoot, worktreeDir, project, name) {
758
- return path4.resolve(repoRoot, worktreeDir, `${project}-${name}`);
846
+ return path5.resolve(repoRoot, worktreeDir, `${project}-${name}`);
759
847
  }
760
848
  function crewBranch(name) {
761
849
  return `crew/${name}`;
@@ -764,15 +852,21 @@ function ensureSpotlightExcluded(repoRoot, worktreeDir) {
764
852
  if (process.platform !== "darwin")
765
853
  return;
766
854
  try {
767
- const dir = path4.resolve(repoRoot, worktreeDir);
768
- fs4.mkdirSync(dir, { recursive: true });
769
- const marker = path4.join(dir, ".metadata_never_index");
770
- if (!fs4.existsSync(marker))
771
- fs4.writeFileSync(marker, "");
855
+ const dir = path5.resolve(repoRoot, worktreeDir);
856
+ fs3.mkdirSync(dir, { recursive: true });
857
+ const marker = path5.join(dir, ".metadata_never_index");
858
+ if (!fs3.existsSync(marker))
859
+ fs3.writeFileSync(marker, "");
772
860
  } catch {
773
861
  }
774
862
  }
775
863
  function resolveWorktreeBase(repoRoot, fallback = "develop") {
864
+ try {
865
+ const head = execFileSync2("git", ["-C", repoRoot, "rev-parse", "--abbrev-ref", "HEAD"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
866
+ if (head && head !== "HEAD")
867
+ return head;
868
+ } catch {
869
+ }
776
870
  try {
777
871
  const ref = execFileSync2("git", ["-C", repoRoot, "symbolic-ref", "refs/remotes/origin/HEAD"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
778
872
  const m = ref.match(/^refs\/remotes\/origin\/(.+)$/);
@@ -824,35 +918,39 @@ function addWorktree(spec) {
824
918
  return wt;
825
919
  }
826
920
  function installWorktreeDependencies(wt) {
827
- if (!fs4.existsSync(path4.join(wt, "package.json")))
921
+ if (!fs3.existsSync(path5.join(wt, "package.json")))
828
922
  return;
829
- if (fs4.existsSync(path4.join(wt, "pnpm-lock.yaml"))) {
923
+ if (fs3.existsSync(path5.join(wt, "pnpm-lock.yaml"))) {
830
924
  execFileSync2("pnpm", ["-C", wt, "install", "--frozen-lockfile"], { stdio: "pipe" });
831
- } else if (fs4.existsSync(path4.join(wt, "yarn.lock"))) {
925
+ } else if (fs3.existsSync(path5.join(wt, "yarn.lock"))) {
832
926
  execFileSync2("yarn", ["install", "--frozen-lockfile"], { cwd: wt, stdio: "pipe" });
833
- } else if (fs4.existsSync(path4.join(wt, "package-lock.json"))) {
927
+ } else if (fs3.existsSync(path5.join(wt, "package-lock.json"))) {
834
928
  execFileSync2("npm", ["ci"], { cwd: wt, stdio: "pipe" });
835
- } else if (fs4.existsSync(path4.join(wt, "bun.lockb"))) {
929
+ } else if (fs3.existsSync(path5.join(wt, "bun.lockb"))) {
836
930
  execFileSync2("bun", ["install", "--frozen-lockfile"], { cwd: wt, stdio: "pipe" });
837
931
  } else {
838
932
  process.stderr.write(`worktree ${wt}: package.json present but no lockfile \u2014 dependencies not installed; local typechecks/tests may resolve against the main checkout instead of this worktree.
839
933
  `);
840
934
  }
841
935
  }
842
- function removeWorktree(repoRoot, wtPath) {
936
+ function worktreeDirtyFiles(wtPath) {
843
937
  try {
844
- execFileSync2("git", ["-C", repoRoot, "worktree", "remove", wtPath], { stdio: "pipe" });
938
+ 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
939
  } catch {
846
- execFileSync2("git", ["-C", repoRoot, "worktree", "remove", "--force", wtPath], { stdio: "pipe" });
940
+ return [];
847
941
  }
848
942
  }
943
+ function removeWorktree(repoRoot, wtPath, opts) {
944
+ const args = ["-C", repoRoot, "worktree", "remove", ...opts?.force ? ["--force"] : [], wtPath];
945
+ execFileSync2("git", args, { stdio: "pipe" });
946
+ }
849
947
  var init_git_worktree = __esm({
850
948
  "packages/shared/dist/lib/git-worktree.js"() {
851
949
  }
852
950
  });
853
951
 
854
952
  // packages/shared/dist/lib/resolve-text-input.js
855
- import fs5 from "fs";
953
+ import fs4 from "fs";
856
954
  async function readAllStdin() {
857
955
  const chunks = [];
858
956
  for await (const chunk of process.stdin) {
@@ -864,7 +962,7 @@ function flagName(label) {
864
962
  return label === "task" ? "--task-file" : "--message-file";
865
963
  }
866
964
  async function resolveTextInput(opts, deps) {
867
- const readFile6 = deps?.readFile ?? ((p) => fs5.readFileSync(p, "utf8"));
965
+ const readFile6 = deps?.readFile ?? ((p) => fs4.readFileSync(p, "utf8"));
868
966
  const readStdin3 = deps?.readStdin ?? readAllStdin;
869
967
  if (opts.filePath) {
870
968
  if (opts.filePath === "-") {
@@ -892,75 +990,75 @@ var init_resolve_text_input = __esm({
892
990
  });
893
991
 
894
992
  // packages/shared/dist/lib/runtime-sync.js
895
- import fs6 from "fs";
896
- import path5 from "path";
993
+ import fs5 from "fs";
994
+ import path6 from "path";
897
995
  function copyIfDifferent(src, dest) {
898
- if (fs6.existsSync(dest)) {
899
- if (fs6.readFileSync(src).equals(fs6.readFileSync(dest)))
996
+ if (fs5.existsSync(dest)) {
997
+ if (fs5.readFileSync(src).equals(fs5.readFileSync(dest)))
900
998
  return false;
901
999
  }
902
- fs6.copyFileSync(src, dest);
1000
+ fs5.copyFileSync(src, dest);
903
1001
  return true;
904
1002
  }
905
1003
  function mirrorDir(src, dest) {
906
- fs6.mkdirSync(dest, { recursive: true });
907
- const srcEntries = fs6.readdirSync(src, { withFileTypes: true });
1004
+ fs5.mkdirSync(dest, { recursive: true });
1005
+ const srcEntries = fs5.readdirSync(src, { withFileTypes: true });
908
1006
  const srcNames = new Set(srcEntries.map((e) => e.name));
909
1007
  for (const entry of srcEntries) {
910
- const srcPath = path5.join(src, entry.name);
911
- const destPath = path5.join(dest, entry.name);
1008
+ const srcPath = path6.join(src, entry.name);
1009
+ const destPath = path6.join(dest, entry.name);
912
1010
  if (entry.isDirectory()) {
913
1011
  mirrorDir(srcPath, destPath);
914
1012
  } else {
915
1013
  copyIfDifferent(srcPath, destPath);
916
1014
  }
917
1015
  }
918
- for (const entry of fs6.readdirSync(dest, { withFileTypes: true })) {
1016
+ for (const entry of fs5.readdirSync(dest, { withFileTypes: true })) {
919
1017
  if (!srcNames.has(entry.name)) {
920
- fs6.rmSync(path5.join(dest, entry.name), { recursive: true, force: true });
1018
+ fs5.rmSync(path6.join(dest, entry.name), { recursive: true, force: true });
921
1019
  }
922
1020
  }
923
1021
  }
924
1022
  function mirrorFlat(src, dest, match, chmod) {
925
- fs6.mkdirSync(dest, { recursive: true });
926
- const matched = fs6.readdirSync(src, { withFileTypes: true }).filter((e) => e.isFile() && match.test(e.name)).map((e) => e.name);
1023
+ fs5.mkdirSync(dest, { recursive: true });
1024
+ const matched = fs5.readdirSync(src, { withFileTypes: true }).filter((e) => e.isFile() && match.test(e.name)).map((e) => e.name);
927
1025
  const matchedSet = new Set(matched);
928
1026
  for (const name of matched) {
929
- const destPath = path5.join(dest, name);
930
- const copied = copyIfDifferent(path5.join(src, name), destPath);
1027
+ const destPath = path6.join(dest, name);
1028
+ const copied = copyIfDifferent(path6.join(src, name), destPath);
931
1029
  if (copied && chmod !== void 0)
932
- fs6.chmodSync(destPath, chmod);
1030
+ fs5.chmodSync(destPath, chmod);
933
1031
  }
934
- for (const entry of fs6.readdirSync(dest, { withFileTypes: true })) {
1032
+ for (const entry of fs5.readdirSync(dest, { withFileTypes: true })) {
935
1033
  if (!matchedSet.has(entry.name)) {
936
- fs6.rmSync(path5.join(dest, entry.name), { recursive: true, force: true });
1034
+ fs5.rmSync(path6.join(dest, entry.name), { recursive: true, force: true });
937
1035
  }
938
1036
  }
939
1037
  }
940
1038
  function mirrorPluginSubset(src, dest, skills) {
941
- fs6.mkdirSync(dest, { recursive: true });
942
- mirrorDir(path5.join(src, ".claude-plugin"), path5.join(dest, ".claude-plugin"));
943
- const skillsDest = path5.join(dest, "skills");
944
- fs6.mkdirSync(skillsDest, { recursive: true });
1039
+ fs5.mkdirSync(dest, { recursive: true });
1040
+ mirrorDir(path6.join(src, ".claude-plugin"), path6.join(dest, ".claude-plugin"));
1041
+ const skillsDest = path6.join(dest, "skills");
1042
+ fs5.mkdirSync(skillsDest, { recursive: true });
945
1043
  for (const name of skills) {
946
- const skillSrc = path5.join(src, "skills", name);
947
- if (fs6.existsSync(skillSrc))
948
- mirrorDir(skillSrc, path5.join(skillsDest, name));
1044
+ const skillSrc = path6.join(src, "skills", name);
1045
+ if (fs5.existsSync(skillSrc))
1046
+ mirrorDir(skillSrc, path6.join(skillsDest, name));
949
1047
  }
950
- for (const entry of fs6.readdirSync(skillsDest, { withFileTypes: true })) {
1048
+ for (const entry of fs5.readdirSync(skillsDest, { withFileTypes: true })) {
951
1049
  if (!skills.includes(entry.name)) {
952
- fs6.rmSync(path5.join(skillsDest, entry.name), { recursive: true, force: true });
1050
+ fs5.rmSync(path6.join(skillsDest, entry.name), { recursive: true, force: true });
953
1051
  }
954
1052
  }
955
1053
  }
956
1054
  function ensureRuntimeSynced(opts) {
957
1055
  const targets = opts.targets ?? MANAGED_TARGETS;
958
1056
  for (const t of targets) {
959
- const srcDir = path5.join(opts.sourceRoot, t.srcRel);
1057
+ const srcDir = path6.join(opts.sourceRoot, t.srcRel);
960
1058
  try {
961
- if (!fs6.existsSync(srcDir))
1059
+ if (!fs5.existsSync(srcDir))
962
1060
  continue;
963
- const destDir = path5.join(opts.runtimeRoot, t.name);
1061
+ const destDir = path6.join(opts.runtimeRoot, t.name);
964
1062
  if (t.mode === "tree") {
965
1063
  mirrorDir(srcDir, destDir);
966
1064
  } else if (t.mode === "flat") {
@@ -977,7 +1075,7 @@ function ensureRuntimeSynced(opts) {
977
1075
  var CREW_SKILLS, MANAGED_TARGETS;
978
1076
  var init_runtime_sync = __esm({
979
1077
  "packages/shared/dist/lib/runtime-sync.js"() {
980
- CREW_SKILLS = ["karpathy-principles"];
1078
+ CREW_SKILLS = ["karpathy-principles", "takeover", "handback"];
981
1079
  MANAGED_TARGETS = [
982
1080
  { name: "plugin", srcRel: "plugin", mode: "tree" },
983
1081
  { name: "plugin-crew", srcRel: "plugin", mode: "subset", skills: CREW_SKILLS },
@@ -1028,8 +1126,8 @@ var init_tool_compat = __esm({
1028
1126
  });
1029
1127
 
1030
1128
  // packages/shared/dist/lib/canonical-source.js
1031
- import fs7 from "fs";
1032
- import path6 from "path";
1129
+ import fs6 from "fs";
1130
+ import path7 from "path";
1033
1131
  function parseSkill(raw) {
1034
1132
  const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
1035
1133
  if (!match)
@@ -1070,10 +1168,10 @@ async function readSkills(driver, skillsDir) {
1070
1168
  function readRoleTemplates(opts) {
1071
1169
  if (!opts.pkgRoot)
1072
1170
  return "";
1073
- const reader = opts.readFile ?? ((p) => fs7.readFileSync(p, "utf-8"));
1171
+ const reader = opts.readFile ?? ((p) => fs6.readFileSync(p, "utf-8"));
1074
1172
  const sections = [];
1075
1173
  for (const { file, heading } of ROLE_TEMPLATES) {
1076
- const full = path6.join(opts.pkgRoot, "templates", file);
1174
+ const full = path7.join(opts.pkgRoot, "templates", file);
1077
1175
  let body = "";
1078
1176
  try {
1079
1177
  body = reader(full);
@@ -1110,8 +1208,8 @@ var init_canonical_source = __esm({
1110
1208
 
1111
1209
  // packages/shared/dist/lib/daily-logs.js
1112
1210
  import { execSync } from "child_process";
1113
- import fs8 from "fs";
1114
- import path7 from "path";
1211
+ import fs7 from "fs";
1212
+ import path8 from "path";
1115
1213
  import matter from "gray-matter";
1116
1214
  function iso(d) {
1117
1215
  return d.toISOString().slice(0, 10);
@@ -1163,7 +1261,7 @@ function getGitCommits(projectPath, dateStr) {
1163
1261
  }
1164
1262
  function getGitCommitsInRange(projectPath, since, until) {
1165
1263
  const resolved = resolveHome(projectPath);
1166
- if (!fs8.existsSync(path7.join(resolved, ".git")))
1264
+ if (!fs7.existsSync(path8.join(resolved, ".git")))
1167
1265
  return [];
1168
1266
  const untilArg = until ? ` --until="${until}"` : "";
1169
1267
  try {
@@ -1177,7 +1275,7 @@ function getGitCommitsInRange(projectPath, since, until) {
1177
1275
  }
1178
1276
  function getMergedPRsInRange(projectPath, since, until) {
1179
1277
  const resolved = resolveHome(projectPath);
1180
- if (!fs8.existsSync(path7.join(resolved, ".git")))
1278
+ if (!fs7.existsSync(path8.join(resolved, ".git")))
1181
1279
  return [];
1182
1280
  const untilArg = until ? ` --until="${until}"` : "";
1183
1281
  try {
@@ -1237,6 +1335,7 @@ var init_daemon_keys = __esm({
1237
1335
  var dist_exports = {};
1238
1336
  __export(dist_exports, {
1239
1337
  AUTOMATION_MODE: () => AUTOMATION_MODE,
1338
+ CONFIG_DIR: () => CONFIG_DIR,
1240
1339
  CREW_SKILLS: () => CREW_SKILLS,
1241
1340
  DEFAULT_CONFIG_PATH: () => DEFAULT_CONFIG_PATH,
1242
1341
  DEFAULT_NOTIFY: () => DEFAULT_NOTIFY,
@@ -1260,7 +1359,9 @@ __export(dist_exports, {
1260
1359
  defaultCmuxConfigPath: () => defaultCmuxConfigPath,
1261
1360
  defaultStatePath: () => defaultStatePath,
1262
1361
  detectDrift: () => detectDrift,
1362
+ detectInstallManager: () => detectInstallManager,
1263
1363
  ensureCmuxAutoConfig: () => ensureCmuxAutoConfig,
1364
+ ensureDirSync: () => ensureDirSync,
1264
1365
  ensureRuntimeSynced: () => ensureRuntimeSynced,
1265
1366
  ensureSocketAutomation: () => ensureSocketAutomation,
1266
1367
  ensureSpokeLayout: () => ensureSpokeLayout,
@@ -1279,6 +1380,7 @@ __export(dist_exports, {
1279
1380
  iso: () => iso,
1280
1381
  loadConfig: () => loadConfig,
1281
1382
  loadProjectOverride: () => loadProjectOverride,
1383
+ migrateConfigPermsSync: () => migrateConfigPermsSync,
1282
1384
  mirrorDir: () => mirrorDir,
1283
1385
  mirrorFlat: () => mirrorFlat,
1284
1386
  needsCheck: () => needsCheck,
@@ -1286,6 +1388,7 @@ __export(dist_exports, {
1286
1388
  parseSection: () => parseSection,
1287
1389
  probeCmuxDaemonDirect: () => probeCmuxDaemonDirect,
1288
1390
  projectConfigPath: () => projectConfigPath,
1391
+ readConfigFileSync: () => readConfigFileSync,
1289
1392
  readDailyLog: () => readDailyLog,
1290
1393
  readProjectLevelSource: () => readProjectLevelSource,
1291
1394
  readStamp: () => readStamp,
@@ -1302,7 +1405,9 @@ __export(dist_exports, {
1302
1405
  saveConfig: () => saveConfig,
1303
1406
  saveProjectOverride: () => saveProjectOverride,
1304
1407
  withStamp: () => withStamp,
1408
+ worktreeDirtyFiles: () => worktreeDirtyFiles,
1305
1409
  worktreePath: () => worktreePath,
1410
+ writeConfigFileSync: () => writeConfigFileSync,
1306
1411
  writeUpdateCheckState: () => writeUpdateCheckState
1307
1412
  });
1308
1413
  var init_dist = __esm({
@@ -1324,6 +1429,7 @@ var init_dist = __esm({
1324
1429
  init_config_drift();
1325
1430
  init_config_version();
1326
1431
  init_update_check();
1432
+ init_config_io();
1327
1433
  init_git_worktree();
1328
1434
  init_resolve_text_input();
1329
1435
  init_runtime_sync();
@@ -1361,7 +1467,21 @@ function nextPendingMonitor(current, ev, now) {
1361
1467
  }
1362
1468
  function reduce(rec, ev, now) {
1363
1469
  if (ev.type === "task.reopened") {
1364
- return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type };
1470
+ return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type, workingStretchStartedAt: now };
1471
+ }
1472
+ if (ev.type === "crew.takeover.started") {
1473
+ if (rec.operatorHold)
1474
+ return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
1475
+ return {
1476
+ ...rec,
1477
+ operatorHold: { since: now, ...ev.note !== void 0 ? { note: ev.note } : {} },
1478
+ lastHeartbeat: now,
1479
+ lastEvent: ev.type
1480
+ };
1481
+ }
1482
+ if (ev.type === "crew.takeover.ended") {
1483
+ const { operatorHold: _dropped, ...rest } = rec;
1484
+ return { ...rest, lastHeartbeat: now, lastEvent: ev.type };
1365
1485
  }
1366
1486
  if (TERMINAL_STATES.has(rec.state))
1367
1487
  return rec;
@@ -1377,8 +1497,9 @@ function reduce(rec, ev, now) {
1377
1497
  // resuming after a blocked→reply clears the question
1378
1498
  pendingTool: void 0,
1379
1499
  // #354: a new turn closes any prior tool window
1380
- pendingMonitor: void 0
1500
+ pendingMonitor: void 0,
1381
1501
  // #594a: same reset — a new turn moots any prior watch
1502
+ workingStretchStartedAt: now
1382
1503
  };
1383
1504
  case "task.progress": {
1384
1505
  const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
@@ -1549,6 +1670,8 @@ function firePush(deps, project, prev, next, event, lastCaptainTurnAt) {
1549
1670
  return;
1550
1671
  if (!ATTENTION_STATES.has(next.state))
1551
1672
  return;
1673
+ if (next.operatorHold)
1674
+ return;
1552
1675
  if (next.state === "awaiting-input" && lastCaptainTurnAt != null && deps.now() - lastCaptainTurnAt <= IDLE_DEBOUNCE_MS) {
1553
1676
  return;
1554
1677
  }
@@ -1757,9 +1880,32 @@ function createDaemon(deps) {
1757
1880
  store.delete(r.project, r.id);
1758
1881
  continue;
1759
1882
  }
1883
+ if (r.operatorHold) {
1884
+ const threshold = (deps.takeoverNudgeHours ?? 6) * 36e5;
1885
+ const holdAge = t - r.operatorHold.since;
1886
+ if (holdAge > threshold) {
1887
+ const timeSinceLastNudge = t - (r.operatorHold.lastNudgeAt ?? 0);
1888
+ if (timeSinceLastNudge > threshold) {
1889
+ const hrs = Math.round(holdAge / 36e5);
1890
+ const tag = crewTag(r);
1891
+ const message = `CREW HELD-LONG ${tag} \u2014 held ${hrs}h. Ask the operator whether it is still in use. Do not release it yourself.`;
1892
+ store.put({ ...r, operatorHold: { ...r.operatorHold, lastNudgeAt: t } });
1893
+ if (deps.notify) {
1894
+ try {
1895
+ const p = deps.notify({ project: r.project, message, record: r, event: { type: "task.progress", id: r.id } });
1896
+ if (p && typeof p.catch === "function")
1897
+ p.catch(() => {
1898
+ });
1899
+ } catch {
1900
+ }
1901
+ }
1902
+ }
1903
+ }
1904
+ }
1760
1905
  if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {
1761
1906
  const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
1762
- if (t - r.createdAt > ceiling) {
1907
+ const refTime = r.workingStretchStartedAt ?? r.createdAt;
1908
+ if (t - refTime > ceiling) {
1763
1909
  const prevState = r.state;
1764
1910
  const tag = crewTag(r);
1765
1911
  const hrs = Math.round(ceiling / 36e5);
@@ -1913,14 +2059,17 @@ var init_reduce = __esm({
1913
2059
  "task.reconcile-failed",
1914
2060
  "task.cancelled",
1915
2061
  "task.session.ended",
1916
- "task.first-turn.confirmed"
2062
+ "task.first-turn.confirmed",
1917
2063
  // #466: delivery confirmation
2064
+ "crew.takeover.started",
2065
+ "crew.takeover.ended"
2066
+ // #649: operator takeover
1918
2067
  ]);
1919
2068
  }
1920
2069
  });
1921
2070
 
1922
2071
  // packages/core/dist/mailbox.js
1923
- import { promises as fs9 } from "fs";
2072
+ import { promises as fs8 } from "fs";
1924
2073
  import { join as join5 } from "path";
1925
2074
  import { randomUUID } from "crypto";
1926
2075
  function inboxDir(stateRoot) {
@@ -1937,7 +2086,7 @@ async function listRotatedOldestFirst(stateRoot, project) {
1937
2086
  const dir = inboxDir(stateRoot);
1938
2087
  let entries;
1939
2088
  try {
1940
- entries = await fs9.readdir(dir);
2089
+ entries = await fs8.readdir(dir);
1941
2090
  } catch {
1942
2091
  return [];
1943
2092
  }
@@ -1946,7 +2095,7 @@ async function listRotatedOldestFirst(stateRoot, project) {
1946
2095
  }
1947
2096
  async function readMaxSeqFromFile(file) {
1948
2097
  try {
1949
- const buf = await fs9.readFile(file, "utf-8");
2098
+ const buf = await fs8.readFile(file, "utf-8");
1950
2099
  if (!buf.trim())
1951
2100
  return 0;
1952
2101
  const lines = buf.trim().split("\n");
@@ -1987,12 +2136,12 @@ function withProjectLock(project, fn) {
1987
2136
  function appendEntry(stateRoot, project, build) {
1988
2137
  return withProjectLock(project, async () => {
1989
2138
  const dir = inboxDir(stateRoot);
1990
- await fs9.mkdir(dir, { recursive: true });
2139
+ await fs8.mkdir(dir, { recursive: true });
1991
2140
  const file = logPath(stateRoot, project);
1992
2141
  const lastSeq = await readMaxSeq(stateRoot, project);
1993
2142
  const seq = lastSeq + 1;
1994
2143
  const entry = build(seq);
1995
- await fs9.appendFile(file, JSON.stringify(entry) + "\n", { encoding: "utf-8" });
2144
+ await fs8.appendFile(file, JSON.stringify(entry) + "\n", { encoding: "utf-8" });
1996
2145
  return seq;
1997
2146
  });
1998
2147
  }
@@ -2023,7 +2172,7 @@ function cursorPath(stateRoot, project, subscriber) {
2023
2172
  async function readCursor(opts) {
2024
2173
  let buf;
2025
2174
  try {
2026
- buf = await fs9.readFile(cursorPath(opts.stateRoot, opts.project, opts.subscriber), "utf-8");
2175
+ buf = await fs8.readFile(cursorPath(opts.stateRoot, opts.project, opts.subscriber), "utf-8");
2027
2176
  } catch (e) {
2028
2177
  if (e.code === "ENOENT")
2029
2178
  return null;
@@ -2050,7 +2199,7 @@ async function waitForCaptainDelivery(opts) {
2050
2199
  }
2051
2200
  }
2052
2201
  async function writeCursor(opts) {
2053
- await fs9.mkdir(inboxDir(opts.stateRoot), { recursive: true });
2202
+ await fs8.mkdir(inboxDir(opts.stateRoot), { recursive: true });
2054
2203
  const dest = cursorPath(opts.stateRoot, opts.project, opts.subscriber);
2055
2204
  const tmp = `${dest}.${process.pid}.${randomUUID()}.tmp`;
2056
2205
  const data = {
@@ -2058,7 +2207,7 @@ async function writeCursor(opts) {
2058
2207
  subscriber: opts.subscriber,
2059
2208
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2060
2209
  };
2061
- const handle = await fs9.open(tmp, "w");
2210
+ const handle = await fs8.open(tmp, "w");
2062
2211
  try {
2063
2212
  await handle.writeFile(JSON.stringify(data), { encoding: "utf-8" });
2064
2213
  await handle.sync();
@@ -2066,9 +2215,9 @@ async function writeCursor(opts) {
2066
2215
  await handle.close();
2067
2216
  }
2068
2217
  try {
2069
- await fs9.rename(tmp, dest);
2218
+ await fs8.rename(tmp, dest);
2070
2219
  } catch (e) {
2071
- await fs9.unlink(tmp).catch(() => {
2220
+ await fs8.unlink(tmp).catch(() => {
2072
2221
  });
2073
2222
  throw e;
2074
2223
  }
@@ -2079,7 +2228,7 @@ async function* readFromCursor(opts) {
2079
2228
  for (const file of files) {
2080
2229
  let buf;
2081
2230
  try {
2082
- buf = await fs9.readFile(file, "utf-8");
2231
+ buf = await fs8.readFile(file, "utf-8");
2083
2232
  } catch (e) {
2084
2233
  if (e.code === "ENOENT")
2085
2234
  continue;
@@ -2105,7 +2254,7 @@ async function mailboxStats(stateRoot, project) {
2105
2254
  let sizeBytes = 0;
2106
2255
  for (const f of [file, ...rotated]) {
2107
2256
  try {
2108
- sizeBytes += (await fs9.stat(f)).size;
2257
+ sizeBytes += (await fs8.stat(f)).size;
2109
2258
  } catch (e) {
2110
2259
  if (e.code !== "ENOENT")
2111
2260
  throw e;
@@ -2121,7 +2270,7 @@ async function mailboxStats(stateRoot, project) {
2121
2270
  }
2122
2271
  async function oldestEntryAgeMs(file) {
2123
2272
  try {
2124
- const buf = await fs9.readFile(file, "utf-8");
2273
+ const buf = await fs8.readFile(file, "utf-8");
2125
2274
  const firstLine2 = buf.split("\n").find((l) => l.trim());
2126
2275
  if (!firstLine2)
2127
2276
  return 0;
@@ -2136,7 +2285,7 @@ async function rotateIfNeeded(opts) {
2136
2285
  const file = logPath(opts.stateRoot, opts.project);
2137
2286
  let size = 0;
2138
2287
  try {
2139
- size = (await fs9.stat(file)).size;
2288
+ size = (await fs8.stat(file)).size;
2140
2289
  } catch (e) {
2141
2290
  if (e.code === "ENOENT")
2142
2291
  return { rotated: false };
@@ -2152,22 +2301,22 @@ async function rotateIfNeeded(opts) {
2152
2301
  const dst = `${file}.${n + 1}`;
2153
2302
  if (n + 1 > opts.keepCount) {
2154
2303
  try {
2155
- await fs9.unlink(src);
2304
+ await fs8.unlink(src);
2156
2305
  } catch (e) {
2157
2306
  if (e.code !== "ENOENT")
2158
2307
  throw e;
2159
2308
  }
2160
2309
  } else {
2161
2310
  try {
2162
- await fs9.rename(src, dst);
2311
+ await fs8.rename(src, dst);
2163
2312
  } catch (e) {
2164
2313
  if (e.code !== "ENOENT")
2165
2314
  throw e;
2166
2315
  }
2167
2316
  }
2168
2317
  }
2169
- await fs9.rename(file, `${file}.1`);
2170
- await fs9.writeFile(file, "", { encoding: "utf-8" });
2318
+ await fs8.rename(file, `${file}.1`);
2319
+ await fs8.writeFile(file, "", { encoding: "utf-8" });
2171
2320
  return { rotated: true, from: file, to: `${file}.1` };
2172
2321
  });
2173
2322
  }
@@ -2180,7 +2329,7 @@ var init_mailbox = __esm({
2180
2329
 
2181
2330
  // packages/core/dist/protocol.js
2182
2331
  import { createServer, createConnection } from "net";
2183
- import { existsSync as existsSync5, unlinkSync } from "fs";
2332
+ import { existsSync as existsSync5, unlinkSync, chmodSync } from "fs";
2184
2333
  function encodeMsg(obj) {
2185
2334
  return JSON.stringify(obj) + "\n";
2186
2335
  }
@@ -2293,6 +2442,12 @@ function startServer(sockPath, handlerOrCallbacks, onListenError = defaultListen
2293
2442
  });
2294
2443
  });
2295
2444
  server.on("error", onListenError);
2445
+ server.on("listening", () => {
2446
+ try {
2447
+ chmodSync(sockPath, 384);
2448
+ } catch {
2449
+ }
2450
+ });
2296
2451
  server.listen(sockPath);
2297
2452
  return server;
2298
2453
  }
@@ -2476,7 +2631,7 @@ var init_liveness2 = __esm({
2476
2631
  });
2477
2632
 
2478
2633
  // packages/core/dist/store.js
2479
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync, renameSync, writeFileSync as writeFileSync4, existsSync as existsSync6, rmSync as rmSync3, statSync } from "fs";
2634
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync, renameSync, writeFileSync as writeFileSync3, existsSync as existsSync6, rmSync as rmSync3, statSync } from "fs";
2480
2635
  import { join as join6, resolve, sep } from "path";
2481
2636
  function safeSegment(kind, s) {
2482
2637
  if (typeof s !== "string" || s.length === 0) {
@@ -2502,10 +2657,10 @@ function createStore(root) {
2502
2657
  const taskFile = (p, id) => assertUnderRoot(join6(projDir(p), `${safeSegment("id", id)}.json`));
2503
2658
  return {
2504
2659
  put(rec) {
2505
- mkdirSync3(projDir(rec.project), { recursive: true });
2660
+ mkdirSync2(projDir(rec.project), { recursive: true });
2506
2661
  const dest = taskFile(rec.project, rec.id);
2507
2662
  const tmp = `${dest}.tmp`;
2508
- writeFileSync4(tmp, JSON.stringify(rec, null, 2));
2663
+ writeFileSync3(tmp, JSON.stringify(rec, null, 2));
2509
2664
  renameSync(tmp, dest);
2510
2665
  },
2511
2666
  get(project, id) {
@@ -2513,7 +2668,7 @@ function createStore(root) {
2513
2668
  if (!existsSync6(f))
2514
2669
  return void 0;
2515
2670
  try {
2516
- return JSON.parse(readFileSync5(f, "utf-8"));
2671
+ return JSON.parse(readFileSync4(f, "utf-8"));
2517
2672
  } catch {
2518
2673
  return void 0;
2519
2674
  }
@@ -2524,7 +2679,7 @@ function createStore(root) {
2524
2679
  return [];
2525
2680
  return readdirSync(d).filter((n) => n.endsWith(".json")).map((n) => {
2526
2681
  try {
2527
- return JSON.parse(readFileSync5(join6(d, n), "utf-8"));
2682
+ return JSON.parse(readFileSync4(join6(d, n), "utf-8"));
2528
2683
  } catch {
2529
2684
  return void 0;
2530
2685
  }
@@ -2562,7 +2717,7 @@ var init_store = __esm({
2562
2717
  import { homedir as homedir4 } from "os";
2563
2718
  import { join as join7, resolve as resolve2, sep as sep2 } from "path";
2564
2719
  import { randomBytes } from "crypto";
2565
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, readdirSync as readdirSync2, renameSync as renameSync2, writeFileSync as writeFileSync5, existsSync as existsSync7, rmSync as rmSync4, statSync as statSync2 } from "fs";
2720
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, renameSync as renameSync2, writeFileSync as writeFileSync4, existsSync as existsSync7, rmSync as rmSync4, statSync as statSync2 } from "fs";
2566
2721
  function defaultWorkRoot() {
2567
2722
  return join7(homedir4(), ".config", "squadrant", "work");
2568
2723
  }
@@ -2590,10 +2745,10 @@ function createWorkStore(root = defaultWorkRoot()) {
2590
2745
  const itemFile = (p, id) => assertUnderRoot(join7(projDir(p), `${safeSegment2("id", id)}.json`));
2591
2746
  return {
2592
2747
  put(item) {
2593
- mkdirSync4(projDir(item.project), { recursive: true });
2748
+ mkdirSync3(projDir(item.project), { recursive: true });
2594
2749
  const dest = itemFile(item.project, item.id);
2595
2750
  const tmp = `${dest}.tmp`;
2596
- writeFileSync5(tmp, JSON.stringify(item, null, 2));
2751
+ writeFileSync4(tmp, JSON.stringify(item, null, 2));
2597
2752
  renameSync2(tmp, dest);
2598
2753
  },
2599
2754
  get(project, id) {
@@ -2601,7 +2756,7 @@ function createWorkStore(root = defaultWorkRoot()) {
2601
2756
  if (!existsSync7(f))
2602
2757
  return void 0;
2603
2758
  try {
2604
- return JSON.parse(readFileSync6(f, "utf-8"));
2759
+ return JSON.parse(readFileSync5(f, "utf-8"));
2605
2760
  } catch {
2606
2761
  return void 0;
2607
2762
  }
@@ -2612,7 +2767,7 @@ function createWorkStore(root = defaultWorkRoot()) {
2612
2767
  return [];
2613
2768
  return readdirSync2(d).filter((n) => n.endsWith(".json") && !n.endsWith(".json.tmp")).map((n) => {
2614
2769
  try {
2615
- return JSON.parse(readFileSync6(join7(d, n), "utf-8"));
2770
+ return JSON.parse(readFileSync5(join7(d, n), "utf-8"));
2616
2771
  } catch {
2617
2772
  return void 0;
2618
2773
  }
@@ -2756,15 +2911,15 @@ var init_snapshot = __esm({
2756
2911
 
2757
2912
  // packages/core/dist/launchd.js
2758
2913
  import { execFileSync as execFileSync3 } from "child_process";
2759
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6, readFileSync as readFileSync7, existsSync as existsSync8, openSync, writeSync, closeSync, unlinkSync as unlinkSync2, constants } from "fs";
2914
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync8, openSync, writeSync, closeSync, unlinkSync as unlinkSync2, constants } from "fs";
2760
2915
  import { homedir as homedir5 } from "os";
2761
- import { dirname as dirname3, join as join8 } from "path";
2916
+ import { dirname as dirname2, join as join8 } from "path";
2762
2917
  import { fileURLToPath } from "url";
2763
2918
  function plistPath() {
2764
2919
  return join8(homedir5(), "Library", "LaunchAgents", `${LABEL}.plist`);
2765
2920
  }
2766
2921
  function daemonEntryPath() {
2767
- const p = join8(dirname3(fileURLToPath(import.meta.url)), "squadrantd.js");
2922
+ const p = join8(dirname2(fileURLToPath(import.meta.url)), "squadrantd.js");
2768
2923
  if (!existsSync8(p)) {
2769
2924
  throw new Error(`daemonEntryPath: compiled entry not found at '${p}'; run 'npm run build' \u2014 a src-tree or missing path in the launchd plist causes a MODULE_NOT_FOUND crash-loop (#259)`);
2770
2925
  }
@@ -2773,10 +2928,28 @@ function daemonEntryPath() {
2773
2928
  function xmlEscape(s) {
2774
2929
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2775
2930
  }
2776
- function sanitizePathForPlist(path34) {
2931
+ function xmlUnescape(s) {
2932
+ return s.replace(/&quot;/g, '"').replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
2933
+ }
2934
+ function parseProgramArgs(plistXml) {
2935
+ const m = plistXml.match(/<key>ProgramArguments<\/key>\s*<array><string>([^<]*)<\/string><string>([^<]*)<\/string><\/array>/);
2936
+ if (!m)
2937
+ return null;
2938
+ return { nodeBin: xmlUnescape(m[1]), daemonEntry: xmlUnescape(m[2]) };
2939
+ }
2940
+ function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
2941
+ if (!parsed)
2942
+ return null;
2943
+ if (parsed.daemonEntry === thisEntry)
2944
+ return null;
2945
+ if (!registeredEntryExists)
2946
+ return null;
2947
+ return { registeredEntry: parsed.daemonEntry, thisEntry };
2948
+ }
2949
+ function sanitizePathForPlist(path35) {
2777
2950
  const seen = /* @__PURE__ */ new Set();
2778
2951
  const stable = [];
2779
- for (const p of path34.split(":")) {
2952
+ for (const p of path35.split(":")) {
2780
2953
  if (!p)
2781
2954
  continue;
2782
2955
  if (p.includes("/.claude/plugins/"))
@@ -2795,7 +2968,7 @@ function resolveAgentBinDirs() {
2795
2968
  const out = execFileSync3("which", [bin], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
2796
2969
  const resolved = out.trim();
2797
2970
  if (resolved)
2798
- dirs.push(dirname3(resolved));
2971
+ dirs.push(dirname2(resolved));
2799
2972
  } catch {
2800
2973
  }
2801
2974
  }
@@ -2857,7 +3030,7 @@ function tryAcquireDaemonLock() {
2857
3030
  const lp = daemonLockPath();
2858
3031
  if (existsSync8(lp)) {
2859
3032
  try {
2860
- const pid = parseInt(readFileSync7(lp, "utf-8").trim(), 10);
3033
+ const pid = parseInt(readFileSync6(lp, "utf-8").trim(), 10);
2861
3034
  if (!Number.isFinite(pid) || pid <= 0) {
2862
3035
  unlinkSync2(lp);
2863
3036
  } else {
@@ -2894,17 +3067,19 @@ function computeDaemonDrift(nodeBin) {
2894
3067
  const p = plistPath();
2895
3068
  const entry = daemonEntryPath();
2896
3069
  const desired = renderPlist(nodeBin, entry, buildDaemonPath(process.env.PATH ?? ""));
2897
- const current = existsSync8(p) ? readFileSync7(p, "utf-8") : null;
3070
+ const current = existsSync8(p) ? readFileSync6(p, "utf-8") : null;
2898
3071
  const uid = process.getuid?.() ?? 0;
2899
3072
  const target = `gui/${uid}/${LABEL}`;
2900
3073
  const changed = current !== desired;
2901
3074
  const programChanged = current !== null && changed && !current.includes(programArgsBlock(nodeBin, entry));
2902
- return { plistPath: p, target, desired, current, changed, programChanged };
3075
+ const parsedCurrent = current !== null ? parseProgramArgs(current) : null;
3076
+ const foreignInstall = detectForeignInstall(parsedCurrent, entry, parsedCurrent !== null && existsSync8(parsedCurrent.daemonEntry));
3077
+ return { plistPath: p, target, desired, current, changed, programChanged, foreignInstall };
2903
3078
  }
2904
3079
  function applyDaemonDrift(drift) {
2905
3080
  if (drift.changed) {
2906
- mkdirSync5(dirname3(drift.plistPath), { recursive: true });
2907
- writeFileSync6(drift.plistPath, drift.desired);
3081
+ mkdirSync4(dirname2(drift.plistPath), { recursive: true });
3082
+ writeFileSync5(drift.plistPath, drift.desired);
2908
3083
  }
2909
3084
  if (drift.programChanged) {
2910
3085
  try {
@@ -2940,7 +3115,12 @@ function ensureDaemon(nodeBin = process.execPath, opts = {}) {
2940
3115
  return;
2941
3116
  }
2942
3117
  try {
2943
- applyDaemonDrift(computeDaemonDrift(nodeBin));
3118
+ const drift = computeDaemonDrift(nodeBin);
3119
+ if (drift.foreignInstall) {
3120
+ process.stderr.write(printForeignInstallError(drift.foreignInstall));
3121
+ return;
3122
+ }
3123
+ applyDaemonDrift(drift);
2944
3124
  } catch (e) {
2945
3125
  process.stderr.write(`[squadrant] warn: ensureDaemon failed (${e instanceof Error ? e.message : e})
2946
3126
  `);
@@ -2948,6 +3128,13 @@ function ensureDaemon(nodeBin = process.execPath, opts = {}) {
2948
3128
  releaseDaemonLock();
2949
3129
  }
2950
3130
  }
3131
+ function printForeignInstallError(foreign) {
3132
+ return `[squadrant] refusing to restart the daemon: the registered launchd config belongs to a DIFFERENT squadrant install than this one.
3133
+ registered install: ${foreign.registeredEntry}
3134
+ this install: ${foreign.thisEntry}
3135
+ Two squadrant installs on this machine will keep fighting over the daemon (#670). Uninstall the one you don't use, then run \`squadrant heal daemon\` to reconcile.
3136
+ `;
3137
+ }
2951
3138
  function reregisterDaemon(nodeBin = process.execPath) {
2952
3139
  if (!tryAcquireDaemonLock())
2953
3140
  return;
@@ -3111,7 +3298,7 @@ var init_gate = __esm({
3111
3298
  });
3112
3299
 
3113
3300
  // packages/core/dist/daemon/liveness-registry.js
3114
- import { writeFileSync as writeFileSync7, readFileSync as readFileSync8, renameSync as renameSync3 } from "fs";
3301
+ import { writeFileSync as writeFileSync6, readFileSync as readFileSync7, renameSync as renameSync3 } from "fs";
3115
3302
  var LivenessRegistry;
3116
3303
  var init_liveness_registry = __esm({
3117
3304
  "packages/core/dist/daemon/liveness-registry.js"() {
@@ -3125,13 +3312,13 @@ var init_liveness_registry = __esm({
3125
3312
  this.path = opts.path;
3126
3313
  this.readFile = opts.readFile ?? ((p) => {
3127
3314
  try {
3128
- return readFileSync8(p, "utf-8");
3315
+ return readFileSync7(p, "utf-8");
3129
3316
  } catch {
3130
3317
  return void 0;
3131
3318
  }
3132
3319
  });
3133
3320
  this.writeFile = opts.writeFile ?? ((p, c) => {
3134
- writeFileSync7(`${p}.tmp`, c);
3321
+ writeFileSync6(`${p}.tmp`, c);
3135
3322
  renameSync3(`${p}.tmp`, p);
3136
3323
  });
3137
3324
  }
@@ -3184,7 +3371,7 @@ var init_liveness_registry = __esm({
3184
3371
  import { homedir as homedir6 } from "os";
3185
3372
  import { join as join9 } from "path";
3186
3373
  import { spawn as realSpawn } from "child_process";
3187
- import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
3374
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
3188
3375
  function defaultIsPidAlive(pid) {
3189
3376
  try {
3190
3377
  process.kill(pid, 0);
@@ -3198,14 +3385,16 @@ function buildContext(opts) {
3198
3385
  const sockPath = opts.sockPath ?? join9(homedir6(), ".config", "squadrant", "squadrant.sock");
3199
3386
  const store = createStore(stateRoot);
3200
3387
  const bootedAt = Date.now();
3201
- const taskTimeoutMs = loadConfig().defaults.taskTimeoutMs;
3388
+ const config = loadConfig();
3389
+ const taskTimeoutMs = config.defaults.taskTimeoutMs;
3390
+ const takeoverNudgeHours = config.defaults.takeoverNudgeHours;
3202
3391
  const isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
3203
3392
  const spawn2 = opts.spawn ?? realSpawn;
3204
3393
  const resultsDir = join9(stateRoot, "_results");
3205
- mkdirSync6(resultsDir, { recursive: true });
3394
+ mkdirSync5(resultsDir, { recursive: true });
3206
3395
  const writeResult = (id, payload) => {
3207
3396
  const p = join9(resultsDir, `${id}.txt`);
3208
- writeFileSync8(p, payload);
3397
+ writeFileSync7(p, payload);
3209
3398
  return p;
3210
3399
  };
3211
3400
  const log = (m) => process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${m}
@@ -3218,6 +3407,7 @@ function buildContext(opts) {
3218
3407
  bootedAt,
3219
3408
  lastSweepAt: { value: null },
3220
3409
  taskTimeoutMs,
3410
+ takeoverNudgeHours,
3221
3411
  isPidAlive,
3222
3412
  spawn: spawn2,
3223
3413
  resultsDir,
@@ -3950,7 +4140,7 @@ var init_server = __esm({
3950
4140
  // packages/core/dist/daemon/snapshot-gather.js
3951
4141
  import { fileURLToPath as fileURLToPath2 } from "url";
3952
4142
  import { join as join10 } from "path";
3953
- import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
4143
+ import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
3954
4144
  function distBuiltAt() {
3955
4145
  try {
3956
4146
  return statSync3(SELF_PATH).mtimeMs;
@@ -3958,10 +4148,10 @@ function distBuiltAt() {
3958
4148
  return 0;
3959
4149
  }
3960
4150
  }
3961
- function gatherLogStats(path34, now, windowMs) {
4151
+ function gatherLogStats(path35, now, windowMs) {
3962
4152
  let sizeBytes = 0;
3963
4153
  try {
3964
- sizeBytes = statSync3(path34).size;
4154
+ sizeBytes = statSync3(path35).size;
3965
4155
  } catch {
3966
4156
  return { errorCount: 0, sizeBytes: 0, windowMs };
3967
4157
  }
@@ -3972,7 +4162,7 @@ function gatherLogStats(path34, now, windowMs) {
3972
4162
  const len = sizeBytes - start;
3973
4163
  let text = "";
3974
4164
  try {
3975
- const fd = openSync2(path34, "r");
4165
+ const fd = openSync2(path35, "r");
3976
4166
  try {
3977
4167
  const buf = Buffer.alloc(len);
3978
4168
  readSync(fd, buf, 0, len, start);
@@ -4013,7 +4203,7 @@ function gatherStoreStats(store, stateRoot, project) {
4013
4203
  if (!n.endsWith(".json"))
4014
4204
  continue;
4015
4205
  try {
4016
- JSON.parse(readFileSync9(join10(dir, n), "utf-8"));
4206
+ JSON.parse(readFileSync8(join10(dir, n), "utf-8"));
4017
4207
  } catch {
4018
4208
  corruptCount++;
4019
4209
  }
@@ -4048,7 +4238,7 @@ var init_snapshot_gather = __esm({
4048
4238
  });
4049
4239
 
4050
4240
  // packages/core/dist/daemon/start.js
4051
- import { join as join11, dirname as dirname4 } from "path";
4241
+ import { join as join11, dirname as dirname3 } from "path";
4052
4242
  import { readdir } from "fs/promises";
4053
4243
  function startDaemon(ctx, opts, pkgVersion) {
4054
4244
  const { stateRoot, store, log, isPidAlive, resultsDir, taskTimeoutMs, inFlightHeadlessIds, activeHeadlessKills, broadcast, cancelPromotionsFor } = ctx;
@@ -4068,6 +4258,7 @@ function startDaemon(ctx, opts, pkgVersion) {
4068
4258
  isPidAlive,
4069
4259
  notify,
4070
4260
  taskTimeoutMs,
4261
+ takeoverNudgeHours: ctx.takeoverNudgeHours,
4071
4262
  isSurfaceAlive: surfaceProbe,
4072
4263
  resendFirstTurn: ctx.resendFirstTurn,
4073
4264
  launchHeadless: opts.launchHeadless,
@@ -4123,7 +4314,7 @@ function startDaemon(ctx, opts, pkgVersion) {
4123
4314
  return out;
4124
4315
  }
4125
4316
  async function gatherSnapshotInputs(now) {
4126
- const logPath2 = join11(dirname4(stateRoot), "squadrantd.log");
4317
+ const logPath2 = join11(dirname3(stateRoot), "squadrantd.log");
4127
4318
  const tier2Projects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
4128
4319
  const projects = await Promise.all(tier2Projects.map(async (project) => {
4129
4320
  const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER2 });
@@ -4328,35 +4519,33 @@ var init_start = __esm({
4328
4519
 
4329
4520
  // packages/core/dist/session-freshness.js
4330
4521
  import crypto from "crypto";
4331
- import fs10 from "fs";
4332
- import path8 from "path";
4522
+ import fs9 from "fs";
4523
+ import path9 from "path";
4333
4524
  function loadSessions(sessionsPath) {
4334
4525
  try {
4335
- return JSON.parse(fs10.readFileSync(sessionsPath, "utf-8"));
4526
+ return JSON.parse(readConfigFileSync(sessionsPath));
4336
4527
  } catch {
4337
4528
  return { workspaces: {} };
4338
4529
  }
4339
4530
  }
4340
4531
  function saveSessions(sessionsPath, sessions) {
4341
- const dir = path8.dirname(sessionsPath);
4342
- fs10.mkdirSync(dir, { recursive: true });
4343
- fs10.writeFileSync(sessionsPath, JSON.stringify(sessions, null, 2) + "\n");
4532
+ writeConfigFileSync(sessionsPath, JSON.stringify(sessions, null, 2) + "\n");
4344
4533
  }
4345
4534
  function computeTemplateHash(role, templatesDir) {
4346
4535
  const hash = crypto.createHash("sha256");
4347
- const roleFile = path8.join(templatesDir, `${role}.claude.md`);
4348
- const legacyRoleFile = path8.join(templatesDir, `${role}.CLAUDE.md`);
4349
- if (fs10.existsSync(roleFile)) {
4350
- hash.update(fs10.readFileSync(roleFile, "utf-8"));
4351
- } else if (fs10.existsSync(legacyRoleFile)) {
4352
- hash.update(fs10.readFileSync(legacyRoleFile, "utf-8"));
4536
+ const roleFile = path9.join(templatesDir, `${role}.claude.md`);
4537
+ const legacyRoleFile = path9.join(templatesDir, `${role}.CLAUDE.md`);
4538
+ if (fs9.existsSync(roleFile)) {
4539
+ hash.update(fs9.readFileSync(roleFile, "utf-8"));
4540
+ } else if (fs9.existsSync(legacyRoleFile)) {
4541
+ hash.update(fs9.readFileSync(legacyRoleFile, "utf-8"));
4353
4542
  }
4354
- const pluginSkillsDir = path8.join(templatesDir, "..", "plugin", "skills");
4355
- if (fs10.existsSync(pluginSkillsDir)) {
4356
- for (const skill of fs10.readdirSync(pluginSkillsDir).sort()) {
4357
- const skillFile = path8.join(pluginSkillsDir, skill, "SKILL.md");
4358
- if (fs10.existsSync(skillFile)) {
4359
- hash.update(fs10.readFileSync(skillFile, "utf-8"));
4543
+ const pluginSkillsDir = path9.join(templatesDir, "..", "plugin", "skills");
4544
+ if (fs9.existsSync(pluginSkillsDir)) {
4545
+ for (const skill of fs9.readdirSync(pluginSkillsDir).sort()) {
4546
+ const skillFile = path9.join(pluginSkillsDir, skill, "SKILL.md");
4547
+ if (fs9.existsSync(skillFile)) {
4548
+ hash.update(fs9.readFileSync(skillFile, "utf-8"));
4360
4549
  }
4361
4550
  }
4362
4551
  }
@@ -4388,6 +4577,7 @@ function recordSession(workspaceName, role, opts) {
4388
4577
  }
4389
4578
  var init_session_freshness = __esm({
4390
4579
  "packages/core/dist/session-freshness.js"() {
4580
+ init_dist();
4391
4581
  }
4392
4582
  });
4393
4583
 
@@ -4777,17 +4967,16 @@ var init_format = __esm({
4777
4967
  });
4778
4968
 
4779
4969
  // packages/core/dist/telegram/state.js
4780
- import fs11 from "fs";
4781
- import path9 from "path";
4970
+ import path10 from "path";
4782
4971
  function statePath(stateRoot) {
4783
- return path9.join(stateRoot, "telegram-state.json");
4972
+ return path10.join(stateRoot, "telegram-state.json");
4784
4973
  }
4785
4974
  function topicKey(project, scope = "project") {
4786
4975
  return `${project}::${scope}`;
4787
4976
  }
4788
4977
  function loadState(stateRoot) {
4789
4978
  try {
4790
- const raw = fs11.readFileSync(statePath(stateRoot), "utf-8");
4979
+ const raw = readConfigFileSync(statePath(stateRoot));
4791
4980
  const data = JSON.parse(raw);
4792
4981
  const result = {
4793
4982
  offset: typeof data.offset === "number" ? data.offset : 0,
@@ -4802,8 +4991,7 @@ function loadState(stateRoot) {
4802
4991
  }
4803
4992
  }
4804
4993
  function saveState(stateRoot, s) {
4805
- fs11.mkdirSync(stateRoot, { recursive: true });
4806
- fs11.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
4994
+ writeConfigFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
4807
4995
  }
4808
4996
  function setTopic(stateRoot, project, topicId, scope = "project") {
4809
4997
  const s = loadState(stateRoot);
@@ -4837,6 +5025,7 @@ function findProjectByThread(stateRoot, threadId) {
4837
5025
  }
4838
5026
  var init_state = __esm({
4839
5027
  "packages/core/dist/telegram/state.js"() {
5028
+ init_dist();
4840
5029
  }
4841
5030
  });
4842
5031
 
@@ -4988,7 +5177,7 @@ var init_tiers = __esm({
4988
5177
 
4989
5178
  // packages/core/dist/telegram/bridge.js
4990
5179
  import os4 from "os";
4991
- import path10 from "path";
5180
+ import path11 from "path";
4992
5181
  function parseNotifyPref(text) {
4993
5182
  const parts = text.trim().split(/\s+/);
4994
5183
  if (stripBotMention(parts[0] ?? "").toLowerCase() !== "/notify")
@@ -5015,7 +5204,7 @@ function notifyToggle(text) {
5015
5204
  }
5016
5205
  function createTelegramBridge(opts) {
5017
5206
  const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
5018
- const configRoot = opts.configRoot ?? path10.join(os4.homedir(), ".config", "squadrant");
5207
+ const configRoot = opts.configRoot ?? path11.join(os4.homedir(), ".config", "squadrant");
5019
5208
  const pollMs = cfg.pollMs ?? 1e3;
5020
5209
  let running = false;
5021
5210
  let lastSuccessfulPollAt = null;
@@ -5146,14 +5335,14 @@ function createTelegramBridge(opts) {
5146
5335
  }
5147
5336
  function currentEffort() {
5148
5337
  try {
5149
- return loadConfig(path10.join(configRoot, "config.json")).defaults.effort ?? "balance";
5338
+ return loadConfig(path11.join(configRoot, "config.json")).defaults.effort ?? "balance";
5150
5339
  } catch {
5151
5340
  return "balance";
5152
5341
  }
5153
5342
  }
5154
5343
  function projectNames() {
5155
5344
  try {
5156
- return Object.keys(loadConfig(path10.join(configRoot, "config.json")).projects);
5345
+ return Object.keys(loadConfig(path11.join(configRoot, "config.json")).projects);
5157
5346
  } catch {
5158
5347
  return [];
5159
5348
  }
@@ -5421,7 +5610,7 @@ var init_restart_daemon = __esm({
5421
5610
  });
5422
5611
 
5423
5612
  // packages/core/dist/telegram/setup.js
5424
- import fs12 from "fs";
5613
+ import fs10 from "fs";
5425
5614
  function resolveSetupGroup(existingSupergroupId, opts) {
5426
5615
  if (existingSupergroupId !== void 0 && !opts.redetect)
5427
5616
  return "reuse";
@@ -5472,7 +5661,7 @@ function writeTelegramConfig(configPath, opts) {
5472
5661
  let config;
5473
5662
  let raw = null;
5474
5663
  try {
5475
- raw = fs12.readFileSync(configPath, "utf-8");
5664
+ raw = fs10.readFileSync(configPath, "utf-8");
5476
5665
  } catch (err) {
5477
5666
  if (err.code !== "ENOENT") {
5478
5667
  throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);
@@ -5500,10 +5689,11 @@ function writeTelegramConfig(configPath, opts) {
5500
5689
  if (remoteControl !== void 0)
5501
5690
  next.remoteControl = remoteControl;
5502
5691
  config.telegram = next;
5503
- fs12.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
5692
+ writeConfigFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
5504
5693
  }
5505
5694
  var init_setup = __esm({
5506
5695
  "packages/core/dist/telegram/setup.js"() {
5696
+ init_dist();
5507
5697
  init_state();
5508
5698
  init_bot_commands();
5509
5699
  init_restart_daemon();
@@ -5829,7 +6019,7 @@ var init_launch_workspace = __esm({
5829
6019
  });
5830
6020
 
5831
6021
  // packages/core/dist/side-session.js
5832
- import fs13 from "fs";
6022
+ import fs11 from "fs";
5833
6023
  function sideTitleFor(project, name) {
5834
6024
  return `\u{1F5D2} ${project}:${name}`;
5835
6025
  }
@@ -5931,7 +6121,7 @@ async function runSideClose(runtime, workspaceId, project, name, projPath, workt
5931
6121
  await runtime.closePane(pane);
5932
6122
  if (projPath) {
5933
6123
  const wtPath = worktreePath(projPath, worktreeDir, project, name);
5934
- if (fs13.existsSync(wtPath)) {
6124
+ if (fs11.existsSync(wtPath)) {
5935
6125
  try {
5936
6126
  removeWorktree(projPath, wtPath);
5937
6127
  } catch (e) {
@@ -5951,9 +6141,9 @@ var init_side_session = __esm({
5951
6141
  });
5952
6142
 
5953
6143
  // packages/core/dist/crew-spawn.js
5954
- import fs14 from "fs";
6144
+ import fs12 from "fs";
5955
6145
  import os5 from "os";
5956
- import path11 from "path";
6146
+ import path12 from "path";
5957
6147
  async function listCrewPanes(runtime, workspaceId, project) {
5958
6148
  const surfaces = await runtime.listSurfaces(workspaceId);
5959
6149
  return surfaces.filter((s) => s.title && isCrewTitle(project, s.title));
@@ -6008,18 +6198,23 @@ async function runCrewSpawn(input, config, deps) {
6008
6198
  }
6009
6199
  }
6010
6200
  const name = input.name ?? nextAutoName(existingTitles, input.project);
6201
+ let base = "";
6202
+ if (!input.shared) {
6203
+ base = resolveWorktreeBase(proj.path);
6204
+ deps.onBaseResolved?.(base);
6205
+ }
6011
6206
  const spawnCwd = !input.shared ? addWorktree({
6012
6207
  repoRoot: proj.path,
6013
6208
  worktreeDir: config.defaults.worktreeDir ?? ".worktrees",
6014
6209
  project: input.project,
6015
6210
  name,
6016
- base: resolveWorktreeBase(proj.path)
6211
+ base
6017
6212
  }) : proj.path;
6018
6213
  let firstTurnTask = input.task;
6019
6214
  if (input.taskFile && input.taskFile !== "-" && !input.shared) {
6020
- const absTaskFile = path11.resolve(input.taskFile);
6021
- const basename = path11.basename(absTaskFile);
6022
- fs14.copyFileSync(absTaskFile, path11.join(spawnCwd, basename));
6215
+ const absTaskFile = path12.resolve(input.taskFile);
6216
+ const basename = path12.basename(absTaskFile);
6217
+ fs12.copyFileSync(absTaskFile, path12.join(spawnCwd, basename));
6023
6218
  firstTurnTask = `Read ./${basename} to get your task brief, then execute it.`;
6024
6219
  }
6025
6220
  const route = !input.agentExplicit && !input.model ? resolveCrewRoute(input.task, config) : null;
@@ -6032,8 +6227,8 @@ async function runCrewSpawn(input, config, deps) {
6032
6227
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
6033
6228
  }
6034
6229
  if (agentName === "codex") {
6035
- const codexRoleFile = path11.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
6036
- const roleInstructions = fs14.existsSync(codexRoleFile) ? fs14.readFileSync(codexRoleFile, "utf8") : void 0;
6230
+ const codexRoleFile = path12.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
6231
+ const roleInstructions = fs12.existsSync(codexRoleFile) ? fs12.readFileSync(codexRoleFile, "utf8") : void 0;
6037
6232
  return runCodexInteractiveSpawn({
6038
6233
  project: input.project,
6039
6234
  task: input.task,
@@ -6049,7 +6244,7 @@ async function runCrewSpawn(input, config, deps) {
6049
6244
  sendCodexFirstTurn: deps.sendCodexFirstTurn
6050
6245
  });
6051
6246
  }
6052
- const promptFile = path11.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
6247
+ const promptFile = path12.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
6053
6248
  const interactive = agent.name === "claude" || agent.name === "opencode";
6054
6249
  const crewRole = config.defaults.roles?.crew;
6055
6250
  const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
@@ -6181,7 +6376,7 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
6181
6376
  function pickMostRecentTask(tasks) {
6182
6377
  return tasks.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
6183
6378
  }
6184
- async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
6379
+ async function runCrewSend(project, name, message, runtime, workspaceId, deps, opts) {
6185
6380
  const crew = await findCrewPane(runtime, workspaceId, project, name);
6186
6381
  if (!crew) {
6187
6382
  throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
@@ -6190,9 +6385,17 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
6190
6385
  if (deps.isBlockedByModal && await deps.isBlockedByModal(crew)) {
6191
6386
  throw new Error(blockedByModalMessage());
6192
6387
  }
6388
+ let task;
6193
6389
  try {
6194
6390
  const matches = (await deps.listTasks(project)).filter((t) => t.name === name);
6195
- const task = matches.length > 0 ? pickMostRecentTask(matches) : void 0;
6391
+ task = matches.length > 0 ? pickMostRecentTask(matches) : void 0;
6392
+ } catch {
6393
+ }
6394
+ if (task && task.operatorHold && !opts?.force) {
6395
+ const heldForMin = Math.round((Date.now() - task.operatorHold.since) / 6e4);
6396
+ 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.`);
6397
+ }
6398
+ try {
6196
6399
  if (task) {
6197
6400
  if (TERMINAL_STATES.has(task.state)) {
6198
6401
  await deps.emitEvent(project, { type: "task.reopened", id: task.id });
@@ -6218,23 +6421,60 @@ async function runCrewRead(project, name, runtime, workspaceId) {
6218
6421
  }
6219
6422
  return runtime.readPaneScreen(crew);
6220
6423
  }
6221
- async function runCrewClose(project, name, runtime, workspaceId, deps) {
6424
+ function buildRecoveryHint(sessId, provider, worktreeCwd) {
6425
+ if (!sessId || !worktreeCwd)
6426
+ return "";
6427
+ if (provider === "claude") {
6428
+ const escaped = worktreeCwd.replace(/[^a-zA-Z0-9]/g, "-");
6429
+ const transcriptPath = path12.join(os5.homedir(), ".claude", "projects", escaped, `${sessId}.jsonl`);
6430
+ return `
6431
+ transcript: ${transcriptPath}
6432
+ resume: claude --resume ${sessId} (run from the worktree path above)
6433
+ `;
6434
+ }
6435
+ return "";
6436
+ }
6437
+ async function runCrewClose(project, name, runtime, workspaceId, deps, opts) {
6222
6438
  const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
6223
6439
  const projRoot = loadConfig().projects[project]?.path;
6224
- let taskId;
6225
- let worktreeCwd;
6440
+ let matches = [];
6226
6441
  try {
6227
- let matches = (await deps.listTasks(project)).filter((t) => t.name === name);
6442
+ matches = (await deps.listTasks(project)).filter((t) => t.name === name);
6228
6443
  for (let attempt = 0; attempt < CLOSE_LOOKUP_RETRIES && matches.length === 0; attempt++) {
6229
6444
  await sleep3(CLOSE_LOOKUP_RETRY_DELAY_MS);
6230
6445
  matches = (await deps.listTasks(project)).filter((t) => t.name === name);
6231
6446
  }
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
- }
6447
+ } catch {
6448
+ }
6449
+ let taskId;
6450
+ let worktreeCwd;
6451
+ let sessId;
6452
+ let provider;
6453
+ if (matches.length > 0) {
6454
+ const primary = pickMostRecentTask(matches);
6455
+ taskId = primary.id;
6456
+ sessId = primary.sessionId;
6457
+ provider = primary.provider;
6458
+ if (primary.cwd && projRoot && primary.cwd !== projRoot) {
6459
+ worktreeCwd = primary.cwd;
6460
+ }
6461
+ if (primary.operatorHold && !opts?.force) {
6462
+ const heldForMin = Math.round((Date.now() - primary.operatorHold.since) / 6e4);
6463
+ 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.`);
6464
+ }
6465
+ }
6466
+ if (worktreeCwd && projRoot) {
6467
+ const dirty = worktreeDirtyFiles(worktreeCwd);
6468
+ if (dirty.length > 0 && !opts?.force) {
6469
+ const transcriptStr = buildRecoveryHint(sessId, provider, worktreeCwd);
6470
+ throw new Error(`Worktree '${worktreeCwd}' has uncommitted files:
6471
+ ${dirty.map((f) => ` ${f}`).join("\n")}
6472
+ Why are they uncommitted? Commit them, or pass --force to destroy them.
6473
+ ${transcriptStr}`);
6474
+ }
6475
+ }
6476
+ if (matches.length > 0) {
6477
+ try {
6238
6478
  for (const task of matches) {
6239
6479
  if (!TERMINAL_STATES.has(task.state)) {
6240
6480
  await deps.emitEvent(project, { type: "task.cancelled", id: task.id, reason: "closed by captain" });
@@ -6243,8 +6483,8 @@ async function runCrewClose(project, name, runtime, workspaceId, deps) {
6243
6483
  await deps.closeCodexThread(task.id);
6244
6484
  }
6245
6485
  }
6486
+ } catch {
6246
6487
  }
6247
- } catch {
6248
6488
  }
6249
6489
  const crew = await findCrewPane(runtime, workspaceId, project, name);
6250
6490
  if (crew) {
@@ -6257,12 +6497,16 @@ async function runCrewClose(project, name, runtime, workspaceId, deps) {
6257
6497
  }
6258
6498
  if (worktreeCwd && projRoot) {
6259
6499
  try {
6260
- removeWorktree(projRoot, worktreeCwd);
6500
+ removeWorktree(projRoot, worktreeCwd, opts);
6261
6501
  } catch (e) {
6262
6502
  process.stderr.write(`(worktree remove failed: ${e.message})
6263
6503
  `);
6264
6504
  }
6265
6505
  }
6506
+ const hint = buildRecoveryHint(sessId, provider, worktreeCwd);
6507
+ if (hint) {
6508
+ process.stdout.write(hint);
6509
+ }
6266
6510
  }
6267
6511
  async function runCrewList(project, runtime, workspaceId) {
6268
6512
  const crews = await listCrewPanes(runtime, workspaceId, project);
@@ -6278,8 +6522,8 @@ var init_crew_spawn = __esm({
6278
6522
  init_crew_routing();
6279
6523
  init_crew_protocol();
6280
6524
  init_crew_lifecycle();
6281
- TEMPLATES_DIR = path11.join(os5.homedir(), ".config", "squadrant", "templates");
6282
- STATE_ROOT = path11.join(os5.homedir(), ".config", "squadrant", "state");
6525
+ TEMPLATES_DIR = path12.join(os5.homedir(), ".config", "squadrant", "templates");
6526
+ STATE_ROOT = path12.join(os5.homedir(), ".config", "squadrant", "state");
6283
6527
  CLOSE_LOOKUP_RETRIES = 3;
6284
6528
  CLOSE_LOOKUP_RETRY_DELAY_MS = 150;
6285
6529
  }
@@ -6377,6 +6621,7 @@ __export(dist_exports2, {
6377
6621
  deliverStartupPrompt: () => deliverStartupPrompt,
6378
6622
  deliverable: () => deliverable,
6379
6623
  deriveCaptainState: () => deriveCaptainState,
6624
+ detectForeignInstall: () => detectForeignInstall,
6380
6625
  detectGroupAndUser: () => detectGroupAndUser,
6381
6626
  detectGroupId: () => detectGroupId,
6382
6627
  discoverCaptainSurface: () => discoverCaptainSurface,
@@ -6416,7 +6661,9 @@ __export(dist_exports2, {
6416
6661
  notifyToggle: () => notifyToggle,
6417
6662
  parseCommand: () => parseCommand,
6418
6663
  parseNotifyPref: () => parseNotifyPref,
6664
+ parseProgramArgs: () => parseProgramArgs,
6419
6665
  plistPath: () => plistPath,
6666
+ printForeignInstallError: () => printForeignInstallError,
6420
6667
  programArgsBlock: () => programArgsBlock,
6421
6668
  projectHealth: () => projectHealth,
6422
6669
  purgeExpiredWorkItems: () => purgeExpiredWorkItems,
@@ -7168,13 +7415,13 @@ var init_notifiers = __esm({
7168
7415
  });
7169
7416
 
7170
7417
  // packages/workspaces/dist/workspaces/obsidian.js
7171
- import fs15 from "fs/promises";
7418
+ import fs13 from "fs/promises";
7172
7419
  import { existsSync as existsSync10 } from "fs";
7173
- import path12 from "path";
7420
+ import path13 from "path";
7174
7421
  function resolveInRoot(root, relative) {
7175
- const joined = path12.resolve(root, relative);
7176
- const normalized = path12.resolve(root) + path12.sep;
7177
- if (joined !== path12.resolve(root) && !joined.startsWith(normalized)) {
7422
+ const joined = path13.resolve(root, relative);
7423
+ const normalized = path13.resolve(root) + path13.sep;
7424
+ if (joined !== path13.resolve(root) && !joined.startsWith(normalized)) {
7178
7425
  throw new Error(`Path '${relative}' escapes workspace root`);
7179
7426
  }
7180
7427
  return joined;
@@ -7193,16 +7440,16 @@ function createObsidianDriver(scope) {
7193
7440
  };
7194
7441
  },
7195
7442
  async read(rel) {
7196
- return fs15.readFile(resolveInRoot(root, rel), "utf-8");
7443
+ return fs13.readFile(resolveInRoot(root, rel), "utf-8");
7197
7444
  },
7198
7445
  async write(rel, content) {
7199
7446
  const abs = resolveInRoot(root, rel);
7200
- await fs15.mkdir(path12.dirname(abs), { recursive: true });
7201
- await fs15.writeFile(abs, content);
7447
+ await fs13.mkdir(path13.dirname(abs), { recursive: true });
7448
+ await fs13.writeFile(abs, content);
7202
7449
  },
7203
7450
  async exists(rel) {
7204
7451
  try {
7205
- await fs15.access(resolveInRoot(root, rel));
7452
+ await fs13.access(resolveInRoot(root, rel));
7206
7453
  return true;
7207
7454
  } catch {
7208
7455
  return false;
@@ -7210,13 +7457,13 @@ function createObsidianDriver(scope) {
7210
7457
  },
7211
7458
  async list(rel) {
7212
7459
  try {
7213
- return await fs15.readdir(resolveInRoot(root, rel));
7460
+ return await fs13.readdir(resolveInRoot(root, rel));
7214
7461
  } catch {
7215
7462
  return [];
7216
7463
  }
7217
7464
  },
7218
7465
  async mkdir(rel) {
7219
- await fs15.mkdir(resolveInRoot(root, rel), { recursive: true });
7466
+ await fs13.mkdir(resolveInRoot(root, rel), { recursive: true });
7220
7467
  }
7221
7468
  };
7222
7469
  }
@@ -7480,7 +7727,7 @@ var init_store_fingerprint = __esm({
7480
7727
  });
7481
7728
 
7482
7729
  // packages/workspaces/dist/cmux-daemon/daemon-cmux.js
7483
- import { readdirSync as readdirSync4, readFileSync as readFileSync10 } from "fs";
7730
+ import { readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
7484
7731
  import { join as join14 } from "path";
7485
7732
  import { homedir as homedir9 } from "os";
7486
7733
  var DaemonCmux;
@@ -7553,7 +7800,7 @@ var init_daemon_cmux = __esm({
7553
7800
  } catch (e) {
7554
7801
  throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
7555
7802
  }
7556
- return readLivenessSnapshot(files, (f) => readFileSync10(join14(dir, f), "utf-8"), projects);
7803
+ return readLivenessSnapshot(files, (f) => readFileSync9(join14(dir, f), "utf-8"), projects);
7557
7804
  }
7558
7805
  };
7559
7806
  }
@@ -7562,7 +7809,7 @@ var init_daemon_cmux = __esm({
7562
7809
  // packages/workspaces/dist/cmux-daemon/cmux-store-source.js
7563
7810
  import { join as join15 } from "path";
7564
7811
  import { homedir as homedir10 } from "os";
7565
- import { watch, readdirSync as readdirSync5, readFileSync as readFileSync11, existsSync as existsSync11 } from "fs";
7812
+ import { watch, readdirSync as readdirSync5, readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
7566
7813
  function parseLifecycleState(s) {
7567
7814
  if (s === "running" || s === "idle" || s === "needsInput" || s === "unknown") {
7568
7815
  return s;
@@ -7584,9 +7831,9 @@ function defaultListFiles(dir) {
7584
7831
  return [];
7585
7832
  }
7586
7833
  }
7587
- function defaultReadFile(path34) {
7834
+ function defaultReadFile(path35) {
7588
7835
  try {
7589
- return readFileSync11(path34, "utf-8");
7836
+ return readFileSync10(path35, "utf-8");
7590
7837
  } catch {
7591
7838
  return void 0;
7592
7839
  }
@@ -7740,7 +7987,7 @@ var init_cmux_store_source = __esm({
7740
7987
  // packages/workspaces/dist/native-hooks/native-hook-source.js
7741
7988
  import { join as join16 } from "path";
7742
7989
  import { homedir as homedir11 } from "os";
7743
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
7990
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
7744
7991
  function installClaudeHooks(opts = {}) {
7745
7992
  const settingsPath = opts.settingsPath ?? join16(homedir11(), ".claude", "settings.json");
7746
7993
  const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
@@ -7836,16 +8083,16 @@ function extractDetail(sub, payload) {
7836
8083
  }
7837
8084
  return void 0;
7838
8085
  }
7839
- function defaultReadFile2(path34) {
8086
+ function defaultReadFile2(path35) {
7840
8087
  try {
7841
- return readFileSync12(path34, "utf-8");
8088
+ return readFileSync11(path35, "utf-8");
7842
8089
  } catch {
7843
8090
  return void 0;
7844
8091
  }
7845
8092
  }
7846
- function defaultWriteFile(path34, content) {
7847
- mkdirSync7(path34.replace(/\/[^/]+$/, ""), { recursive: true });
7848
- writeFileSync9(path34, content, "utf-8");
8093
+ function defaultWriteFile(path35, content) {
8094
+ mkdirSync6(path35.replace(/\/[^/]+$/, ""), { recursive: true });
8095
+ writeFileSync8(path35, content, "utf-8");
7849
8096
  }
7850
8097
  var CLAUDE_HOOK_EVENTS, DEFAULT_HOOK_CMD, NativeHookSource;
7851
8098
  var init_native_hook_source = __esm({
@@ -8464,8 +8711,8 @@ var init_registry4 = __esm({
8464
8711
  });
8465
8712
 
8466
8713
  // packages/agents/dist/drivers/launch-cmd.js
8467
- import fs16 from "fs";
8468
- import path13 from "path";
8714
+ import fs14 from "fs";
8715
+ import path14 from "path";
8469
8716
  function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir) {
8470
8717
  const driver = registry.getDriver(agentName);
8471
8718
  if (driver.name === "claude") {
@@ -8481,27 +8728,27 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
8481
8728
  cmd += ` --model ${model}`;
8482
8729
  }
8483
8730
  if (templatesDir) {
8484
- const roleFile2 = path13.join(templatesDir, `${role}.claude.md`);
8485
- const legacyRoleFile = path13.join(templatesDir, `${role}.CLAUDE.md`);
8486
- const actualRoleFile = fs16.existsSync(roleFile2) ? roleFile2 : fs16.existsSync(legacyRoleFile) ? legacyRoleFile : null;
8731
+ const roleFile2 = path14.join(templatesDir, `${role}.claude.md`);
8732
+ const legacyRoleFile = path14.join(templatesDir, `${role}.CLAUDE.md`);
8733
+ const actualRoleFile = fs14.existsSync(roleFile2) ? roleFile2 : fs14.existsSync(legacyRoleFile) ? legacyRoleFile : null;
8487
8734
  if (actualRoleFile) {
8488
8735
  cmd += ` --append-system-prompt-file ${actualRoleFile}`;
8489
8736
  }
8490
- const pluginDir = path13.join(templatesDir, "..", "plugin");
8491
- if (fs16.existsSync(pluginDir)) {
8737
+ const pluginDir = path14.join(templatesDir, "..", "plugin");
8738
+ if (fs14.existsSync(pluginDir)) {
8492
8739
  cmd += ` --plugin-dir ${pluginDir}`;
8493
8740
  }
8494
8741
  }
8495
8742
  return cmd;
8496
8743
  }
8497
- const roleFile = templatesDir ? path13.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
8744
+ const roleFile = templatesDir ? path14.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
8498
8745
  return driver.buildCommand({
8499
8746
  prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
8500
8747
  workdir: process.cwd(),
8501
8748
  role,
8502
8749
  model,
8503
8750
  autoApprove: true,
8504
- promptFile: roleFile && fs16.existsSync(roleFile) ? roleFile : void 0
8751
+ promptFile: roleFile && fs14.existsSync(roleFile) ? roleFile : void 0
8505
8752
  });
8506
8753
  }
8507
8754
  var init_launch_cmd = __esm({
@@ -8524,7 +8771,7 @@ var init_drivers = __esm({
8524
8771
 
8525
8772
  // packages/agents/dist/projection/cursor.js
8526
8773
  import { mkdir, readFile, writeFile } from "fs/promises";
8527
- import path14 from "path";
8774
+ import path15 from "path";
8528
8775
  import os6 from "os";
8529
8776
  function renderMdc(source) {
8530
8777
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -8573,7 +8820,7 @@ function createCursorEmitter() {
8573
8820
  if (scope === "user") {
8574
8821
  return [
8575
8822
  {
8576
- path: path14.join(os6.homedir(), ".cursor/rules/squadrant-global.mdc"),
8823
+ path: path15.join(os6.homedir(), ".cursor/rules/squadrant-global.mdc"),
8577
8824
  shared: false,
8578
8825
  format: "mdc"
8579
8826
  }
@@ -8583,7 +8830,7 @@ function createCursorEmitter() {
8583
8830
  return [];
8584
8831
  return [
8585
8832
  {
8586
- path: path14.join(projectRoot, ".cursor/rules/squadrant.mdc"),
8833
+ path: path15.join(projectRoot, ".cursor/rules/squadrant.mdc"),
8587
8834
  shared: false,
8588
8835
  format: "mdc"
8589
8836
  }
@@ -8600,7 +8847,7 @@ function createCursorEmitter() {
8600
8847
  diff: buildDiff(existing, generated)
8601
8848
  };
8602
8849
  }
8603
- await mkdir(path14.dirname(dest.path), { recursive: true });
8850
+ await mkdir(path15.dirname(dest.path), { recursive: true });
8604
8851
  await writeFile(dest.path, generated, "utf-8");
8605
8852
  return {
8606
8853
  written: true,
@@ -8651,7 +8898,7 @@ var init_marker = __esm({
8651
8898
 
8652
8899
  // packages/agents/dist/projection/codex.js
8653
8900
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
8654
- import path15 from "path";
8901
+ import path16 from "path";
8655
8902
  import os7 from "os";
8656
8903
  function renderMarkdown(source) {
8657
8904
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -8676,7 +8923,7 @@ function createCodexEmitter() {
8676
8923
  destinations(scope, projectRoot) {
8677
8924
  if (scope === "user") {
8678
8925
  return [{
8679
- path: path15.join(os7.homedir(), ".codex/AGENTS.md"),
8926
+ path: path16.join(os7.homedir(), ".codex/AGENTS.md"),
8680
8927
  shared: true,
8681
8928
  format: "markdown"
8682
8929
  }];
@@ -8684,7 +8931,7 @@ function createCodexEmitter() {
8684
8931
  if (!projectRoot)
8685
8932
  return [];
8686
8933
  return [{
8687
- path: path15.join(projectRoot, "AGENTS.md"),
8934
+ path: path16.join(projectRoot, "AGENTS.md"),
8688
8935
  shared: true,
8689
8936
  format: "markdown"
8690
8937
  }];
@@ -8705,7 +8952,7 @@ ${existing ?? ""}
8705
8952
  ${generated}`
8706
8953
  };
8707
8954
  }
8708
- await mkdir2(path15.dirname(dest.path), { recursive: true });
8955
+ await mkdir2(path16.dirname(dest.path), { recursive: true });
8709
8956
  await writeFile2(dest.path, generated, "utf-8");
8710
8957
  return {
8711
8958
  written: true,
@@ -8723,7 +8970,7 @@ var init_codex2 = __esm({
8723
8970
 
8724
8971
  // packages/agents/dist/projection/gemini.js
8725
8972
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
8726
- import path16 from "path";
8973
+ import path17 from "path";
8727
8974
  import os8 from "os";
8728
8975
  function renderMarkdown2(source) {
8729
8976
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -8748,7 +8995,7 @@ function createGeminiEmitter() {
8748
8995
  destinations(scope, projectRoot) {
8749
8996
  if (scope === "user") {
8750
8997
  return [{
8751
- path: path16.join(os8.homedir(), ".gemini/GEMINI.md"),
8998
+ path: path17.join(os8.homedir(), ".gemini/GEMINI.md"),
8752
8999
  shared: true,
8753
9000
  format: "markdown"
8754
9001
  }];
@@ -8756,7 +9003,7 @@ function createGeminiEmitter() {
8756
9003
  if (!projectRoot)
8757
9004
  return [];
8758
9005
  return [{
8759
- path: path16.join(projectRoot, "GEMINI.md"),
9006
+ path: path17.join(projectRoot, "GEMINI.md"),
8760
9007
  shared: true,
8761
9008
  format: "markdown"
8762
9009
  }];
@@ -8777,7 +9024,7 @@ ${existing ?? ""}
8777
9024
  ${generated}`
8778
9025
  };
8779
9026
  }
8780
- await mkdir3(path16.dirname(dest.path), { recursive: true });
9027
+ await mkdir3(path17.dirname(dest.path), { recursive: true });
8781
9028
  await writeFile3(dest.path, generated, "utf-8");
8782
9029
  return {
8783
9030
  written: true,
@@ -8795,7 +9042,7 @@ var init_gemini2 = __esm({
8795
9042
 
8796
9043
  // packages/agents/dist/projection/opencode.js
8797
9044
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
8798
- import path17 from "path";
9045
+ import path18 from "path";
8799
9046
  import os9 from "os";
8800
9047
  function renderMarkdown3(source) {
8801
9048
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -8820,7 +9067,7 @@ function createOpencodeEmitter() {
8820
9067
  destinations(scope, projectRoot) {
8821
9068
  if (scope === "user") {
8822
9069
  return [{
8823
- path: path17.join(os9.homedir(), ".config", "opencode", "AGENTS.md"),
9070
+ path: path18.join(os9.homedir(), ".config", "opencode", "AGENTS.md"),
8824
9071
  shared: true,
8825
9072
  format: "markdown"
8826
9073
  }];
@@ -8828,7 +9075,7 @@ function createOpencodeEmitter() {
8828
9075
  if (!projectRoot)
8829
9076
  return [];
8830
9077
  return [{
8831
- path: path17.join(projectRoot, "AGENTS.md"),
9078
+ path: path18.join(projectRoot, "AGENTS.md"),
8832
9079
  shared: true,
8833
9080
  format: "markdown"
8834
9081
  }];
@@ -8849,7 +9096,7 @@ ${existing ?? ""}
8849
9096
  ${generated}`
8850
9097
  };
8851
9098
  }
8852
- await mkdir4(path17.dirname(dest.path), { recursive: true });
9099
+ await mkdir4(path18.dirname(dest.path), { recursive: true });
8853
9100
  await writeFile4(dest.path, generated, "utf-8");
8854
9101
  return {
8855
9102
  written: true,
@@ -9708,7 +9955,7 @@ var init_sse_bridge = __esm({
9708
9955
 
9709
9956
  // packages/agents/dist/interactive/claude.js
9710
9957
  import { execSync as execSync7 } from "child_process";
9711
- import { readFileSync as readFileSync13 } from "fs";
9958
+ import { readFileSync as readFileSync12 } from "fs";
9712
9959
  import { homedir as homedir14 } from "os";
9713
9960
  import { join as join19 } from "path";
9714
9961
  function probeClaudeSettingsFlag() {
@@ -9772,7 +10019,7 @@ function deriveTranscriptPath(sessionId, cwd) {
9772
10019
  }
9773
10020
  function readLastAssistantText(transcriptPath) {
9774
10021
  try {
9775
- const raw = readFileSync13(transcriptPath, "utf-8");
10022
+ const raw = readFileSync12(transcriptPath, "utf-8");
9776
10023
  const lines = raw.split(/\r?\n/);
9777
10024
  for (let i = lines.length - 1; i >= 0; i--) {
9778
10025
  const line = lines[i].trim();
@@ -9814,8 +10061,8 @@ function resolveLastAssistantText(payload) {
9814
10061
  const derived = deriveTranscriptPath(p?.session_id, cwd);
9815
10062
  if (derived)
9816
10063
  candidates.push(derived);
9817
- for (const path34 of candidates) {
9818
- const text = readLastAssistantText(path34);
10064
+ for (const path35 of candidates) {
10065
+ const text = readLastAssistantText(path35);
9819
10066
  if (text != null)
9820
10067
  return text;
9821
10068
  }
@@ -10269,8 +10516,8 @@ var require_daemon_exports = {};
10269
10516
  __export(require_daemon_exports, {
10270
10517
  requireDaemon: () => requireDaemon
10271
10518
  });
10272
- import { join as join25 } from "path";
10273
- import { homedir as homedir20 } from "os";
10519
+ import { join as join22 } from "path";
10520
+ import { homedir as homedir17 } from "os";
10274
10521
  async function requireDaemon(sockPath = DEFAULT_SOCK_PATH3) {
10275
10522
  const isLive = await isDaemonSocketLive(sockPath);
10276
10523
  if (!isLive) {
@@ -10281,89 +10528,250 @@ var DEFAULT_SOCK_PATH3;
10281
10528
  var init_require_daemon = __esm({
10282
10529
  "packages/cli/src/lib/require-daemon.ts"() {
10283
10530
  init_dist2();
10284
- DEFAULT_SOCK_PATH3 = join25(homedir20(), ".config", "squadrant", "squadrant.sock");
10531
+ DEFAULT_SOCK_PATH3 = join22(homedir17(), ".config", "squadrant", "squadrant.sock");
10285
10532
  }
10286
10533
  });
10287
10534
 
10288
- // packages/cli/src/index.ts
10289
- init_dist();
10290
- init_dist2();
10291
- import { Command as Command35 } from "commander";
10292
- import { readFileSync as readFileSync16, existsSync as existsSync13, writeFileSync as writeFileSync12 } from "fs";
10293
- import { fileURLToPath as fileURLToPath6 } from "url";
10294
- import { dirname as dirname9, join as join30 } from "path";
10295
- import { homedir as homedir22 } from "os";
10296
-
10297
- // packages/cli/src/commands/doctor.ts
10298
- init_dist();
10299
- init_dist();
10300
- init_dist();
10301
- init_dist3();
10302
- import { Command } from "commander";
10303
- import { execSync as execSync8 } from "child_process";
10304
- import fs17 from "fs";
10305
- import { stat } from "fs/promises";
10306
- import path18 from "path";
10307
- import chalk3 from "chalk";
10308
-
10309
- // packages/cli/src/commands/health-view.ts
10310
- init_dist2();
10311
- init_dist2();
10312
- import { homedir as homedir12 } from "os";
10313
- import { join as join17 } from "path";
10314
- import chalk2 from "chalk";
10315
- var SOCK = join17(homedir12(), ".config", "squadrant", "squadrant.sock");
10316
- async function queryHealth(project) {
10317
- try {
10318
- const reply = await sendRequest(SOCK, { kind: "health", project });
10319
- return Array.isArray(reply) ? reply : [];
10320
- } catch {
10321
- return null;
10322
- }
10535
+ // packages/cli/src/commands/runtime.ts
10536
+ var runtime_exports = {};
10537
+ __export(runtime_exports, {
10538
+ buildRegistry: () => buildRegistry,
10539
+ needRef: () => needRef,
10540
+ resolveTarget: () => resolveTarget,
10541
+ runRuntimeSend: () => runRuntimeSend,
10542
+ runtimeCommand: () => runtimeCommand
10543
+ });
10544
+ import { Command as Command8 } from "commander";
10545
+ import chalk9 from "chalk";
10546
+ function buildRegistry() {
10547
+ return new RuntimeRegistry({
10548
+ cmux: createCmuxDriver()
10549
+ });
10323
10550
  }
10324
- function healthIcon(state) {
10325
- switch (state) {
10326
- case "alive":
10327
- return "\u2714";
10328
- case "stale":
10329
- return "\u2022";
10330
- case "gone":
10331
- return "\u2718";
10332
- case "stopped":
10333
- return "\u23FB";
10334
- case "unknown":
10335
- return "\u25CB";
10551
+ function resolveTarget(registry, config, target, useCommand) {
10552
+ if (useCommand) {
10553
+ return {
10554
+ driver: registry.global(config),
10555
+ workspaceName: config.commandName
10556
+ };
10336
10557
  }
10337
- }
10338
- function colorState(state) {
10339
- switch (state) {
10340
- case "alive":
10341
- return chalk2.green(state);
10342
- case "stale":
10343
- return chalk2.yellow(state);
10344
- case "gone":
10345
- return chalk2.red(state);
10346
- // stopped = intentional shutdown, not a fault — magenta, never alarm-red.
10347
- case "stopped":
10348
- return chalk2.magenta(state);
10349
- case "unknown":
10350
- return chalk2.dim(state);
10558
+ if (!target) {
10559
+ throw new Error("Missing target: pass a project name or use --command");
10351
10560
  }
10561
+ const proj = config.projects[target];
10562
+ if (!proj) {
10563
+ throw new Error(`Project '${target}' not found. Run 'squadrant projects list'.`);
10564
+ }
10565
+ return {
10566
+ driver: registry.forProject(target, config),
10567
+ workspaceName: proj.captainName
10568
+ };
10352
10569
  }
10353
- function healthRow(c, now) {
10354
- const head = `${healthIcon(c.state)} ${c.kind.padEnd(8)} ${c.ref.padEnd(16)} ${c.state.padEnd(8)} ${ageText(c.lastSeenMs, now)}`;
10355
- return c.detail ? `${head} ${c.detail}` : head;
10356
- }
10357
- function printServiceHealth(rows, now = Date.now()) {
10358
- console.log(chalk2.bold("\nService Health\n"));
10359
- if (rows == null) {
10360
- console.log(` ${chalk2.red("\u2718")} daemon unreachable \u2014 squadrant liveness is unknown`);
10361
- console.log(` ${chalk2.cyan("\u2192")} ${chalk2.dim("Run: squadrant heal daemon")}`);
10362
- return;
10570
+ async function needRef(resolved) {
10571
+ const ref = await resolved.driver.status(resolved.workspaceName);
10572
+ if (!ref) {
10573
+ throw new Error(`Workspace '${resolved.workspaceName}' is not running`);
10363
10574
  }
10364
- if (rows.length === 0) {
10365
- console.log(chalk2.dim(" no registered projects"));
10366
- return;
10575
+ return ref.id;
10576
+ }
10577
+ async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
10578
+ const config = loadConfig();
10579
+ const registry = buildRegistry();
10580
+ if (opts.command && arg2 !== void 0) {
10581
+ throw new Error("With --command, pass only the message (not a project name)");
10582
+ }
10583
+ const target = opts.command ? void 0 : arg1;
10584
+ const message = opts.command ? arg1 : arg2;
10585
+ if (!message) throw new Error("Message is required");
10586
+ const { requireDaemon: requireDaemon2 } = await Promise.resolve().then(() => (init_require_daemon(), require_daemon_exports));
10587
+ const { appendCaptainMessage: appendCaptainMessage2, waitForCaptainDelivery: waitForCaptainDelivery2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
10588
+ await requireDaemon2();
10589
+ const resolved = resolveTarget(registry, config, target, !!opts.command);
10590
+ await needRef(resolved);
10591
+ const finalProject = opts.command ? config.commandName : target;
10592
+ const { join: join31, dirname: dirname9 } = await import("path");
10593
+ const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
10594
+ const stateRoot = join31(dirname9(DEFAULT_CONFIG_PATH2), "state");
10595
+ const seq = await appendCaptainMessage2({
10596
+ stateRoot,
10597
+ project: finalProject,
10598
+ text: message,
10599
+ source: "cli"
10600
+ });
10601
+ const timeoutMs = confirmOpts?.timeoutMs ?? SEND_CONFIRM_TIMEOUT_MS;
10602
+ const delivered = await waitForCaptainDelivery2({
10603
+ stateRoot,
10604
+ project: finalProject,
10605
+ seq,
10606
+ timeoutMs,
10607
+ pollMs: confirmOpts?.pollMs ?? SEND_CONFIRM_POLL_MS
10608
+ });
10609
+ if (!delivered) {
10610
+ throw new Error(
10611
+ `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" : ""}'.`
10612
+ );
10613
+ }
10614
+ }
10615
+ var runtimeCommand, SEND_CONFIRM_TIMEOUT_MS, SEND_CONFIRM_POLL_MS;
10616
+ var init_runtime2 = __esm({
10617
+ "packages/cli/src/commands/runtime.ts"() {
10618
+ init_dist();
10619
+ init_dist3();
10620
+ runtimeCommand = new Command8("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
10621
+ 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) => {
10622
+ const config = loadConfig();
10623
+ const registry = buildRegistry();
10624
+ try {
10625
+ const resolved = resolveTarget(registry, config, target, !!opts.command);
10626
+ const ref = await resolved.driver.status(resolved.workspaceName);
10627
+ if (ref) {
10628
+ console.log("running");
10629
+ process.exit(0);
10630
+ } else {
10631
+ console.log("stopped");
10632
+ process.exit(1);
10633
+ }
10634
+ } catch (err) {
10635
+ console.error(chalk9.red(err.message));
10636
+ process.exit(2);
10637
+ }
10638
+ });
10639
+ SEND_CONFIRM_TIMEOUT_MS = 15e3;
10640
+ SEND_CONFIRM_POLL_MS = 500;
10641
+ 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) => {
10642
+ try {
10643
+ await runRuntimeSend(arg1, arg2, opts);
10644
+ console.log(chalk9.green("\u2714 Delivered (confirmed)"));
10645
+ } catch (err) {
10646
+ console.error(chalk9.red(err.message));
10647
+ process.exit(1);
10648
+ }
10649
+ });
10650
+ runtimeCommand.command("list").description("List all workspaces from the global runtime").option("-j, --json", "Output as JSON").action(async (opts) => {
10651
+ const config = loadConfig();
10652
+ const registry = buildRegistry();
10653
+ const driver = registry.global(config);
10654
+ const refs = await driver.list();
10655
+ if (opts.json) {
10656
+ console.log(JSON.stringify(refs, null, 2));
10657
+ } else {
10658
+ for (const r of refs) {
10659
+ console.log(`${r.id} ${r.name} ${r.status}`);
10660
+ }
10661
+ }
10662
+ });
10663
+ 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) => {
10664
+ const config = loadConfig();
10665
+ const registry = buildRegistry();
10666
+ try {
10667
+ const resolved = resolveTarget(registry, config, target, !!opts.command);
10668
+ const ref = await needRef(resolved);
10669
+ const screen = await resolved.driver.readScreen(ref);
10670
+ process.stdout.write(screen);
10671
+ } catch (err) {
10672
+ console.error(chalk9.red(err.message));
10673
+ process.exit(1);
10674
+ }
10675
+ });
10676
+ runtimeCommand.command("stop").description("Stop a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
10677
+ const config = loadConfig();
10678
+ const registry = buildRegistry();
10679
+ try {
10680
+ const resolved = resolveTarget(registry, config, target, !!opts.command);
10681
+ const ref = await resolved.driver.status(resolved.workspaceName);
10682
+ if (!ref) {
10683
+ console.log(chalk9.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
10684
+ return;
10685
+ }
10686
+ await resolved.driver.stop(ref.id);
10687
+ console.log(chalk9.green(`\u2714 Stopped ${resolved.workspaceName}`));
10688
+ } catch (err) {
10689
+ console.error(chalk9.red(err.message));
10690
+ process.exit(1);
10691
+ }
10692
+ });
10693
+ }
10694
+ });
10695
+
10696
+ // packages/cli/src/index.ts
10697
+ init_dist();
10698
+ init_dist2();
10699
+ import { Command as Command35 } from "commander";
10700
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
10701
+ import { fileURLToPath as fileURLToPath6 } from "url";
10702
+ import { dirname as dirname8, join as join30 } from "path";
10703
+ import { homedir as homedir22 } from "os";
10704
+
10705
+ // packages/cli/src/commands/doctor.ts
10706
+ init_dist();
10707
+ init_dist();
10708
+ init_dist();
10709
+ init_dist3();
10710
+ import { Command } from "commander";
10711
+ import { execSync as execSync8 } from "child_process";
10712
+ import fs15 from "fs";
10713
+ import { stat } from "fs/promises";
10714
+ import path19 from "path";
10715
+ import chalk3 from "chalk";
10716
+
10717
+ // packages/cli/src/commands/health-view.ts
10718
+ init_dist2();
10719
+ init_dist2();
10720
+ import { homedir as homedir12 } from "os";
10721
+ import { join as join17 } from "path";
10722
+ import chalk2 from "chalk";
10723
+ var SOCK = join17(homedir12(), ".config", "squadrant", "squadrant.sock");
10724
+ async function queryHealth(project) {
10725
+ try {
10726
+ const reply = await sendRequest(SOCK, { kind: "health", project });
10727
+ return Array.isArray(reply) ? reply : [];
10728
+ } catch {
10729
+ return null;
10730
+ }
10731
+ }
10732
+ function healthIcon(state) {
10733
+ switch (state) {
10734
+ case "alive":
10735
+ return "\u2714";
10736
+ case "stale":
10737
+ return "\u2022";
10738
+ case "gone":
10739
+ return "\u2718";
10740
+ case "stopped":
10741
+ return "\u23FB";
10742
+ case "unknown":
10743
+ return "\u25CB";
10744
+ }
10745
+ }
10746
+ function colorState(state) {
10747
+ switch (state) {
10748
+ case "alive":
10749
+ return chalk2.green(state);
10750
+ case "stale":
10751
+ return chalk2.yellow(state);
10752
+ case "gone":
10753
+ return chalk2.red(state);
10754
+ // stopped = intentional shutdown, not a fault — magenta, never alarm-red.
10755
+ case "stopped":
10756
+ return chalk2.magenta(state);
10757
+ case "unknown":
10758
+ return chalk2.dim(state);
10759
+ }
10760
+ }
10761
+ function healthRow(c, now) {
10762
+ const head = `${healthIcon(c.state)} ${c.kind.padEnd(8)} ${c.ref.padEnd(16)} ${c.state.padEnd(8)} ${ageText(c.lastSeenMs, now)}`;
10763
+ return c.detail ? `${head} ${c.detail}` : head;
10764
+ }
10765
+ function printServiceHealth(rows, now = Date.now()) {
10766
+ console.log(chalk2.bold("\nService Health\n"));
10767
+ if (rows == null) {
10768
+ console.log(` ${chalk2.red("\u2718")} daemon unreachable \u2014 squadrant liveness is unknown`);
10769
+ console.log(` ${chalk2.cyan("\u2192")} ${chalk2.dim("Run: squadrant heal daemon")}`);
10770
+ return;
10771
+ }
10772
+ if (rows.length === 0) {
10773
+ console.log(chalk2.dim(" no registered projects"));
10774
+ return;
10367
10775
  }
10368
10776
  const byProject = /* @__PURE__ */ new Map();
10369
10777
  for (const c of rows) {
@@ -10406,7 +10814,7 @@ function settingsHaveAgentTeams() {
10406
10814
  try {
10407
10815
  const home = process.env.HOME || "";
10408
10816
  const settings = JSON.parse(
10409
- fs17.readFileSync(`${home}/.claude/settings.json`, "utf-8")
10817
+ fs15.readFileSync(`${home}/.claude/settings.json`, "utf-8")
10410
10818
  );
10411
10819
  return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
10412
10820
  } catch {
@@ -10417,7 +10825,7 @@ function pluginInstalled(pluginKey) {
10417
10825
  try {
10418
10826
  const home = process.env.HOME || "";
10419
10827
  const plugins = JSON.parse(
10420
- fs17.readFileSync(
10828
+ fs15.readFileSync(
10421
10829
  `${home}/.claude/plugins/installed_plugins.json`,
10422
10830
  "utf-8"
10423
10831
  )
@@ -10439,6 +10847,43 @@ function tryGetVersion(cmd) {
10439
10847
  return "";
10440
10848
  }
10441
10849
  }
10850
+ function candidateGlobalInstalls(roots) {
10851
+ const out = [];
10852
+ if (roots.npm) out.push({ manager: "npm", packageJsonPath: path19.join(roots.npm, "squadrant", "package.json") });
10853
+ if (roots.pnpm) out.push({ manager: "pnpm", packageJsonPath: path19.join(roots.pnpm, "squadrant", "package.json") });
10854
+ if (roots.yarn) out.push({ manager: "yarn", packageJsonPath: path19.join(roots.yarn, "node_modules", "squadrant", "package.json") });
10855
+ return out;
10856
+ }
10857
+ function findInstalledSquadrants(candidates, readVersion) {
10858
+ const out = [];
10859
+ for (const c of candidates) {
10860
+ const version = readVersion(c.packageJsonPath);
10861
+ if (version) out.push({ manager: c.manager, packageJsonPath: c.packageJsonPath, version });
10862
+ }
10863
+ return out;
10864
+ }
10865
+ function formatDuplicateInstallWarning(installs) {
10866
+ if (installs.length <= 1) return null;
10867
+ const lines = installs.map((i) => ` ${i.manager}: ${i.packageJsonPath} (v${i.version})`);
10868
+ return `Multiple squadrant installs detected:
10869
+ ${lines.join("\n")}
10870
+ Two installs fight over the daemon plist (#670) \u2014 uninstall the one you don't use, then run \`squadrant heal daemon\`.`;
10871
+ }
10872
+ function readPackageVersion(packageJsonPath) {
10873
+ try {
10874
+ const pkg2 = JSON.parse(fs15.readFileSync(packageJsonPath, "utf-8"));
10875
+ return typeof pkg2.version === "string" ? pkg2.version : null;
10876
+ } catch {
10877
+ return null;
10878
+ }
10879
+ }
10880
+ function tryGetGlobalRoot(cmd, args) {
10881
+ try {
10882
+ return execSync8(`${cmd} ${args.join(" ")}`, { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim() || void 0;
10883
+ } catch {
10884
+ return void 0;
10885
+ }
10886
+ }
10442
10887
  function check(label, pass, hint) {
10443
10888
  const icon = pass ? chalk3.green("\u2714 PASS") : chalk3.red("\u2718 FAIL");
10444
10889
  console.log(` ${icon} ${label}`);
@@ -10462,7 +10907,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
10462
10907
  ));
10463
10908
  results.push(check(
10464
10909
  "Obsidian installed",
10465
- commandExists("obsidian") || fs17.existsSync("/Applications/Obsidian.app"),
10910
+ commandExists("obsidian") || fs15.existsSync("/Applications/Obsidian.app"),
10466
10911
  "Install from: https://obsidian.md"
10467
10912
  ));
10468
10913
  results.push(check(
@@ -10556,7 +11001,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
10556
11001
  const emitter = projectionRegistry.get(name);
10557
11002
  const [userDest] = emitter.destinations("user");
10558
11003
  if (!userDest) continue;
10559
- const dir = path18.dirname(userDest.path);
11004
+ const dir = path19.dirname(userDest.path);
10560
11005
  let status;
10561
11006
  try {
10562
11007
  await stat(dir);
@@ -10569,7 +11014,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
10569
11014
  results.push(
10570
11015
  check(
10571
11016
  "Squadrant config exists",
10572
- fs17.existsSync(
11017
+ fs15.existsSync(
10573
11018
  process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
10574
11019
  ),
10575
11020
  "Run: squadrant init"
@@ -10582,6 +11027,16 @@ var doctorCommand = new Command("doctor").description("Check system health and p
10582
11027
  ${passed === total ? chalk3.green("All checks passed") : chalk3.yellow(`${passed}/${total} checks passed`)}
10583
11028
  `
10584
11029
  );
11030
+ const installCandidates = candidateGlobalInstalls({
11031
+ npm: tryGetGlobalRoot("npm", ["root", "-g"]),
11032
+ pnpm: tryGetGlobalRoot("pnpm", ["root", "-g"]),
11033
+ yarn: tryGetGlobalRoot("yarn", ["global", "dir"])
11034
+ });
11035
+ const duplicateWarning = formatDuplicateInstallWarning(findInstalledSquadrants(installCandidates, readPackageVersion));
11036
+ if (duplicateWarning) {
11037
+ console.log(`
11038
+ ${chalk3.yellow("\u26A0 WARN")} ${duplicateWarning}`);
11039
+ }
10585
11040
  printServiceHealth(await queryHealth());
10586
11041
  console.log(chalk3.bold("\nTool Version Compat\n"));
10587
11042
  const toolVersionMap = {
@@ -10643,16 +11098,16 @@ init_dist();
10643
11098
  init_dist3();
10644
11099
  init_dist();
10645
11100
  import { Command as Command2 } from "commander";
10646
- import fs18 from "fs";
10647
- import path19 from "path";
11101
+ import fs16 from "fs";
11102
+ import path20 from "path";
10648
11103
  import os10 from "os";
10649
11104
  import readline from "readline";
10650
11105
  import chalk4 from "chalk";
10651
11106
 
10652
11107
  // packages/cli/src/lib/per-crew-settings.ts
10653
11108
  init_dist4();
10654
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
10655
- import { dirname as dirname5, join as join20 } from "path";
11109
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
11110
+ import { dirname as dirname4, join as join20 } from "path";
10656
11111
  import { homedir as homedir15 } from "os";
10657
11112
  var CREW_PERMISSION_ALLOWLIST = [
10658
11113
  // git — read + safe mutations (reset/clean/config intentionally excluded)
@@ -10745,28 +11200,28 @@ function mergeCrewPermissions(settings) {
10745
11200
  }
10746
11201
  function writePerCrewSettingsLocal(o) {
10747
11202
  const dir = join20(o.projectCwd, ".claude");
10748
- mkdirSync8(dir, { recursive: true });
11203
+ mkdirSync7(dir, { recursive: true });
10749
11204
  const file = join20(dir, "settings.local.json");
10750
11205
  let existing = {};
10751
11206
  try {
10752
- const raw = healStaleCockpitRefs(readFileSync14(file, "utf-8"));
11207
+ const raw = healStaleCockpitRefs(readFileSync13(file, "utf-8"));
10753
11208
  existing = JSON.parse(raw);
10754
11209
  } catch {
10755
11210
  }
10756
11211
  const withHooks = mergeClaudeHooks(existing, o.hookCmd ?? "squadrant crew _hook");
10757
11212
  const merged = mergeCrewPermissions(withHooks);
10758
- writeFileSync10(file, JSON.stringify(merged, null, 2));
11213
+ writeFileSync9(file, JSON.stringify(merged, null, 2));
10759
11214
  return file;
10760
11215
  }
10761
11216
  var DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH = join20(homedir15(), ".config", "opencode", "opencode.json");
10762
11217
  function ensureGlobalOpencodeConfig(configPath = DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH) {
10763
- mkdirSync8(dirname5(configPath), { recursive: true });
11218
+ mkdirSync7(dirname4(configPath), { recursive: true });
10764
11219
  const defaultConfig = {
10765
11220
  $schema: "https://opencode.ai/config.json",
10766
11221
  model: "anthropic/claude-sonnet-4-5"
10767
11222
  };
10768
11223
  try {
10769
- writeFileSync10(configPath, JSON.stringify(defaultConfig, null, 2) + "\n", { flag: "wx" });
11224
+ writeFileSync9(configPath, JSON.stringify(defaultConfig, null, 2) + "\n", { flag: "wx" });
10770
11225
  return configPath;
10771
11226
  } catch (err) {
10772
11227
  if (err.code === "EEXIST") return null;
@@ -10775,7 +11230,7 @@ function ensureGlobalOpencodeConfig(configPath = DEFAULT_GLOBAL_OPENCODE_CONFIG_
10775
11230
  }
10776
11231
  function writePerCrewOpencodeConfig(o) {
10777
11232
  const dir = join20(o.stateRoot, o.project, o.taskId);
10778
- mkdirSync8(dir, { recursive: true });
11233
+ mkdirSync7(dir, { recursive: true });
10779
11234
  const file = join20(dir, "opencode.json");
10780
11235
  const config = {
10781
11236
  permission: {
@@ -10791,29 +11246,29 @@ function writePerCrewOpencodeConfig(o) {
10791
11246
  external_directory: { "**": "allow" }
10792
11247
  }
10793
11248
  };
10794
- writeFileSync10(file, JSON.stringify(config, null, 2));
11249
+ writeFileSync9(file, JSON.stringify(config, null, 2));
10795
11250
  return file;
10796
11251
  }
10797
11252
 
10798
11253
  // packages/cli/src/commands/init.ts
10799
11254
  init_dist4();
10800
11255
  function findPackageRoot() {
10801
- let dir = path19.dirname(new URL(import.meta.url).pathname);
11256
+ let dir = path20.dirname(new URL(import.meta.url).pathname);
10802
11257
  while (dir !== "/") {
10803
- if (fs18.existsSync(path19.join(dir, "package.json"))) return dir;
10804
- dir = path19.dirname(dir);
11258
+ if (fs16.existsSync(path20.join(dir, "package.json"))) return dir;
11259
+ dir = path20.dirname(dir);
10805
11260
  }
10806
11261
  return process.cwd();
10807
11262
  }
10808
11263
  function copyDirRecursive(src, dest) {
10809
- fs18.mkdirSync(dest, { recursive: true });
10810
- for (const entry of fs18.readdirSync(src, { withFileTypes: true })) {
10811
- const srcPath = path19.join(src, entry.name);
10812
- const destPath = path19.join(dest, entry.name);
11264
+ fs16.mkdirSync(dest, { recursive: true });
11265
+ for (const entry of fs16.readdirSync(src, { withFileTypes: true })) {
11266
+ const srcPath = path20.join(src, entry.name);
11267
+ const destPath = path20.join(dest, entry.name);
10813
11268
  if (entry.isDirectory()) {
10814
11269
  copyDirRecursive(srcPath, destPath);
10815
11270
  } else {
10816
- fs18.copyFileSync(srcPath, destPath);
11271
+ fs16.copyFileSync(srcPath, destPath);
10817
11272
  }
10818
11273
  }
10819
11274
  }
@@ -10833,7 +11288,7 @@ function promptLine(question) {
10833
11288
  var initCommand = new Command2("init").description("Guided first-time setup: hub vault, agents, plugins, projects (re-run-safe)").option("--hub <path>", "Hub vault path", "~/squadrant-hub").action(async (opts) => {
10834
11289
  const hubPath = resolveHome(opts.hub);
10835
11290
  const pkgRoot = findPackageRoot();
10836
- const configDir = path19.join(os10.homedir(), ".config", "squadrant");
11291
+ const configDir = path20.join(os10.homedir(), ".config", "squadrant");
10837
11292
  const isTTY = process.stdin.isTTY === true;
10838
11293
  console.log(chalk4.bold("\nSquadrant Init\n"));
10839
11294
  if (!isTTY) {
@@ -10859,8 +11314,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
10859
11314
  }
10860
11315
  const wsRegistry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
10861
11316
  try {
10862
- if (fs18.existsSync(DEFAULT_CONFIG_PATH)) {
10863
- const existing = JSON.parse(fs18.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
11317
+ if (fs16.existsSync(DEFAULT_CONFIG_PATH)) {
11318
+ const existing = JSON.parse(fs16.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
10864
11319
  wsRegistry.get(existing.workspace ?? "obsidian");
10865
11320
  }
10866
11321
  } catch (err) {
@@ -10868,7 +11323,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
10868
11323
  return;
10869
11324
  }
10870
11325
  stepHeader(1, 5, "Hub vault");
10871
- if (fs18.existsSync(DEFAULT_CONFIG_PATH)) {
11326
+ if (fs16.existsSync(DEFAULT_CONFIG_PATH)) {
10872
11327
  console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
10873
11328
  } else {
10874
11329
  const config = getDefaultConfig();
@@ -10876,37 +11331,37 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
10876
11331
  saveConfig(config);
10877
11332
  console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
10878
11333
  }
10879
- const hubTemplate = path19.join(pkgRoot, "obsidian", "hub");
10880
- if (fs18.existsSync(hubPath)) {
11334
+ const hubTemplate = path20.join(pkgRoot, "obsidian", "hub");
11335
+ if (fs16.existsSync(hubPath)) {
10881
11336
  console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}`));
10882
- } else if (fs18.existsSync(hubTemplate)) {
11337
+ } else if (fs16.existsSync(hubTemplate)) {
10883
11338
  copyDirRecursive(hubTemplate, hubPath);
10884
11339
  console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
10885
11340
  } else {
10886
- fs18.mkdirSync(hubPath, { recursive: true });
11341
+ fs16.mkdirSync(hubPath, { recursive: true });
10887
11342
  console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty directory at ${hubPath}`));
10888
11343
  }
10889
- const hubDashboardSrc = path19.join(pkgRoot, "obsidian", "hub", "dashboard.md");
10890
- const hubDashboardDest = path19.join(hubPath, "dashboard.md");
10891
- if (fs18.existsSync(hubDashboardSrc)) {
10892
- fs18.copyFileSync(hubDashboardSrc, hubDashboardDest);
11344
+ const hubDashboardSrc = path20.join(pkgRoot, "obsidian", "hub", "dashboard.md");
11345
+ const hubDashboardDest = path20.join(hubPath, "dashboard.md");
11346
+ if (fs16.existsSync(hubDashboardSrc)) {
11347
+ fs16.copyFileSync(hubDashboardSrc, hubDashboardDest);
10893
11348
  console.log(chalk4.green(` \u2714 Dashboard refreshed`));
10894
11349
  }
10895
- fs18.mkdirSync(path19.join(hubPath, "projects"), { recursive: true });
11350
+ fs16.mkdirSync(path20.join(hubPath, "projects"), { recursive: true });
10896
11351
  ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
10897
11352
  console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
10898
11353
  stepHeader(2, 5, "Agent + projection setup");
10899
- const settingsPath = path19.join(os10.homedir(), ".claude", "settings.json");
11354
+ const settingsPath = path20.join(os10.homedir(), ".claude", "settings.json");
10900
11355
  try {
10901
11356
  let settings = {};
10902
- if (fs18.existsSync(settingsPath)) {
10903
- settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
11357
+ if (fs16.existsSync(settingsPath)) {
11358
+ settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
10904
11359
  }
10905
11360
  const env = settings.env || {};
10906
11361
  if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
10907
11362
  settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
10908
- fs18.mkdirSync(path19.dirname(settingsPath), { recursive: true });
10909
- fs18.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
11363
+ fs16.mkdirSync(path20.dirname(settingsPath), { recursive: true });
11364
+ fs16.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
10910
11365
  console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
10911
11366
  } else {
10912
11367
  console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
@@ -10958,7 +11413,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
10958
11413
  chalk4.cyan(" Absolute path to your first project (Enter to skip): ")
10959
11414
  );
10960
11415
  if (projectPath) {
10961
- const projectName = path19.basename(projectPath);
11416
+ const projectName = path20.basename(projectPath);
10962
11417
  console.log(chalk4.bold(`
10963
11418
  Run this to register it:`));
10964
11419
  console.log(chalk4.cyan(` squadrant projects add ${projectName} ${projectPath}
@@ -10986,8 +11441,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
10986
11441
  init_dist();
10987
11442
  init_dist2();
10988
11443
  import { Command as Command3 } from "commander";
10989
- import fs19 from "fs";
10990
- import path20 from "path";
11444
+ import fs17 from "fs";
11445
+ import path21 from "path";
10991
11446
  import chalk5 from "chalk";
10992
11447
  function restartAfterProjectsAdd(opts) {
10993
11448
  const doRestart = opts.doRestart ?? restartDaemonIfRunning;
@@ -10999,22 +11454,22 @@ function restartAfterProjectsAdd(opts) {
10999
11454
  }
11000
11455
  }
11001
11456
  function findPackageRoot2() {
11002
- let dir = path20.dirname(new URL(import.meta.url).pathname);
11457
+ let dir = path21.dirname(new URL(import.meta.url).pathname);
11003
11458
  while (dir !== "/") {
11004
- if (fs19.existsSync(path20.join(dir, "package.json"))) return dir;
11005
- dir = path20.dirname(dir);
11459
+ if (fs17.existsSync(path21.join(dir, "package.json"))) return dir;
11460
+ dir = path21.dirname(dir);
11006
11461
  }
11007
11462
  return process.cwd();
11008
11463
  }
11009
11464
  function copyDirRecursive2(src, dest) {
11010
- fs19.mkdirSync(dest, { recursive: true });
11011
- for (const entry of fs19.readdirSync(src, { withFileTypes: true })) {
11012
- const srcPath = path20.join(src, entry.name);
11013
- const destPath = path20.join(dest, entry.name);
11465
+ fs17.mkdirSync(dest, { recursive: true });
11466
+ for (const entry of fs17.readdirSync(src, { withFileTypes: true })) {
11467
+ const srcPath = path21.join(src, entry.name);
11468
+ const destPath = path21.join(dest, entry.name);
11014
11469
  if (entry.isDirectory()) {
11015
11470
  copyDirRecursive2(srcPath, destPath);
11016
11471
  } else {
11017
- fs19.copyFileSync(srcPath, destPath);
11472
+ fs17.copyFileSync(srcPath, destPath);
11018
11473
  }
11019
11474
  }
11020
11475
  }
@@ -11050,7 +11505,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
11050
11505
  process.exit(1);
11051
11506
  }
11052
11507
  const resolvedPath = resolveHome(projectPath);
11053
- if (!fs19.existsSync(path20.join(resolvedPath, ".git"))) {
11508
+ if (!fs17.existsSync(path21.join(resolvedPath, ".git"))) {
11054
11509
  console.log(chalk5.yellow(`
11055
11510
  \u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
11056
11511
  `));
@@ -11103,7 +11558,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
11103
11558
  \u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
11104
11559
  }
11105
11560
  }
11106
- const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path20.join(config.hubVault, "spokes", name);
11561
+ const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path21.join(config.hubVault, "spokes", name);
11107
11562
  const project = {
11108
11563
  path: resolvedPath,
11109
11564
  captainName,
@@ -11118,20 +11573,20 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
11118
11573
  \u2714 Project '${name}' registered`));
11119
11574
  restartAfterProjectsAdd({ noRestart: opts.restart === false });
11120
11575
  const pkgRoot = findPackageRoot2();
11121
- const spokeTemplate = path20.join(pkgRoot, "obsidian", "spoke");
11122
- if (fs19.existsSync(spokeVault)) {
11576
+ const spokeTemplate = path21.join(pkgRoot, "obsidian", "spoke");
11577
+ if (fs17.existsSync(spokeVault)) {
11123
11578
  console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
11124
- } else if (fs19.existsSync(spokeTemplate)) {
11579
+ } else if (fs17.existsSync(spokeTemplate)) {
11125
11580
  copyDirRecursive2(spokeTemplate, spokeVault);
11126
- const statusPath = path20.join(spokeVault, "status.md");
11127
- if (fs19.existsSync(statusPath)) {
11128
- const content = fs19.readFileSync(statusPath, "utf-8");
11581
+ const statusPath = path21.join(spokeVault, "status.md");
11582
+ if (fs17.existsSync(statusPath)) {
11583
+ const content = fs17.readFileSync(statusPath, "utf-8");
11129
11584
  const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
11130
- fs19.writeFileSync(statusPath, updated);
11585
+ fs17.writeFileSync(statusPath, updated);
11131
11586
  }
11132
11587
  console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
11133
11588
  } else {
11134
- fs19.mkdirSync(spokeVault, { recursive: true });
11589
+ fs17.mkdirSync(spokeVault, { recursive: true });
11135
11590
  console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
11136
11591
  }
11137
11592
  console.log("");
@@ -11200,10 +11655,10 @@ init_dist4();
11200
11655
  init_dist();
11201
11656
  import { Command as Command5 } from "commander";
11202
11657
  import { execSync as execSync9 } from "child_process";
11203
- import path21 from "path";
11658
+ import path22 from "path";
11204
11659
  import os11 from "os";
11205
11660
  import chalk7 from "chalk";
11206
- var TEMPLATES_DIR2 = path21.join(os11.homedir(), ".config", "squadrant", "templates");
11661
+ var TEMPLATES_DIR2 = path22.join(os11.homedir(), ".config", "squadrant", "templates");
11207
11662
  var TASK_PROMPTS = {
11208
11663
  briefing: "Run your daily briefing using the squadrant:command-ops skill. Read all spoke handoffs, yesterday's logs, current status; produce a concise cross-project briefing; save to {hubVault}/daily-logs/YYYY-MM-DD.md; then exit.",
11209
11664
  "learnings-review": "Run a learnings review using the squadrant:command-ops skill. Scan {spokeVault}/learnings across all projects, identify cross-project patterns, propose captured-skill or fix actions, and exit when done.",
@@ -11236,7 +11691,7 @@ async function runCommandSpawn(input) {
11236
11691
  if (!agent) {
11237
11692
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
11238
11693
  }
11239
- const promptFile = path21.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
11694
+ const promptFile = path22.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
11240
11695
  const cliCommand = agent.buildCommand({
11241
11696
  prompt,
11242
11697
  workdir: process.cwd(),
@@ -11264,21 +11719,21 @@ init_dist();
11264
11719
  init_dist3();
11265
11720
  init_dist4();
11266
11721
  init_dist2();
11267
- import { Command as Command9 } from "commander";
11268
- import chalk9 from "chalk";
11722
+ import { Command as Command10 } from "commander";
11723
+ import chalk10 from "chalk";
11269
11724
 
11270
11725
  // packages/cli/src/commands/crew-control.ts
11271
11726
  init_dist2();
11272
11727
  init_dist2();
11273
11728
  init_dist();
11274
11729
  init_dist4();
11275
- import { Command as Command8 } from "commander";
11730
+ import { Command as Command9 } from "commander";
11276
11731
  import { createConnection as createConnection3 } from "net";
11277
11732
  import { randomUUID as randomUUID4 } from "crypto";
11278
11733
  import { execFileSync as execFileSync6 } from "child_process";
11279
- import { homedir as homedir17 } from "os";
11280
- import { join as join22 } from "path";
11281
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync11 } from "fs";
11734
+ import { homedir as homedir18 } from "os";
11735
+ import { join as join23 } from "path";
11736
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "fs";
11282
11737
 
11283
11738
  // packages/cli/src/commands/crew-output.ts
11284
11739
  function tailLines(text, maxLines = 40, maxBytes = 4096) {
@@ -11328,7 +11783,26 @@ function formatCompactTasks(records, opts) {
11328
11783
  if (opts.compact === false) {
11329
11784
  return JSON.stringify(records, null, 2);
11330
11785
  }
11331
- return records.map(formatTaskLine).join("\n");
11786
+ const active = records.filter((r) => !r.operatorHold);
11787
+ const held = records.filter((r) => r.operatorHold);
11788
+ let out = "";
11789
+ if (active.length > 0) {
11790
+ if (held.length > 0) out += `active (${active.length}):
11791
+ `;
11792
+ out += active.map((r) => (held.length > 0 ? " " : "") + formatTaskLine(r)).join("\n");
11793
+ }
11794
+ if (held.length > 0) {
11795
+ if (out) out += "\n";
11796
+ out += `HELD BY OPERATOR (${held.length}) \u2014 not counted toward maxCrew:
11797
+ `;
11798
+ out += held.map((r) => {
11799
+ const m = Math.round((Date.now() - r.operatorHold.since) / 6e4);
11800
+ const hm = m >= 60 ? `${Math.floor(m / 60)}h${m % 60}m` : `${m}m`;
11801
+ const note = r.operatorHold.note ? ` \xB7 "${r.operatorHold.note}"` : "";
11802
+ return ` ${formatTaskLine(r)} \xB7 held ${hm}${note}`;
11803
+ }).join("\n");
11804
+ }
11805
+ return out;
11332
11806
  }
11333
11807
 
11334
11808
  // packages/cli/src/commands/crew-attach.ts
@@ -11576,7 +12050,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
11576
12050
  });
11577
12051
 
11578
12052
  // packages/cli/src/commands/crew-control.ts
11579
- var SOCK2 = join22(homedir17(), ".config", "squadrant", "squadrant.sock");
12053
+ var SOCK2 = join23(homedir18(), ".config", "squadrant", "squadrant.sock");
11580
12054
  var CODEX_FIRST_TURN_DELAY_MS = 1500;
11581
12055
  async function sendCodexFirstTurn(taskId, text) {
11582
12056
  await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
@@ -11640,6 +12114,40 @@ function buildGateResolveRequest(o) {
11640
12114
  payload: { text: o.message, ...decision ? { decision } : {} }
11641
12115
  };
11642
12116
  }
12117
+ async function runCrewTakeover(mode, opts, deps) {
12118
+ const taskId = opts.taskId ?? process.env.SQUADRANT_CREW_TASK_ID;
12119
+ let project = opts.project ?? process.env.SQUADRANT_CREW_PROJECT;
12120
+ let target;
12121
+ if (taskId) {
12122
+ if (!project) {
12123
+ throw new Error("not running under a crew (SQUADRANT_CREW_PROJECT unset)");
12124
+ }
12125
+ const tasks = await deps.listTasks(project);
12126
+ target = tasks.find((t) => t.id === taskId);
12127
+ if (!target) throw new Error(`Task '${taskId}' not found for project '${project}'`);
12128
+ } else if (project && opts.crew) {
12129
+ const tasks = await deps.listTasks(project);
12130
+ target = resolveApproveTarget(tasks, opts.crew) || void 0;
12131
+ if (!target) throw new Error(`Crew '${opts.crew}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
12132
+ } else {
12133
+ throw new Error("must provide either <project> <crew> or --task-id");
12134
+ }
12135
+ const nameDisp = project && opts.crew ? `${project}/${opts.crew}` : target.name ? `${target.project}/${target.name}` : `${target.project}/${target.id}`;
12136
+ if (mode === "start") {
12137
+ const event = { type: "crew.takeover.started", id: target.id, ...opts.note !== void 0 ? { note: opts.note } : {} };
12138
+ await deps.emitEvent(target.project, event);
12139
+ 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.`;
12140
+ await deps.runtimeSend(target.project, msg);
12141
+ deps.printSuccess(msg);
12142
+ } else {
12143
+ const event = { type: "crew.takeover.ended", id: target.id };
12144
+ await deps.emitEvent(target.project, event);
12145
+ 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.`;
12146
+ await deps.runtimeSend(target.project, msg);
12147
+ deps.printSuccess(msg);
12148
+ }
12149
+ return target;
12150
+ }
11643
12151
  async function squadrantdCall(req) {
11644
12152
  try {
11645
12153
  return await sendRequest(SOCK2, req);
@@ -11683,10 +12191,10 @@ function buildSignalRequest(signal, o) {
11683
12191
  return { kind: "event", project, event };
11684
12192
  }
11685
12193
  function defaultWriteResult(id, payload) {
11686
- const dir = join22(homedir17(), ".config", "squadrant", "state", "_results");
11687
- mkdirSync9(dir, { recursive: true });
11688
- const file = join22(dir, `${id}.txt`);
11689
- writeFileSync11(file, payload);
12194
+ const dir = join23(homedir18(), ".config", "squadrant", "state", "_results");
12195
+ mkdirSync8(dir, { recursive: true });
12196
+ const file = join23(dir, `${id}.txt`);
12197
+ writeFileSync10(file, payload);
11690
12198
  return file;
11691
12199
  }
11692
12200
  async function runCrewSignal(signal, o, deps) {
@@ -11865,6 +12373,60 @@ function addControlPlaneCrewCommands(crew) {
11865
12373
  `);
11866
12374
  } catch (e) {
11867
12375
  process.stderr.write(`${e.message}
12376
+ `);
12377
+ process.exit(1);
12378
+ }
12379
+ });
12380
+ 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) => {
12381
+ try {
12382
+ await runCrewTakeover("start", { project, crew: crewName, taskId: opts.taskId, note: opts.note }, {
12383
+ listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
12384
+ emitEvent: async (p, event) => {
12385
+ await squadrantdCall({ kind: "event", project: p, event });
12386
+ },
12387
+ runtimeSend: async (p, msg) => {
12388
+ const { runRuntimeSend: runRuntimeSend2 } = await Promise.resolve().then(() => (init_runtime2(), runtime_exports));
12389
+ await runRuntimeSend2(p, msg, {});
12390
+ },
12391
+ printError: (msg) => {
12392
+ process.stderr.write(`${msg}
12393
+ `);
12394
+ },
12395
+ printSuccess: (msg) => {
12396
+ process.stdout.write(`\u2714 ${msg}
12397
+ `);
12398
+ }
12399
+ });
12400
+ process.exit(0);
12401
+ } catch (e) {
12402
+ process.stderr.write(`${e.message}
12403
+ `);
12404
+ process.exit(1);
12405
+ }
12406
+ });
12407
+ 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) => {
12408
+ try {
12409
+ await runCrewTakeover("end", { project, crew: crewName, taskId: opts.taskId }, {
12410
+ listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
12411
+ emitEvent: async (p, event) => {
12412
+ await squadrantdCall({ kind: "event", project: p, event });
12413
+ },
12414
+ runtimeSend: async (p, msg) => {
12415
+ const { runRuntimeSend: runRuntimeSend2 } = await Promise.resolve().then(() => (init_runtime2(), runtime_exports));
12416
+ await runRuntimeSend2(p, msg, {});
12417
+ },
12418
+ printError: (msg) => {
12419
+ process.stderr.write(`${msg}
12420
+ `);
12421
+ },
12422
+ printSuccess: (msg) => {
12423
+ process.stdout.write(`\u2714 ${msg}
12424
+ `);
12425
+ }
12426
+ });
12427
+ process.exit(0);
12428
+ } catch (e) {
12429
+ process.stderr.write(`${e.message}
11868
12430
  `);
11869
12431
  process.exit(1);
11870
12432
  }
@@ -11872,7 +12434,7 @@ function addControlPlaneCrewCommands(crew) {
11872
12434
  crew.addCommand(crewAttachCommand);
11873
12435
  crew.addCommand(crewChatCommand);
11874
12436
  }
11875
- var crewControlCommand = new Command8("crew").description("Dispatch and track crew via the squadrant control plane");
12437
+ var crewControlCommand = new Command9("crew").description("Dispatch and track crew via the squadrant control plane");
11876
12438
  addControlPlaneCrewCommands(crewControlCommand);
11877
12439
 
11878
12440
  // packages/cli/src/commands/crew.ts
@@ -11904,13 +12466,14 @@ async function runCrewSpawn2(input) {
11904
12466
  await squadrantdCall({ kind: "event", project: p, event });
11905
12467
  },
11906
12468
  onRouted: (route) => console.log(
11907
- chalk9.dim(
12469
+ chalk10.dim(
11908
12470
  `routed: tier=${route.tier} \u2192 ${route.agent}${route.model ? `/${route.model}` : ""} (rule: "${route.matchedRule}")`
11909
12471
  )
11910
- )
12472
+ ),
12473
+ onBaseResolved: (base) => console.log(chalk10.dim(`base: ${base}`))
11911
12474
  });
11912
12475
  }
11913
- async function runCrewSend2(project, name, message) {
12476
+ async function runCrewSend2(project, name, message, opts) {
11914
12477
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
11915
12478
  return runCrewSend(project, name, message, runtime, workspaceId, {
11916
12479
  listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
@@ -11922,13 +12485,13 @@ async function runCrewSend2(project, name, message) {
11922
12485
  sendToPane: (pane, msg) => confirmedSendToPane(runtime, pane, msg),
11923
12486
  // #516: side-effect-free modal precheck, run before any daemon-state emit.
11924
12487
  isBlockedByModal: (pane) => paneHasOpenModal(runtime, pane)
11925
- });
12488
+ }, opts);
11926
12489
  }
11927
12490
  async function runCrewRead2(project, name) {
11928
12491
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
11929
12492
  return runCrewRead(project, name, runtime, workspaceId);
11930
12493
  }
11931
- async function runCrewClose2(project, name) {
12494
+ async function runCrewClose2(project, name, opts) {
11932
12495
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
11933
12496
  return runCrewClose(project, name, runtime, workspaceId, {
11934
12497
  listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
@@ -11938,13 +12501,13 @@ async function runCrewClose2(project, name) {
11938
12501
  closeCodexThread: async (taskId) => {
11939
12502
  await squadrantdCall({ kind: "codex-close", taskId });
11940
12503
  }
11941
- });
12504
+ }, opts);
11942
12505
  }
11943
12506
  async function runCrewList2(project) {
11944
12507
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
11945
12508
  return runCrewList(project, runtime, workspaceId);
11946
12509
  }
11947
- var crewCommand = new Command9("crew").description(
12510
+ var crewCommand = new Command10("crew").description(
11948
12511
  "Spawn and manage interactive crew sessions next to the project's captain"
11949
12512
  );
11950
12513
  crewCommand.command("spawn").description(
@@ -11970,9 +12533,9 @@ crewCommand.command("spawn").description(
11970
12533
  // into the isolated worktree root for relative-path access.
11971
12534
  ...opts.taskFile && opts.taskFile !== "-" ? { taskFile: opts.taskFile } : {}
11972
12535
  });
11973
- console.log(chalk9.green(`\u2714 Crew '${pane.title}' spawned (${pane.surfaceId})`));
12536
+ console.log(chalk10.green(`\u2714 Crew '${pane.title}' spawned (${pane.surfaceId})`));
11974
12537
  } catch (err) {
11975
- console.error(chalk9.red(err.message));
12538
+ console.error(chalk10.red(err.message));
11976
12539
  process.exit(1);
11977
12540
  }
11978
12541
  }
@@ -11981,24 +12544,48 @@ crewCommand.command("list").description("List live crew sessions for a project")
11981
12544
  try {
11982
12545
  const crews = await runCrewList2(project);
11983
12546
  if (crews.length === 0) {
11984
- console.log(chalk9.yellow(`No live crew sessions for ${project}.`));
12547
+ console.log(chalk10.yellow(`No live crew sessions for ${project}.`));
11985
12548
  return;
11986
12549
  }
12550
+ const tasks = await squadrantdCall({ kind: "list", project }).catch(() => []);
12551
+ const active = [];
12552
+ const held = [];
11987
12553
  for (const c of crews) {
11988
- console.log(` ${c.name} (${c.surfaceId})`);
12554
+ const task = tasks.find((t2) => t2.name === c.name);
12555
+ const t = task ? resolveApproveTarget(tasks, c.name) : void 0;
12556
+ if (t?.operatorHold) {
12557
+ held.push({ c, t });
12558
+ } else {
12559
+ active.push({ c, t });
12560
+ }
12561
+ }
12562
+ if (active.length > 0) {
12563
+ console.log(`active (${active.length}):`);
12564
+ for (const { c, t } of active) {
12565
+ console.log(` ${c.name.padEnd(10)} ${t ? t.state : "unknown"} (${c.surfaceId})`);
12566
+ }
12567
+ }
12568
+ if (held.length > 0) {
12569
+ console.log(`HELD BY OPERATOR (${held.length}) \u2014 not counted toward maxCrew:`);
12570
+ for (const { c, t } of held) {
12571
+ const m = Math.round((Date.now() - t.operatorHold.since) / 6e4);
12572
+ const hm = m >= 60 ? `${Math.floor(m / 60)}h${m % 60}m` : `${m}m`;
12573
+ const note = t.operatorHold.note ? ` \xB7 "${t.operatorHold.note}"` : "";
12574
+ console.log(` ${c.name.padEnd(10)} ${t.state} \xB7 held ${hm}${note} (${c.surfaceId})`);
12575
+ }
11989
12576
  }
11990
12577
  } catch (err) {
11991
- console.error(chalk9.red(err.message));
12578
+ console.error(chalk10.red(err.message));
11992
12579
  process.exit(1);
11993
12580
  }
11994
12581
  });
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) => {
12582
+ 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
12583
  try {
11997
12584
  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));
12585
+ await runCrewSend2(project, name, resolvedMessage, opts);
12586
+ console.log(chalk10.green(`\u2714 Sent to ${project}:${name}`));
12587
+ } catch (e) {
12588
+ console.error(chalk10.red(e.message));
12002
12589
  process.exit(1);
12003
12590
  }
12004
12591
  });
@@ -12008,16 +12595,16 @@ crewCommand.command("read").description("Read the current screen of a crew sessi
12008
12595
  const out = opts.full ? screen : tailLines(screen, Number(opts.lines ?? 40));
12009
12596
  console.log(out);
12010
12597
  } catch (err) {
12011
- console.error(chalk9.red(err.message));
12598
+ console.error(chalk10.red(err.message));
12012
12599
  process.exit(1);
12013
12600
  }
12014
12601
  });
12015
- crewCommand.command("close").description("Shutdown a crew session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Crew name").action(async (project, name) => {
12602
+ 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
12603
  try {
12017
- await runCrewClose2(project, name);
12018
- console.log(chalk9.green(`\u2714 Closed ${project}:${name}`));
12604
+ await runCrewClose2(project, name, opts);
12605
+ console.log(chalk10.green(`\u2714 Closed ${project}:${name}`));
12019
12606
  } catch (err) {
12020
- console.error(chalk9.red(err.message));
12607
+ console.error(chalk10.red(err.message));
12021
12608
  process.exit(1);
12022
12609
  }
12023
12610
  });
@@ -12025,8 +12612,8 @@ crewCommand.command("close").description("Shutdown a crew session (closes its ta
12025
12612
  // packages/cli/src/commands/diff.ts
12026
12613
  init_dist();
12027
12614
  init_dist3();
12028
- import { Command as Command10 } from "commander";
12029
- import chalk10 from "chalk";
12615
+ import { Command as Command11 } from "commander";
12616
+ import chalk11 from "chalk";
12030
12617
  import { execFileSync as execFileSync7 } from "child_process";
12031
12618
  import readline2 from "readline";
12032
12619
  function resolveDiffTarget(tasks, crew, projectPath) {
@@ -12143,7 +12730,7 @@ async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
12143
12730
  const label = sources.length > 1 ? "staged or unstaged" : sources[0];
12144
12731
  console.log(`No ${label} changes on ${branchLabel}.`);
12145
12732
  } else {
12146
- console.log(chalk10.dim(`Opened ${opened} working-tree diff(s) (${sources.join(", ")}) for ${branchLabel}.`));
12733
+ console.log(chalk11.dim(`Opened ${opened} working-tree diff(s) (${sources.join(", ")}) for ${branchLabel}.`));
12147
12734
  }
12148
12735
  return;
12149
12736
  }
@@ -12166,7 +12753,7 @@ async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
12166
12753
  lastTurn: opts.lastTurn,
12167
12754
  source: "branch"
12168
12755
  });
12169
- console.log(chalk10.dim(`Opened ${branchLabel} vs ${base} in cmux diff.`));
12756
+ console.log(chalk11.dim(`Opened ${branchLabel} vs ${base} in cmux diff.`));
12170
12757
  }
12171
12758
  async function runDiff(project, crewArg, opts) {
12172
12759
  const config = loadConfig();
@@ -12187,7 +12774,7 @@ async function runDiff(project, crewArg, opts) {
12187
12774
  return;
12188
12775
  }
12189
12776
  await runtime.showPatch({ workspaceId, patch, title, layout: opts.layout, focus: opts.focus });
12190
- console.log(chalk10.dim(`Opened ${title} in cmux diff.`));
12777
+ console.log(chalk11.dim(`Opened ${title} in cmux diff.`));
12191
12778
  return;
12192
12779
  }
12193
12780
  let crew;
@@ -12213,7 +12800,7 @@ async function runDiff(project, crewArg, opts) {
12213
12800
  }
12214
12801
  await openCrewDiff(project, proj, crew, opts, runtime, workspaceId);
12215
12802
  }
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);
12803
+ 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
12804
 
12218
12805
  // packages/cli/src/commands/side.ts
12219
12806
  init_dist();
@@ -12222,12 +12809,12 @@ init_dist4();
12222
12809
  init_dist3();
12223
12810
  init_dist();
12224
12811
  init_dist2();
12225
- import { Command as Command11 } from "commander";
12226
- import fs20 from "fs";
12227
- import path22 from "path";
12812
+ import { Command as Command12 } from "commander";
12813
+ import fs18 from "fs";
12814
+ import path23 from "path";
12228
12815
  import os12 from "os";
12229
- import chalk11 from "chalk";
12230
- var TEMPLATES_DIR3 = path22.join(os12.homedir(), ".config", "squadrant", "templates");
12816
+ import chalk12 from "chalk";
12817
+ var TEMPLATES_DIR3 = path23.join(os12.homedir(), ".config", "squadrant", "templates");
12231
12818
  async function runSideSpawn2(input) {
12232
12819
  const config = loadConfig();
12233
12820
  const proj = config.projects[input.project];
@@ -12251,7 +12838,7 @@ async function runSideSpawn2(input) {
12251
12838
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
12252
12839
  }
12253
12840
  const sideModel = sideRole?.model;
12254
- const promptFile = path22.join(
12841
+ const promptFile = path23.join(
12255
12842
  TEMPLATES_DIR3,
12256
12843
  `side.${input.role}.${agent.templateSuffix}.md`
12257
12844
  );
@@ -12259,7 +12846,7 @@ async function runSideSpawn2(input) {
12259
12846
  prompt: input.topic,
12260
12847
  workdir: spawnCwd,
12261
12848
  role: "side",
12262
- promptFile: fs20.existsSync(promptFile) ? promptFile : void 0,
12849
+ promptFile: fs18.existsSync(promptFile) ? promptFile : void 0,
12263
12850
  interactive: true,
12264
12851
  permissionMode: config.defaults.permissions?.crew ?? "auto",
12265
12852
  ...sideModel ? { model: sideModel } : {}
@@ -12288,7 +12875,7 @@ async function runSideClose2(project, name) {
12288
12875
  config.defaults.worktreeDir ?? ".worktrees"
12289
12876
  );
12290
12877
  }
12291
- var sideCommand = new Command11("side").description(
12878
+ var sideCommand = new Command12("side").description(
12292
12879
  "Spawn and manage side-sessions (research/debug) \u2014 fresh-context tabs off the daemon lifecycle"
12293
12880
  );
12294
12881
  sideCommand.command("spawn").description(
@@ -12313,9 +12900,9 @@ sideCommand.command("spawn").description(
12313
12900
  direction: opts.direction,
12314
12901
  agent: opts.agent
12315
12902
  });
12316
- console.log(chalk11.green(`\u2714 Side session '${pane.title}' spawned (${pane.surfaceId})`));
12903
+ console.log(chalk12.green(`\u2714 Side session '${pane.title}' spawned (${pane.surfaceId})`));
12317
12904
  } catch (err) {
12318
- console.error(chalk11.red(err.message));
12905
+ console.error(chalk12.red(err.message));
12319
12906
  process.exit(1);
12320
12907
  }
12321
12908
  }
@@ -12324,14 +12911,14 @@ sideCommand.command("list").description("List live side-sessions for a project")
12324
12911
  try {
12325
12912
  const sessions = await runSideList2(project);
12326
12913
  if (sessions.length === 0) {
12327
- console.log(chalk11.yellow(`No live side-sessions for ${project}.`));
12914
+ console.log(chalk12.yellow(`No live side-sessions for ${project}.`));
12328
12915
  return;
12329
12916
  }
12330
12917
  for (const s of sessions) {
12331
12918
  console.log(` ${s.name} (${s.surfaceId})`);
12332
12919
  }
12333
12920
  } catch (err) {
12334
- console.error(chalk11.red(err.message));
12921
+ console.error(chalk12.red(err.message));
12335
12922
  process.exit(1);
12336
12923
  }
12337
12924
  });
@@ -12344,9 +12931,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
12344
12931
  label: "message"
12345
12932
  });
12346
12933
  await runSideSend2(project, name, resolvedMessage);
12347
- console.log(chalk11.green(`\u2714 Sent to ${project}:${name}`));
12934
+ console.log(chalk12.green(`\u2714 Sent to ${project}:${name}`));
12348
12935
  } catch (err) {
12349
- console.error(chalk11.red(err.message));
12936
+ console.error(chalk12.red(err.message));
12350
12937
  process.exit(1);
12351
12938
  }
12352
12939
  }
@@ -12354,9 +12941,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
12354
12941
  sideCommand.command("close").description("Close a side-session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Session name").action(async (project, name) => {
12355
12942
  try {
12356
12943
  await runSideClose2(project, name);
12357
- console.log(chalk11.green(`\u2714 Closed ${project}:${name}`));
12944
+ console.log(chalk12.green(`\u2714 Closed ${project}:${name}`));
12358
12945
  } catch (err) {
12359
- console.error(chalk11.red(err.message));
12946
+ console.error(chalk12.red(err.message));
12360
12947
  process.exit(1);
12361
12948
  }
12362
12949
  });
@@ -12364,11 +12951,11 @@ sideCommand.command("close").description("Close a side-session (closes its tab)"
12364
12951
  // packages/cli/src/commands/dashboard.ts
12365
12952
  init_dist();
12366
12953
  init_dist3();
12367
- import { Command as Command12 } from "commander";
12954
+ import { Command as Command13 } from "commander";
12368
12955
  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";
12956
+ import { homedir as homedir20 } from "os";
12957
+ import { join as join25 } from "path";
12958
+ import chalk14 from "chalk";
12372
12959
 
12373
12960
  // packages/web/dist/read-status.js
12374
12961
  function deriveState(tasks) {
@@ -12445,14 +13032,14 @@ async function readAllStatuses(deps) {
12445
13032
  }
12446
13033
 
12447
13034
  // packages/web/dist/render.js
12448
- import chalk12 from "chalk";
13035
+ import chalk13 from "chalk";
12449
13036
  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
13037
+ idle: chalk13.green,
13038
+ busy: chalk13.cyan,
13039
+ blocked: chalk13.yellow,
13040
+ errored: chalk13.red,
13041
+ offline: chalk13.dim,
13042
+ unknown: chalk13.gray
12456
13043
  };
12457
13044
  var ICON_CHAR = {
12458
13045
  idle: "\u25CF",
@@ -12495,10 +13082,10 @@ function renderDashboard(rows, opts) {
12495
13082
  const width = opts.width ?? 100;
12496
13083
  const lines = [];
12497
13084
  lines.push("");
12498
- lines.push(" " + chalk12.bold("\u{1F4CA} Squadrant Dashboard") + " " + chalk12.dim(opts.now));
13085
+ lines.push(" " + chalk13.bold("\u{1F4CA} Squadrant Dashboard") + " " + chalk13.dim(opts.now));
12499
13086
  lines.push("");
12500
13087
  if (rows.length === 0) {
12501
- lines.push(" " + chalk12.yellow("No projects registered. Add one with: squadrant projects add <name> <path>"));
13088
+ lines.push(" " + chalk13.yellow("No projects registered. Add one with: squadrant projects add <name> <path>"));
12502
13089
  lines.push("");
12503
13090
  return lines.join("\n");
12504
13091
  }
@@ -12509,22 +13096,22 @@ function renderDashboard(rows, opts) {
12509
13096
  const excerptW = Math.max(20, width - FIXED);
12510
13097
  for (const r of rows) {
12511
13098
  const icon = ICON[r.state](ICON_CHAR[r.state]);
12512
- const name = chalk12.cyan(pad(r.project, NAME_W));
13099
+ const name = chalk13.cyan(pad(r.project, NAME_W));
12513
13100
  const state = ICON[r.state](pad(r.state, STATE_W));
12514
13101
  const age = pad(formatAge(r.lastChecked, opts.now), AGE_W);
12515
- const excerpt = chalk12.dim(truncate(firstLine(r.excerpt), excerptW));
13102
+ const excerpt = chalk13.dim(truncate(firstLine(r.excerpt), excerptW));
12516
13103
  lines.push(` ${icon} ${name} ${state} ${age} \u2502 ${excerpt}`);
12517
13104
  }
12518
13105
  lines.push("");
12519
- lines.push(chalk12.dim(" Refreshes every 10s \xB7 Ctrl+C to exit"));
13106
+ lines.push(chalk13.dim(" Refreshes every 10s \xB7 Ctrl+C to exit"));
12520
13107
  lines.push("");
12521
13108
  return lines.join("\n");
12522
13109
  }
12523
13110
 
12524
13111
  // packages/web/dist/sync-hub.js
12525
13112
  init_dist();
12526
- import fs21 from "fs";
12527
- import path23 from "path";
13113
+ import fs19 from "fs";
13114
+ import path24 from "path";
12528
13115
  function buildMirrorMarkdown(s) {
12529
13116
  const fenced = "```";
12530
13117
  return [
@@ -12550,15 +13137,15 @@ function buildMirrorMarkdown(s) {
12550
13137
  function syncHub(deps) {
12551
13138
  if (!deps.config.hubVault)
12552
13139
  return [];
12553
- const writeFile5 = deps.writeFile ?? ((p, c) => fs21.writeFileSync(p, c));
12554
- const mkdir5 = deps.mkdir ?? ((p) => fs21.mkdirSync(p, { recursive: true }));
12555
- const projectsDir = path23.join(resolveHome(deps.config.hubVault), "projects");
13140
+ const writeFile5 = deps.writeFile ?? ((p, c) => fs19.writeFileSync(p, c));
13141
+ const mkdir5 = deps.mkdir ?? ((p) => fs19.mkdirSync(p, { recursive: true }));
13142
+ const projectsDir = path24.join(resolveHome(deps.config.hubVault), "projects");
12556
13143
  mkdir5(projectsDir);
12557
13144
  const out = [];
12558
13145
  for (const s of deps.statuses) {
12559
13146
  if (s.state === "unknown")
12560
13147
  continue;
12561
- const hubPath = path23.join(projectsDir, `${s.project}.md`);
13148
+ const hubPath = path24.join(projectsDir, `${s.project}.md`);
12562
13149
  try {
12563
13150
  writeFile5(hubPath, buildMirrorMarkdown(s));
12564
13151
  out.push({ project: s.project, hubPath });
@@ -12580,9 +13167,9 @@ function mergeSnapshot(daemon, external, now) {
12580
13167
  // packages/web/dist/probes.js
12581
13168
  init_dist();
12582
13169
  init_dist();
12583
- import { join as join23 } from "path";
12584
- import { homedir as homedir18 } from "os";
12585
- import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
13170
+ import { join as join24 } from "path";
13171
+ import { homedir as homedir19 } from "os";
13172
+ import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
12586
13173
  import { execFile as execFile4 } from "child_process";
12587
13174
  var DEFAULT_TIMEOUT_MS = 2e3;
12588
13175
  var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
@@ -12618,7 +13205,7 @@ function vaultProbe(run, dir) {
12618
13205
  return { state: "unknown", detail: "no vault configured" };
12619
13206
  if (!run.pathExists(dir))
12620
13207
  return { state: "gone", detail: "vault directory missing" };
12621
- if (!run.pathExists(join23(dir, ".obsidian")))
13208
+ if (!run.pathExists(join24(dir, ".obsidian")))
12622
13209
  return { state: "gone", detail: "no .obsidian/ (not a vault)" };
12623
13210
  return { state: "alive" };
12624
13211
  } catch {
@@ -12686,13 +13273,13 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
12686
13273
  const sessions = probeSessions(run);
12687
13274
  return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
12688
13275
  }
12689
- var SESSIONS_PATH = join23(homedir18(), ".config", "squadrant", "sessions.json");
13276
+ var SESSIONS_PATH = join24(homedir19(), ".config", "squadrant", "sessions.json");
12690
13277
  function onPath(cli) {
12691
13278
  const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
12692
- return dirs.some((d) => existsSync12(join23(d, cli)));
13279
+ return dirs.some((d) => existsSync12(join24(d, cli)));
12693
13280
  }
12694
13281
  function readSessionsHashes() {
12695
- const raw = JSON.parse(readFileSync15(SESSIONS_PATH, "utf-8"));
13282
+ const raw = JSON.parse(readFileSync14(SESSIONS_PATH, "utf-8"));
12696
13283
  const hashes = Object.values(raw.workspaces ?? {}).map((w) => w.templateHash).filter((h) => typeof h === "string" && h.length > 0);
12697
13284
  return [...new Set(hashes)];
12698
13285
  }
@@ -13597,7 +14184,7 @@ async function startWebServer(opts) {
13597
14184
 
13598
14185
  // packages/cli/src/commands/dashboard.ts
13599
14186
  init_dist();
13600
- var SOCK3 = join24(homedir19(), ".config", "squadrant", "squadrant.sock");
14187
+ var SOCK3 = join25(homedir20(), ".config", "squadrant", "squadrant.sock");
13601
14188
  function detectCurrentWorkspace2() {
13602
14189
  const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
13603
14190
  const match = out.match(/workspace:\d+/);
@@ -13639,10 +14226,10 @@ async function runDashboardWeb(input) {
13639
14226
  sockPath: SOCK3,
13640
14227
  runners: defaultProbeRunners()
13641
14228
  });
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`));
14229
+ console.log(chalk14.green(`\u2714 Squadrant system dashboard \u2192 http://127.0.0.1:${handle.port}`));
14230
+ console.log(chalk14.dim(` polling the daemon every ${input.interval}s \xB7 localhost only \xB7 read-only \xB7 Ctrl-C to stop`));
13644
14231
  }
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) => {
14232
+ 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
14233
  try {
13647
14234
  if (opts.web) {
13648
14235
  await runDashboardWeb({ port: opts.port, interval: opts.interval ?? 5 });
@@ -13650,12 +14237,12 @@ var dashboardCommand = new Command12("dashboard").description("Live status grid
13650
14237
  }
13651
14238
  if (opts.pane) {
13652
14239
  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}`));
14240
+ console.log(chalk14.green(`\u2714 Dashboard pane opened in ${pane.workspaceId} ${pane.surfaceId}`));
13654
14241
  return;
13655
14242
  }
13656
14243
  await runDashboardOnce();
13657
14244
  } catch (err) {
13658
- console.error(chalk13.red(err.message));
14245
+ console.error(chalk14.red(err.message));
13659
14246
  process.exit(1);
13660
14247
  }
13661
14248
  });
@@ -13666,12 +14253,12 @@ dashboardCommand.command("sync-hub").description("Mirror each spoke status.md in
13666
14253
  return;
13667
14254
  }
13668
14255
  if (results.length === 0) {
13669
- console.log(chalk13.dim("\n No mirrors written (no projects with usable status.md, or hubVault unset).\n"));
14256
+ console.log(chalk14.dim("\n No mirrors written (no projects with usable status.md, or hubVault unset).\n"));
13670
14257
  return;
13671
14258
  }
13672
- console.log(chalk13.bold("\n \u{1F4CA} Hub mirror sync\n"));
14259
+ console.log(chalk14.bold("\n \u{1F4CA} Hub mirror sync\n"));
13673
14260
  for (const r of results) {
13674
- console.log(` ${chalk13.green("\u2714")} ${chalk13.cyan(r.project.padEnd(16))} \u2192 ${chalk13.dim(r.hubPath)}`);
14261
+ console.log(` ${chalk14.green("\u2714")} ${chalk14.cyan(r.project.padEnd(16))} \u2192 ${chalk14.dim(r.hubPath)}`);
13675
14262
  }
13676
14263
  console.log("");
13677
14264
  });
@@ -13681,12 +14268,12 @@ init_dist();
13681
14268
  init_dist4();
13682
14269
  init_dist3();
13683
14270
  init_dist2();
13684
- import { Command as Command13 } from "commander";
14271
+ import { Command as Command14 } from "commander";
13685
14272
  import { execSync as execSync11 } from "child_process";
13686
- import fs22 from "fs";
13687
- import path24 from "path";
14273
+ import fs20 from "fs";
14274
+ import path25 from "path";
13688
14275
  import os13 from "os";
13689
- import chalk14 from "chalk";
14276
+ import chalk15 from "chalk";
13690
14277
 
13691
14278
  // packages/cli/src/commands/launch-interactive.ts
13692
14279
  import checkbox, { Separator } from "@inquirer/checkbox";
@@ -13763,20 +14350,20 @@ async function selectCaptainsInteractive(entries, yesterday = getYesterday()) {
13763
14350
  // packages/cli/src/commands/launch.ts
13764
14351
  init_dist2();
13765
14352
  var CMUX_APP = "/Applications/cmux.app";
13766
- var TEMPLATES_DIR4 = path24.join(os13.homedir(), ".config", "squadrant", "templates");
13767
- var SESSIONS_PATH2 = path24.join(os13.homedir(), ".config", "squadrant", "sessions.json");
14353
+ var TEMPLATES_DIR4 = path25.join(os13.homedir(), ".config", "squadrant", "templates");
14354
+ var SESSIONS_PATH2 = path25.join(os13.homedir(), ".config", "squadrant", "sessions.json");
13768
14355
  function ensureCmuxReady(headless) {
13769
14356
  if (headless || isInsideCmux()) return;
13770
- console.log(chalk14.yellow("\n Not running inside cmux. Opening cmux app...\n"));
14357
+ console.log(chalk15.yellow("\n Not running inside cmux. Opening cmux app...\n"));
13771
14358
  execSync11(`open "${CMUX_APP}"`, { stdio: "inherit" });
13772
- console.log(chalk14.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
14359
+ console.log(chalk15.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
13773
14360
  process.exit(0);
13774
14361
  }
13775
- var launchCommand = new Command13("launch").description(
14362
+ var launchCommand = new Command14("launch").description(
13776
14363
  "Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
13777
14364
  ).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
14365
  if (opts.fresh && opts.keep) {
13779
- console.error(chalk14.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
14366
+ console.error(chalk15.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
13780
14367
  process.exit(1);
13781
14368
  }
13782
14369
  const config = loadConfig();
@@ -13824,29 +14411,29 @@ var launchCommand = new Command13("launch").description(
13824
14411
  return null;
13825
14412
  }
13826
14413
  },
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`))
14414
+ onFreshReason: (reason) => console.log(chalk15.cyan(` \u21BB ${reason}`)),
14415
+ onStoppingStale: (name) => console.log(chalk15.yellow(` Closing stale workspace '${name}' for fresh start`)),
14416
+ onAlreadyExists: (name) => console.log(chalk15.yellow(` Workspace '${name}' already exists \u2014 switching to it`)),
14417
+ onCreated: (name) => console.log(chalk15.green(` \u2714 Workspace '${name}' created`))
13831
14418
  });
13832
14419
  } catch (err) {
13833
- console.error(chalk14.red(` \u2718 Failed: ${err.message}`));
14420
+ console.error(chalk15.red(` \u2718 Failed: ${err.message}`));
13834
14421
  hadFailure = true;
13835
14422
  }
13836
14423
  }
13837
14424
  if (opts.all) {
13838
14425
  const hubPath = resolveHome(config.hubVault);
13839
- fs22.mkdirSync(hubPath, { recursive: true });
13840
- console.log(chalk14.bold("\nLaunching all captain workspaces\n"));
14426
+ fs20.mkdirSync(hubPath, { recursive: true });
14427
+ console.log(chalk15.bold("\nLaunching all captain workspaces\n"));
13841
14428
  for (const [name, proj] of Object.entries(config.projects)) {
13842
14429
  const projPath = resolveHome(proj.path);
13843
14430
  const spokePath = resolveHome(proj.spokeVault);
13844
- if (!fs22.existsSync(spokePath)) {
14431
+ if (!fs20.existsSync(spokePath)) {
13845
14432
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
13846
14433
  await ensureSpokeLayout(spokeDriver);
13847
- console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
14434
+ console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13848
14435
  }
13849
- console.log(chalk14.bold(`
14436
+ console.log(chalk15.bold(`
13850
14437
  Captain: ${proj.captainName} (${name})`));
13851
14438
  await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
13852
14439
  }
@@ -13854,7 +14441,7 @@ var launchCommand = new Command13("launch").description(
13854
14441
  } else if (!project) {
13855
14442
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
13856
14443
  console.error(
13857
- chalk14.red(
14444
+ chalk15.red(
13858
14445
  "\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
14446
  )
13860
14447
  );
@@ -13869,22 +14456,22 @@ var launchCommand = new Command13("launch").description(
13869
14456
  }));
13870
14457
  const selected = await selectCaptainsInteractive(entries);
13871
14458
  if (selected.length === 0) {
13872
- console.log(chalk14.yellow("\n No captains selected.\n"));
14459
+ console.log(chalk15.yellow("\n No captains selected.\n"));
13873
14460
  return;
13874
14461
  }
13875
- console.log(chalk14.bold(`
14462
+ console.log(chalk15.bold(`
13876
14463
  Launching ${selected.length} captain workspace(s) in parallel
13877
14464
  `));
13878
14465
  await Promise.all(selected.map(async (name) => {
13879
14466
  const proj = config.projects[name];
13880
14467
  const projPath = resolveHome(proj.path);
13881
14468
  const spokePath = resolveHome(proj.spokeVault);
13882
- if (!fs22.existsSync(spokePath)) {
14469
+ if (!fs20.existsSync(spokePath)) {
13883
14470
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
13884
14471
  await ensureSpokeLayout(spokeDriver);
13885
- console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
14472
+ console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13886
14473
  }
13887
- console.log(chalk14.bold(`
14474
+ console.log(chalk15.bold(`
13888
14475
  Captain: ${proj.captainName} (${name})`));
13889
14476
  await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
13890
14477
  }));
@@ -13892,7 +14479,7 @@ Launching ${selected.length} captain workspace(s) in parallel
13892
14479
  } else {
13893
14480
  if (!config.projects[project]) {
13894
14481
  console.error(
13895
- chalk14.red(
14482
+ chalk15.red(
13896
14483
  `
13897
14484
  \u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
13898
14485
  `
@@ -13903,13 +14490,13 @@ Launching ${selected.length} captain workspace(s) in parallel
13903
14490
  const proj = config.projects[project];
13904
14491
  const projPath = resolveHome(proj.path);
13905
14492
  const spokePath = resolveHome(proj.spokeVault);
13906
- if (!fs22.existsSync(spokePath)) {
14493
+ if (!fs20.existsSync(spokePath)) {
13907
14494
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
13908
14495
  await ensureSpokeLayout(spokeDriver);
13909
- console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
14496
+ console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
13910
14497
  }
13911
14498
  console.log(
13912
- chalk14.bold(
14499
+ chalk15.bold(
13913
14500
  `
13914
14501
  Launching captain workspace for '${project}' (${proj.captainName})
13915
14502
  `
@@ -13923,8 +14510,8 @@ Launching captain workspace for '${project}' (${proj.captainName})
13923
14510
  // packages/cli/src/commands/shutdown.ts
13924
14511
  init_dist();
13925
14512
  init_dist3();
13926
- import { Command as Command14 } from "commander";
13927
- import chalk15 from "chalk";
14513
+ import { Command as Command15 } from "commander";
14514
+ import chalk16 from "chalk";
13928
14515
  init_dist();
13929
14516
  function nameVariants(name) {
13930
14517
  const stripped = name.replace(/^⚓\s+/, "").trim();
@@ -13937,23 +14524,23 @@ async function closeMatching(runtime, variants, label) {
13937
14524
  const failed = [];
13938
14525
  if (matches.length === 0) {
13939
14526
  console.log(
13940
- chalk15.yellow(` \u26A0 Workspace '${label}' not found \u2014 already closed?`)
14527
+ chalk16.yellow(` \u26A0 Workspace '${label}' not found \u2014 already closed?`)
13941
14528
  );
13942
14529
  return { closed, failed };
13943
14530
  }
13944
14531
  for (const ws of matches) {
13945
14532
  try {
13946
14533
  await runtime.stop(ws.id);
13947
- console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
14534
+ console.log(chalk16.green(` \u2714 Closed: ${ws.name}`));
13948
14535
  closed.push(ws.name);
13949
14536
  } catch {
13950
- console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
14537
+ console.log(chalk16.red(` \u2718 Failed to close: ${ws.name}`));
13951
14538
  failed.push(ws.name);
13952
14539
  }
13953
14540
  }
13954
14541
  return { closed, failed };
13955
14542
  }
13956
- var shutdownCommand = new Command14("shutdown").description(
14543
+ var shutdownCommand = new Command15("shutdown").description(
13957
14544
  "Shutdown command + all captain workspaces (no args) or one captain workspace"
13958
14545
  ).argument("[project]", "Project name to shut down captain for").action(async (project) => {
13959
14546
  const config = loadConfig();
@@ -13969,11 +14556,11 @@ var shutdownCommand = new Command14("shutdown").description(
13969
14556
  const allVariants = /* @__PURE__ */ new Set([...captainVariants, ...commandVariants]);
13970
14557
  const squadrantWorkspaces = workspaces.filter((w) => allVariants.has(w.name));
13971
14558
  if (squadrantWorkspaces.length === 0) {
13972
- console.log(chalk15.yellow("\nNo squadrant workspaces found to close.\n"));
14559
+ console.log(chalk16.yellow("\nNo squadrant workspaces found to close.\n"));
13973
14560
  return;
13974
14561
  }
13975
14562
  console.log(
13976
- chalk15.bold(
14563
+ chalk16.bold(
13977
14564
  `
13978
14565
  Shutting down ${squadrantWorkspaces.length} workspace(s)...
13979
14566
  `
@@ -13993,9 +14580,9 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
13993
14580
  for (const ws of squadrantWorkspaces) {
13994
14581
  try {
13995
14582
  await globalRuntime.stop(ws.id);
13996
- console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
14583
+ console.log(chalk16.green(` \u2714 Closed: ${ws.name}`));
13997
14584
  } catch {
13998
- console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
14585
+ console.log(chalk16.red(` \u2718 Failed to close: ${ws.name}`));
13999
14586
  }
14000
14587
  }
14001
14588
  console.log("");
@@ -14003,7 +14590,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
14003
14590
  }
14004
14591
  if (!config.projects[project]) {
14005
14592
  console.error(
14006
- chalk15.red(
14593
+ chalk16.red(
14007
14594
  `
14008
14595
  \u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
14009
14596
  `
@@ -14014,7 +14601,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
14014
14601
  const captainName = config.projects[project].captainName;
14015
14602
  const runtime = runtimes.forProject(project, config);
14016
14603
  console.log(
14017
- chalk15.bold(`
14604
+ chalk16.bold(`
14018
14605
  Shutting down captain workspace for '${project}'...
14019
14606
  `)
14020
14607
  );
@@ -14038,25 +14625,25 @@ Shutting down captain workspace for '${project}'...
14038
14625
 
14039
14626
  // packages/cli/src/commands/feedback.ts
14040
14627
  init_dist();
14041
- import { Command as Command15 } from "commander";
14042
- import fs23 from "fs";
14628
+ import { Command as Command16 } from "commander";
14629
+ import fs21 from "fs";
14043
14630
  import os14 from "os";
14044
- import path25 from "path";
14631
+ import path26 from "path";
14045
14632
  import { fileURLToPath as fileURLToPath3 } from "url";
14046
14633
  import { execSync as execSync12 } from "child_process";
14047
- import chalk16 from "chalk";
14634
+ import chalk17 from "chalk";
14048
14635
  var REPO_URL = "https://github.com/tu11aa/squadrant";
14049
14636
  function readPkgVersion() {
14050
14637
  try {
14051
- const pkgPath = path25.join(path25.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
14052
- return JSON.parse(fs23.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
14638
+ const pkgPath = path26.join(path26.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
14639
+ return JSON.parse(fs21.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
14053
14640
  } catch {
14054
14641
  return "unknown";
14055
14642
  }
14056
14643
  }
14057
14644
  function readMetrics(metricsPath) {
14058
14645
  try {
14059
- return JSON.parse(fs23.readFileSync(metricsPath, "utf-8"));
14646
+ return JSON.parse(fs21.readFileSync(metricsPath, "utf-8"));
14060
14647
  } catch {
14061
14648
  return {};
14062
14649
  }
@@ -14092,21 +14679,21 @@ function buildIssueUrl(metrics, squadrantVersion) {
14092
14679
  });
14093
14680
  return `${REPO_URL}/issues/new?${params.toString()}`;
14094
14681
  }
14095
- var feedbackCommand = new Command15("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
14682
+ var feedbackCommand = new Command16("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
14096
14683
  const config = loadConfig();
14097
- const metricsPath = config.metrics?.path || path25.join(os14.homedir(), ".config", "squadrant", "metrics.json");
14684
+ const metricsPath = config.metrics?.path || path26.join(os14.homedir(), ".config", "squadrant", "metrics.json");
14098
14685
  const metrics = readMetrics(metricsPath);
14099
14686
  const version = readStamp(config) ?? readPkgVersion();
14100
14687
  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)}...
14688
+ console.log(chalk17.bold("\nOpening feedback issue in browser...\n"));
14689
+ console.log(chalk17.dim(` URL: ${issueUrl.substring(0, 80)}...
14103
14690
  `));
14104
14691
  try {
14105
14692
  execSync12(`open "${issueUrl}"`, { stdio: "ignore" });
14106
- console.log(chalk16.green(" \u2714 Browser opened\n"));
14693
+ console.log(chalk17.green(" \u2714 Browser opened\n"));
14107
14694
  } catch {
14108
- console.log(chalk16.yellow(" \u26A0 Could not open browser automatically."));
14109
- console.log(` Open manually: ${chalk16.cyan(issueUrl)}
14695
+ console.log(chalk17.yellow(" \u26A0 Could not open browser automatically."));
14696
+ console.log(` Open manually: ${chalk17.cyan(issueUrl)}
14110
14697
  `);
14111
14698
  }
14112
14699
  });
@@ -14115,8 +14702,8 @@ var feedbackCommand = new Command15("feedback").description("Open a pre-filled G
14115
14702
  init_dist();
14116
14703
  init_dist();
14117
14704
  init_dist3();
14118
- import { Command as Command16 } from "commander";
14119
- import chalk17 from "chalk";
14705
+ import { Command as Command17 } from "commander";
14706
+ import chalk18 from "chalk";
14120
14707
  function getDateStr(yesterday) {
14121
14708
  return iso(daysAgo(yesterday ? 1 : 0));
14122
14709
  }
@@ -14135,7 +14722,7 @@ function formatStandup(standups, dateStr, raw) {
14135
14722
  const lines = [];
14136
14723
  const header = `Standup \u2014 ${dateStr}`;
14137
14724
  if (!raw) {
14138
- lines.push(chalk17.bold(`
14725
+ lines.push(chalk18.bold(`
14139
14726
  ${header}
14140
14727
  `));
14141
14728
  } else {
@@ -14145,12 +14732,12 @@ ${header}
14145
14732
  let hasBlockers = false;
14146
14733
  for (const s of standups) {
14147
14734
  if (!raw) {
14148
- lines.push(chalk17.cyan.bold(`## ${s.name}`));
14735
+ lines.push(chalk18.cyan.bold(`## ${s.name}`));
14149
14736
  } else {
14150
14737
  lines.push(`## ${s.name}`);
14151
14738
  }
14152
14739
  if (s.gitCommits.length > 0) {
14153
- lines.push(!raw ? chalk17.green("Done:") : "**Done:**");
14740
+ lines.push(!raw ? chalk18.green("Done:") : "**Done:**");
14154
14741
  for (const commit of s.gitCommits) {
14155
14742
  lines.push(` - ${commit}`);
14156
14743
  }
@@ -14162,7 +14749,7 @@ ${header}
14162
14749
  if (match) {
14163
14750
  const items = match[1].trim().split("\n").filter((l) => l.trim().startsWith("-"));
14164
14751
  if (items.length > 0 && section === "Tomorrow") {
14165
- lines.push(!raw ? chalk17.blue("Next:") : "**Next:**");
14752
+ lines.push(!raw ? chalk18.blue("Next:") : "**Next:**");
14166
14753
  for (const item of items) lines.push(` ${item.trim()}`);
14167
14754
  }
14168
14755
  }
@@ -14170,19 +14757,19 @@ ${header}
14170
14757
  }
14171
14758
  if (s.blockers.length > 0) {
14172
14759
  hasBlockers = true;
14173
- lines.push(!raw ? chalk17.red("Blocked:") : "**Blocked:**");
14760
+ lines.push(!raw ? chalk18.red("Blocked:") : "**Blocked:**");
14174
14761
  for (const b of s.blockers) {
14175
14762
  lines.push(` - ${b}`);
14176
14763
  }
14177
14764
  }
14178
14765
  if (s.gitCommits.length === 0 && !s.dailyLog) {
14179
- lines.push(!raw ? chalk17.dim(" (no activity)") : " (no activity)");
14766
+ lines.push(!raw ? chalk18.dim(" (no activity)") : " (no activity)");
14180
14767
  }
14181
14768
  lines.push("");
14182
14769
  }
14183
14770
  const totalCommits = standups.reduce((sum, s) => sum + s.gitCommits.length, 0);
14184
14771
  if (!raw) {
14185
- lines.push(chalk17.dim(`--- ${totalCommits} commits${hasBlockers ? ", HAS BLOCKERS" : ""} (task tracking: no data source \u2014 #630) ---
14772
+ lines.push(chalk18.dim(`--- ${totalCommits} commits${hasBlockers ? ", HAS BLOCKERS" : ""} (task tracking: no data source \u2014 #630) ---
14186
14773
  `));
14187
14774
  } else {
14188
14775
  lines.push(`---
@@ -14191,12 +14778,12 @@ ${header}
14191
14778
  }
14192
14779
  return lines.join("\n");
14193
14780
  }
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) => {
14781
+ 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
14782
  const config = loadConfig();
14196
14783
  const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
14197
14784
  const projects = Object.entries(config.projects);
14198
14785
  if (projects.length === 0) {
14199
- console.log(chalk17.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
14786
+ console.log(chalk18.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
14200
14787
  return;
14201
14788
  }
14202
14789
  const dateStr = getDateStr(!!opts.yesterday);
@@ -14205,7 +14792,7 @@ var standupCommand = new Command16("standup").description("Generate daily standu
14205
14792
  if (opts.project) {
14206
14793
  const match = projects.find(([name]) => name === opts.project);
14207
14794
  if (!match) {
14208
- console.error(chalk17.red(`Project "${opts.project}" not found.`));
14795
+ console.error(chalk18.red(`Project "${opts.project}" not found.`));
14209
14796
  process.exit(1);
14210
14797
  }
14211
14798
  targets = [match];
@@ -14223,8 +14810,8 @@ var standupCommand = new Command16("standup").description("Generate daily standu
14223
14810
  init_dist();
14224
14811
  init_dist();
14225
14812
  init_dist3();
14226
- import { Command as Command17 } from "commander";
14227
- import chalk18 from "chalk";
14813
+ import { Command as Command18 } from "commander";
14814
+ import chalk19 from "chalk";
14228
14815
  function dedupe(items) {
14229
14816
  const seen = /* @__PURE__ */ new Set();
14230
14817
  const out = [];
@@ -14280,7 +14867,7 @@ function formatRetro(retros, fromStr, toStr, raw) {
14280
14867
  const lines = [];
14281
14868
  const header = `Retro \u2014 ${fromStr} \u2192 ${toStr}`;
14282
14869
  lines.push(raw ? `# ${header}
14283
- ` : chalk18.bold(`
14870
+ ` : chalk19.bold(`
14284
14871
  ${header}
14285
14872
  `));
14286
14873
  let totalCommits = 0;
@@ -14290,39 +14877,39 @@ ${header}
14290
14877
  totalCommits += r.commits.length;
14291
14878
  totalPRs += r.mergedPRs.length;
14292
14879
  totalShipped += r.shipped.length;
14293
- lines.push(raw ? `## ${r.name}` : chalk18.cyan.bold(`## ${r.name}`));
14294
- renderList(lines, r.shipped, raw, "Shipped", chalk18.green);
14880
+ lines.push(raw ? `## ${r.name}` : chalk19.cyan.bold(`## ${r.name}`));
14881
+ renderList(lines, r.shipped, raw, "Shipped", chalk19.green);
14295
14882
  if (r.mergedPRs.length > 0) {
14296
- lines.push(raw ? `**PRs merged:**` : chalk18.green("PRs merged:"));
14883
+ lines.push(raw ? `**PRs merged:**` : chalk19.green("PRs merged:"));
14297
14884
  for (const pr of r.mergedPRs) lines.push(` - ${pr}`);
14298
14885
  }
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);
14886
+ renderList(lines, r.inProgress, raw, "In Progress", chalk19.yellow);
14887
+ renderList(lines, r.blocked, raw, "Blocked", chalk19.red);
14888
+ renderList(lines, r.decisions, raw, "Key Decisions", chalk19.magenta);
14302
14889
  const metricBits = [
14303
14890
  `${r.commits.length} commits`,
14304
14891
  `${r.mergedPRs.length} PRs merged`,
14305
14892
  `${r.shipped.length} shipped`
14306
14893
  ];
14307
- lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` : chalk18.dim(` ${metricBits.join(" \xB7 ")}`));
14894
+ lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` : chalk19.dim(` ${metricBits.join(" \xB7 ")}`));
14308
14895
  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)"));
14896
+ lines.push(raw ? "_(no activity in this window)_" : chalk19.dim(" (no activity in this window)"));
14310
14897
  }
14311
14898
  lines.push("");
14312
14899
  }
14313
14900
  const summary = `${totalShipped} items shipped \xB7 ${totalCommits} commits \xB7 ${totalPRs} PRs merged`;
14314
14901
  lines.push(raw ? `---
14315
14902
  *${summary}*
14316
- ` : chalk18.dim(`--- ${summary} ---
14903
+ ` : chalk19.dim(`--- ${summary} ---
14317
14904
  `));
14318
14905
  return lines.join("\n");
14319
14906
  }
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) => {
14907
+ 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
14908
  const config = loadConfig();
14322
14909
  const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
14323
14910
  const projects = Object.entries(config.projects);
14324
14911
  if (projects.length === 0) {
14325
- console.log(chalk18.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
14912
+ console.log(chalk19.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
14326
14913
  return;
14327
14914
  }
14328
14915
  let windowDays = 7;
@@ -14339,7 +14926,7 @@ var retroCommand = new Command17("retro").description("Generate a retro (weekly/
14339
14926
  if (opts.project) {
14340
14927
  const match = projects.find(([name]) => name === opts.project);
14341
14928
  if (!match) {
14342
- console.error(chalk18.red(`Project "${opts.project}" not found.`));
14929
+ console.error(chalk19.red(`Project "${opts.project}" not found.`));
14343
14930
  process.exit(1);
14344
14931
  }
14345
14932
  targets = [match];
@@ -14352,153 +14939,8 @@ var retroCommand = new Command17("retro").description("Generate a retro (weekly/
14352
14939
  console.log(formatRetro(retros, fromStr, toStr, raw));
14353
14940
  });
14354
14941
 
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
- });
14942
+ // packages/cli/src/index.ts
14943
+ init_runtime2();
14502
14944
 
14503
14945
  // packages/cli/src/commands/workspace.ts
14504
14946
  init_dist();
@@ -14539,9 +14981,9 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
14539
14981
  const config = loadConfig();
14540
14982
  const registry = buildRegistry2();
14541
14983
  try {
14542
- const { projectTarget, path: path34 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
14984
+ const { projectTarget, path: path35 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
14543
14985
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
14544
- const content = await driver.read(path34);
14986
+ const content = await driver.read(path35);
14545
14987
  process.stdout.write(content);
14546
14988
  } catch (err) {
14547
14989
  console.error(chalk20.red(err.message));
@@ -14553,26 +14995,26 @@ workspaceCommand.command("write").description("Write content to a scope-relative
14553
14995
  const registry = buildRegistry2();
14554
14996
  try {
14555
14997
  let projectTarget;
14556
- let path34;
14998
+ let path35;
14557
14999
  let rawContent;
14558
15000
  if (opts.hub) {
14559
15001
  if (arg3 !== void 0) {
14560
15002
  throw new Error("With --hub, pass only the path and content");
14561
15003
  }
14562
15004
  projectTarget = void 0;
14563
- path34 = arg1;
15005
+ path35 = arg1;
14564
15006
  rawContent = arg2;
14565
15007
  } else {
14566
15008
  if (arg3 === void 0) {
14567
15009
  throw new Error("Missing content \u2014 usage: <project> <path> <content>");
14568
15010
  }
14569
15011
  projectTarget = arg1;
14570
- path34 = arg2;
15012
+ path35 = arg2;
14571
15013
  rawContent = arg3;
14572
15014
  }
14573
15015
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
14574
15016
  const payload = rawContent === "-" ? await readStdin() : rawContent;
14575
- await driver.write(path34, payload);
15017
+ await driver.write(path35, payload);
14576
15018
  } catch (err) {
14577
15019
  console.error(chalk20.red(err.message));
14578
15020
  process.exit(1);
@@ -14582,9 +15024,9 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
14582
15024
  const config = loadConfig();
14583
15025
  const registry = buildRegistry2();
14584
15026
  try {
14585
- const { projectTarget, path: path34 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
15027
+ const { projectTarget, path: path35 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
14586
15028
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
14587
- const entries = await driver.list(path34);
15029
+ const entries = await driver.list(path35);
14588
15030
  for (const entry of entries) console.log(entry);
14589
15031
  } catch (err) {
14590
15032
  console.error(chalk20.red(err.message));
@@ -14595,9 +15037,9 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
14595
15037
  const config = loadConfig();
14596
15038
  const registry = buildRegistry2();
14597
15039
  try {
14598
- const { projectTarget, path: path34 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
15040
+ const { projectTarget, path: path35 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
14599
15041
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
14600
- const ok2 = await driver.exists(path34);
15042
+ const ok2 = await driver.exists(path35);
14601
15043
  process.exit(ok2 ? 0 : 1);
14602
15044
  } catch (err) {
14603
15045
  console.error(chalk20.red(err.message));
@@ -14608,9 +15050,9 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
14608
15050
  const config = loadConfig();
14609
15051
  const registry = buildRegistry2();
14610
15052
  try {
14611
- const { projectTarget, path: path34 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
15053
+ const { projectTarget, path: path35 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
14612
15054
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
14613
- await driver.mkdir(path34);
15055
+ await driver.mkdir(path35);
14614
15056
  } catch (err) {
14615
15057
  console.error(chalk20.red(err.message));
14616
15058
  process.exit(1);
@@ -14649,8 +15091,8 @@ init_dist3();
14649
15091
  init_dist();
14650
15092
  import { Command as Command21 } from "commander";
14651
15093
  import chalk22 from "chalk";
14652
- import fs24 from "fs";
14653
- import path26 from "path";
15094
+ import fs22 from "fs";
15095
+ import path27 from "path";
14654
15096
  import { fileURLToPath as fileURLToPath4 } from "url";
14655
15097
  function parseScope(v) {
14656
15098
  if (v !== "user" && v !== "project") {
@@ -14659,10 +15101,10 @@ function parseScope(v) {
14659
15101
  return v;
14660
15102
  }
14661
15103
  function findPackageRoot3() {
14662
- let dir = path26.dirname(fileURLToPath4(import.meta.url));
15104
+ let dir = path27.dirname(fileURLToPath4(import.meta.url));
14663
15105
  while (dir !== "/" && dir !== "") {
14664
- if (fs24.existsSync(path26.join(dir, "package.json"))) return dir;
14665
- dir = path26.dirname(dir);
15106
+ if (fs22.existsSync(path27.join(dir, "package.json"))) return dir;
15107
+ dir = path27.dirname(dir);
14666
15108
  }
14667
15109
  return process.cwd();
14668
15110
  }
@@ -14857,12 +15299,12 @@ init_dist();
14857
15299
  init_dist();
14858
15300
  init_dist2();
14859
15301
  import { Command as Command23 } from "commander";
14860
- import fs25 from "fs";
15302
+ import fs23 from "fs";
14861
15303
  import { fileURLToPath as fileURLToPath5 } from "url";
14862
- import { dirname as dirname6, join as join26 } from "path";
15304
+ import { dirname as dirname5, join as join26 } from "path";
14863
15305
  import chalk23 from "chalk";
14864
15306
  function runConfigCheck(opts) {
14865
- const raw = JSON.parse(fs25.readFileSync(opts.configPath, "utf-8"));
15307
+ const raw = JSON.parse(fs23.readFileSync(opts.configPath, "utf-8"));
14866
15308
  const def = getDefaultConfig();
14867
15309
  const items = detectDrift(raw, def);
14868
15310
  let working = raw;
@@ -14879,7 +15321,7 @@ function runConfigCheck(opts) {
14879
15321
  stamped = true;
14880
15322
  }
14881
15323
  if (opts.fix || opts.accept || stamped) {
14882
- fs25.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
15324
+ writeConfigFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
14883
15325
  }
14884
15326
  return { items, applied, remaining, stamped };
14885
15327
  }
@@ -14950,7 +15392,7 @@ function printItems(items) {
14950
15392
  var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
14951
15393
  configCommand.command("check").description("Detect config drift vs the current default schema").option("--fix", "Apply the safe tier (add missing, remove deprecated)", false).option("--accept", "Stamp the current version without changing config (dismiss advisories)", false).option("--json", "Output drift items as JSON", false).action((opts) => {
14952
15394
  const pkgVersion = readPkgVersion2();
14953
- if (!fs25.existsSync(DEFAULT_CONFIG_PATH)) {
15395
+ if (!fs23.existsSync(DEFAULT_CONFIG_PATH)) {
14954
15396
  console.log(chalk23.yellow("No config found \u2014 run `squadrant init` first."));
14955
15397
  return;
14956
15398
  }
@@ -14995,8 +15437,8 @@ configCommand.command("set").description("Write a config value by dotted key (e.
14995
15437
  }
14996
15438
  });
14997
15439
  function readPkgVersion2() {
14998
- const pkgPath = join26(dirname6(fileURLToPath5(import.meta.url)), "..", "package.json");
14999
- return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version;
15440
+ const pkgPath = join26(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
15441
+ return JSON.parse(fs23.readFileSync(pkgPath, "utf-8")).version;
15000
15442
  }
15001
15443
 
15002
15444
  // packages/cli/src/commands/heal.ts
@@ -15020,6 +15462,15 @@ function buildHealStatus(components) {
15020
15462
  }
15021
15463
  async function runHealStatus(opts) {
15022
15464
  const { project, json, stdout, stderr } = opts;
15465
+ const isDaemonAlive = opts.isDaemonAlive ?? (() => isDaemonSocketLive(SOCK));
15466
+ if (!await isDaemonAlive()) {
15467
+ if (json) {
15468
+ stdout.write(JSON.stringify({ healthy: false, daemonUnreachable: true, components: [] }) + "\n");
15469
+ } else {
15470
+ stderr.write("daemon unreachable \u2014 start the daemon first (squadrant heal daemon)\n");
15471
+ }
15472
+ return 1;
15473
+ }
15023
15474
  let rows;
15024
15475
  try {
15025
15476
  rows = await opts.queryHealth(project);
@@ -15149,17 +15600,18 @@ var groupCommand = new Command26("group").description("Cross-project intra-group
15149
15600
  // packages/cli/src/commands/ping.ts
15150
15601
  init_dist();
15151
15602
  init_dist2();
15152
- import { join as join27, dirname as dirname7 } from "path";
15603
+ init_runtime2();
15604
+ init_require_daemon();
15605
+ import { join as join27, dirname as dirname6 } from "path";
15153
15606
  import { Command as Command27 } from "commander";
15154
15607
  import chalk27 from "chalk";
15155
- init_require_daemon();
15156
15608
  async function runPing(project, message) {
15157
15609
  const config = loadConfig();
15158
15610
  const registry = buildRegistry();
15159
15611
  const resolved = resolveTarget(registry, config, project, false);
15160
15612
  await requireDaemon();
15161
15613
  await needRef(resolved);
15162
- const stateRoot = join27(dirname7(DEFAULT_CONFIG_PATH), "state");
15614
+ const stateRoot = join27(dirname6(DEFAULT_CONFIG_PATH), "state");
15163
15615
  await appendCaptainMessage({
15164
15616
  stateRoot,
15165
15617
  project,
@@ -15236,8 +15688,8 @@ var cmuxCommand = new Command28("cmux").description("cmux integration helpers").
15236
15688
  // packages/cli/src/commands/effort.ts
15237
15689
  init_dist();
15238
15690
  init_dist2();
15239
- import fs26 from "fs";
15240
- import path27 from "path";
15691
+ import fs24 from "fs";
15692
+ import path28 from "path";
15241
15693
  import { Command as Command29 } from "commander";
15242
15694
  import chalk29 from "chalk";
15243
15695
  var VALID_EFFORTS = ["max", "balance", "low"];
@@ -15280,9 +15732,9 @@ function effortScopeLabel(projectName) {
15280
15732
  }
15281
15733
  function canonical(p) {
15282
15734
  try {
15283
- return fs26.realpathSync(p);
15735
+ return fs24.realpathSync(p);
15284
15736
  } catch {
15285
- return path27.resolve(p);
15737
+ return path28.resolve(p);
15286
15738
  }
15287
15739
  }
15288
15740
  async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(), append, scopeProject, projectConfigRoot) {
@@ -15332,7 +15784,7 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
15332
15784
  const config = loadConfig();
15333
15785
  const registry = new RuntimeRegistry2({ cmux: createCmuxDriver2() });
15334
15786
  const driver = registry.global(config);
15335
- const stateRoot = path27.join(path27.dirname(DEFAULT_CONFIG_PATH), "state");
15787
+ const stateRoot = path28.join(path28.dirname(DEFAULT_CONFIG_PATH), "state");
15336
15788
  const append = (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
15337
15789
  await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append, options.project);
15338
15790
  } catch {
@@ -15342,13 +15794,13 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
15342
15794
 
15343
15795
  // packages/cli/src/commands/tokens.ts
15344
15796
  init_dist();
15345
- import fs27 from "fs";
15346
- import path28 from "path";
15797
+ import fs25 from "fs";
15798
+ import path29 from "path";
15347
15799
  import os15 from "os";
15348
15800
  import readline3 from "readline";
15349
15801
  import { Command as Command30 } from "commander";
15350
15802
  import chalk30 from "chalk";
15351
- var CLAUDE_PROJECTS_DIR = path28.join(os15.homedir(), ".claude", "projects");
15803
+ var CLAUDE_PROJECTS_DIR = path29.join(os15.homedir(), ".claude", "projects");
15352
15804
  function parseTranscriptLine(rawLine) {
15353
15805
  const line = rawLine.trim();
15354
15806
  if (!line) return { timestamp: null, usage: null };
@@ -15398,7 +15850,7 @@ function foldTranscriptLine(agg, rawLine, state) {
15398
15850
  async function aggregateTranscriptFile(filePath) {
15399
15851
  const agg = emptySessionAggregate();
15400
15852
  const state = { lastCacheRead: null };
15401
- const rl = readline3.createInterface({ input: fs27.createReadStream(filePath), crlfDelay: Infinity });
15853
+ const rl = readline3.createInterface({ input: fs25.createReadStream(filePath), crlfDelay: Infinity });
15402
15854
  for await (const line of rl) {
15403
15855
  foldTranscriptLine(agg, line, state);
15404
15856
  }
@@ -15466,22 +15918,22 @@ function buildRoleReport(role, sessions) {
15466
15918
  }
15467
15919
  async function readdirSafe(dir) {
15468
15920
  try {
15469
- return await fs27.promises.readdir(dir);
15921
+ return await fs25.promises.readdir(dir);
15470
15922
  } catch {
15471
15923
  return [];
15472
15924
  }
15473
15925
  }
15474
15926
  async function listJsonlFiles(dir) {
15475
15927
  const entries = await readdirSafe(dir);
15476
- return entries.filter((e) => e.endsWith(".jsonl")).map((e) => path28.join(dir, e));
15928
+ return entries.filter((e) => e.endsWith(".jsonl")).map((e) => path29.join(dir, e));
15477
15929
  }
15478
15930
  async function findTranscriptDirs(claudeProjectsDir, captainSlug) {
15479
15931
  const entries = await readdirSafe(claudeProjectsDir);
15480
15932
  const captainDirs = [];
15481
15933
  const crewDirs = [];
15482
15934
  for (const entry of entries) {
15483
- if (entry === captainSlug) captainDirs.push(path28.join(claudeProjectsDir, entry));
15484
- else if (isCrewDirName(entry, captainSlug)) crewDirs.push(path28.join(claudeProjectsDir, entry));
15935
+ if (entry === captainSlug) captainDirs.push(path29.join(claudeProjectsDir, entry));
15936
+ else if (isCrewDirName(entry, captainSlug)) crewDirs.push(path29.join(claudeProjectsDir, entry));
15485
15937
  }
15486
15938
  return { captainDirs, crewDirs };
15487
15939
  }
@@ -15633,12 +16085,12 @@ var tokensCommand = new Command30("tokens").description(
15633
16085
  // packages/cli/src/commands/telegram.ts
15634
16086
  init_dist();
15635
16087
  init_dist2();
15636
- import { join as join28, dirname as dirname8 } from "path";
16088
+ import { join as join28, dirname as dirname7 } from "path";
15637
16089
  import { emitKeypressEvents } from "readline";
15638
16090
  import { Command as Command31 } from "commander";
15639
16091
  import chalk31 from "chalk";
15640
16092
  function defaultStateRoot() {
15641
- return join28(dirname8(DEFAULT_CONFIG_PATH), "state");
16093
+ return join28(dirname7(DEFAULT_CONFIG_PATH), "state");
15642
16094
  }
15643
16095
  async function questionMasked() {
15644
16096
  return new Promise((resolve4) => {
@@ -15968,8 +16420,8 @@ import { join as join29 } from "path";
15968
16420
  import { homedir as homedir21 } from "os";
15969
16421
 
15970
16422
  // packages/cli/src/lib/captain-session-registry.ts
15971
- import fs28 from "fs";
15972
- import path29 from "path";
16423
+ import fs26 from "fs";
16424
+ import path30 from "path";
15973
16425
 
15974
16426
  // packages/cli/src/lib/handoff-facts.ts
15975
16427
  var STALE_FETCH_WARNING_MS = 24 * 60 * 60 * 1e3;
@@ -16020,15 +16472,15 @@ function assembleHandoffFacts(live, claudeMem, gapSessions, checkpoint, now, ext
16020
16472
  // packages/cli/src/lib/captain-session-registry.ts
16021
16473
  var CAPTAIN_SESSION_REGISTRY_FILE = "captain-sessions.jsonl";
16022
16474
  function appendCaptainSession(spokeVault, record) {
16023
- fs28.mkdirSync(spokeVault, { recursive: true });
16024
- const file = path29.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
16025
- fs28.appendFileSync(file, JSON.stringify(record) + "\n");
16475
+ fs26.mkdirSync(spokeVault, { recursive: true });
16476
+ const file = path30.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
16477
+ fs26.appendFileSync(file, JSON.stringify(record) + "\n");
16026
16478
  }
16027
16479
  function readCaptainSessionRegistry(spokeVault) {
16028
- const file = path29.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
16029
- if (!fs28.existsSync(file)) return [];
16480
+ const file = path30.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
16481
+ if (!fs26.existsSync(file)) return [];
16030
16482
  const records = [];
16031
- for (const line of fs28.readFileSync(file, "utf-8").split("\n")) {
16483
+ for (const line of fs26.readFileSync(file, "utf-8").split("\n")) {
16032
16484
  if (!line.trim()) continue;
16033
16485
  try {
16034
16486
  records.push(JSON.parse(line));
@@ -16131,13 +16583,13 @@ function hooksCommand() {
16131
16583
  // packages/cli/src/commands/work.ts
16132
16584
  init_dist();
16133
16585
  init_dist2();
16134
- import path30 from "path";
16586
+ import path31 from "path";
16135
16587
  import { Command as Command33 } from "commander";
16136
16588
  import chalk32 from "chalk";
16137
16589
  function detectCurrentProject(config, cwd = process.cwd()) {
16138
16590
  for (const [name, proj] of Object.entries(config.projects)) {
16139
16591
  const projPath = resolveHome(proj.path);
16140
- if (cwd === projPath || cwd.startsWith(projPath + path30.sep)) return name;
16592
+ if (cwd === projPath || cwd.startsWith(projPath + path31.sep)) return name;
16141
16593
  }
16142
16594
  return void 0;
16143
16595
  }
@@ -16269,14 +16721,14 @@ var workCommand = new Command33("work").description("Track your own in-flight wo
16269
16721
  // packages/cli/src/commands/handoff.ts
16270
16722
  init_dist();
16271
16723
  import { Command as Command34 } from "commander";
16272
- import path33 from "path";
16724
+ import path34 from "path";
16273
16725
  import os16 from "os";
16274
16726
 
16275
16727
  // packages/cli/src/lib/handoff-live-repo.ts
16276
16728
  init_dist();
16277
16729
  import { execFileSync as execFileSync8 } from "child_process";
16278
- import fs29 from "fs";
16279
- import path31 from "path";
16730
+ import fs27 from "fs";
16731
+ import path32 from "path";
16280
16732
 
16281
16733
  // packages/cli/src/lib/handoff-branch-state.ts
16282
16734
  function tryRun(runner, cmd, args, cwd) {
@@ -16395,7 +16847,7 @@ function gatherOpenPRs(runner, projectPath) {
16395
16847
  }
16396
16848
  }
16397
16849
  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 }));
16850
+ 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
16851
  }
16400
16852
  function ghAheadOfBase(runner, projectPath, nameWithOwner, base, branch) {
16401
16853
  return tryInt(
@@ -16415,7 +16867,7 @@ function localAheadOfBase(runner, projectPath, base) {
16415
16867
  }
16416
16868
  function readFetchAgeMs(projectPath, now) {
16417
16869
  try {
16418
- const stat2 = fs29.statSync(path31.join(projectPath, ".git", "FETCH_HEAD"));
16870
+ const stat2 = fs27.statSync(path32.join(projectPath, ".git", "FETCH_HEAD"));
16419
16871
  return Math.max(0, now - stat2.mtime.getTime());
16420
16872
  } catch {
16421
16873
  return null;
@@ -16486,7 +16938,7 @@ function gatherLiveRepoState(projectPath, fallbackBaseBranch, tasks, runner = de
16486
16938
 
16487
16939
  // packages/cli/src/lib/handoff-claude-mem.ts
16488
16940
  import { createRequire } from "module";
16489
- import fs30 from "fs";
16941
+ import fs28 from "fs";
16490
16942
  var { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
16491
16943
  var CLAUDE_MEM_RECENCY_LIMIT = 20;
16492
16944
  function decisionText(row) {
@@ -16500,7 +16952,7 @@ function decisionText(row) {
16500
16952
  return row.narrative ?? "";
16501
16953
  }
16502
16954
  function queryClaudeMem(dbPath, project) {
16503
- if (!fs30.existsSync(dbPath)) return null;
16955
+ if (!fs28.existsSync(dbPath)) return null;
16504
16956
  let db;
16505
16957
  try {
16506
16958
  db = new DatabaseSync(dbPath, { readOnly: true });
@@ -16543,7 +16995,7 @@ function queryClaudeMem(dbPath, project) {
16543
16995
  }
16544
16996
 
16545
16997
  // packages/cli/src/lib/handoff-transcript.ts
16546
- import fs31 from "fs";
16998
+ import fs29 from "fs";
16547
16999
  var TRANSCRIPT_BYTE_CAP = 2e5;
16548
17000
  function tailOf(content, byteCap) {
16549
17001
  const buf = Buffer.from(content, "utf-8");
@@ -16572,27 +17024,27 @@ function extractMessages(tailText) {
16572
17024
  return { lastUserMessage, lastAssistantText };
16573
17025
  }
16574
17026
  function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
16575
- if (!fs31.existsSync(transcriptPath)) return null;
16576
- const content = fs31.readFileSync(transcriptPath, "utf-8");
17027
+ if (!fs29.existsSync(transcriptPath)) return null;
17028
+ const content = fs29.readFileSync(transcriptPath, "utf-8");
16577
17029
  const { lastUserMessage, lastAssistantText } = extractMessages(tailOf(content, byteCap));
16578
- const mtimeIso = fs31.statSync(transcriptPath).mtime.toISOString();
17030
+ const mtimeIso = fs29.statSync(transcriptPath).mtime.toISOString();
16579
17031
  return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText };
16580
17032
  }
16581
17033
 
16582
17034
  // packages/cli/src/lib/handoff-archive.ts
16583
- import fs32 from "fs";
16584
- import path32 from "path";
17035
+ import fs30 from "fs";
17036
+ import path33 from "path";
16585
17037
  function readNewestArchivedHandoff(spokeVault, now) {
16586
- const dir = path32.join(spokeVault, "handoffs");
16587
- if (!fs32.existsSync(dir)) return null;
16588
- const candidates = fs32.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
16589
- const full = path32.join(dir, e.name);
16590
- return { name: e.name, full, mtime: fs32.statSync(full).mtime };
17038
+ const dir = path33.join(spokeVault, "handoffs");
17039
+ if (!fs30.existsSync(dir)) return null;
17040
+ const candidates = fs30.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
17041
+ const full = path33.join(dir, e.name);
17042
+ return { name: e.name, full, mtime: fs30.statSync(full).mtime };
16591
17043
  }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
16592
17044
  for (const candidate of candidates) {
16593
17045
  let content;
16594
17046
  try {
16595
- content = JSON.parse(fs32.readFileSync(candidate.full, "utf-8"));
17047
+ content = JSON.parse(fs30.readFileSync(candidate.full, "utf-8"));
16596
17048
  } catch {
16597
17049
  continue;
16598
17050
  }
@@ -16602,7 +17054,7 @@ function readNewestArchivedHandoff(spokeVault, now) {
16602
17054
  }
16603
17055
 
16604
17056
  // packages/cli/src/commands/handoff.ts
16605
- var CLAUDE_MEM_DB_PATH = path33.join(os16.homedir(), ".claude-mem", "claude-mem.db");
17057
+ var CLAUDE_MEM_DB_PATH = path34.join(os16.homedir(), ".claude-mem", "claude-mem.db");
16606
17058
  async function defaultFetchTasks(project) {
16607
17059
  return await squadrantdCall({ kind: "list", project });
16608
17060
  }
@@ -16661,8 +17113,8 @@ init_dist();
16661
17113
  init_dist();
16662
17114
  init_dist();
16663
17115
  init_dist();
16664
- var __dirname = dirname9(fileURLToPath6(import.meta.url));
16665
- var pkg = JSON.parse(readFileSync16(join30(__dirname, "..", "package.json"), "utf-8"));
17116
+ var __dirname = dirname8(fileURLToPath6(import.meta.url));
17117
+ var pkg = JSON.parse(readFileSync15(join30(__dirname, "..", "package.json"), "utf-8"));
16666
17118
  ensureRuntimeSynced({
16667
17119
  sourceRoot: join30(__dirname, ".."),
16668
17120
  runtimeRoot: join30(homedir22(), ".config", "squadrant")
@@ -16671,11 +17123,11 @@ if (process.argv[2] !== "config") {
16671
17123
  try {
16672
17124
  const cfgPath = join30(homedir22(), ".config", "squadrant", "config.json");
16673
17125
  if (existsSync13(cfgPath)) {
16674
- const cfg = JSON.parse(readFileSync16(cfgPath, "utf-8"));
17126
+ const cfg = JSON.parse(readConfigFileSync(cfgPath));
16675
17127
  if (needsCheck(cfg, pkg.version)) {
16676
17128
  const items = detectDrift(cfg, getDefaultConfig());
16677
17129
  if (items.length === 0) {
16678
- writeFileSync12(cfgPath, JSON.stringify(withStamp(cfg, pkg.version), null, 2) + "\n");
17130
+ writeConfigFileSync(cfgPath, JSON.stringify(withStamp(cfg, pkg.version), null, 2) + "\n");
16679
17131
  } else {
16680
17132
  const from = cfg._squadrantVersion ?? "an earlier version";
16681
17133
  process.stderr.write(