squadrant 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -107,6 +107,66 @@ var init_config = __esm({
107
107
  }
108
108
  });
109
109
 
110
+ // packages/shared/dist/project-config.js
111
+ import fs2 from "fs";
112
+ import os2 from "os";
113
+ import path2 from "path";
114
+ function defaultRoot() {
115
+ return path2.join(os2.homedir(), ".config", "squadrant");
116
+ }
117
+ function projectConfigPath(name, root = defaultRoot()) {
118
+ return path2.join(root, "projects", `${name}.json`);
119
+ }
120
+ function loadProjectOverride(name, root = defaultRoot()) {
121
+ try {
122
+ return JSON.parse(fs2.readFileSync(projectConfigPath(name, root), "utf-8"));
123
+ } catch {
124
+ return {};
125
+ }
126
+ }
127
+ function deepMerge(base, patch) {
128
+ if (patch === null || typeof patch !== "object" || Array.isArray(patch))
129
+ return patch ?? base;
130
+ const out = { ...base };
131
+ for (const [k, v] of Object.entries(patch)) {
132
+ out[k] = deepMerge(out[k], v);
133
+ }
134
+ return out;
135
+ }
136
+ function saveProjectOverride(name, patch, root = defaultRoot()) {
137
+ const merged = deepMerge(loadProjectOverride(name, root), patch);
138
+ const file = projectConfigPath(name, root);
139
+ fs2.mkdirSync(path2.dirname(file), { recursive: true });
140
+ fs2.writeFileSync(file, JSON.stringify(merged, null, 2) + "\n");
141
+ }
142
+ function crewRank(tier) {
143
+ return CREW_RANK[tier];
144
+ }
145
+ function isQuieter(before, after) {
146
+ if (before.active && !after.active)
147
+ return { quieter: true, dim: "active" };
148
+ if (before.cap && !after.cap)
149
+ return { quieter: true, dim: "cap" };
150
+ if (crewRank(after.crew) < crewRank(before.crew))
151
+ return { quieter: true, dim: "crew" };
152
+ return { quieter: false, dim: null };
153
+ }
154
+ function resolveNotify(globalNotify, override) {
155
+ let n = { ...DEFAULT_NOTIFY };
156
+ if (globalNotify)
157
+ n = deepMerge(n, globalNotify);
158
+ if (override.telegram?.notify)
159
+ n = deepMerge(n, override.telegram.notify);
160
+ return n;
161
+ }
162
+ var DEFAULT_NOTIFY, CREW_RANK;
163
+ var init_project_config = __esm({
164
+ "packages/shared/dist/project-config.js"() {
165
+ DEFAULT_NOTIFY = { active: false, cap: true, crew: "alert_only" };
166
+ CREW_RANK = { none: 0, done_only: 1, alert_only: 2, all: 3 };
167
+ }
168
+ });
169
+
110
170
  // packages/shared/dist/effort.js
111
171
  function resolveEffort(config) {
112
172
  return config.defaults.effort ?? "balance";
@@ -155,22 +215,22 @@ function defaultCmuxConfigPath() {
155
215
  return join(homedir(), ".config", "cmux", "cmux.json");
156
216
  }
157
217
  function ensureSocketAutomation(opts = {}) {
158
- const path27 = opts.path ?? defaultCmuxConfigPath();
159
- if (!existsSync(path27)) {
160
- mkdirSync(dirname(path27), { recursive: true });
161
- writeFileSync(path27, MINIMAL_TEMPLATE);
162
- return { path: path27, changed: true, alreadySet: false };
218
+ const path29 = opts.path ?? defaultCmuxConfigPath();
219
+ if (!existsSync(path29)) {
220
+ mkdirSync(dirname(path29), { recursive: true });
221
+ writeFileSync(path29, MINIMAL_TEMPLATE);
222
+ return { path: path29, changed: true, alreadySet: false };
163
223
  }
164
- const text = readFileSync(path27, "utf-8");
224
+ const text = readFileSync(path29, "utf-8");
165
225
  const current = parse(text)?.automation?.socketControlMode;
166
226
  if (current === AUTOMATION_MODE) {
167
- return { path: path27, changed: false, alreadySet: true };
227
+ return { path: path29, changed: false, alreadySet: true };
168
228
  }
169
229
  const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
170
230
  formattingOptions: { insertSpaces: true, tabSize: 2 }
171
231
  });
172
- writeFileSync(path27, applyEdits(text, edits));
173
- return { path: path27, changed: true, alreadySet: false };
232
+ writeFileSync(path29, applyEdits(text, edits));
233
+ return { path: path29, changed: true, alreadySet: false };
174
234
  }
175
235
  var SOCKET_CONTROL_MODE_PATH, AUTOMATION_MODE, MINIMAL_TEMPLATE;
176
236
  var init_cmux_config = __esm({
@@ -320,9 +380,9 @@ import { dirname as dirname2, join as join4 } from "path";
320
380
  function defaultStatePath() {
321
381
  return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
322
382
  }
323
- function readState(path27) {
383
+ function readState(path29) {
324
384
  try {
325
- return JSON.parse(readFileSync4(path27, "utf-8"));
385
+ return JSON.parse(readFileSync4(path29, "utf-8"));
326
386
  } catch {
327
387
  return {};
328
388
  }
@@ -543,9 +603,9 @@ var init_config_version = __esm({
543
603
 
544
604
  // packages/shared/dist/lib/git-worktree.js
545
605
  import { execFileSync as execFileSync2 } from "child_process";
546
- import path2 from "path";
606
+ import path3 from "path";
547
607
  function worktreePath(repoRoot, worktreeDir, project, name) {
548
- return path2.resolve(repoRoot, worktreeDir, `${project}-${name}`);
608
+ return path3.resolve(repoRoot, worktreeDir, `${project}-${name}`);
549
609
  }
550
610
  function crewBranch(name) {
551
611
  return `crew/${name}`;
@@ -579,7 +639,7 @@ var init_git_worktree = __esm({
579
639
  });
580
640
 
581
641
  // packages/shared/dist/lib/resolve-text-input.js
582
- import fs2 from "fs";
642
+ import fs3 from "fs";
583
643
  async function readAllStdin() {
584
644
  const chunks = [];
585
645
  for await (const chunk of process.stdin) {
@@ -591,7 +651,7 @@ function flagName(label) {
591
651
  return label === "task" ? "--task-file" : "--message-file";
592
652
  }
593
653
  async function resolveTextInput(opts, deps) {
594
- const readFile6 = deps?.readFile ?? ((p) => fs2.readFileSync(p, "utf8"));
654
+ const readFile6 = deps?.readFile ?? ((p) => fs3.readFileSync(p, "utf8"));
595
655
  const readStdin3 = deps?.readStdin ?? readAllStdin;
596
656
  if (opts.filePath) {
597
657
  if (opts.filePath === "-") {
@@ -619,59 +679,59 @@ var init_resolve_text_input = __esm({
619
679
  });
620
680
 
621
681
  // packages/shared/dist/lib/runtime-sync.js
622
- import fs3 from "fs";
623
- import path3 from "path";
682
+ import fs4 from "fs";
683
+ import path4 from "path";
624
684
  function copyIfDifferent(src, dest) {
625
- if (fs3.existsSync(dest)) {
626
- if (fs3.readFileSync(src).equals(fs3.readFileSync(dest)))
685
+ if (fs4.existsSync(dest)) {
686
+ if (fs4.readFileSync(src).equals(fs4.readFileSync(dest)))
627
687
  return false;
628
688
  }
629
- fs3.copyFileSync(src, dest);
689
+ fs4.copyFileSync(src, dest);
630
690
  return true;
631
691
  }
632
692
  function mirrorDir(src, dest) {
633
- fs3.mkdirSync(dest, { recursive: true });
634
- const srcEntries = fs3.readdirSync(src, { withFileTypes: true });
693
+ fs4.mkdirSync(dest, { recursive: true });
694
+ const srcEntries = fs4.readdirSync(src, { withFileTypes: true });
635
695
  const srcNames = new Set(srcEntries.map((e) => e.name));
636
696
  for (const entry of srcEntries) {
637
- const srcPath = path3.join(src, entry.name);
638
- const destPath = path3.join(dest, entry.name);
697
+ const srcPath = path4.join(src, entry.name);
698
+ const destPath = path4.join(dest, entry.name);
639
699
  if (entry.isDirectory()) {
640
700
  mirrorDir(srcPath, destPath);
641
701
  } else {
642
702
  copyIfDifferent(srcPath, destPath);
643
703
  }
644
704
  }
645
- for (const entry of fs3.readdirSync(dest, { withFileTypes: true })) {
705
+ for (const entry of fs4.readdirSync(dest, { withFileTypes: true })) {
646
706
  if (!srcNames.has(entry.name)) {
647
- fs3.rmSync(path3.join(dest, entry.name), { recursive: true, force: true });
707
+ fs4.rmSync(path4.join(dest, entry.name), { recursive: true, force: true });
648
708
  }
649
709
  }
650
710
  }
651
711
  function mirrorFlat(src, dest, match, chmod) {
652
- fs3.mkdirSync(dest, { recursive: true });
653
- const matched = fs3.readdirSync(src, { withFileTypes: true }).filter((e) => e.isFile() && match.test(e.name)).map((e) => e.name);
712
+ fs4.mkdirSync(dest, { recursive: true });
713
+ const matched = fs4.readdirSync(src, { withFileTypes: true }).filter((e) => e.isFile() && match.test(e.name)).map((e) => e.name);
654
714
  const matchedSet = new Set(matched);
655
715
  for (const name of matched) {
656
- const destPath = path3.join(dest, name);
657
- const copied = copyIfDifferent(path3.join(src, name), destPath);
716
+ const destPath = path4.join(dest, name);
717
+ const copied = copyIfDifferent(path4.join(src, name), destPath);
658
718
  if (copied && chmod !== void 0)
659
- fs3.chmodSync(destPath, chmod);
719
+ fs4.chmodSync(destPath, chmod);
660
720
  }
661
- for (const entry of fs3.readdirSync(dest, { withFileTypes: true })) {
721
+ for (const entry of fs4.readdirSync(dest, { withFileTypes: true })) {
662
722
  if (!matchedSet.has(entry.name)) {
663
- fs3.rmSync(path3.join(dest, entry.name), { recursive: true, force: true });
723
+ fs4.rmSync(path4.join(dest, entry.name), { recursive: true, force: true });
664
724
  }
665
725
  }
666
726
  }
667
727
  function ensureRuntimeSynced(opts) {
668
728
  const targets = opts.targets ?? MANAGED_TARGETS;
669
729
  for (const t of targets) {
670
- const srcDir = path3.join(opts.sourceRoot, t.srcRel);
730
+ const srcDir = path4.join(opts.sourceRoot, t.srcRel);
671
731
  try {
672
- if (!fs3.existsSync(srcDir))
732
+ if (!fs4.existsSync(srcDir))
673
733
  continue;
674
- const destDir = path3.join(opts.runtimeRoot, t.name);
734
+ const destDir = path4.join(opts.runtimeRoot, t.name);
675
735
  if (t.mode === "tree") {
676
736
  mirrorDir(srcDir, destDir);
677
737
  } else {
@@ -735,8 +795,8 @@ var init_tool_compat = __esm({
735
795
  });
736
796
 
737
797
  // packages/shared/dist/lib/canonical-source.js
738
- import fs4 from "fs";
739
- import path4 from "path";
798
+ import fs5 from "fs";
799
+ import path5 from "path";
740
800
  function parseSkill(raw) {
741
801
  const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
742
802
  if (!match)
@@ -777,10 +837,10 @@ async function readSkills(driver, skillsDir) {
777
837
  function readRoleTemplates(opts) {
778
838
  if (!opts.pkgRoot)
779
839
  return "";
780
- const reader = opts.readFile ?? ((p) => fs4.readFileSync(p, "utf-8"));
840
+ const reader = opts.readFile ?? ((p) => fs5.readFileSync(p, "utf-8"));
781
841
  const sections = [];
782
842
  for (const { file, heading } of ROLE_TEMPLATES) {
783
- const full = path4.join(opts.pkgRoot, "templates", file);
843
+ const full = path5.join(opts.pkgRoot, "templates", file);
784
844
  let body = "";
785
845
  try {
786
846
  body = reader(full);
@@ -817,8 +877,8 @@ var init_canonical_source = __esm({
817
877
 
818
878
  // packages/shared/dist/lib/daily-logs.js
819
879
  import { execSync } from "child_process";
820
- import fs5 from "fs";
821
- import path5 from "path";
880
+ import fs6 from "fs";
881
+ import path6 from "path";
822
882
  import matter from "gray-matter";
823
883
  function iso(d) {
824
884
  return d.toISOString().slice(0, 10);
@@ -870,7 +930,7 @@ function getGitCommits(projectPath, dateStr) {
870
930
  }
871
931
  function getGitCommitsInRange(projectPath, since, until) {
872
932
  const resolved = resolveHome(projectPath);
873
- if (!fs5.existsSync(path5.join(resolved, ".git")))
933
+ if (!fs6.existsSync(path6.join(resolved, ".git")))
874
934
  return [];
875
935
  const untilArg = until ? ` --until="${until}"` : "";
876
936
  try {
@@ -884,7 +944,7 @@ function getGitCommitsInRange(projectPath, since, until) {
884
944
  }
885
945
  function getMergedPRsInRange(projectPath, since, until) {
886
946
  const resolved = resolveHome(projectPath);
887
- if (!fs5.existsSync(path5.join(resolved, ".git")))
947
+ if (!fs6.existsSync(path6.join(resolved, ".git")))
888
948
  return [];
889
949
  const untilArg = until ? ` --until="${until}"` : "";
890
950
  try {
@@ -924,10 +984,27 @@ var init_vault_layout = __esm({
924
984
  }
925
985
  });
926
986
 
987
+ // packages/shared/dist/daemon-keys.js
988
+ function isDaemonCachedKey(dottedKey) {
989
+ return DAEMON_CACHED_PREFIXES.some((p) => dottedKey === p || dottedKey.startsWith(p));
990
+ }
991
+ var DAEMON_CACHED_PREFIXES;
992
+ var init_daemon_keys = __esm({
993
+ "packages/shared/dist/daemon-keys.js"() {
994
+ DAEMON_CACHED_PREFIXES = [
995
+ "telegram.",
996
+ "defaults.taskTimeoutMs",
997
+ "defaults.cmuxEventsBridge",
998
+ "projects."
999
+ ];
1000
+ }
1001
+ });
1002
+
927
1003
  // packages/shared/dist/index.js
928
1004
  var init_dist = __esm({
929
1005
  "packages/shared/dist/index.js"() {
930
1006
  init_config();
1007
+ init_project_config();
931
1008
  init_effort();
932
1009
  init_runtime();
933
1010
  init_control();
@@ -947,6 +1024,7 @@ var init_dist = __esm({
947
1024
  init_canonical_source();
948
1025
  init_daily_logs();
949
1026
  init_vault_layout();
1027
+ init_daemon_keys();
950
1028
  }
951
1029
  });
952
1030
 
@@ -978,7 +1056,7 @@ var init_daemon = __esm({
978
1056
  });
979
1057
 
980
1058
  // packages/core/dist/mailbox.js
981
- import { promises as fs6 } from "fs";
1059
+ import { promises as fs7 } from "fs";
982
1060
  import { join as join5 } from "path";
983
1061
  import { randomUUID } from "crypto";
984
1062
  var init_mailbox = __esm({
@@ -1131,10 +1209,10 @@ function daemonEntryPath() {
1131
1209
  function xmlEscape(s) {
1132
1210
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1133
1211
  }
1134
- function sanitizePathForPlist(path27) {
1212
+ function sanitizePathForPlist(path29) {
1135
1213
  const seen = /* @__PURE__ */ new Set();
1136
1214
  const stable = [];
1137
- for (const p of path27.split(":")) {
1215
+ for (const p of path29.split(":")) {
1138
1216
  if (!p)
1139
1217
  continue;
1140
1218
  if (p.includes("/.claude/plugins/"))
@@ -1202,6 +1280,9 @@ function renderPlist(nodeBin, daemonEntry, pathEnv = "") {
1202
1280
  function programArgsBlock(nodeBin, daemonEntry) {
1203
1281
  return `<array><string>${xmlEscape(nodeBin)}</string><string>${xmlEscape(daemonEntry)}</string></array>`;
1204
1282
  }
1283
+ function kickstartArgv(target, plistChanged) {
1284
+ return plistChanged ? ["kickstart", "-k", target] : ["kickstart", target];
1285
+ }
1205
1286
  function daemonLockPath() {
1206
1287
  return join7(homedir4(), ".config", "squadrant", "daemon.lock");
1207
1288
  }
@@ -1423,35 +1504,35 @@ var init_start = __esm({
1423
1504
 
1424
1505
  // packages/core/dist/session-freshness.js
1425
1506
  import crypto from "crypto";
1426
- import fs7 from "fs";
1427
- import path6 from "path";
1507
+ import fs8 from "fs";
1508
+ import path7 from "path";
1428
1509
  function loadSessions(sessionsPath) {
1429
1510
  try {
1430
- return JSON.parse(fs7.readFileSync(sessionsPath, "utf-8"));
1511
+ return JSON.parse(fs8.readFileSync(sessionsPath, "utf-8"));
1431
1512
  } catch {
1432
1513
  return { workspaces: {} };
1433
1514
  }
1434
1515
  }
1435
1516
  function saveSessions(sessionsPath, sessions) {
1436
- const dir = path6.dirname(sessionsPath);
1437
- fs7.mkdirSync(dir, { recursive: true });
1438
- fs7.writeFileSync(sessionsPath, JSON.stringify(sessions, null, 2) + "\n");
1517
+ const dir = path7.dirname(sessionsPath);
1518
+ fs8.mkdirSync(dir, { recursive: true });
1519
+ fs8.writeFileSync(sessionsPath, JSON.stringify(sessions, null, 2) + "\n");
1439
1520
  }
1440
1521
  function computeTemplateHash(role, templatesDir) {
1441
1522
  const hash = crypto.createHash("sha256");
1442
- const roleFile = path6.join(templatesDir, `${role}.claude.md`);
1443
- const legacyRoleFile = path6.join(templatesDir, `${role}.CLAUDE.md`);
1444
- if (fs7.existsSync(roleFile)) {
1445
- hash.update(fs7.readFileSync(roleFile, "utf-8"));
1446
- } else if (fs7.existsSync(legacyRoleFile)) {
1447
- hash.update(fs7.readFileSync(legacyRoleFile, "utf-8"));
1523
+ const roleFile = path7.join(templatesDir, `${role}.claude.md`);
1524
+ const legacyRoleFile = path7.join(templatesDir, `${role}.CLAUDE.md`);
1525
+ if (fs8.existsSync(roleFile)) {
1526
+ hash.update(fs8.readFileSync(roleFile, "utf-8"));
1527
+ } else if (fs8.existsSync(legacyRoleFile)) {
1528
+ hash.update(fs8.readFileSync(legacyRoleFile, "utf-8"));
1448
1529
  }
1449
- const pluginSkillsDir = path6.join(templatesDir, "..", "plugin", "skills");
1450
- if (fs7.existsSync(pluginSkillsDir)) {
1451
- for (const skill of fs7.readdirSync(pluginSkillsDir).sort()) {
1452
- const skillFile = path6.join(pluginSkillsDir, skill, "SKILL.md");
1453
- if (fs7.existsSync(skillFile)) {
1454
- hash.update(fs7.readFileSync(skillFile, "utf-8"));
1530
+ const pluginSkillsDir = path7.join(templatesDir, "..", "plugin", "skills");
1531
+ if (fs8.existsSync(pluginSkillsDir)) {
1532
+ for (const skill of fs8.readdirSync(pluginSkillsDir).sort()) {
1533
+ const skillFile = path7.join(pluginSkillsDir, skill, "SKILL.md");
1534
+ if (fs8.existsSync(skillFile)) {
1535
+ hash.update(fs8.readFileSync(skillFile, "utf-8"));
1455
1536
  }
1456
1537
  }
1457
1538
  }
@@ -1571,6 +1652,25 @@ var init_crew_lifecycle = __esm({
1571
1652
  }
1572
1653
  });
1573
1654
 
1655
+ // packages/core/dist/telegram/bot-commands.js
1656
+ var BOT_COMMANDS;
1657
+ var init_bot_commands = __esm({
1658
+ "packages/core/dist/telegram/bot-commands.js"() {
1659
+ BOT_COMMANDS = [
1660
+ { command: "status", description: "squadrant status" },
1661
+ { command: "projects", description: "list registered projects" },
1662
+ { command: "crews", description: "list crews for a project" },
1663
+ { command: "launch", description: "launch a project's captain" },
1664
+ { command: "effort", description: "set effort: max | balance | low" },
1665
+ { command: "spawn", description: "spawn a crew (guided)" },
1666
+ { command: "notify", description: "notification panel for a project topic" },
1667
+ { command: "mute", description: "mute a project's topic" },
1668
+ { command: "unmute", description: "unmute a project's topic" },
1669
+ { command: "help", description: "list commands" }
1670
+ ];
1671
+ }
1672
+ });
1673
+
1574
1674
  // packages/core/dist/telegram/auth.js
1575
1675
  var init_auth = __esm({
1576
1676
  "packages/core/dist/telegram/auth.js"() {
@@ -1604,35 +1704,47 @@ var init_format = __esm({
1604
1704
  });
1605
1705
 
1606
1706
  // packages/core/dist/telegram/state.js
1607
- import fs8 from "fs";
1608
- import path7 from "path";
1707
+ import fs9 from "fs";
1708
+ import path8 from "path";
1609
1709
  function statePath(stateRoot) {
1610
- return path7.join(stateRoot, "telegram-state.json");
1710
+ return path8.join(stateRoot, "telegram-state.json");
1611
1711
  }
1612
1712
  function topicKey(project, scope = "project") {
1613
1713
  return `${project}::${scope}`;
1614
1714
  }
1615
1715
  function loadState(stateRoot) {
1616
1716
  try {
1617
- const raw = fs8.readFileSync(statePath(stateRoot), "utf-8");
1717
+ const raw = fs9.readFileSync(statePath(stateRoot), "utf-8");
1618
1718
  const data = JSON.parse(raw);
1619
- return {
1719
+ const result = {
1620
1720
  offset: typeof data.offset === "number" ? data.offset : 0,
1621
- topics: data.topics ?? {}
1721
+ topics: data.topics ?? {},
1722
+ notify: data.notify ?? {}
1622
1723
  };
1724
+ if (typeof data.lastUserId === "number")
1725
+ result.lastUserId = data.lastUserId;
1726
+ return result;
1623
1727
  } catch {
1624
- return { offset: 0, topics: {} };
1728
+ return { offset: 0, topics: {}, notify: {} };
1625
1729
  }
1626
1730
  }
1627
1731
  function saveState(stateRoot, s) {
1628
- fs8.mkdirSync(stateRoot, { recursive: true });
1629
- fs8.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
1732
+ fs9.mkdirSync(stateRoot, { recursive: true });
1733
+ fs9.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
1630
1734
  }
1631
1735
  function setTopic(stateRoot, project, topicId, scope = "project") {
1632
1736
  const s = loadState(stateRoot);
1633
1737
  s.topics[topicKey(project, scope)] = topicId;
1634
1738
  saveState(stateRoot, s);
1635
1739
  }
1740
+ function isNotifyActive(stateRoot, project) {
1741
+ return loadState(stateRoot).notify[project] === true;
1742
+ }
1743
+ function setNotify(stateRoot, project, active) {
1744
+ const s = loadState(stateRoot);
1745
+ s.notify[project] = active;
1746
+ saveState(stateRoot, s);
1747
+ }
1636
1748
  var init_state = __esm({
1637
1749
  "packages/core/dist/telegram/state.js"() {
1638
1750
  }
@@ -1664,15 +1776,29 @@ function createTelegramClient(opts) {
1664
1776
  getUpdates(offset, timeoutSec = 50) {
1665
1777
  return call("getUpdates", { offset, timeout: timeoutSec });
1666
1778
  },
1667
- async sendMessage(chatId, threadId, text) {
1779
+ async sendMessage(chatId, threadId, text, replyMarkup) {
1668
1780
  const body = { chat_id: chatId, text };
1669
1781
  if (threadId !== void 0)
1670
1782
  body.message_thread_id = threadId;
1783
+ if (replyMarkup !== void 0)
1784
+ body.reply_markup = replyMarkup;
1671
1785
  await call("sendMessage", body);
1672
1786
  },
1787
+ async answerCallbackQuery(callbackQueryId, text) {
1788
+ const body = { callback_query_id: callbackQueryId };
1789
+ if (text !== void 0)
1790
+ body.text = text;
1791
+ await call("answerCallbackQuery", body);
1792
+ },
1793
+ async editMessageReplyMarkup(chatId, messageId, replyMarkup) {
1794
+ await call("editMessageReplyMarkup", { chat_id: chatId, message_id: messageId, reply_markup: replyMarkup });
1795
+ },
1673
1796
  async createForumTopic(chatId, name) {
1674
1797
  const r = await call("createForumTopic", { chat_id: chatId, name });
1675
1798
  return r.message_thread_id;
1799
+ },
1800
+ async setMyCommands(commands) {
1801
+ await call("setMyCommands", { commands });
1676
1802
  }
1677
1803
  };
1678
1804
  }
@@ -1681,18 +1807,49 @@ var init_client = __esm({
1681
1807
  }
1682
1808
  });
1683
1809
 
1810
+ // packages/core/dist/telegram/panels.js
1811
+ var init_panels = __esm({
1812
+ "packages/core/dist/telegram/panels.js"() {
1813
+ }
1814
+ });
1815
+
1816
+ // packages/core/dist/telegram/tiers.js
1817
+ var DONE_ONLY, ALERTS;
1818
+ var init_tiers = __esm({
1819
+ "packages/core/dist/telegram/tiers.js"() {
1820
+ DONE_ONLY = /* @__PURE__ */ new Set(["task.done", "task.failed"]);
1821
+ ALERTS = /* @__PURE__ */ new Set([
1822
+ ...DONE_ONLY,
1823
+ "task.blocked",
1824
+ "task.approval.requested",
1825
+ "task.input.requested",
1826
+ "task.timeout"
1827
+ ]);
1828
+ }
1829
+ });
1830
+
1684
1831
  // packages/core/dist/telegram/bridge.js
1832
+ import os3 from "os";
1833
+ import path9 from "path";
1685
1834
  var init_bridge = __esm({
1686
1835
  "packages/core/dist/telegram/bridge.js"() {
1836
+ init_dist();
1687
1837
  init_auth();
1688
1838
  init_commands();
1689
1839
  init_format();
1840
+ init_panels();
1690
1841
  init_state();
1842
+ init_tiers();
1691
1843
  }
1692
1844
  });
1693
1845
 
1694
1846
  // packages/core/dist/telegram/setup.js
1695
- import fs9 from "fs";
1847
+ import fs10 from "fs";
1848
+ function resolveSetupGroup(existingSupergroupId, opts) {
1849
+ if (existingSupergroupId !== void 0 && !opts.redetect)
1850
+ return "reuse";
1851
+ return "detect";
1852
+ }
1696
1853
  async function detectGroupAndUser(client, opts = {}) {
1697
1854
  const timeoutMs = opts.timeoutMs ?? 6e4;
1698
1855
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -1715,7 +1872,7 @@ function writeTelegramConfig(configPath, opts) {
1715
1872
  let config;
1716
1873
  let raw = null;
1717
1874
  try {
1718
- raw = fs9.readFileSync(configPath, "utf-8");
1875
+ raw = fs10.readFileSync(configPath, "utf-8");
1719
1876
  } catch (err) {
1720
1877
  if (err.code !== "ENOENT") {
1721
1878
  throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);
@@ -1743,7 +1900,7 @@ function writeTelegramConfig(configPath, opts) {
1743
1900
  if (remoteControl !== void 0)
1744
1901
  next.remoteControl = remoteControl;
1745
1902
  config.telegram = next;
1746
- fs9.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1903
+ fs10.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1747
1904
  }
1748
1905
  var init_setup = __esm({
1749
1906
  "packages/core/dist/telegram/setup.js"() {
@@ -1753,6 +1910,7 @@ var init_setup = __esm({
1753
1910
  // packages/core/dist/telegram/index.js
1754
1911
  var init_telegram = __esm({
1755
1912
  "packages/core/dist/telegram/index.js"() {
1913
+ init_bot_commands();
1756
1914
  init_auth();
1757
1915
  init_commands();
1758
1916
  init_ensure_captain();
@@ -2308,13 +2466,13 @@ var init_notifiers = __esm({
2308
2466
  });
2309
2467
 
2310
2468
  // packages/workspaces/dist/workspaces/obsidian.js
2311
- import fs10 from "fs/promises";
2469
+ import fs11 from "fs/promises";
2312
2470
  import { existsSync as existsSync8 } from "fs";
2313
- import path8 from "path";
2471
+ import path10 from "path";
2314
2472
  function resolveInRoot(root, relative) {
2315
- const joined = path8.resolve(root, relative);
2316
- const normalized = path8.resolve(root) + path8.sep;
2317
- if (joined !== path8.resolve(root) && !joined.startsWith(normalized)) {
2473
+ const joined = path10.resolve(root, relative);
2474
+ const normalized = path10.resolve(root) + path10.sep;
2475
+ if (joined !== path10.resolve(root) && !joined.startsWith(normalized)) {
2318
2476
  throw new Error(`Path '${relative}' escapes workspace root`);
2319
2477
  }
2320
2478
  return joined;
@@ -2333,16 +2491,16 @@ function createObsidianDriver(scope) {
2333
2491
  };
2334
2492
  },
2335
2493
  async read(rel) {
2336
- return fs10.readFile(resolveInRoot(root, rel), "utf-8");
2494
+ return fs11.readFile(resolveInRoot(root, rel), "utf-8");
2337
2495
  },
2338
2496
  async write(rel, content) {
2339
2497
  const abs = resolveInRoot(root, rel);
2340
- await fs10.mkdir(path8.dirname(abs), { recursive: true });
2341
- await fs10.writeFile(abs, content);
2498
+ await fs11.mkdir(path10.dirname(abs), { recursive: true });
2499
+ await fs11.writeFile(abs, content);
2342
2500
  },
2343
2501
  async exists(rel) {
2344
2502
  try {
2345
- await fs10.access(resolveInRoot(root, rel));
2503
+ await fs11.access(resolveInRoot(root, rel));
2346
2504
  return true;
2347
2505
  } catch {
2348
2506
  return false;
@@ -2350,13 +2508,13 @@ function createObsidianDriver(scope) {
2350
2508
  },
2351
2509
  async list(rel) {
2352
2510
  try {
2353
- return await fs10.readdir(resolveInRoot(root, rel));
2511
+ return await fs11.readdir(resolveInRoot(root, rel));
2354
2512
  } catch {
2355
2513
  return [];
2356
2514
  }
2357
2515
  },
2358
2516
  async mkdir(rel) {
2359
- await fs10.mkdir(resolveInRoot(root, rel), { recursive: true });
2517
+ await fs11.mkdir(resolveInRoot(root, rel), { recursive: true });
2360
2518
  }
2361
2519
  };
2362
2520
  }
@@ -3025,8 +3183,8 @@ var init_registry4 = __esm({
3025
3183
  });
3026
3184
 
3027
3185
  // packages/agents/dist/drivers/launch-cmd.js
3028
- import fs11 from "fs";
3029
- import path9 from "path";
3186
+ import fs12 from "fs";
3187
+ import path11 from "path";
3030
3188
  function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir) {
3031
3189
  const driver = registry.getDriver(agentName);
3032
3190
  if (driver.name === "claude") {
@@ -3042,27 +3200,27 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
3042
3200
  cmd += ` --model ${model}`;
3043
3201
  }
3044
3202
  if (templatesDir) {
3045
- const roleFile2 = path9.join(templatesDir, `${role}.claude.md`);
3046
- const legacyRoleFile = path9.join(templatesDir, `${role}.CLAUDE.md`);
3047
- const actualRoleFile = fs11.existsSync(roleFile2) ? roleFile2 : fs11.existsSync(legacyRoleFile) ? legacyRoleFile : null;
3203
+ const roleFile2 = path11.join(templatesDir, `${role}.claude.md`);
3204
+ const legacyRoleFile = path11.join(templatesDir, `${role}.CLAUDE.md`);
3205
+ const actualRoleFile = fs12.existsSync(roleFile2) ? roleFile2 : fs12.existsSync(legacyRoleFile) ? legacyRoleFile : null;
3048
3206
  if (actualRoleFile) {
3049
3207
  cmd += ` --append-system-prompt-file ${actualRoleFile}`;
3050
3208
  }
3051
- const pluginDir = path9.join(templatesDir, "..", "plugin");
3052
- if (fs11.existsSync(pluginDir)) {
3209
+ const pluginDir = path11.join(templatesDir, "..", "plugin");
3210
+ if (fs12.existsSync(pluginDir)) {
3053
3211
  cmd += ` --plugin-dir ${pluginDir}`;
3054
3212
  }
3055
3213
  }
3056
3214
  return cmd;
3057
3215
  }
3058
- const roleFile = templatesDir ? path9.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
3216
+ const roleFile = templatesDir ? path11.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
3059
3217
  return driver.buildCommand({
3060
3218
  prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
3061
3219
  workdir: process.cwd(),
3062
3220
  role,
3063
3221
  model,
3064
3222
  autoApprove: true,
3065
- promptFile: roleFile && fs11.existsSync(roleFile) ? roleFile : void 0
3223
+ promptFile: roleFile && fs12.existsSync(roleFile) ? roleFile : void 0
3066
3224
  });
3067
3225
  }
3068
3226
  var init_launch_cmd = __esm({
@@ -3085,8 +3243,8 @@ var init_drivers = __esm({
3085
3243
 
3086
3244
  // packages/agents/dist/projection/cursor.js
3087
3245
  import { mkdir, readFile, writeFile } from "fs/promises";
3088
- import path10 from "path";
3089
- import os2 from "os";
3246
+ import path12 from "path";
3247
+ import os4 from "os";
3090
3248
  function renderMdc(source) {
3091
3249
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
3092
3250
 
@@ -3134,7 +3292,7 @@ function createCursorEmitter() {
3134
3292
  if (scope === "user") {
3135
3293
  return [
3136
3294
  {
3137
- path: path10.join(os2.homedir(), ".cursor/rules/squadrant-global.mdc"),
3295
+ path: path12.join(os4.homedir(), ".cursor/rules/squadrant-global.mdc"),
3138
3296
  shared: false,
3139
3297
  format: "mdc"
3140
3298
  }
@@ -3144,7 +3302,7 @@ function createCursorEmitter() {
3144
3302
  return [];
3145
3303
  return [
3146
3304
  {
3147
- path: path10.join(projectRoot, ".cursor/rules/squadrant.mdc"),
3305
+ path: path12.join(projectRoot, ".cursor/rules/squadrant.mdc"),
3148
3306
  shared: false,
3149
3307
  format: "mdc"
3150
3308
  }
@@ -3161,7 +3319,7 @@ function createCursorEmitter() {
3161
3319
  diff: buildDiff(existing, generated)
3162
3320
  };
3163
3321
  }
3164
- await mkdir(path10.dirname(dest.path), { recursive: true });
3322
+ await mkdir(path12.dirname(dest.path), { recursive: true });
3165
3323
  await writeFile(dest.path, generated, "utf-8");
3166
3324
  return {
3167
3325
  written: true,
@@ -3212,8 +3370,8 @@ var init_marker = __esm({
3212
3370
 
3213
3371
  // packages/agents/dist/projection/codex.js
3214
3372
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
3215
- import path11 from "path";
3216
- import os3 from "os";
3373
+ import path13 from "path";
3374
+ import os5 from "os";
3217
3375
  function renderMarkdown(source) {
3218
3376
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
3219
3377
 
@@ -3237,7 +3395,7 @@ function createCodexEmitter() {
3237
3395
  destinations(scope, projectRoot) {
3238
3396
  if (scope === "user") {
3239
3397
  return [{
3240
- path: path11.join(os3.homedir(), ".codex/AGENTS.md"),
3398
+ path: path13.join(os5.homedir(), ".codex/AGENTS.md"),
3241
3399
  shared: true,
3242
3400
  format: "markdown"
3243
3401
  }];
@@ -3245,7 +3403,7 @@ function createCodexEmitter() {
3245
3403
  if (!projectRoot)
3246
3404
  return [];
3247
3405
  return [{
3248
- path: path11.join(projectRoot, "AGENTS.md"),
3406
+ path: path13.join(projectRoot, "AGENTS.md"),
3249
3407
  shared: true,
3250
3408
  format: "markdown"
3251
3409
  }];
@@ -3266,7 +3424,7 @@ ${existing ?? ""}
3266
3424
  ${generated}`
3267
3425
  };
3268
3426
  }
3269
- await mkdir2(path11.dirname(dest.path), { recursive: true });
3427
+ await mkdir2(path13.dirname(dest.path), { recursive: true });
3270
3428
  await writeFile2(dest.path, generated, "utf-8");
3271
3429
  return {
3272
3430
  written: true,
@@ -3284,8 +3442,8 @@ var init_codex2 = __esm({
3284
3442
 
3285
3443
  // packages/agents/dist/projection/gemini.js
3286
3444
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
3287
- import path12 from "path";
3288
- import os4 from "os";
3445
+ import path14 from "path";
3446
+ import os6 from "os";
3289
3447
  function renderMarkdown2(source) {
3290
3448
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
3291
3449
 
@@ -3309,7 +3467,7 @@ function createGeminiEmitter() {
3309
3467
  destinations(scope, projectRoot) {
3310
3468
  if (scope === "user") {
3311
3469
  return [{
3312
- path: path12.join(os4.homedir(), ".gemini/GEMINI.md"),
3470
+ path: path14.join(os6.homedir(), ".gemini/GEMINI.md"),
3313
3471
  shared: true,
3314
3472
  format: "markdown"
3315
3473
  }];
@@ -3317,7 +3475,7 @@ function createGeminiEmitter() {
3317
3475
  if (!projectRoot)
3318
3476
  return [];
3319
3477
  return [{
3320
- path: path12.join(projectRoot, "GEMINI.md"),
3478
+ path: path14.join(projectRoot, "GEMINI.md"),
3321
3479
  shared: true,
3322
3480
  format: "markdown"
3323
3481
  }];
@@ -3338,7 +3496,7 @@ ${existing ?? ""}
3338
3496
  ${generated}`
3339
3497
  };
3340
3498
  }
3341
- await mkdir3(path12.dirname(dest.path), { recursive: true });
3499
+ await mkdir3(path14.dirname(dest.path), { recursive: true });
3342
3500
  await writeFile3(dest.path, generated, "utf-8");
3343
3501
  return {
3344
3502
  written: true,
@@ -3356,8 +3514,8 @@ var init_gemini2 = __esm({
3356
3514
 
3357
3515
  // packages/agents/dist/projection/opencode.js
3358
3516
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
3359
- import path13 from "path";
3360
- import os5 from "os";
3517
+ import path15 from "path";
3518
+ import os7 from "os";
3361
3519
  function renderMarkdown3(source) {
3362
3520
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
3363
3521
 
@@ -3381,7 +3539,7 @@ function createOpencodeEmitter() {
3381
3539
  destinations(scope, projectRoot) {
3382
3540
  if (scope === "user") {
3383
3541
  return [{
3384
- path: path13.join(os5.homedir(), ".config", "opencode", "AGENTS.md"),
3542
+ path: path15.join(os7.homedir(), ".config", "opencode", "AGENTS.md"),
3385
3543
  shared: true,
3386
3544
  format: "markdown"
3387
3545
  }];
@@ -3389,7 +3547,7 @@ function createOpencodeEmitter() {
3389
3547
  if (!projectRoot)
3390
3548
  return [];
3391
3549
  return [{
3392
- path: path13.join(projectRoot, "AGENTS.md"),
3550
+ path: path15.join(projectRoot, "AGENTS.md"),
3393
3551
  shared: true,
3394
3552
  format: "markdown"
3395
3553
  }];
@@ -3410,7 +3568,7 @@ ${existing ?? ""}
3410
3568
  ${generated}`
3411
3569
  };
3412
3570
  }
3413
- await mkdir4(path13.dirname(dest.path), { recursive: true });
3571
+ await mkdir4(path15.dirname(dest.path), { recursive: true });
3414
3572
  await writeFile4(dest.path, generated, "utf-8");
3415
3573
  return {
3416
3574
  written: true,
@@ -4275,8 +4433,8 @@ function resolveLastAssistantText(payload) {
4275
4433
  const derived = deriveTranscriptPath(p?.session_id, cwd);
4276
4434
  if (derived)
4277
4435
  candidates.push(derived);
4278
- for (const path27 of candidates) {
4279
- const text = readLastAssistantText(path27);
4436
+ for (const path29 of candidates) {
4437
+ const text = readLastAssistantText(path29);
4280
4438
  if (text != null)
4281
4439
  return text;
4282
4440
  }
@@ -4676,10 +4834,10 @@ var init_dist4 = __esm({
4676
4834
  init_dist();
4677
4835
  init_dist2();
4678
4836
  import { Command as Command28 } from "commander";
4679
- import { readFileSync as readFileSync11, existsSync as existsSync10, writeFileSync as writeFileSync9 } from "fs";
4837
+ import { readFileSync as readFileSync11, existsSync as existsSync11, writeFileSync as writeFileSync9 } from "fs";
4680
4838
  import { fileURLToPath as fileURLToPath6 } from "url";
4681
- import { dirname as dirname7, join as join22 } from "path";
4682
- import { homedir as homedir14 } from "os";
4839
+ import { dirname as dirname7, join as join23 } from "path";
4840
+ import { homedir as homedir15 } from "os";
4683
4841
 
4684
4842
  // packages/cli/src/commands/doctor.ts
4685
4843
  init_dist();
@@ -4688,9 +4846,9 @@ init_dist();
4688
4846
  init_dist3();
4689
4847
  import { Command } from "commander";
4690
4848
  import { execSync as execSync8 } from "child_process";
4691
- import fs12 from "fs";
4849
+ import fs13 from "fs";
4692
4850
  import { stat } from "fs/promises";
4693
- import path14 from "path";
4851
+ import path16 from "path";
4694
4852
  import chalk3 from "chalk";
4695
4853
 
4696
4854
  // packages/cli/src/commands/health-view.ts
@@ -4792,7 +4950,7 @@ function settingsHaveAgentTeams() {
4792
4950
  try {
4793
4951
  const home = process.env.HOME || "";
4794
4952
  const settings = JSON.parse(
4795
- fs12.readFileSync(`${home}/.claude/settings.json`, "utf-8")
4953
+ fs13.readFileSync(`${home}/.claude/settings.json`, "utf-8")
4796
4954
  );
4797
4955
  return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
4798
4956
  } catch {
@@ -4803,7 +4961,7 @@ function pluginInstalled(pluginKey) {
4803
4961
  try {
4804
4962
  const home = process.env.HOME || "";
4805
4963
  const plugins = JSON.parse(
4806
- fs12.readFileSync(
4964
+ fs13.readFileSync(
4807
4965
  `${home}/.claude/plugins/installed_plugins.json`,
4808
4966
  "utf-8"
4809
4967
  )
@@ -4835,7 +4993,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4835
4993
  const results = [];
4836
4994
  results.push(check("Claude Code installed", commandExists("claude")));
4837
4995
  results.push(check(`Claude Code version >= ${compatManifest.tools.claude.min}`, claudeVersionOk()));
4838
- results.push(check("Obsidian installed", commandExists("obsidian") || fs12.existsSync("/Applications/Obsidian.app")));
4996
+ results.push(check("Obsidian installed", commandExists("obsidian") || fs13.existsSync("/Applications/Obsidian.app")));
4839
4997
  results.push(check("Node.js >= 18", nodeVersionOk()));
4840
4998
  results.push(
4841
4999
  check(
@@ -4908,7 +5066,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4908
5066
  const emitter = projectionRegistry.get(name);
4909
5067
  const [userDest] = emitter.destinations("user");
4910
5068
  if (!userDest) continue;
4911
- const dir = path14.dirname(userDest.path);
5069
+ const dir = path16.dirname(userDest.path);
4912
5070
  let status;
4913
5071
  try {
4914
5072
  await stat(dir);
@@ -4921,7 +5079,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4921
5079
  results.push(
4922
5080
  check(
4923
5081
  "Squadrant config exists",
4924
- fs12.existsSync(
5082
+ fs13.existsSync(
4925
5083
  process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
4926
5084
  )
4927
5085
  )
@@ -4994,39 +5152,39 @@ init_dist();
4994
5152
  init_dist3();
4995
5153
  init_dist();
4996
5154
  import { Command as Command2 } from "commander";
4997
- import fs13 from "fs";
4998
- import path15 from "path";
4999
- import os6 from "os";
5155
+ import fs14 from "fs";
5156
+ import path17 from "path";
5157
+ import os8 from "os";
5000
5158
  import chalk4 from "chalk";
5001
5159
  function findPackageRoot() {
5002
- let dir = path15.dirname(new URL(import.meta.url).pathname);
5160
+ let dir = path17.dirname(new URL(import.meta.url).pathname);
5003
5161
  while (dir !== "/") {
5004
- if (fs13.existsSync(path15.join(dir, "package.json"))) return dir;
5005
- dir = path15.dirname(dir);
5162
+ if (fs14.existsSync(path17.join(dir, "package.json"))) return dir;
5163
+ dir = path17.dirname(dir);
5006
5164
  }
5007
5165
  return process.cwd();
5008
5166
  }
5009
5167
  function copyDirRecursive(src, dest) {
5010
- fs13.mkdirSync(dest, { recursive: true });
5011
- for (const entry of fs13.readdirSync(src, { withFileTypes: true })) {
5012
- const srcPath = path15.join(src, entry.name);
5013
- const destPath = path15.join(dest, entry.name);
5168
+ fs14.mkdirSync(dest, { recursive: true });
5169
+ for (const entry of fs14.readdirSync(src, { withFileTypes: true })) {
5170
+ const srcPath = path17.join(src, entry.name);
5171
+ const destPath = path17.join(dest, entry.name);
5014
5172
  if (entry.isDirectory()) {
5015
5173
  copyDirRecursive(srcPath, destPath);
5016
5174
  } else {
5017
- fs13.copyFileSync(srcPath, destPath);
5175
+ fs14.copyFileSync(srcPath, destPath);
5018
5176
  }
5019
5177
  }
5020
5178
  }
5021
5179
  var initCommand = new Command2("init").description("First-time setup: scaffold hub vault, scripts, and config").option("--hub <path>", "Hub vault path", "~/squadrant-hub").action((opts) => {
5022
5180
  const hubPath = resolveHome(opts.hub);
5023
5181
  const pkgRoot = findPackageRoot();
5024
- const configDir = path15.join(os6.homedir(), ".config", "squadrant");
5182
+ const configDir = path17.join(os8.homedir(), ".config", "squadrant");
5025
5183
  console.log(chalk4.bold("\nSquadrant Init\n"));
5026
5184
  const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
5027
5185
  try {
5028
- if (fs13.existsSync(DEFAULT_CONFIG_PATH)) {
5029
- const existing = JSON.parse(fs13.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
5186
+ if (fs14.existsSync(DEFAULT_CONFIG_PATH)) {
5187
+ const existing = JSON.parse(fs14.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
5030
5188
  const wsName = existing.workspace ?? "obsidian";
5031
5189
  registry.get(wsName);
5032
5190
  }
@@ -5034,7 +5192,7 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
5034
5192
  console.log(chalk4.red(` \u2718 ${err.message}`));
5035
5193
  return;
5036
5194
  }
5037
- if (fs13.existsSync(DEFAULT_CONFIG_PATH)) {
5195
+ if (fs14.existsSync(DEFAULT_CONFIG_PATH)) {
5038
5196
  console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
5039
5197
  } else {
5040
5198
  const config = getDefaultConfig();
@@ -5042,37 +5200,37 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
5042
5200
  saveConfig(config);
5043
5201
  console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
5044
5202
  }
5045
- const hubTemplate = path15.join(pkgRoot, "obsidian", "hub");
5046
- if (fs13.existsSync(hubPath)) {
5203
+ const hubTemplate = path17.join(pkgRoot, "obsidian", "hub");
5204
+ if (fs14.existsSync(hubPath)) {
5047
5205
  console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}, skipping`));
5048
- } else if (fs13.existsSync(hubTemplate)) {
5206
+ } else if (fs14.existsSync(hubTemplate)) {
5049
5207
  copyDirRecursive(hubTemplate, hubPath);
5050
5208
  console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
5051
5209
  } else {
5052
- fs13.mkdirSync(hubPath, { recursive: true });
5210
+ fs14.mkdirSync(hubPath, { recursive: true });
5053
5211
  console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty dir at ${hubPath}`));
5054
5212
  }
5055
- const hubDashboardSrc = path15.join(pkgRoot, "obsidian", "hub", "dashboard.md");
5056
- const hubDashboardDest = path15.join(hubPath, "dashboard.md");
5057
- if (fs13.existsSync(hubDashboardSrc)) {
5058
- fs13.copyFileSync(hubDashboardSrc, hubDashboardDest);
5213
+ const hubDashboardSrc = path17.join(pkgRoot, "obsidian", "hub", "dashboard.md");
5214
+ const hubDashboardDest = path17.join(hubPath, "dashboard.md");
5215
+ if (fs14.existsSync(hubDashboardSrc)) {
5216
+ fs14.copyFileSync(hubDashboardSrc, hubDashboardDest);
5059
5217
  console.log(chalk4.green(` \u2714 Dashboard page refreshed at ${hubDashboardDest}`));
5060
5218
  }
5061
- const projectsDir = path15.join(hubPath, "projects");
5062
- fs13.mkdirSync(projectsDir, { recursive: true });
5219
+ const projectsDir = path17.join(hubPath, "projects");
5220
+ fs14.mkdirSync(projectsDir, { recursive: true });
5063
5221
  ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
5064
5222
  console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
5065
- const settingsPath = path15.join(os6.homedir(), ".claude", "settings.json");
5223
+ const settingsPath = path17.join(os8.homedir(), ".claude", "settings.json");
5066
5224
  try {
5067
5225
  let settings = {};
5068
- if (fs13.existsSync(settingsPath)) {
5069
- settings = JSON.parse(fs13.readFileSync(settingsPath, "utf-8"));
5226
+ if (fs14.existsSync(settingsPath)) {
5227
+ settings = JSON.parse(fs14.readFileSync(settingsPath, "utf-8"));
5070
5228
  }
5071
5229
  const env = settings.env || {};
5072
5230
  if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
5073
5231
  settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
5074
- fs13.mkdirSync(path15.dirname(settingsPath), { recursive: true });
5075
- fs13.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5232
+ fs14.mkdirSync(path17.dirname(settingsPath), { recursive: true });
5233
+ fs14.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5076
5234
  console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
5077
5235
  } else {
5078
5236
  console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
@@ -5093,7 +5251,7 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
5093
5251
  console.log(chalk4.cyan(` ${hubPath}`));
5094
5252
  console.log("");
5095
5253
  console.log(" 4. Run " + chalk4.cyan("squadrant doctor") + " to verify setup\n");
5096
- if (!fs13.existsSync("/Applications/cmux.app")) {
5254
+ if (!fs14.existsSync("/Applications/cmux.app")) {
5097
5255
  console.log(chalk4.yellow(" \u26A0 cmux not found \u2014 download from https://cmux.dev\n"));
5098
5256
  }
5099
5257
  });
@@ -5101,26 +5259,70 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
5101
5259
  // packages/cli/src/commands/projects.ts
5102
5260
  init_dist();
5103
5261
  import { Command as Command3 } from "commander";
5104
- import fs14 from "fs";
5105
- import path16 from "path";
5262
+ import fs15 from "fs";
5263
+ import path18 from "path";
5106
5264
  import chalk5 from "chalk";
5265
+
5266
+ // packages/cli/src/control/restart-daemon.ts
5267
+ init_dist2();
5268
+ import { execFileSync as execFileSync6 } from "child_process";
5269
+ import { existsSync as existsSync9 } from "fs";
5270
+ import { homedir as homedir9 } from "os";
5271
+ import { join as join14 } from "path";
5272
+ var DEFAULT_SOCK_PATH = join14(homedir9(), ".config", "squadrant", "squadrant.sock");
5273
+ function defaultIsRunning() {
5274
+ return existsSync9(DEFAULT_SOCK_PATH);
5275
+ }
5276
+ function defaultRunKickstart() {
5277
+ const uid = process.getuid?.() ?? 0;
5278
+ const target = `gui/${uid}/${LABEL}`;
5279
+ if (tryAcquireDaemonLock()) {
5280
+ try {
5281
+ execFileSync6("launchctl", kickstartArgv(target, true), { stdio: "ignore" });
5282
+ } finally {
5283
+ releaseDaemonLock();
5284
+ }
5285
+ }
5286
+ }
5287
+ function restartDaemonIfRunning(opts) {
5288
+ const env = opts.env ?? process.env;
5289
+ if (env["VITEST"] || opts.noRestart) return "skipped-opt-out";
5290
+ const isRunning = opts.isRunning ?? defaultIsRunning;
5291
+ if (!isRunning()) return "skipped-not-running";
5292
+ const log = opts.log ?? console.log;
5293
+ log(`\u21BB restarting daemon to apply ${opts.reason}\u2026`);
5294
+ const runKickstart = opts.runKickstart ?? defaultRunKickstart;
5295
+ runKickstart();
5296
+ return "restarted";
5297
+ }
5298
+
5299
+ // packages/cli/src/commands/projects.ts
5300
+ function restartAfterProjectsAdd(opts) {
5301
+ const doRestart = opts.doRestart ?? restartDaemonIfRunning;
5302
+ const outcome = doRestart({ reason: "project registration", noRestart: opts.noRestart });
5303
+ if (outcome === "skipped-not-running") {
5304
+ console.log(chalk5.dim(" (daemon not running \u2014 change applies on next start)"));
5305
+ } else if (outcome === "skipped-opt-out") {
5306
+ console.log(chalk5.dim(" (run 'squadrant heal daemon' to apply)"));
5307
+ }
5308
+ }
5107
5309
  function findPackageRoot2() {
5108
- let dir = path16.dirname(new URL(import.meta.url).pathname);
5310
+ let dir = path18.dirname(new URL(import.meta.url).pathname);
5109
5311
  while (dir !== "/") {
5110
- if (fs14.existsSync(path16.join(dir, "package.json"))) return dir;
5111
- dir = path16.dirname(dir);
5312
+ if (fs15.existsSync(path18.join(dir, "package.json"))) return dir;
5313
+ dir = path18.dirname(dir);
5112
5314
  }
5113
5315
  return process.cwd();
5114
5316
  }
5115
5317
  function copyDirRecursive2(src, dest) {
5116
- fs14.mkdirSync(dest, { recursive: true });
5117
- for (const entry of fs14.readdirSync(src, { withFileTypes: true })) {
5118
- const srcPath = path16.join(src, entry.name);
5119
- const destPath = path16.join(dest, entry.name);
5318
+ fs15.mkdirSync(dest, { recursive: true });
5319
+ for (const entry of fs15.readdirSync(src, { withFileTypes: true })) {
5320
+ const srcPath = path18.join(src, entry.name);
5321
+ const destPath = path18.join(dest, entry.name);
5120
5322
  if (entry.isDirectory()) {
5121
5323
  copyDirRecursive2(srcPath, destPath);
5122
5324
  } else {
5123
- fs14.copyFileSync(srcPath, destPath);
5325
+ fs15.copyFileSync(srcPath, destPath);
5124
5326
  }
5125
5327
  }
5126
5328
  }
@@ -5147,7 +5349,7 @@ var listCmd = new Command3("list").description("List registered projects").actio
5147
5349
  }
5148
5350
  console.log("");
5149
5351
  });
5150
- var addCmd = new Command3("add").description("Register a project").argument("<name>", "Project name").argument("<path>", "Path to project directory").option("--captain <name>", "Captain workspace name (default: <project>-captain)").option("--spoke <path>", "Spoke vault path (default: ~/squadrant-hub/spokes/<name>)").option("--group <name>", "Project group name (siblings share context)").option("--group-role <role>", "Role within the group (auto-set to 'primary' if first in group)").action((name, projectPath, opts) => {
5352
+ var addCmd = new Command3("add").description("Register a project").argument("<name>", "Project name").argument("<path>", "Path to project directory").option("--captain <name>", "Captain workspace name (default: <project>-captain)").option("--spoke <path>", "Spoke vault path (default: ~/squadrant-hub/spokes/<name>)").option("--group <name>", "Project group name (siblings share context)").option("--group-role <role>", "Role within the group (auto-set to 'primary' if first in group)").option("--no-restart", "skip daemon restart after registration").action((name, projectPath, opts) => {
5151
5353
  const config = loadConfig();
5152
5354
  if (config.projects[name]) {
5153
5355
  console.log(chalk5.yellow(`
@@ -5156,7 +5358,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
5156
5358
  process.exit(1);
5157
5359
  }
5158
5360
  const resolvedPath = resolveHome(projectPath);
5159
- if (!fs14.existsSync(path16.join(resolvedPath, ".git"))) {
5361
+ if (!fs15.existsSync(path18.join(resolvedPath, ".git"))) {
5160
5362
  console.log(chalk5.yellow(`
5161
5363
  \u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
5162
5364
  `));
@@ -5209,7 +5411,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
5209
5411
  \u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
5210
5412
  }
5211
5413
  }
5212
- const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path16.join(config.hubVault, "spokes", name);
5414
+ const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path18.join(config.hubVault, "spokes", name);
5213
5415
  const project = {
5214
5416
  path: resolvedPath,
5215
5417
  captainName,
@@ -5222,21 +5424,22 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
5222
5424
  saveConfig(config);
5223
5425
  console.log(chalk5.green(`
5224
5426
  \u2714 Project '${name}' registered`));
5427
+ restartAfterProjectsAdd({ noRestart: opts.restart === false });
5225
5428
  const pkgRoot = findPackageRoot2();
5226
- const spokeTemplate = path16.join(pkgRoot, "obsidian", "spoke");
5227
- if (fs14.existsSync(spokeVault)) {
5429
+ const spokeTemplate = path18.join(pkgRoot, "obsidian", "spoke");
5430
+ if (fs15.existsSync(spokeVault)) {
5228
5431
  console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
5229
- } else if (fs14.existsSync(spokeTemplate)) {
5432
+ } else if (fs15.existsSync(spokeTemplate)) {
5230
5433
  copyDirRecursive2(spokeTemplate, spokeVault);
5231
- const statusPath = path16.join(spokeVault, "status.md");
5232
- if (fs14.existsSync(statusPath)) {
5233
- const content = fs14.readFileSync(statusPath, "utf-8");
5434
+ const statusPath = path18.join(spokeVault, "status.md");
5435
+ if (fs15.existsSync(statusPath)) {
5436
+ const content = fs15.readFileSync(statusPath, "utf-8");
5234
5437
  const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
5235
- fs14.writeFileSync(statusPath, updated);
5438
+ fs15.writeFileSync(statusPath, updated);
5236
5439
  }
5237
5440
  console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
5238
5441
  } else {
5239
- fs14.mkdirSync(spokeVault, { recursive: true });
5442
+ fs15.mkdirSync(spokeVault, { recursive: true });
5240
5443
  console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
5241
5444
  }
5242
5445
  console.log("");
@@ -5337,10 +5540,10 @@ init_dist4();
5337
5540
  init_dist();
5338
5541
  import { Command as Command5 } from "commander";
5339
5542
  import { execSync as execSync9 } from "child_process";
5340
- import path17 from "path";
5341
- import os7 from "os";
5543
+ import path19 from "path";
5544
+ import os9 from "os";
5342
5545
  import chalk7 from "chalk";
5343
- var TEMPLATES_DIR = path17.join(os7.homedir(), ".config", "squadrant", "templates");
5546
+ var TEMPLATES_DIR = path19.join(os9.homedir(), ".config", "squadrant", "templates");
5344
5547
  var TASK_PROMPTS = {
5345
5548
  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.",
5346
5549
  "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.",
@@ -5373,7 +5576,7 @@ async function runCommandSpawn(input) {
5373
5576
  if (!agent) {
5374
5577
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
5375
5578
  }
5376
- const promptFile = path17.join(TEMPLATES_DIR, `command.${agent.templateSuffix}.md`);
5579
+ const promptFile = path19.join(TEMPLATES_DIR, `command.${agent.templateSuffix}.md`);
5377
5580
  const cliCommand = agent.buildCommand({
5378
5581
  prompt,
5379
5582
  workdir: process.cwd(),
@@ -5400,9 +5603,9 @@ var commandCommand = new Command5("command").description("Spawn a one-shot Comma
5400
5603
  init_dist();
5401
5604
  init_dist();
5402
5605
  import { Command as Command9 } from "commander";
5403
- import fs15 from "fs";
5404
- import path18 from "path";
5405
- import os8 from "os";
5606
+ import fs16 from "fs";
5607
+ import path20 from "path";
5608
+ import os10 from "os";
5406
5609
  import chalk9 from "chalk";
5407
5610
 
5408
5611
  // packages/cli/src/control/crew-routing.ts
@@ -5435,8 +5638,8 @@ init_dist4();
5435
5638
  import { Command as Command8 } from "commander";
5436
5639
  import { createConnection as createConnection3 } from "net";
5437
5640
  import { randomUUID as randomUUID3 } from "crypto";
5438
- import { homedir as homedir10 } from "os";
5439
- import { join as join15 } from "path";
5641
+ import { homedir as homedir11 } from "os";
5642
+ import { join as join16 } from "path";
5440
5643
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
5441
5644
 
5442
5645
  // packages/cli/src/commands/crew-output.ts
@@ -5495,11 +5698,11 @@ init_dist2();
5495
5698
  import { Command as Command6 } from "commander";
5496
5699
  import chalk8 from "chalk";
5497
5700
  import { createConnection as createConnection2 } from "net";
5498
- import { homedir as homedir9 } from "os";
5499
- import { join as join14 } from "path";
5701
+ import { homedir as homedir10 } from "os";
5702
+ import { join as join15 } from "path";
5500
5703
  import { createInterface } from "readline";
5501
5704
  function socketPath() {
5502
- return process.env.SQUADRANTD_SOCK ?? join14(homedir9(), ".config", "squadrant", "squadrant.sock");
5705
+ return process.env.SQUADRANTD_SOCK ?? join15(homedir10(), ".config", "squadrant", "squadrant.sock");
5503
5706
  }
5504
5707
  function rule(width, ch = "\u2500") {
5505
5708
  return ch.repeat(Math.max(0, width));
@@ -5735,7 +5938,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
5735
5938
  });
5736
5939
 
5737
5940
  // packages/cli/src/commands/crew-control.ts
5738
- var SOCK2 = join15(homedir10(), ".config", "squadrant", "squadrant.sock");
5941
+ var SOCK2 = join16(homedir11(), ".config", "squadrant", "squadrant.sock");
5739
5942
  var CODEX_FIRST_TURN_DELAY_MS = 1500;
5740
5943
  async function sendCodexFirstTurn(taskId, text) {
5741
5944
  await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
@@ -5840,9 +6043,9 @@ function buildSignalRequest(signal, o) {
5840
6043
  return { kind: "event", project, event };
5841
6044
  }
5842
6045
  function defaultWriteResult(id, payload) {
5843
- const dir = join15(homedir10(), ".config", "squadrant", "state", "_results");
6046
+ const dir = join16(homedir11(), ".config", "squadrant", "state", "_results");
5844
6047
  mkdirSync6(dir, { recursive: true });
5845
- const file = join15(dir, `${id}.txt`);
6048
+ const file = join16(dir, `${id}.txt`);
5846
6049
  writeFileSync7(file, payload);
5847
6050
  return file;
5848
6051
  }
@@ -5943,7 +6146,7 @@ addControlPlaneCrewCommands(crewControlCommand);
5943
6146
  // packages/cli/src/lib/per-crew-settings.ts
5944
6147
  init_dist4();
5945
6148
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
5946
- import { join as join16 } from "path";
6149
+ import { join as join17 } from "path";
5947
6150
  var CREW_PERMISSION_ALLOWLIST = [
5948
6151
  // git — read + safe mutations (reset/clean/config intentionally excluded)
5949
6152
  "Bash(git status:*)",
@@ -6031,9 +6234,9 @@ function mergeCrewPermissions(settings) {
6031
6234
  return next;
6032
6235
  }
6033
6236
  function writePerCrewSettingsLocal(o) {
6034
- const dir = join16(o.projectCwd, ".claude");
6237
+ const dir = join17(o.projectCwd, ".claude");
6035
6238
  mkdirSync7(dir, { recursive: true });
6036
- const file = join16(dir, "settings.local.json");
6239
+ const file = join17(dir, "settings.local.json");
6037
6240
  let existing = {};
6038
6241
  try {
6039
6242
  const raw = readFileSync9(file, "utf-8");
@@ -6046,9 +6249,9 @@ function writePerCrewSettingsLocal(o) {
6046
6249
  return file;
6047
6250
  }
6048
6251
  function writePerCrewOpencodeConfig(o) {
6049
- const dir = join16(o.stateRoot, o.project, o.taskId);
6252
+ const dir = join17(o.stateRoot, o.project, o.taskId);
6050
6253
  mkdirSync7(dir, { recursive: true });
6051
- const file = join16(dir, "opencode.json");
6254
+ const file = join17(dir, "opencode.json");
6052
6255
  const config = {
6053
6256
  permission: {
6054
6257
  read: "allow",
@@ -6069,7 +6272,7 @@ function writePerCrewOpencodeConfig(o) {
6069
6272
 
6070
6273
  // packages/cli/src/commands/crew.ts
6071
6274
  init_dist2();
6072
- var TEMPLATES_DIR2 = path18.join(os8.homedir(), ".config", "squadrant", "templates");
6275
+ var TEMPLATES_DIR2 = path20.join(os10.homedir(), ".config", "squadrant", "templates");
6073
6276
  async function runCrewSpawn(input) {
6074
6277
  const config = loadConfig();
6075
6278
  const proj = config.projects[input.project];
@@ -6117,8 +6320,8 @@ async function runCrewSpawn(input) {
6117
6320
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
6118
6321
  }
6119
6322
  if (agentName === "codex") {
6120
- const codexRoleFile = path18.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
6121
- const roleInstructions = fs15.existsSync(codexRoleFile) ? fs15.readFileSync(codexRoleFile, "utf8") : void 0;
6323
+ const codexRoleFile = path20.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
6324
+ const roleInstructions = fs16.existsSync(codexRoleFile) ? fs16.readFileSync(codexRoleFile, "utf8") : void 0;
6122
6325
  return runCodexInteractiveSpawn({
6123
6326
  project: input.project,
6124
6327
  task: input.task,
@@ -6131,7 +6334,7 @@ async function runCrewSpawn(input) {
6131
6334
  roleInstructions
6132
6335
  });
6133
6336
  }
6134
- const promptFile = path18.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
6337
+ const promptFile = path20.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
6135
6338
  const interactive = agent.name === "claude" || agent.name === "opencode";
6136
6339
  const crewRole = config.defaults.roles?.crew;
6137
6340
  const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
@@ -6188,7 +6391,7 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
6188
6391
  });
6189
6392
  const rec = await squadrantdCall(req);
6190
6393
  const opencodeConfigPath = writePerCrewOpencodeConfig({
6191
- stateRoot: path18.join(os8.homedir(), ".config", "squadrant", "state"),
6394
+ stateRoot: path20.join(os10.homedir(), ".config", "squadrant", "state"),
6192
6395
  project: input.project,
6193
6396
  taskId: rec.id,
6194
6397
  // CP3 opt-in: --approval gates bash so the captain approves shell commands.
@@ -6428,11 +6631,11 @@ init_dist3();
6428
6631
  init_dist();
6429
6632
  init_dist();
6430
6633
  import { Command as Command10 } from "commander";
6431
- import fs16 from "fs";
6432
- import path19 from "path";
6433
- import os9 from "os";
6634
+ import fs17 from "fs";
6635
+ import path21 from "path";
6636
+ import os11 from "os";
6434
6637
  import chalk10 from "chalk";
6435
- var TEMPLATES_DIR3 = path19.join(os9.homedir(), ".config", "squadrant", "templates");
6638
+ var TEMPLATES_DIR3 = path21.join(os11.homedir(), ".config", "squadrant", "templates");
6436
6639
  var SIDE_ROLES = ["research", "debug"];
6437
6640
  function shellQuote2(p) {
6438
6641
  return "'" + p.replace(/'/g, "'\\''") + "'";
@@ -6523,7 +6726,7 @@ async function runSideSpawn(input) {
6523
6726
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
6524
6727
  }
6525
6728
  const sideModel = sideRole?.model;
6526
- const promptFile = path19.join(
6729
+ const promptFile = path21.join(
6527
6730
  TEMPLATES_DIR3,
6528
6731
  `side.${input.role}.${agent.templateSuffix}.md`
6529
6732
  );
@@ -6534,7 +6737,7 @@ async function runSideSpawn(input) {
6534
6737
  prompt: input.topic,
6535
6738
  workdir: spawnCwd,
6536
6739
  role: "side",
6537
- promptFile: fs16.existsSync(promptFile) ? promptFile : void 0,
6740
+ promptFile: fs17.existsSync(promptFile) ? promptFile : void 0,
6538
6741
  interactive: true,
6539
6742
  permissionMode: config.defaults.permissions?.crew ?? "auto",
6540
6743
  ...sideModel ? { model: sideModel } : {}
@@ -6591,7 +6794,7 @@ async function runSideClose(project, name) {
6591
6794
  project,
6592
6795
  name
6593
6796
  );
6594
- if (fs16.existsSync(wtPath)) {
6797
+ if (fs17.existsSync(wtPath)) {
6595
6798
  try {
6596
6799
  removeWorktree(proj.path, wtPath);
6597
6800
  } catch (e) {
@@ -6679,8 +6882,8 @@ init_dist();
6679
6882
  init_dist3();
6680
6883
  import { Command as Command11 } from "commander";
6681
6884
  import { execSync as execSync10 } from "child_process";
6682
- import { homedir as homedir12 } from "os";
6683
- import { join as join18 } from "path";
6885
+ import { homedir as homedir13 } from "os";
6886
+ import { join as join19 } from "path";
6684
6887
  import chalk12 from "chalk";
6685
6888
 
6686
6889
  // packages/web/dist/read-status.js
@@ -6819,8 +7022,8 @@ function renderDashboard(rows, opts) {
6819
7022
 
6820
7023
  // packages/web/dist/sync-hub.js
6821
7024
  init_dist();
6822
- import fs17 from "fs";
6823
- import path20 from "path";
7025
+ import fs18 from "fs";
7026
+ import path22 from "path";
6824
7027
  function buildMirrorMarkdown(s) {
6825
7028
  const fenced = "```";
6826
7029
  return [
@@ -6846,15 +7049,15 @@ function buildMirrorMarkdown(s) {
6846
7049
  function syncHub(deps) {
6847
7050
  if (!deps.config.hubVault)
6848
7051
  return [];
6849
- const writeFile5 = deps.writeFile ?? ((p, c) => fs17.writeFileSync(p, c));
6850
- const mkdir5 = deps.mkdir ?? ((p) => fs17.mkdirSync(p, { recursive: true }));
6851
- const projectsDir = path20.join(resolveHome(deps.config.hubVault), "projects");
7052
+ const writeFile5 = deps.writeFile ?? ((p, c) => fs18.writeFileSync(p, c));
7053
+ const mkdir5 = deps.mkdir ?? ((p) => fs18.mkdirSync(p, { recursive: true }));
7054
+ const projectsDir = path22.join(resolveHome(deps.config.hubVault), "projects");
6852
7055
  mkdir5(projectsDir);
6853
7056
  const out = [];
6854
7057
  for (const s of deps.statuses) {
6855
7058
  if (s.state === "unknown")
6856
7059
  continue;
6857
- const hubPath = path20.join(projectsDir, `${s.project}.md`);
7060
+ const hubPath = path22.join(projectsDir, `${s.project}.md`);
6858
7061
  try {
6859
7062
  writeFile5(hubPath, buildMirrorMarkdown(s));
6860
7063
  out.push({ project: s.project, hubPath });
@@ -6876,9 +7079,9 @@ function mergeSnapshot(daemon, external, now) {
6876
7079
  // packages/web/dist/probes.js
6877
7080
  init_dist();
6878
7081
  init_dist();
6879
- import { join as join17 } from "path";
6880
- import { homedir as homedir11 } from "os";
6881
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
7082
+ import { join as join18 } from "path";
7083
+ import { homedir as homedir12 } from "os";
7084
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
6882
7085
  import { execFile as execFile2 } from "child_process";
6883
7086
  var DEFAULT_TIMEOUT_MS = 2e3;
6884
7087
  var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
@@ -6914,7 +7117,7 @@ function vaultProbe(run, dir) {
6914
7117
  return { state: "unknown", detail: "no vault configured" };
6915
7118
  if (!run.pathExists(dir))
6916
7119
  return { state: "gone", detail: "vault directory missing" };
6917
- if (!run.pathExists(join17(dir, ".obsidian")))
7120
+ if (!run.pathExists(join18(dir, ".obsidian")))
6918
7121
  return { state: "gone", detail: "no .obsidian/ (not a vault)" };
6919
7122
  return { state: "alive" };
6920
7123
  } catch {
@@ -6982,10 +7185,10 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
6982
7185
  const sessions = probeSessions(run);
6983
7186
  return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
6984
7187
  }
6985
- var SESSIONS_PATH = join17(homedir11(), ".config", "squadrant", "sessions.json");
7188
+ var SESSIONS_PATH = join18(homedir12(), ".config", "squadrant", "sessions.json");
6986
7189
  function onPath(cli) {
6987
7190
  const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
6988
- return dirs.some((d) => existsSync9(join17(d, cli)));
7191
+ return dirs.some((d) => existsSync10(join18(d, cli)));
6989
7192
  }
6990
7193
  function readSessionsHashes() {
6991
7194
  const raw = JSON.parse(readFileSync10(SESSIONS_PATH, "utf-8"));
@@ -7002,7 +7205,7 @@ function defaultProbeRunners() {
7002
7205
  }
7003
7206
  }),
7004
7207
  probeOnPath: async (cli) => onPath(cli),
7005
- pathExists: (p) => existsSync9(p),
7208
+ pathExists: (p) => existsSync10(p),
7006
7209
  loadConfig: () => loadConfig(),
7007
7210
  loadSessionsHashes: () => readSessionsHashes()
7008
7211
  };
@@ -7655,7 +7858,7 @@ async function startWebServer(opts) {
7655
7858
 
7656
7859
  // packages/cli/src/commands/dashboard.ts
7657
7860
  init_dist();
7658
- var SOCK3 = join18(homedir12(), ".config", "squadrant", "squadrant.sock");
7861
+ var SOCK3 = join19(homedir13(), ".config", "squadrant", "squadrant.sock");
7659
7862
  function detectCurrentWorkspace2() {
7660
7863
  const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
7661
7864
  const match = out.match(/workspace:\d+/);
@@ -7743,13 +7946,13 @@ init_dist3();
7743
7946
  init_dist2();
7744
7947
  import { Command as Command12 } from "commander";
7745
7948
  import { execSync as execSync11 } from "child_process";
7746
- import fs18 from "fs";
7747
- import path21 from "path";
7748
- import os10 from "os";
7949
+ import fs19 from "fs";
7950
+ import path23 from "path";
7951
+ import os12 from "os";
7749
7952
  import chalk13 from "chalk";
7750
7953
  var CMUX_APP = "/Applications/cmux.app";
7751
- var TEMPLATES_DIR4 = path21.join(os10.homedir(), ".config", "squadrant", "templates");
7752
- var SESSIONS_PATH2 = path21.join(os10.homedir(), ".config", "squadrant", "sessions.json");
7954
+ var TEMPLATES_DIR4 = path23.join(os12.homedir(), ".config", "squadrant", "templates");
7955
+ var SESSIONS_PATH2 = path23.join(os12.homedir(), ".config", "squadrant", "sessions.json");
7753
7956
  function ensureCmuxReady() {
7754
7957
  if (isInsideCmux()) return;
7755
7958
  console.log(chalk13.yellow("\n Not running inside cmux. Opening cmux app...\n"));
@@ -7856,12 +8059,12 @@ var launchCommand = new Command12("launch").description(
7856
8059
  }
7857
8060
  if (opts.all) {
7858
8061
  const hubPath = resolveHome(config.hubVault);
7859
- fs18.mkdirSync(hubPath, { recursive: true });
8062
+ fs19.mkdirSync(hubPath, { recursive: true });
7860
8063
  console.log(chalk13.bold("\nLaunching all captain workspaces\n"));
7861
8064
  for (const [name, proj] of Object.entries(config.projects)) {
7862
8065
  const projPath = resolveHome(proj.path);
7863
8066
  const spokePath = resolveHome(proj.spokeVault);
7864
- if (!fs18.existsSync(spokePath)) {
8067
+ if (!fs19.existsSync(spokePath)) {
7865
8068
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
7866
8069
  await ensureSpokeLayout(spokeDriver);
7867
8070
  console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -7892,7 +8095,7 @@ var launchCommand = new Command12("launch").description(
7892
8095
  const proj = config.projects[project];
7893
8096
  const projPath = resolveHome(proj.path);
7894
8097
  const spokePath = resolveHome(proj.spokeVault);
7895
- if (!fs18.existsSync(spokePath)) {
8098
+ if (!fs19.existsSync(spokePath)) {
7896
8099
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
7897
8100
  await ensureSpokeLayout(spokeDriver);
7898
8101
  console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -8027,24 +8230,24 @@ Shutting down captain workspace for '${project}'...
8027
8230
  // packages/cli/src/commands/feedback.ts
8028
8231
  init_dist();
8029
8232
  import { Command as Command14 } from "commander";
8030
- import fs19 from "fs";
8031
- import os11 from "os";
8032
- import path22 from "path";
8233
+ import fs20 from "fs";
8234
+ import os13 from "os";
8235
+ import path24 from "path";
8033
8236
  import { fileURLToPath as fileURLToPath3 } from "url";
8034
8237
  import { execSync as execSync12 } from "child_process";
8035
8238
  import chalk15 from "chalk";
8036
8239
  var REPO_URL = "https://github.com/tu11aa/squadrant";
8037
8240
  function readPkgVersion() {
8038
8241
  try {
8039
- const pkgPath = path22.join(path22.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
8040
- return JSON.parse(fs19.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
8242
+ const pkgPath = path24.join(path24.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
8243
+ return JSON.parse(fs20.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
8041
8244
  } catch {
8042
8245
  return "unknown";
8043
8246
  }
8044
8247
  }
8045
8248
  function readMetrics(metricsPath) {
8046
8249
  try {
8047
- return JSON.parse(fs19.readFileSync(metricsPath, "utf-8"));
8250
+ return JSON.parse(fs20.readFileSync(metricsPath, "utf-8"));
8048
8251
  } catch {
8049
8252
  return {};
8050
8253
  }
@@ -8082,7 +8285,7 @@ function buildIssueUrl(metrics, squadrantVersion) {
8082
8285
  }
8083
8286
  var feedbackCommand = new Command14("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
8084
8287
  const config = loadConfig();
8085
- const metricsPath = config.metrics?.path || path22.join(os11.homedir(), ".config", "squadrant", "metrics.json");
8288
+ const metricsPath = config.metrics?.path || path24.join(os13.homedir(), ".config", "squadrant", "metrics.json");
8086
8289
  const metrics = readMetrics(metricsPath);
8087
8290
  const version = readStamp(config) ?? readPkgVersion();
8088
8291
  const issueUrl = buildIssueUrl(metrics, version);
@@ -8104,8 +8307,8 @@ init_dist();
8104
8307
  init_dist();
8105
8308
  init_dist3();
8106
8309
  import { Command as Command15 } from "commander";
8107
- import fs20 from "fs";
8108
- import path23 from "path";
8310
+ import fs21 from "fs";
8311
+ import path25 from "path";
8109
8312
  import chalk16 from "chalk";
8110
8313
  import matter3 from "gray-matter";
8111
8314
  function getDateStr(yesterday) {
@@ -8114,11 +8317,11 @@ function getDateStr(yesterday) {
8114
8317
  async function getProjectStandup(name, project, dateStr, registry, config) {
8115
8318
  const workspace = registry.forProject(name, config);
8116
8319
  const spokeVault = resolveHome(project.spokeVault);
8117
- const statusFile = path23.join(spokeVault, "status.md");
8320
+ const statusFile = path25.join(spokeVault, "status.md");
8118
8321
  let status = {};
8119
- if (fs20.existsSync(statusFile)) {
8322
+ if (fs21.existsSync(statusFile)) {
8120
8323
  try {
8121
- status = matter3(fs20.readFileSync(statusFile, "utf-8")).data;
8324
+ status = matter3(fs21.readFileSync(statusFile, "utf-8")).data;
8122
8325
  } catch {
8123
8326
  }
8124
8327
  }
@@ -8236,15 +8439,15 @@ init_dist();
8236
8439
  init_dist();
8237
8440
  init_dist3();
8238
8441
  import { Command as Command16 } from "commander";
8239
- import fs21 from "fs";
8240
- import path24 from "path";
8442
+ import fs22 from "fs";
8443
+ import path26 from "path";
8241
8444
  import chalk17 from "chalk";
8242
8445
  import matter4 from "gray-matter";
8243
8446
  function readStatus(spokeVault) {
8244
- const statusFile = path24.join(spokeVault, "status.md");
8245
- if (!fs21.existsSync(statusFile)) return {};
8447
+ const statusFile = path26.join(spokeVault, "status.md");
8448
+ if (!fs22.existsSync(statusFile)) return {};
8246
8449
  try {
8247
- return matter4(fs21.readFileSync(statusFile, "utf-8")).data;
8450
+ return matter4(fs22.readFileSync(statusFile, "utf-8")).data;
8248
8451
  } catch {
8249
8452
  return {};
8250
8453
  }
@@ -8554,9 +8757,9 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
8554
8757
  const config = loadConfig();
8555
8758
  const registry = buildRegistry2();
8556
8759
  try {
8557
- const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8760
+ const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8558
8761
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8559
- const content = await driver.read(path27);
8762
+ const content = await driver.read(path29);
8560
8763
  process.stdout.write(content);
8561
8764
  } catch (err) {
8562
8765
  console.error(chalk19.red(err.message));
@@ -8568,26 +8771,26 @@ workspaceCommand.command("write").description("Write content to a scope-relative
8568
8771
  const registry = buildRegistry2();
8569
8772
  try {
8570
8773
  let projectTarget;
8571
- let path27;
8774
+ let path29;
8572
8775
  let rawContent;
8573
8776
  if (opts.hub) {
8574
8777
  if (arg3 !== void 0) {
8575
8778
  throw new Error("With --hub, pass only the path and content");
8576
8779
  }
8577
8780
  projectTarget = void 0;
8578
- path27 = arg1;
8781
+ path29 = arg1;
8579
8782
  rawContent = arg2;
8580
8783
  } else {
8581
8784
  if (arg3 === void 0) {
8582
8785
  throw new Error("Missing content \u2014 usage: <project> <path> <content>");
8583
8786
  }
8584
8787
  projectTarget = arg1;
8585
- path27 = arg2;
8788
+ path29 = arg2;
8586
8789
  rawContent = arg3;
8587
8790
  }
8588
8791
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8589
8792
  const payload = rawContent === "-" ? await readStdin() : rawContent;
8590
- await driver.write(path27, payload);
8793
+ await driver.write(path29, payload);
8591
8794
  } catch (err) {
8592
8795
  console.error(chalk19.red(err.message));
8593
8796
  process.exit(1);
@@ -8597,9 +8800,9 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
8597
8800
  const config = loadConfig();
8598
8801
  const registry = buildRegistry2();
8599
8802
  try {
8600
- const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8803
+ const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8601
8804
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8602
- const entries = await driver.list(path27);
8805
+ const entries = await driver.list(path29);
8603
8806
  for (const entry of entries) console.log(entry);
8604
8807
  } catch (err) {
8605
8808
  console.error(chalk19.red(err.message));
@@ -8610,9 +8813,9 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
8610
8813
  const config = loadConfig();
8611
8814
  const registry = buildRegistry2();
8612
8815
  try {
8613
- const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8816
+ const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8614
8817
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8615
- const ok = await driver.exists(path27);
8818
+ const ok = await driver.exists(path29);
8616
8819
  process.exit(ok ? 0 : 1);
8617
8820
  } catch (err) {
8618
8821
  console.error(chalk19.red(err.message));
@@ -8623,9 +8826,9 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
8623
8826
  const config = loadConfig();
8624
8827
  const registry = buildRegistry2();
8625
8828
  try {
8626
- const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8829
+ const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8627
8830
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8628
- await driver.mkdir(path27);
8831
+ await driver.mkdir(path29);
8629
8832
  } catch (err) {
8630
8833
  console.error(chalk19.red(err.message));
8631
8834
  process.exit(1);
@@ -8664,8 +8867,8 @@ init_dist3();
8664
8867
  init_dist();
8665
8868
  import { Command as Command20 } from "commander";
8666
8869
  import chalk21 from "chalk";
8667
- import fs22 from "fs";
8668
- import path25 from "path";
8870
+ import fs23 from "fs";
8871
+ import path27 from "path";
8669
8872
  import { fileURLToPath as fileURLToPath4 } from "url";
8670
8873
  function parseScope(v) {
8671
8874
  if (v !== "user" && v !== "project") {
@@ -8674,10 +8877,10 @@ function parseScope(v) {
8674
8877
  return v;
8675
8878
  }
8676
8879
  function findPackageRoot3() {
8677
- let dir = path25.dirname(fileURLToPath4(import.meta.url));
8880
+ let dir = path27.dirname(fileURLToPath4(import.meta.url));
8678
8881
  while (dir !== "/" && dir !== "") {
8679
- if (fs22.existsSync(path25.join(dir, "package.json"))) return dir;
8680
- dir = path25.dirname(dir);
8882
+ if (fs23.existsSync(path27.join(dir, "package.json"))) return dir;
8883
+ dir = path27.dirname(dir);
8681
8884
  }
8682
8885
  return process.cwd();
8683
8886
  }
@@ -8871,12 +9074,12 @@ init_dist();
8871
9074
  init_dist();
8872
9075
  init_dist();
8873
9076
  import { Command as Command22 } from "commander";
8874
- import fs23 from "fs";
9077
+ import fs24 from "fs";
8875
9078
  import { fileURLToPath as fileURLToPath5 } from "url";
8876
- import { dirname as dirname5, join as join19 } from "path";
9079
+ import { dirname as dirname5, join as join20 } from "path";
8877
9080
  import chalk22 from "chalk";
8878
9081
  function runConfigCheck(opts) {
8879
- const raw = JSON.parse(fs23.readFileSync(opts.configPath, "utf-8"));
9082
+ const raw = JSON.parse(fs24.readFileSync(opts.configPath, "utf-8"));
8880
9083
  const def = getDefaultConfig();
8881
9084
  const items = detectDrift(raw, def);
8882
9085
  let working = raw;
@@ -8893,7 +9096,7 @@ function runConfigCheck(opts) {
8893
9096
  stamped = true;
8894
9097
  }
8895
9098
  if (opts.fix || opts.accept || stamped) {
8896
- fs23.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
9099
+ fs24.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
8897
9100
  }
8898
9101
  return { items, applied, remaining, stamped };
8899
9102
  }
@@ -8927,6 +9130,22 @@ function runConfigSet(key, value, configPath = DEFAULT_CONFIG_PATH) {
8927
9130
  node[parts[parts.length - 1]] = parsed;
8928
9131
  saveConfig(config, configPath);
8929
9132
  }
9133
+ function printRestartOutcome(outcome) {
9134
+ if (outcome === "skipped-not-running") {
9135
+ console.log(chalk22.dim("(daemon not running \u2014 change applies on next start)"));
9136
+ } else if (outcome === "skipped-opt-out") {
9137
+ console.log(chalk22.dim("(run 'squadrant heal daemon' to apply)"));
9138
+ }
9139
+ }
9140
+ function runConfigSetAction(opts) {
9141
+ runConfigSet(opts.key, opts.value, opts.configPath);
9142
+ console.log(chalk22.green(`\u2714 set ${opts.key} = ${opts.value}`));
9143
+ if (isDaemonCachedKey(opts.key)) {
9144
+ const doRestart = opts.doRestart ?? restartDaemonIfRunning;
9145
+ const outcome = doRestart({ reason: `config ${opts.key}`, noRestart: opts.noRestart });
9146
+ printRestartOutcome(outcome);
9147
+ }
9148
+ }
8930
9149
  var SEV_COLOR = {
8931
9150
  info: chalk22.green,
8932
9151
  advisory: chalk22.yellow,
@@ -8948,7 +9167,7 @@ function printItems(items) {
8948
9167
  var configCommand = new Command22("config").description("Inspect and reconcile squadrant config");
8949
9168
  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) => {
8950
9169
  const pkgVersion = readPkgVersion2();
8951
- if (!fs23.existsSync(DEFAULT_CONFIG_PATH)) {
9170
+ if (!fs24.existsSync(DEFAULT_CONFIG_PATH)) {
8952
9171
  console.log(chalk22.yellow("No config found \u2014 run `squadrant init` first."));
8953
9172
  return;
8954
9173
  }
@@ -8984,25 +9203,23 @@ configCommand.command("get").description("Read a config value by dotted key (e.g
8984
9203
  process.exit(1);
8985
9204
  }
8986
9205
  });
8987
- configCommand.command("set").description("Write a config value by dotted key (e.g. defaults.effort low)").argument("<key>", "dotted config key").argument("<value>", "value (JSON-parsed when possible, else a bare string)").action((key, value) => {
9206
+ configCommand.command("set").description("Write a config value by dotted key (e.g. defaults.effort low)").argument("<key>", "dotted config key").argument("<value>", "value (JSON-parsed when possible, else a bare string)").option("--no-restart", "skip daemon restart even if the key is daemon-cached").action((key, value, opts) => {
8988
9207
  try {
8989
- runConfigSet(key, value);
8990
- console.log(chalk22.green(`\u2714 set ${key} = ${value}`));
9208
+ runConfigSetAction({ key, value, noRestart: opts.restart === false });
8991
9209
  } catch (e) {
8992
9210
  console.error(chalk22.red(e.message));
8993
9211
  process.exit(1);
8994
9212
  }
8995
9213
  });
8996
9214
  function readPkgVersion2() {
8997
- const pkgPath = join19(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
8998
- return JSON.parse(fs23.readFileSync(pkgPath, "utf-8")).version;
9215
+ const pkgPath = join20(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
9216
+ return JSON.parse(fs24.readFileSync(pkgPath, "utf-8")).version;
8999
9217
  }
9000
9218
 
9001
9219
  // packages/cli/src/commands/heal.ts
9002
9220
  import { Command as Command23 } from "commander";
9003
9221
  import chalk23 from "chalk";
9004
9222
  init_dist2();
9005
- init_dist2();
9006
9223
  function buildHealStatus(components) {
9007
9224
  if (components === null) {
9008
9225
  return { healthy: false, daemonUnreachable: true, components: [] };
@@ -9082,7 +9299,7 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
9082
9299
  ).addCommand(
9083
9300
  new Command23("daemon").description("Restart squadrantd via the idempotent launchd kickstart path").action(async () => {
9084
9301
  const code = await runHealDaemon({
9085
- ensureDaemon,
9302
+ ensureDaemon: () => restartDaemonIfRunning({ reason: "heal", isRunning: () => true }),
9086
9303
  stdout: process.stdout,
9087
9304
  stderr: process.stderr
9088
9305
  });
@@ -9096,10 +9313,10 @@ init_dist2();
9096
9313
  import { Command as Command24 } from "commander";
9097
9314
  import { execSync as execSync13 } from "child_process";
9098
9315
  import { randomUUID as randomUUID4 } from "crypto";
9099
- import { homedir as homedir13 } from "os";
9100
- import { join as join20 } from "path";
9316
+ import { homedir as homedir14 } from "os";
9317
+ import { join as join21 } from "path";
9101
9318
  import chalk24 from "chalk";
9102
- var SOCK4 = join20(homedir13(), ".config", "squadrant", "squadrant.sock");
9319
+ var SOCK4 = join21(homedir14(), ".config", "squadrant", "squadrant.sock");
9103
9320
  var WARMUP_TIMEOUT_MS = 12e4;
9104
9321
  var WARMUP_POLL_MS = 1e3;
9105
9322
  function resolveCurrentProject(config) {
@@ -9261,8 +9478,8 @@ var cmuxCommand = new Command25("cmux").description("cmux integration helpers").
9261
9478
 
9262
9479
  // packages/cli/src/commands/effort.ts
9263
9480
  init_dist();
9264
- import fs24 from "fs";
9265
- import path26 from "path";
9481
+ import fs25 from "fs";
9482
+ import path28 from "path";
9266
9483
  import { Command as Command26 } from "commander";
9267
9484
  import chalk26 from "chalk";
9268
9485
  var VALID_EFFORTS = ["max", "balance", "low"];
@@ -9289,9 +9506,9 @@ function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH) {
9289
9506
  }
9290
9507
  function canonical(p) {
9291
9508
  try {
9292
- return fs24.realpathSync(p);
9509
+ return fs25.realpathSync(p);
9293
9510
  } catch {
9294
- return path26.resolve(p);
9511
+ return path28.resolve(p);
9295
9512
  }
9296
9513
  }
9297
9514
  async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd()) {
@@ -9338,12 +9555,12 @@ var effortCommand = new Command26("effort").description("Get or set the global c
9338
9555
  // packages/cli/src/commands/telegram.ts
9339
9556
  init_dist();
9340
9557
  init_dist2();
9341
- import { join as join21, dirname as dirname6 } from "path";
9558
+ import { join as join22, dirname as dirname6 } from "path";
9342
9559
  import { emitKeypressEvents } from "readline";
9343
9560
  import { Command as Command27 } from "commander";
9344
9561
  import chalk27 from "chalk";
9345
9562
  function defaultStateRoot() {
9346
- return join21(dirname6(DEFAULT_CONFIG_PATH), "state");
9563
+ return join22(dirname6(DEFAULT_CONFIG_PATH), "state");
9347
9564
  }
9348
9565
  function runTelegramStatus(opts) {
9349
9566
  const tg = opts.config.telegram;
@@ -9363,6 +9580,34 @@ async function runTelegramSend(opts) {
9363
9580
  await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
9364
9581
  return { chatId: opts.cfg.supergroupId, topicId };
9365
9582
  }
9583
+ function runTelegramNotifySet(opts) {
9584
+ setNotify(opts.stateRoot, opts.project, opts.active);
9585
+ }
9586
+ function runTelegramNotifyPref(args) {
9587
+ const { project, dimension, value, root } = args;
9588
+ if (dimension === "crew") {
9589
+ if (!["all", "alert_only", "done_only", "none"].includes(value))
9590
+ return { ok: false, message: "crew must be all|alert_only|done_only|none" };
9591
+ saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
9592
+ return { ok: true };
9593
+ }
9594
+ if (value !== "on" && value !== "off") return { ok: false, message: "cap must be on|off" };
9595
+ saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
9596
+ return { ok: true };
9597
+ }
9598
+ function capAllowed(project, globalNotify, root) {
9599
+ return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
9600
+ }
9601
+ function runTelegramNotifyStatus(opts) {
9602
+ const s = loadState(opts.stateRoot);
9603
+ const projects = /* @__PURE__ */ new Set();
9604
+ for (const key of Object.keys(s.topics)) {
9605
+ const sep2 = key.indexOf("::");
9606
+ projects.add(sep2 === -1 ? key : key.slice(0, sep2));
9607
+ }
9608
+ for (const p of Object.keys(s.notify)) projects.add(p);
9609
+ return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
9610
+ }
9366
9611
  async function runTelegramLink(opts) {
9367
9612
  const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
9368
9613
  if (existing !== void 0) return { topicId: existing, created: false };
@@ -9413,6 +9658,44 @@ async function questionYesNo(prompt) {
9413
9658
  });
9414
9659
  });
9415
9660
  }
9661
+ function confirmationText(project, before, after, dim) {
9662
+ if (dim === "active") return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
9663
+ if (dim === "cap") return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
9664
+ return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
9665
+ }
9666
+ async function runNotifyConfirmation(opts) {
9667
+ const { quieter, dim } = isQuieter(opts.before, opts.after);
9668
+ if (!quieter || dim === null) return false;
9669
+ const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
9670
+ if (topicId === void 0) return false;
9671
+ const text = confirmationText(opts.project, opts.before, opts.after, dim);
9672
+ try {
9673
+ await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
9674
+ return true;
9675
+ } catch {
9676
+ console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
9677
+ return false;
9678
+ }
9679
+ }
9680
+ function resolveSetupToken(existingToken, opts) {
9681
+ if (opts.resetToken || !existingToken) return "prompt";
9682
+ return "try-reuse";
9683
+ }
9684
+ function resolveSetupUserId(flagUserId, detectedUserId, stateRoot) {
9685
+ return flagUserId ?? detectedUserId ?? loadState(stateRoot).lastUserId;
9686
+ }
9687
+ async function runRegisterCommands(opts) {
9688
+ await opts.client.setMyCommands(BOT_COMMANDS);
9689
+ }
9690
+ function runTelegramPostSetup(opts) {
9691
+ const doRestart = opts.doRestart ?? restartDaemonIfRunning;
9692
+ const outcome = doRestart({ reason: "telegram config" });
9693
+ if (outcome === "skipped-not-running") {
9694
+ console.log(chalk27.dim("(daemon not running \u2014 change applies on next start)"));
9695
+ } else if (outcome === "skipped-opt-out") {
9696
+ console.log(chalk27.dim("(run 'squadrant heal daemon' to apply)"));
9697
+ }
9698
+ }
9416
9699
  var telegramCommand = new Command27("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
9417
9700
  telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
9418
9701
  const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
@@ -9439,7 +9722,7 @@ telegramCommand.command("link").argument("<project>", "project to bind to a Tele
9439
9722
  const { topicId, created } = await runTelegramLink({ project, cfg, client, stateRoot: defaultStateRoot() });
9440
9723
  console.log(chalk27.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
9441
9724
  });
9442
- telegramCommand.command("setup").description("Interactive wizard \u2014 bot token, validate, auto-detect supergroup, write config").action(async () => {
9725
+ telegramCommand.command("setup").description("Interactive wizard \u2014 bot token, validate, auto-detect supergroup, write config").option("--reset-token", "force re-entry of the bot token even if one already exists").option("--redetect", "force group re-detection even when a supergroup is already configured").option("--user-id <id>", "allowlist user-id \u2014 enables remote control on a re-run without getUpdates detection", (v) => parseInt(v, 10)).action(async (opts) => {
9443
9726
  if (!process.stdin.isTTY) {
9444
9727
  console.error(chalk27.red("setup requires a TTY \u2014 pipe input is not supported"));
9445
9728
  process.exit(1);
@@ -9453,63 +9736,200 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
9453
9736
  console.log(" 3. Bot privacy mode set to OFF (@BotFather \u2192 /setprivacy \u2192 Disable)");
9454
9737
  console.log();
9455
9738
  console.log(chalk27.bold("Step 1/3 \u2014 Bot token"));
9456
- console.log("Paste your bot token then press Enter (input is hidden):");
9457
- const token = await questionMasked();
9458
- if (!token) {
9459
- console.error(chalk27.red("token required"));
9460
- process.exit(1);
9461
- }
9462
- const client = createTelegramClient({ token });
9739
+ const existingCfg = loadConfig().telegram;
9740
+ const existingToken = existingCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
9741
+ const decision = resolveSetupToken(existingToken, { resetToken: opts.resetToken ?? false });
9742
+ let token;
9743
+ let client;
9463
9744
  let botUser;
9464
- try {
9465
- botUser = await client.getMe();
9466
- } catch (e) {
9467
- console.error(chalk27.red(`token rejected: ${e.message}`));
9468
- process.exit(1);
9745
+ if (decision === "try-reuse") {
9746
+ client = createTelegramClient({ token: existingToken });
9747
+ try {
9748
+ botUser = await client.getMe();
9749
+ token = existingToken;
9750
+ console.log(chalk27.green(`Using existing bot token (@${botUser.username})`));
9751
+ console.log();
9752
+ } catch {
9753
+ console.log(chalk27.yellow("Existing token is invalid \u2014 please enter a new one."));
9754
+ console.log("Paste your bot token then press Enter (input is hidden):");
9755
+ token = await questionMasked();
9756
+ if (!token) {
9757
+ console.error(chalk27.red("token required"));
9758
+ process.exit(1);
9759
+ }
9760
+ client = createTelegramClient({ token });
9761
+ try {
9762
+ botUser = await client.getMe();
9763
+ } catch (e) {
9764
+ console.error(chalk27.red(`token rejected: ${e.message}`));
9765
+ process.exit(1);
9766
+ }
9767
+ console.log(chalk27.green(`Connected as @${botUser.username}`));
9768
+ console.log();
9769
+ }
9770
+ } else {
9771
+ console.log("Paste your bot token then press Enter (input is hidden):");
9772
+ token = await questionMasked();
9773
+ if (!token) {
9774
+ console.error(chalk27.red("token required"));
9775
+ process.exit(1);
9776
+ }
9777
+ client = createTelegramClient({ token });
9778
+ try {
9779
+ botUser = await client.getMe();
9780
+ } catch (e) {
9781
+ console.error(chalk27.red(`token rejected: ${e.message}`));
9782
+ process.exit(1);
9783
+ }
9784
+ console.log(chalk27.green(`Connected as @${botUser.username}`));
9785
+ console.log();
9469
9786
  }
9470
- console.log(chalk27.green(`Connected as @${botUser.username}`));
9471
- console.log();
9472
- console.log(chalk27.bold("Step 2/3 \u2014 Find your group"));
9473
- console.log("Add the bot to your forum supergroup, then send any message in it.");
9474
- console.log(chalk27.dim("Waiting for a message (up to 60s)\u2026"));
9787
+ console.log(chalk27.bold("Step 2/3 \u2014 Supergroup"));
9788
+ const groupDecision = resolveSetupGroup(existingCfg?.supergroupId, { redetect: opts.redetect ?? false });
9475
9789
  let supergroupId;
9476
- let userId;
9477
- try {
9478
- ({ supergroupId, userId } = await detectGroupAndUser(client, { timeoutMs: 6e4 }));
9479
- } catch {
9480
- console.error(chalk27.red("Timed out \u2014 no supergroup message received within 60s."));
9481
- console.error(chalk27.yellow("Check: bot is an admin in the group \xB7 privacy mode is OFF \xB7 Topics enabled"));
9482
- process.exit(1);
9790
+ let detectedUserId;
9791
+ if (groupDecision === "reuse") {
9792
+ supergroupId = existingCfg.supergroupId;
9793
+ console.log(chalk27.green(`Using existing group: ${supergroupId}`));
9794
+ console.log();
9795
+ } else {
9796
+ console.log("Add the bot to your forum supergroup, then send any message in it.");
9797
+ console.log(chalk27.dim("Waiting for a message (up to 60s)\u2026"));
9798
+ try {
9799
+ ({ supergroupId, userId: detectedUserId } = await detectGroupAndUser(client, { timeoutMs: 6e4 }));
9800
+ } catch {
9801
+ console.error(chalk27.red("Timed out \u2014 no supergroup message received within 60s."));
9802
+ console.error(chalk27.yellow("Check: bot is an admin in the group \xB7 privacy mode is OFF \xB7 Topics enabled"));
9803
+ process.exit(1);
9804
+ }
9805
+ console.log(chalk27.green(`Found group: ${supergroupId}`));
9806
+ console.log();
9483
9807
  }
9484
- console.log(chalk27.green(`Found group: ${supergroupId}`));
9485
- console.log();
9486
9808
  console.log(chalk27.bold("Step 3/3 \u2014 Remote control + Save"));
9487
9809
  console.log(chalk27.dim("Remote control enables auto-launching captains and the General command channel"));
9488
9810
  console.log(chalk27.dim("from your phone \u2014 gated to your Telegram user-id only (fail-closed)."));
9811
+ const finalUserId = resolveSetupUserId(opts.userId, detectedUserId, defaultStateRoot());
9489
9812
  let users;
9490
9813
  let remoteControl;
9491
- if (userId === void 0) {
9492
- console.log(chalk27.yellow("Could not read your user-id from that message \u2014 skipping remote control."));
9493
- console.log(chalk27.yellow("Re-run setup once it's available, or edit telegram.users in config manually."));
9494
- } else {
9814
+ let printedRemoteControlState = false;
9815
+ if (finalUserId !== void 0) {
9495
9816
  const enable = await questionYesNo(
9496
- `Enable remote control for your user-id ${userId}? [y/N] `
9817
+ `Enable remote control for your user-id ${finalUserId}? [y/N] `
9497
9818
  );
9498
9819
  if (enable) {
9499
- users = [userId];
9820
+ users = [finalUserId];
9500
9821
  remoteControl = true;
9501
9822
  }
9823
+ } else if (groupDecision === "detect") {
9824
+ console.log(chalk27.yellow("Could not read your user-id from that message \u2014 skipping remote control."));
9825
+ console.log(chalk27.yellow("Re-run with --user-id <id> to enable, or edit telegram.users in config manually."));
9826
+ printedRemoteControlState = true;
9827
+ } else {
9828
+ const existingUsers = existingCfg?.users;
9829
+ if (existingUsers && existingUsers.length > 0) {
9830
+ console.log(chalk27.dim(`Remote control: already configured (user-id ${existingUsers[0]}). Use --user-id to update.`));
9831
+ } else {
9832
+ console.log(chalk27.dim("Remote control: off. Re-run with --user-id <id> to enable."));
9833
+ }
9834
+ printedRemoteControlState = true;
9502
9835
  }
9503
9836
  writeTelegramConfig(DEFAULT_CONFIG_PATH, { token, supergroupId, users, remoteControl });
9504
9837
  console.log(chalk27.green(`Wrote telegram config \u2014 token: ${maskToken(token)} group: ${supergroupId}`));
9505
- if (remoteControl) {
9506
- console.log(chalk27.green(`Remote control: ON (allowlisted user-id ${users?.[0]})`));
9838
+ if (!printedRemoteControlState) {
9839
+ if (remoteControl) {
9840
+ console.log(chalk27.green(`Remote control: ON (allowlisted user-id ${users[0]})`));
9841
+ } else {
9842
+ console.log(chalk27.dim("Remote control: off (default). Re-run with --user-id <id> to enable."));
9843
+ }
9844
+ }
9845
+ try {
9846
+ await runRegisterCommands({ client });
9847
+ console.log(chalk27.dim("Registered the /command menu."));
9848
+ } catch (e) {
9849
+ console.log(chalk27.yellow(`command-menu registration skipped: ${e.message}`));
9850
+ }
9851
+ const topics = loadState(defaultStateRoot()).topics;
9852
+ const topicEntries = Object.entries(topics);
9853
+ if (topicEntries.length > 0) {
9854
+ const summary = topicEntries.map(([key, id]) => {
9855
+ const project = key.slice(0, key.indexOf("::"));
9856
+ return `${project}\u2192${id}`;
9857
+ }).join(", ");
9858
+ console.log(chalk27.dim(`Existing topics: ${summary} (already created \u2014 not recreated)`));
9507
9859
  } else {
9508
- console.log(chalk27.dim("Remote control: off (default). Re-run setup to enable later."));
9860
+ console.log(chalk27.dim("No project topics yet \u2014 they're created on first delivery or via: squadrant telegram link <project>"));
9509
9861
  }
9862
+ runTelegramPostSetup({});
9510
9863
  console.log();
9511
9864
  console.log(`Next: ${chalk27.cyan("squadrant telegram link <project>")}`);
9512
9865
  });
9866
+ telegramCommand.command("register-commands").description("Register (or re-register) the bot's / command menu with Telegram").action(async () => {
9867
+ const cfg = loadConfig().telegram;
9868
+ if (!cfg) {
9869
+ console.error(chalk27.red("telegram config absent \u2014 run: squadrant telegram setup"));
9870
+ process.exit(1);
9871
+ }
9872
+ const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
9873
+ if (!token) {
9874
+ console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
9875
+ process.exit(1);
9876
+ }
9877
+ const client = createTelegramClient({ token });
9878
+ await runRegisterCommands({ client });
9879
+ console.log(chalk27.green(`registered ${BOT_COMMANDS.length} bot commands`));
9880
+ });
9881
+ telegramCommand.command("notify").argument("[project]", "project to toggle").argument("[state]", "on | off | crew | cap").argument("[value]", "tier for crew (all|alert_only|done_only|none) or on|off for cap").option("--status", "list notification state for all projects").description("Live on|off (state), or crew <tier> / cap <on|off> preference (per-project config)").action(async (project, state, value, opts) => {
9882
+ const stateRoot = defaultStateRoot();
9883
+ if (opts.status || !project) {
9884
+ const rows = runTelegramNotifyStatus({ stateRoot });
9885
+ if (rows.length === 0) {
9886
+ console.log("no projects linked");
9887
+ return;
9888
+ }
9889
+ for (const r of rows) {
9890
+ console.log(` ${r.project}: ${r.active ? chalk27.green("on") : chalk27.dim("off (muted)")}`);
9891
+ }
9892
+ return;
9893
+ }
9894
+ const tgCfg = loadConfig().telegram;
9895
+ const globalNotify = tgCfg?.notify;
9896
+ const token = tgCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
9897
+ if (state === "crew" || state === "cap") {
9898
+ if (value === void 0) {
9899
+ console.error(chalk27.red(`usage: squadrant telegram notify <project> ${state} <value>`));
9900
+ process.exit(1);
9901
+ }
9902
+ const resolved2 = resolveNotify(globalNotify, loadProjectOverride(project));
9903
+ const before2 = { ...resolved2, active: isNotifyActive(stateRoot, project) };
9904
+ const res = runTelegramNotifyPref({ project, dimension: state, value });
9905
+ if (!res.ok) {
9906
+ console.error(chalk27.red(res.message));
9907
+ process.exit(1);
9908
+ }
9909
+ console.log(chalk27.green(`${project} ${state} = ${value}`));
9910
+ const after2 = state === "crew" ? { ...before2, crew: value } : { ...before2, cap: value === "on" };
9911
+ if (tgCfg && token) {
9912
+ const client = createTelegramClient({ token });
9913
+ const sent = await runNotifyConfirmation({ project, before: before2, after: after2, cfg: tgCfg, client, stateRoot });
9914
+ if (sent) console.log(chalk27.dim(`\u2192 notified ${project} topic`));
9915
+ }
9916
+ return;
9917
+ }
9918
+ if (state !== "on" && state !== "off") {
9919
+ console.error(chalk27.red("usage: squadrant telegram notify <project> <on|off|crew <tier>|cap <on|off>>"));
9920
+ process.exit(1);
9921
+ }
9922
+ const resolved = resolveNotify(globalNotify, loadProjectOverride(project));
9923
+ const before = { ...resolved, active: isNotifyActive(stateRoot, project) };
9924
+ const after = { ...before, active: state === "on" };
9925
+ runTelegramNotifySet({ project, active: state === "on", stateRoot });
9926
+ console.log(chalk27.green(`${project} notifications ${state === "on" ? "ON" : "OFF"}`));
9927
+ if (tgCfg && token) {
9928
+ const client = createTelegramClient({ token });
9929
+ const sent = await runNotifyConfirmation({ project, before, after, cfg: tgCfg, client, stateRoot });
9930
+ if (sent) console.log(chalk27.dim(`\u2192 notified ${project} topic`));
9931
+ }
9932
+ });
9513
9933
  telegramCommand.command("send").argument("<project>", "project whose topic receives the message").argument("[message...]", "message text (omit to read from stdin)").description("Send a message to a project's linked Telegram topic").action(async (project, messageParts) => {
9514
9934
  const cfg = loadConfig().telegram;
9515
9935
  if (!cfg) {
@@ -9521,6 +9941,10 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
9521
9941
  console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
9522
9942
  process.exit(1);
9523
9943
  }
9944
+ if (!capAllowed(project, cfg.notify)) {
9945
+ console.log(chalk27.dim(`${project}: captain messages muted (cap=off) \u2014 not sent`));
9946
+ return;
9947
+ }
9524
9948
  let message;
9525
9949
  if (messageParts.length > 0) {
9526
9950
  message = messageParts.join(" ");
@@ -9553,15 +9977,15 @@ init_dist();
9553
9977
  init_dist();
9554
9978
  init_dist();
9555
9979
  var __dirname = dirname7(fileURLToPath6(import.meta.url));
9556
- var pkg = JSON.parse(readFileSync11(join22(__dirname, "..", "package.json"), "utf-8"));
9980
+ var pkg = JSON.parse(readFileSync11(join23(__dirname, "..", "package.json"), "utf-8"));
9557
9981
  ensureRuntimeSynced({
9558
- sourceRoot: join22(__dirname, ".."),
9559
- runtimeRoot: join22(homedir14(), ".config", "squadrant")
9982
+ sourceRoot: join23(__dirname, ".."),
9983
+ runtimeRoot: join23(homedir15(), ".config", "squadrant")
9560
9984
  });
9561
9985
  if (process.argv[2] !== "config") {
9562
9986
  try {
9563
- const cfgPath = join22(homedir14(), ".config", "squadrant", "config.json");
9564
- if (existsSync10(cfgPath)) {
9987
+ const cfgPath = join23(homedir15(), ".config", "squadrant", "config.json");
9988
+ if (existsSync11(cfgPath)) {
9565
9989
  const cfg = JSON.parse(readFileSync11(cfgPath, "utf-8"));
9566
9990
  if (needsCheck(cfg, pkg.version)) {
9567
9991
  const items = detectDrift(cfg, getDefaultConfig());