squadrant 0.9.2 → 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 path26 = opts.path ?? defaultCmuxConfigPath();
159
- if (!existsSync(path26)) {
160
- mkdirSync(dirname(path26), { recursive: true });
161
- writeFileSync(path26, MINIMAL_TEMPLATE);
162
- return { path: path26, 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(path26, "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: path26, 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(path26, applyEdits(text, edits));
173
- return { path: path26, 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,15 +380,15 @@ 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(path26) {
383
+ function readState(path29) {
324
384
  try {
325
- return JSON.parse(readFileSync4(path26, "utf-8"));
385
+ return JSON.parse(readFileSync4(path29, "utf-8"));
326
386
  } catch {
327
387
  return {};
328
388
  }
329
389
  }
330
390
  async function ensureCmuxAutoConfig(opts = {}) {
331
- const statePath = opts.statePath ?? defaultStatePath();
391
+ const statePath2 = opts.statePath ?? defaultStatePath();
332
392
  const ensureConfig = opts.ensureConfig ?? ensureSocketAutomation;
333
393
  const probe = opts.probe ?? probeCmuxDaemonDirect;
334
394
  const cfg = ensureConfig({ path: opts.configPath });
@@ -336,15 +396,15 @@ async function ensureCmuxAutoConfig(opts = {}) {
336
396
  const needsRestart = verdict === "denied";
337
397
  let promptedThisRun = false;
338
398
  if (needsRestart) {
339
- const already = readState(statePath).promptedRestart === true;
399
+ const already = readState(statePath2).promptedRestart === true;
340
400
  if (!already) {
341
- mkdirSync2(dirname2(statePath), { recursive: true });
342
- writeFileSync3(statePath, JSON.stringify({ promptedRestart: true }));
401
+ mkdirSync2(dirname2(statePath2), { recursive: true });
402
+ writeFileSync3(statePath2, JSON.stringify({ promptedRestart: true }));
343
403
  promptedThisRun = true;
344
404
  }
345
405
  } else if (verdict === "reachable") {
346
- if (existsSync4(statePath))
347
- rmSync2(statePath, { force: true });
406
+ if (existsSync4(statePath2))
407
+ rmSync2(statePath2, { force: true });
348
408
  }
349
409
  return {
350
410
  configPath: cfg.path,
@@ -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(path26) {
1212
+ function sanitizePathForPlist(path29) {
1135
1213
  const seen = /* @__PURE__ */ new Set();
1136
1214
  const stable = [];
1137
- for (const p of path26.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,276 @@ 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
+
1674
+ // packages/core/dist/telegram/auth.js
1675
+ var init_auth = __esm({
1676
+ "packages/core/dist/telegram/auth.js"() {
1677
+ }
1678
+ });
1679
+
1680
+ // packages/core/dist/telegram/commands.js
1681
+ var init_commands = __esm({
1682
+ "packages/core/dist/telegram/commands.js"() {
1683
+ }
1684
+ });
1685
+
1686
+ // packages/core/dist/telegram/ensure-captain.js
1687
+ var init_ensure_captain = __esm({
1688
+ "packages/core/dist/telegram/ensure-captain.js"() {
1689
+ }
1690
+ });
1691
+
1692
+ // packages/core/dist/telegram/format.js
1693
+ function topicName(project) {
1694
+ return project;
1695
+ }
1696
+ function maskToken(token) {
1697
+ if (token.length <= 4)
1698
+ return token;
1699
+ return "*".repeat(token.length - 4) + token.slice(-4);
1700
+ }
1701
+ var init_format = __esm({
1702
+ "packages/core/dist/telegram/format.js"() {
1703
+ }
1704
+ });
1705
+
1706
+ // packages/core/dist/telegram/state.js
1707
+ import fs9 from "fs";
1708
+ import path8 from "path";
1709
+ function statePath(stateRoot) {
1710
+ return path8.join(stateRoot, "telegram-state.json");
1711
+ }
1712
+ function topicKey(project, scope = "project") {
1713
+ return `${project}::${scope}`;
1714
+ }
1715
+ function loadState(stateRoot) {
1716
+ try {
1717
+ const raw = fs9.readFileSync(statePath(stateRoot), "utf-8");
1718
+ const data = JSON.parse(raw);
1719
+ const result = {
1720
+ offset: typeof data.offset === "number" ? data.offset : 0,
1721
+ topics: data.topics ?? {},
1722
+ notify: data.notify ?? {}
1723
+ };
1724
+ if (typeof data.lastUserId === "number")
1725
+ result.lastUserId = data.lastUserId;
1726
+ return result;
1727
+ } catch {
1728
+ return { offset: 0, topics: {}, notify: {} };
1729
+ }
1730
+ }
1731
+ function saveState(stateRoot, s) {
1732
+ fs9.mkdirSync(stateRoot, { recursive: true });
1733
+ fs9.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
1734
+ }
1735
+ function setTopic(stateRoot, project, topicId, scope = "project") {
1736
+ const s = loadState(stateRoot);
1737
+ s.topics[topicKey(project, scope)] = topicId;
1738
+ saveState(stateRoot, s);
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
+ }
1748
+ var init_state = __esm({
1749
+ "packages/core/dist/telegram/state.js"() {
1750
+ }
1751
+ });
1752
+
1753
+ // packages/core/dist/telegram/client.js
1754
+ function createTelegramClient(opts) {
1755
+ const fetchImpl = opts.fetch ?? fetch;
1756
+ const base = `https://api.telegram.org/bot${opts.token}`;
1757
+ async function call(method, body) {
1758
+ const res = await fetchImpl(`${base}/${method}`, {
1759
+ method: "POST",
1760
+ headers: { "content-type": "application/json" },
1761
+ body: JSON.stringify(body)
1762
+ });
1763
+ const json = await res.json();
1764
+ if (!res.ok || !json.ok) {
1765
+ const code = json.error_code ?? res.status;
1766
+ const desc = json.description ?? "unknown error";
1767
+ throw new Error(`telegram ${method} failed (${code}): ${desc}`);
1768
+ }
1769
+ return json.result;
1770
+ }
1771
+ return {
1772
+ async getMe() {
1773
+ const r = await call("getMe", {});
1774
+ return { id: r.id, username: r.username };
1775
+ },
1776
+ getUpdates(offset, timeoutSec = 50) {
1777
+ return call("getUpdates", { offset, timeout: timeoutSec });
1778
+ },
1779
+ async sendMessage(chatId, threadId, text, replyMarkup) {
1780
+ const body = { chat_id: chatId, text };
1781
+ if (threadId !== void 0)
1782
+ body.message_thread_id = threadId;
1783
+ if (replyMarkup !== void 0)
1784
+ body.reply_markup = replyMarkup;
1785
+ await call("sendMessage", body);
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
+ },
1796
+ async createForumTopic(chatId, name) {
1797
+ const r = await call("createForumTopic", { chat_id: chatId, name });
1798
+ return r.message_thread_id;
1799
+ },
1800
+ async setMyCommands(commands) {
1801
+ await call("setMyCommands", { commands });
1802
+ }
1803
+ };
1804
+ }
1805
+ var init_client = __esm({
1806
+ "packages/core/dist/telegram/client.js"() {
1807
+ }
1808
+ });
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
+
1831
+ // packages/core/dist/telegram/bridge.js
1832
+ import os3 from "os";
1833
+ import path9 from "path";
1834
+ var init_bridge = __esm({
1835
+ "packages/core/dist/telegram/bridge.js"() {
1836
+ init_dist();
1837
+ init_auth();
1838
+ init_commands();
1839
+ init_format();
1840
+ init_panels();
1841
+ init_state();
1842
+ init_tiers();
1843
+ }
1844
+ });
1845
+
1846
+ // packages/core/dist/telegram/setup.js
1847
+ import fs10 from "fs";
1848
+ function resolveSetupGroup(existingSupergroupId, opts) {
1849
+ if (existingSupergroupId !== void 0 && !opts.redetect)
1850
+ return "reuse";
1851
+ return "detect";
1852
+ }
1853
+ async function detectGroupAndUser(client, opts = {}) {
1854
+ const timeoutMs = opts.timeoutMs ?? 6e4;
1855
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
1856
+ const deadline = Date.now() + timeoutMs;
1857
+ let offset = 0;
1858
+ while (Date.now() < deadline) {
1859
+ const updates = await client.getUpdates(offset, 10);
1860
+ for (const u of updates) {
1861
+ if (u.update_id >= offset)
1862
+ offset = u.update_id + 1;
1863
+ if (u.message?.chat?.type === "supergroup") {
1864
+ return { supergroupId: u.message.chat.id, userId: u.message.from?.id };
1865
+ }
1866
+ }
1867
+ await sleep2(2e3);
1868
+ }
1869
+ throw new Error("Timed out waiting for the bot to receive a message in a supergroup");
1870
+ }
1871
+ function writeTelegramConfig(configPath, opts) {
1872
+ let config;
1873
+ let raw = null;
1874
+ try {
1875
+ raw = fs10.readFileSync(configPath, "utf-8");
1876
+ } catch (err) {
1877
+ if (err.code !== "ENOENT") {
1878
+ throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);
1879
+ }
1880
+ }
1881
+ if (raw !== null) {
1882
+ try {
1883
+ config = JSON.parse(raw);
1884
+ } catch (err) {
1885
+ throw new Error(`refusing to overwrite corrupt config at ${configPath}: ${String(err)}`);
1886
+ }
1887
+ } else {
1888
+ config = {};
1889
+ }
1890
+ const prev = config.telegram && typeof config.telegram === "object" ? config.telegram : {};
1891
+ const next = {
1892
+ botToken: opts.token,
1893
+ supergroupId: opts.supergroupId,
1894
+ chats: [opts.supergroupId]
1895
+ };
1896
+ const users = opts.users ?? prev.users;
1897
+ const remoteControl = opts.remoteControl ?? prev.remoteControl;
1898
+ if (users !== void 0)
1899
+ next.users = users;
1900
+ if (remoteControl !== void 0)
1901
+ next.remoteControl = remoteControl;
1902
+ config.telegram = next;
1903
+ fs10.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1904
+ }
1905
+ var init_setup = __esm({
1906
+ "packages/core/dist/telegram/setup.js"() {
1907
+ }
1908
+ });
1909
+
1910
+ // packages/core/dist/telegram/index.js
1911
+ var init_telegram = __esm({
1912
+ "packages/core/dist/telegram/index.js"() {
1913
+ init_bot_commands();
1914
+ init_auth();
1915
+ init_commands();
1916
+ init_ensure_captain();
1917
+ init_format();
1918
+ init_state();
1919
+ init_client();
1920
+ init_bridge();
1921
+ init_setup();
1922
+ }
1923
+ });
1924
+
1574
1925
  // packages/core/dist/index.js
1575
1926
  var init_dist2 = __esm({
1576
1927
  "packages/core/dist/index.js"() {
@@ -1596,6 +1947,7 @@ var init_dist2 = __esm({
1596
1947
  init_session_freshness();
1597
1948
  init_crew_protocol();
1598
1949
  init_crew_lifecycle();
1950
+ init_telegram();
1599
1951
  }
1600
1952
  });
1601
1953
 
@@ -2114,13 +2466,13 @@ var init_notifiers = __esm({
2114
2466
  });
2115
2467
 
2116
2468
  // packages/workspaces/dist/workspaces/obsidian.js
2117
- import fs8 from "fs/promises";
2469
+ import fs11 from "fs/promises";
2118
2470
  import { existsSync as existsSync8 } from "fs";
2119
- import path7 from "path";
2471
+ import path10 from "path";
2120
2472
  function resolveInRoot(root, relative) {
2121
- const joined = path7.resolve(root, relative);
2122
- const normalized = path7.resolve(root) + path7.sep;
2123
- if (joined !== path7.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)) {
2124
2476
  throw new Error(`Path '${relative}' escapes workspace root`);
2125
2477
  }
2126
2478
  return joined;
@@ -2139,16 +2491,16 @@ function createObsidianDriver(scope) {
2139
2491
  };
2140
2492
  },
2141
2493
  async read(rel) {
2142
- return fs8.readFile(resolveInRoot(root, rel), "utf-8");
2494
+ return fs11.readFile(resolveInRoot(root, rel), "utf-8");
2143
2495
  },
2144
2496
  async write(rel, content) {
2145
2497
  const abs = resolveInRoot(root, rel);
2146
- await fs8.mkdir(path7.dirname(abs), { recursive: true });
2147
- await fs8.writeFile(abs, content);
2498
+ await fs11.mkdir(path10.dirname(abs), { recursive: true });
2499
+ await fs11.writeFile(abs, content);
2148
2500
  },
2149
2501
  async exists(rel) {
2150
2502
  try {
2151
- await fs8.access(resolveInRoot(root, rel));
2503
+ await fs11.access(resolveInRoot(root, rel));
2152
2504
  return true;
2153
2505
  } catch {
2154
2506
  return false;
@@ -2156,13 +2508,13 @@ function createObsidianDriver(scope) {
2156
2508
  },
2157
2509
  async list(rel) {
2158
2510
  try {
2159
- return await fs8.readdir(resolveInRoot(root, rel));
2511
+ return await fs11.readdir(resolveInRoot(root, rel));
2160
2512
  } catch {
2161
2513
  return [];
2162
2514
  }
2163
2515
  },
2164
2516
  async mkdir(rel) {
2165
- await fs8.mkdir(resolveInRoot(root, rel), { recursive: true });
2517
+ await fs11.mkdir(resolveInRoot(root, rel), { recursive: true });
2166
2518
  }
2167
2519
  };
2168
2520
  }
@@ -2831,8 +3183,8 @@ var init_registry4 = __esm({
2831
3183
  });
2832
3184
 
2833
3185
  // packages/agents/dist/drivers/launch-cmd.js
2834
- import fs9 from "fs";
2835
- import path8 from "path";
3186
+ import fs12 from "fs";
3187
+ import path11 from "path";
2836
3188
  function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir) {
2837
3189
  const driver = registry.getDriver(agentName);
2838
3190
  if (driver.name === "claude") {
@@ -2848,27 +3200,27 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
2848
3200
  cmd += ` --model ${model}`;
2849
3201
  }
2850
3202
  if (templatesDir) {
2851
- const roleFile2 = path8.join(templatesDir, `${role}.claude.md`);
2852
- const legacyRoleFile = path8.join(templatesDir, `${role}.CLAUDE.md`);
2853
- const actualRoleFile = fs9.existsSync(roleFile2) ? roleFile2 : fs9.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;
2854
3206
  if (actualRoleFile) {
2855
3207
  cmd += ` --append-system-prompt-file ${actualRoleFile}`;
2856
3208
  }
2857
- const pluginDir = path8.join(templatesDir, "..", "plugin");
2858
- if (fs9.existsSync(pluginDir)) {
3209
+ const pluginDir = path11.join(templatesDir, "..", "plugin");
3210
+ if (fs12.existsSync(pluginDir)) {
2859
3211
  cmd += ` --plugin-dir ${pluginDir}`;
2860
3212
  }
2861
3213
  }
2862
3214
  return cmd;
2863
3215
  }
2864
- const roleFile = templatesDir ? path8.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
3216
+ const roleFile = templatesDir ? path11.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
2865
3217
  return driver.buildCommand({
2866
3218
  prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
2867
3219
  workdir: process.cwd(),
2868
3220
  role,
2869
3221
  model,
2870
3222
  autoApprove: true,
2871
- promptFile: roleFile && fs9.existsSync(roleFile) ? roleFile : void 0
3223
+ promptFile: roleFile && fs12.existsSync(roleFile) ? roleFile : void 0
2872
3224
  });
2873
3225
  }
2874
3226
  var init_launch_cmd = __esm({
@@ -2891,8 +3243,8 @@ var init_drivers = __esm({
2891
3243
 
2892
3244
  // packages/agents/dist/projection/cursor.js
2893
3245
  import { mkdir, readFile, writeFile } from "fs/promises";
2894
- import path9 from "path";
2895
- import os2 from "os";
3246
+ import path12 from "path";
3247
+ import os4 from "os";
2896
3248
  function renderMdc(source) {
2897
3249
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
2898
3250
 
@@ -2940,7 +3292,7 @@ function createCursorEmitter() {
2940
3292
  if (scope === "user") {
2941
3293
  return [
2942
3294
  {
2943
- path: path9.join(os2.homedir(), ".cursor/rules/squadrant-global.mdc"),
3295
+ path: path12.join(os4.homedir(), ".cursor/rules/squadrant-global.mdc"),
2944
3296
  shared: false,
2945
3297
  format: "mdc"
2946
3298
  }
@@ -2950,7 +3302,7 @@ function createCursorEmitter() {
2950
3302
  return [];
2951
3303
  return [
2952
3304
  {
2953
- path: path9.join(projectRoot, ".cursor/rules/squadrant.mdc"),
3305
+ path: path12.join(projectRoot, ".cursor/rules/squadrant.mdc"),
2954
3306
  shared: false,
2955
3307
  format: "mdc"
2956
3308
  }
@@ -2967,7 +3319,7 @@ function createCursorEmitter() {
2967
3319
  diff: buildDiff(existing, generated)
2968
3320
  };
2969
3321
  }
2970
- await mkdir(path9.dirname(dest.path), { recursive: true });
3322
+ await mkdir(path12.dirname(dest.path), { recursive: true });
2971
3323
  await writeFile(dest.path, generated, "utf-8");
2972
3324
  return {
2973
3325
  written: true,
@@ -3018,8 +3370,8 @@ var init_marker = __esm({
3018
3370
 
3019
3371
  // packages/agents/dist/projection/codex.js
3020
3372
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
3021
- import path10 from "path";
3022
- import os3 from "os";
3373
+ import path13 from "path";
3374
+ import os5 from "os";
3023
3375
  function renderMarkdown(source) {
3024
3376
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
3025
3377
 
@@ -3043,7 +3395,7 @@ function createCodexEmitter() {
3043
3395
  destinations(scope, projectRoot) {
3044
3396
  if (scope === "user") {
3045
3397
  return [{
3046
- path: path10.join(os3.homedir(), ".codex/AGENTS.md"),
3398
+ path: path13.join(os5.homedir(), ".codex/AGENTS.md"),
3047
3399
  shared: true,
3048
3400
  format: "markdown"
3049
3401
  }];
@@ -3051,7 +3403,7 @@ function createCodexEmitter() {
3051
3403
  if (!projectRoot)
3052
3404
  return [];
3053
3405
  return [{
3054
- path: path10.join(projectRoot, "AGENTS.md"),
3406
+ path: path13.join(projectRoot, "AGENTS.md"),
3055
3407
  shared: true,
3056
3408
  format: "markdown"
3057
3409
  }];
@@ -3072,7 +3424,7 @@ ${existing ?? ""}
3072
3424
  ${generated}`
3073
3425
  };
3074
3426
  }
3075
- await mkdir2(path10.dirname(dest.path), { recursive: true });
3427
+ await mkdir2(path13.dirname(dest.path), { recursive: true });
3076
3428
  await writeFile2(dest.path, generated, "utf-8");
3077
3429
  return {
3078
3430
  written: true,
@@ -3090,8 +3442,8 @@ var init_codex2 = __esm({
3090
3442
 
3091
3443
  // packages/agents/dist/projection/gemini.js
3092
3444
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
3093
- import path11 from "path";
3094
- import os4 from "os";
3445
+ import path14 from "path";
3446
+ import os6 from "os";
3095
3447
  function renderMarkdown2(source) {
3096
3448
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
3097
3449
 
@@ -3115,7 +3467,7 @@ function createGeminiEmitter() {
3115
3467
  destinations(scope, projectRoot) {
3116
3468
  if (scope === "user") {
3117
3469
  return [{
3118
- path: path11.join(os4.homedir(), ".gemini/GEMINI.md"),
3470
+ path: path14.join(os6.homedir(), ".gemini/GEMINI.md"),
3119
3471
  shared: true,
3120
3472
  format: "markdown"
3121
3473
  }];
@@ -3123,7 +3475,7 @@ function createGeminiEmitter() {
3123
3475
  if (!projectRoot)
3124
3476
  return [];
3125
3477
  return [{
3126
- path: path11.join(projectRoot, "GEMINI.md"),
3478
+ path: path14.join(projectRoot, "GEMINI.md"),
3127
3479
  shared: true,
3128
3480
  format: "markdown"
3129
3481
  }];
@@ -3144,7 +3496,7 @@ ${existing ?? ""}
3144
3496
  ${generated}`
3145
3497
  };
3146
3498
  }
3147
- await mkdir3(path11.dirname(dest.path), { recursive: true });
3499
+ await mkdir3(path14.dirname(dest.path), { recursive: true });
3148
3500
  await writeFile3(dest.path, generated, "utf-8");
3149
3501
  return {
3150
3502
  written: true,
@@ -3162,8 +3514,8 @@ var init_gemini2 = __esm({
3162
3514
 
3163
3515
  // packages/agents/dist/projection/opencode.js
3164
3516
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
3165
- import path12 from "path";
3166
- import os5 from "os";
3517
+ import path15 from "path";
3518
+ import os7 from "os";
3167
3519
  function renderMarkdown3(source) {
3168
3520
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
3169
3521
 
@@ -3187,7 +3539,7 @@ function createOpencodeEmitter() {
3187
3539
  destinations(scope, projectRoot) {
3188
3540
  if (scope === "user") {
3189
3541
  return [{
3190
- path: path12.join(os5.homedir(), ".config", "opencode", "AGENTS.md"),
3542
+ path: path15.join(os7.homedir(), ".config", "opencode", "AGENTS.md"),
3191
3543
  shared: true,
3192
3544
  format: "markdown"
3193
3545
  }];
@@ -3195,7 +3547,7 @@ function createOpencodeEmitter() {
3195
3547
  if (!projectRoot)
3196
3548
  return [];
3197
3549
  return [{
3198
- path: path12.join(projectRoot, "AGENTS.md"),
3550
+ path: path15.join(projectRoot, "AGENTS.md"),
3199
3551
  shared: true,
3200
3552
  format: "markdown"
3201
3553
  }];
@@ -3216,7 +3568,7 @@ ${existing ?? ""}
3216
3568
  ${generated}`
3217
3569
  };
3218
3570
  }
3219
- await mkdir4(path12.dirname(dest.path), { recursive: true });
3571
+ await mkdir4(path15.dirname(dest.path), { recursive: true });
3220
3572
  await writeFile4(dest.path, generated, "utf-8");
3221
3573
  return {
3222
3574
  written: true,
@@ -4081,8 +4433,8 @@ function resolveLastAssistantText(payload) {
4081
4433
  const derived = deriveTranscriptPath(p?.session_id, cwd);
4082
4434
  if (derived)
4083
4435
  candidates.push(derived);
4084
- for (const path26 of candidates) {
4085
- const text = readLastAssistantText(path26);
4436
+ for (const path29 of candidates) {
4437
+ const text = readLastAssistantText(path29);
4086
4438
  if (text != null)
4087
4439
  return text;
4088
4440
  }
@@ -4481,11 +4833,11 @@ var init_dist4 = __esm({
4481
4833
  // packages/cli/src/index.ts
4482
4834
  init_dist();
4483
4835
  init_dist2();
4484
- import { Command as Command27 } from "commander";
4485
- import { readFileSync as readFileSync11, existsSync as existsSync10, writeFileSync as writeFileSync9 } from "fs";
4836
+ import { Command as Command28 } from "commander";
4837
+ import { readFileSync as readFileSync11, existsSync as existsSync11, writeFileSync as writeFileSync9 } from "fs";
4486
4838
  import { fileURLToPath as fileURLToPath6 } from "url";
4487
- import { dirname as dirname6, join as join21 } from "path";
4488
- import { homedir as homedir14 } from "os";
4839
+ import { dirname as dirname7, join as join23 } from "path";
4840
+ import { homedir as homedir15 } from "os";
4489
4841
 
4490
4842
  // packages/cli/src/commands/doctor.ts
4491
4843
  init_dist();
@@ -4494,9 +4846,9 @@ init_dist();
4494
4846
  init_dist3();
4495
4847
  import { Command } from "commander";
4496
4848
  import { execSync as execSync8 } from "child_process";
4497
- import fs10 from "fs";
4849
+ import fs13 from "fs";
4498
4850
  import { stat } from "fs/promises";
4499
- import path13 from "path";
4851
+ import path16 from "path";
4500
4852
  import chalk3 from "chalk";
4501
4853
 
4502
4854
  // packages/cli/src/commands/health-view.ts
@@ -4598,7 +4950,7 @@ function settingsHaveAgentTeams() {
4598
4950
  try {
4599
4951
  const home = process.env.HOME || "";
4600
4952
  const settings = JSON.parse(
4601
- fs10.readFileSync(`${home}/.claude/settings.json`, "utf-8")
4953
+ fs13.readFileSync(`${home}/.claude/settings.json`, "utf-8")
4602
4954
  );
4603
4955
  return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
4604
4956
  } catch {
@@ -4609,7 +4961,7 @@ function pluginInstalled(pluginKey) {
4609
4961
  try {
4610
4962
  const home = process.env.HOME || "";
4611
4963
  const plugins = JSON.parse(
4612
- fs10.readFileSync(
4964
+ fs13.readFileSync(
4613
4965
  `${home}/.claude/plugins/installed_plugins.json`,
4614
4966
  "utf-8"
4615
4967
  )
@@ -4641,7 +4993,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4641
4993
  const results = [];
4642
4994
  results.push(check("Claude Code installed", commandExists("claude")));
4643
4995
  results.push(check(`Claude Code version >= ${compatManifest.tools.claude.min}`, claudeVersionOk()));
4644
- results.push(check("Obsidian installed", commandExists("obsidian") || fs10.existsSync("/Applications/Obsidian.app")));
4996
+ results.push(check("Obsidian installed", commandExists("obsidian") || fs13.existsSync("/Applications/Obsidian.app")));
4645
4997
  results.push(check("Node.js >= 18", nodeVersionOk()));
4646
4998
  results.push(
4647
4999
  check(
@@ -4714,7 +5066,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4714
5066
  const emitter = projectionRegistry.get(name);
4715
5067
  const [userDest] = emitter.destinations("user");
4716
5068
  if (!userDest) continue;
4717
- const dir = path13.dirname(userDest.path);
5069
+ const dir = path16.dirname(userDest.path);
4718
5070
  let status;
4719
5071
  try {
4720
5072
  await stat(dir);
@@ -4727,7 +5079,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4727
5079
  results.push(
4728
5080
  check(
4729
5081
  "Squadrant config exists",
4730
- fs10.existsSync(
5082
+ fs13.existsSync(
4731
5083
  process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
4732
5084
  )
4733
5085
  )
@@ -4800,39 +5152,39 @@ init_dist();
4800
5152
  init_dist3();
4801
5153
  init_dist();
4802
5154
  import { Command as Command2 } from "commander";
4803
- import fs11 from "fs";
4804
- import path14 from "path";
4805
- import os6 from "os";
5155
+ import fs14 from "fs";
5156
+ import path17 from "path";
5157
+ import os8 from "os";
4806
5158
  import chalk4 from "chalk";
4807
5159
  function findPackageRoot() {
4808
- let dir = path14.dirname(new URL(import.meta.url).pathname);
5160
+ let dir = path17.dirname(new URL(import.meta.url).pathname);
4809
5161
  while (dir !== "/") {
4810
- if (fs11.existsSync(path14.join(dir, "package.json"))) return dir;
4811
- dir = path14.dirname(dir);
5162
+ if (fs14.existsSync(path17.join(dir, "package.json"))) return dir;
5163
+ dir = path17.dirname(dir);
4812
5164
  }
4813
5165
  return process.cwd();
4814
5166
  }
4815
5167
  function copyDirRecursive(src, dest) {
4816
- fs11.mkdirSync(dest, { recursive: true });
4817
- for (const entry of fs11.readdirSync(src, { withFileTypes: true })) {
4818
- const srcPath = path14.join(src, entry.name);
4819
- const destPath = path14.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);
4820
5172
  if (entry.isDirectory()) {
4821
5173
  copyDirRecursive(srcPath, destPath);
4822
5174
  } else {
4823
- fs11.copyFileSync(srcPath, destPath);
5175
+ fs14.copyFileSync(srcPath, destPath);
4824
5176
  }
4825
5177
  }
4826
5178
  }
4827
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) => {
4828
5180
  const hubPath = resolveHome(opts.hub);
4829
5181
  const pkgRoot = findPackageRoot();
4830
- const configDir = path14.join(os6.homedir(), ".config", "squadrant");
5182
+ const configDir = path17.join(os8.homedir(), ".config", "squadrant");
4831
5183
  console.log(chalk4.bold("\nSquadrant Init\n"));
4832
5184
  const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
4833
5185
  try {
4834
- if (fs11.existsSync(DEFAULT_CONFIG_PATH)) {
4835
- const existing = JSON.parse(fs11.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"));
4836
5188
  const wsName = existing.workspace ?? "obsidian";
4837
5189
  registry.get(wsName);
4838
5190
  }
@@ -4840,7 +5192,7 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4840
5192
  console.log(chalk4.red(` \u2718 ${err.message}`));
4841
5193
  return;
4842
5194
  }
4843
- if (fs11.existsSync(DEFAULT_CONFIG_PATH)) {
5195
+ if (fs14.existsSync(DEFAULT_CONFIG_PATH)) {
4844
5196
  console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
4845
5197
  } else {
4846
5198
  const config = getDefaultConfig();
@@ -4848,37 +5200,37 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4848
5200
  saveConfig(config);
4849
5201
  console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
4850
5202
  }
4851
- const hubTemplate = path14.join(pkgRoot, "obsidian", "hub");
4852
- if (fs11.existsSync(hubPath)) {
5203
+ const hubTemplate = path17.join(pkgRoot, "obsidian", "hub");
5204
+ if (fs14.existsSync(hubPath)) {
4853
5205
  console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}, skipping`));
4854
- } else if (fs11.existsSync(hubTemplate)) {
5206
+ } else if (fs14.existsSync(hubTemplate)) {
4855
5207
  copyDirRecursive(hubTemplate, hubPath);
4856
5208
  console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
4857
5209
  } else {
4858
- fs11.mkdirSync(hubPath, { recursive: true });
5210
+ fs14.mkdirSync(hubPath, { recursive: true });
4859
5211
  console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty dir at ${hubPath}`));
4860
5212
  }
4861
- const hubDashboardSrc = path14.join(pkgRoot, "obsidian", "hub", "dashboard.md");
4862
- const hubDashboardDest = path14.join(hubPath, "dashboard.md");
4863
- if (fs11.existsSync(hubDashboardSrc)) {
4864
- fs11.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);
4865
5217
  console.log(chalk4.green(` \u2714 Dashboard page refreshed at ${hubDashboardDest}`));
4866
5218
  }
4867
- const projectsDir = path14.join(hubPath, "projects");
4868
- fs11.mkdirSync(projectsDir, { recursive: true });
5219
+ const projectsDir = path17.join(hubPath, "projects");
5220
+ fs14.mkdirSync(projectsDir, { recursive: true });
4869
5221
  ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
4870
5222
  console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
4871
- const settingsPath = path14.join(os6.homedir(), ".claude", "settings.json");
5223
+ const settingsPath = path17.join(os8.homedir(), ".claude", "settings.json");
4872
5224
  try {
4873
5225
  let settings = {};
4874
- if (fs11.existsSync(settingsPath)) {
4875
- settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
5226
+ if (fs14.existsSync(settingsPath)) {
5227
+ settings = JSON.parse(fs14.readFileSync(settingsPath, "utf-8"));
4876
5228
  }
4877
5229
  const env = settings.env || {};
4878
5230
  if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
4879
5231
  settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
4880
- fs11.mkdirSync(path14.dirname(settingsPath), { recursive: true });
4881
- fs11.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");
4882
5234
  console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
4883
5235
  } else {
4884
5236
  console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
@@ -4899,7 +5251,7 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4899
5251
  console.log(chalk4.cyan(` ${hubPath}`));
4900
5252
  console.log("");
4901
5253
  console.log(" 4. Run " + chalk4.cyan("squadrant doctor") + " to verify setup\n");
4902
- if (!fs11.existsSync("/Applications/cmux.app")) {
5254
+ if (!fs14.existsSync("/Applications/cmux.app")) {
4903
5255
  console.log(chalk4.yellow(" \u26A0 cmux not found \u2014 download from https://cmux.dev\n"));
4904
5256
  }
4905
5257
  });
@@ -4907,26 +5259,70 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4907
5259
  // packages/cli/src/commands/projects.ts
4908
5260
  init_dist();
4909
5261
  import { Command as Command3 } from "commander";
4910
- import fs12 from "fs";
4911
- import path15 from "path";
5262
+ import fs15 from "fs";
5263
+ import path18 from "path";
4912
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
+ }
4913
5309
  function findPackageRoot2() {
4914
- let dir = path15.dirname(new URL(import.meta.url).pathname);
5310
+ let dir = path18.dirname(new URL(import.meta.url).pathname);
4915
5311
  while (dir !== "/") {
4916
- if (fs12.existsSync(path15.join(dir, "package.json"))) return dir;
4917
- dir = path15.dirname(dir);
5312
+ if (fs15.existsSync(path18.join(dir, "package.json"))) return dir;
5313
+ dir = path18.dirname(dir);
4918
5314
  }
4919
5315
  return process.cwd();
4920
5316
  }
4921
5317
  function copyDirRecursive2(src, dest) {
4922
- fs12.mkdirSync(dest, { recursive: true });
4923
- for (const entry of fs12.readdirSync(src, { withFileTypes: true })) {
4924
- const srcPath = path15.join(src, entry.name);
4925
- const destPath = path15.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);
4926
5322
  if (entry.isDirectory()) {
4927
5323
  copyDirRecursive2(srcPath, destPath);
4928
5324
  } else {
4929
- fs12.copyFileSync(srcPath, destPath);
5325
+ fs15.copyFileSync(srcPath, destPath);
4930
5326
  }
4931
5327
  }
4932
5328
  }
@@ -4953,7 +5349,7 @@ var listCmd = new Command3("list").description("List registered projects").actio
4953
5349
  }
4954
5350
  console.log("");
4955
5351
  });
4956
- 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) => {
4957
5353
  const config = loadConfig();
4958
5354
  if (config.projects[name]) {
4959
5355
  console.log(chalk5.yellow(`
@@ -4962,7 +5358,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
4962
5358
  process.exit(1);
4963
5359
  }
4964
5360
  const resolvedPath = resolveHome(projectPath);
4965
- if (!fs12.existsSync(path15.join(resolvedPath, ".git"))) {
5361
+ if (!fs15.existsSync(path18.join(resolvedPath, ".git"))) {
4966
5362
  console.log(chalk5.yellow(`
4967
5363
  \u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
4968
5364
  `));
@@ -5015,7 +5411,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
5015
5411
  \u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
5016
5412
  }
5017
5413
  }
5018
- const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path15.join(config.hubVault, "spokes", name);
5414
+ const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path18.join(config.hubVault, "spokes", name);
5019
5415
  const project = {
5020
5416
  path: resolvedPath,
5021
5417
  captainName,
@@ -5028,21 +5424,22 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
5028
5424
  saveConfig(config);
5029
5425
  console.log(chalk5.green(`
5030
5426
  \u2714 Project '${name}' registered`));
5427
+ restartAfterProjectsAdd({ noRestart: opts.restart === false });
5031
5428
  const pkgRoot = findPackageRoot2();
5032
- const spokeTemplate = path15.join(pkgRoot, "obsidian", "spoke");
5033
- if (fs12.existsSync(spokeVault)) {
5429
+ const spokeTemplate = path18.join(pkgRoot, "obsidian", "spoke");
5430
+ if (fs15.existsSync(spokeVault)) {
5034
5431
  console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
5035
- } else if (fs12.existsSync(spokeTemplate)) {
5432
+ } else if (fs15.existsSync(spokeTemplate)) {
5036
5433
  copyDirRecursive2(spokeTemplate, spokeVault);
5037
- const statusPath = path15.join(spokeVault, "status.md");
5038
- if (fs12.existsSync(statusPath)) {
5039
- const content = fs12.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");
5040
5437
  const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
5041
- fs12.writeFileSync(statusPath, updated);
5438
+ fs15.writeFileSync(statusPath, updated);
5042
5439
  }
5043
5440
  console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
5044
5441
  } else {
5045
- fs12.mkdirSync(spokeVault, { recursive: true });
5442
+ fs15.mkdirSync(spokeVault, { recursive: true });
5046
5443
  console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
5047
5444
  }
5048
5445
  console.log("");
@@ -5143,10 +5540,10 @@ init_dist4();
5143
5540
  init_dist();
5144
5541
  import { Command as Command5 } from "commander";
5145
5542
  import { execSync as execSync9 } from "child_process";
5146
- import path16 from "path";
5147
- import os7 from "os";
5543
+ import path19 from "path";
5544
+ import os9 from "os";
5148
5545
  import chalk7 from "chalk";
5149
- var TEMPLATES_DIR = path16.join(os7.homedir(), ".config", "squadrant", "templates");
5546
+ var TEMPLATES_DIR = path19.join(os9.homedir(), ".config", "squadrant", "templates");
5150
5547
  var TASK_PROMPTS = {
5151
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.",
5152
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.",
@@ -5179,7 +5576,7 @@ async function runCommandSpawn(input) {
5179
5576
  if (!agent) {
5180
5577
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
5181
5578
  }
5182
- const promptFile = path16.join(TEMPLATES_DIR, `command.${agent.templateSuffix}.md`);
5579
+ const promptFile = path19.join(TEMPLATES_DIR, `command.${agent.templateSuffix}.md`);
5183
5580
  const cliCommand = agent.buildCommand({
5184
5581
  prompt,
5185
5582
  workdir: process.cwd(),
@@ -5206,9 +5603,9 @@ var commandCommand = new Command5("command").description("Spawn a one-shot Comma
5206
5603
  init_dist();
5207
5604
  init_dist();
5208
5605
  import { Command as Command9 } from "commander";
5209
- import fs13 from "fs";
5210
- import path17 from "path";
5211
- import os8 from "os";
5606
+ import fs16 from "fs";
5607
+ import path20 from "path";
5608
+ import os10 from "os";
5212
5609
  import chalk9 from "chalk";
5213
5610
 
5214
5611
  // packages/cli/src/control/crew-routing.ts
@@ -5241,8 +5638,8 @@ init_dist4();
5241
5638
  import { Command as Command8 } from "commander";
5242
5639
  import { createConnection as createConnection3 } from "net";
5243
5640
  import { randomUUID as randomUUID3 } from "crypto";
5244
- import { homedir as homedir10 } from "os";
5245
- import { join as join15 } from "path";
5641
+ import { homedir as homedir11 } from "os";
5642
+ import { join as join16 } from "path";
5246
5643
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
5247
5644
 
5248
5645
  // packages/cli/src/commands/crew-output.ts
@@ -5301,11 +5698,11 @@ init_dist2();
5301
5698
  import { Command as Command6 } from "commander";
5302
5699
  import chalk8 from "chalk";
5303
5700
  import { createConnection as createConnection2 } from "net";
5304
- import { homedir as homedir9 } from "os";
5305
- import { join as join14 } from "path";
5701
+ import { homedir as homedir10 } from "os";
5702
+ import { join as join15 } from "path";
5306
5703
  import { createInterface } from "readline";
5307
5704
  function socketPath() {
5308
- return process.env.SQUADRANTD_SOCK ?? join14(homedir9(), ".config", "squadrant", "squadrant.sock");
5705
+ return process.env.SQUADRANTD_SOCK ?? join15(homedir10(), ".config", "squadrant", "squadrant.sock");
5309
5706
  }
5310
5707
  function rule(width, ch = "\u2500") {
5311
5708
  return ch.repeat(Math.max(0, width));
@@ -5541,7 +5938,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
5541
5938
  });
5542
5939
 
5543
5940
  // packages/cli/src/commands/crew-control.ts
5544
- var SOCK2 = join15(homedir10(), ".config", "squadrant", "squadrant.sock");
5941
+ var SOCK2 = join16(homedir11(), ".config", "squadrant", "squadrant.sock");
5545
5942
  var CODEX_FIRST_TURN_DELAY_MS = 1500;
5546
5943
  async function sendCodexFirstTurn(taskId, text) {
5547
5944
  await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
@@ -5646,9 +6043,9 @@ function buildSignalRequest(signal, o) {
5646
6043
  return { kind: "event", project, event };
5647
6044
  }
5648
6045
  function defaultWriteResult(id, payload) {
5649
- const dir = join15(homedir10(), ".config", "squadrant", "state", "_results");
6046
+ const dir = join16(homedir11(), ".config", "squadrant", "state", "_results");
5650
6047
  mkdirSync6(dir, { recursive: true });
5651
- const file = join15(dir, `${id}.txt`);
6048
+ const file = join16(dir, `${id}.txt`);
5652
6049
  writeFileSync7(file, payload);
5653
6050
  return file;
5654
6051
  }
@@ -5749,7 +6146,7 @@ addControlPlaneCrewCommands(crewControlCommand);
5749
6146
  // packages/cli/src/lib/per-crew-settings.ts
5750
6147
  init_dist4();
5751
6148
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
5752
- import { join as join16 } from "path";
6149
+ import { join as join17 } from "path";
5753
6150
  var CREW_PERMISSION_ALLOWLIST = [
5754
6151
  // git — read + safe mutations (reset/clean/config intentionally excluded)
5755
6152
  "Bash(git status:*)",
@@ -5837,9 +6234,9 @@ function mergeCrewPermissions(settings) {
5837
6234
  return next;
5838
6235
  }
5839
6236
  function writePerCrewSettingsLocal(o) {
5840
- const dir = join16(o.projectCwd, ".claude");
6237
+ const dir = join17(o.projectCwd, ".claude");
5841
6238
  mkdirSync7(dir, { recursive: true });
5842
- const file = join16(dir, "settings.local.json");
6239
+ const file = join17(dir, "settings.local.json");
5843
6240
  let existing = {};
5844
6241
  try {
5845
6242
  const raw = readFileSync9(file, "utf-8");
@@ -5852,9 +6249,9 @@ function writePerCrewSettingsLocal(o) {
5852
6249
  return file;
5853
6250
  }
5854
6251
  function writePerCrewOpencodeConfig(o) {
5855
- const dir = join16(o.stateRoot, o.project, o.taskId);
6252
+ const dir = join17(o.stateRoot, o.project, o.taskId);
5856
6253
  mkdirSync7(dir, { recursive: true });
5857
- const file = join16(dir, "opencode.json");
6254
+ const file = join17(dir, "opencode.json");
5858
6255
  const config = {
5859
6256
  permission: {
5860
6257
  read: "allow",
@@ -5875,7 +6272,7 @@ function writePerCrewOpencodeConfig(o) {
5875
6272
 
5876
6273
  // packages/cli/src/commands/crew.ts
5877
6274
  init_dist2();
5878
- var TEMPLATES_DIR2 = path17.join(os8.homedir(), ".config", "squadrant", "templates");
6275
+ var TEMPLATES_DIR2 = path20.join(os10.homedir(), ".config", "squadrant", "templates");
5879
6276
  async function runCrewSpawn(input) {
5880
6277
  const config = loadConfig();
5881
6278
  const proj = config.projects[input.project];
@@ -5923,8 +6320,8 @@ async function runCrewSpawn(input) {
5923
6320
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
5924
6321
  }
5925
6322
  if (agentName === "codex") {
5926
- const codexRoleFile = path17.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
5927
- const roleInstructions = fs13.existsSync(codexRoleFile) ? fs13.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;
5928
6325
  return runCodexInteractiveSpawn({
5929
6326
  project: input.project,
5930
6327
  task: input.task,
@@ -5937,7 +6334,7 @@ async function runCrewSpawn(input) {
5937
6334
  roleInstructions
5938
6335
  });
5939
6336
  }
5940
- const promptFile = path17.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
6337
+ const promptFile = path20.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
5941
6338
  const interactive = agent.name === "claude" || agent.name === "opencode";
5942
6339
  const crewRole = config.defaults.roles?.crew;
5943
6340
  const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
@@ -5994,7 +6391,7 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
5994
6391
  });
5995
6392
  const rec = await squadrantdCall(req);
5996
6393
  const opencodeConfigPath = writePerCrewOpencodeConfig({
5997
- stateRoot: path17.join(os8.homedir(), ".config", "squadrant", "state"),
6394
+ stateRoot: path20.join(os10.homedir(), ".config", "squadrant", "state"),
5998
6395
  project: input.project,
5999
6396
  taskId: rec.id,
6000
6397
  // CP3 opt-in: --approval gates bash so the captain approves shell commands.
@@ -6234,11 +6631,11 @@ init_dist3();
6234
6631
  init_dist();
6235
6632
  init_dist();
6236
6633
  import { Command as Command10 } from "commander";
6237
- import fs14 from "fs";
6238
- import path18 from "path";
6239
- import os9 from "os";
6634
+ import fs17 from "fs";
6635
+ import path21 from "path";
6636
+ import os11 from "os";
6240
6637
  import chalk10 from "chalk";
6241
- var TEMPLATES_DIR3 = path18.join(os9.homedir(), ".config", "squadrant", "templates");
6638
+ var TEMPLATES_DIR3 = path21.join(os11.homedir(), ".config", "squadrant", "templates");
6242
6639
  var SIDE_ROLES = ["research", "debug"];
6243
6640
  function shellQuote2(p) {
6244
6641
  return "'" + p.replace(/'/g, "'\\''") + "'";
@@ -6329,7 +6726,7 @@ async function runSideSpawn(input) {
6329
6726
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
6330
6727
  }
6331
6728
  const sideModel = sideRole?.model;
6332
- const promptFile = path18.join(
6729
+ const promptFile = path21.join(
6333
6730
  TEMPLATES_DIR3,
6334
6731
  `side.${input.role}.${agent.templateSuffix}.md`
6335
6732
  );
@@ -6340,7 +6737,7 @@ async function runSideSpawn(input) {
6340
6737
  prompt: input.topic,
6341
6738
  workdir: spawnCwd,
6342
6739
  role: "side",
6343
- promptFile: fs14.existsSync(promptFile) ? promptFile : void 0,
6740
+ promptFile: fs17.existsSync(promptFile) ? promptFile : void 0,
6344
6741
  interactive: true,
6345
6742
  permissionMode: config.defaults.permissions?.crew ?? "auto",
6346
6743
  ...sideModel ? { model: sideModel } : {}
@@ -6397,7 +6794,7 @@ async function runSideClose(project, name) {
6397
6794
  project,
6398
6795
  name
6399
6796
  );
6400
- if (fs14.existsSync(wtPath)) {
6797
+ if (fs17.existsSync(wtPath)) {
6401
6798
  try {
6402
6799
  removeWorktree(proj.path, wtPath);
6403
6800
  } catch (e) {
@@ -6485,8 +6882,8 @@ init_dist();
6485
6882
  init_dist3();
6486
6883
  import { Command as Command11 } from "commander";
6487
6884
  import { execSync as execSync10 } from "child_process";
6488
- import { homedir as homedir12 } from "os";
6489
- import { join as join18 } from "path";
6885
+ import { homedir as homedir13 } from "os";
6886
+ import { join as join19 } from "path";
6490
6887
  import chalk12 from "chalk";
6491
6888
 
6492
6889
  // packages/web/dist/read-status.js
@@ -6625,8 +7022,8 @@ function renderDashboard(rows, opts) {
6625
7022
 
6626
7023
  // packages/web/dist/sync-hub.js
6627
7024
  init_dist();
6628
- import fs15 from "fs";
6629
- import path19 from "path";
7025
+ import fs18 from "fs";
7026
+ import path22 from "path";
6630
7027
  function buildMirrorMarkdown(s) {
6631
7028
  const fenced = "```";
6632
7029
  return [
@@ -6652,15 +7049,15 @@ function buildMirrorMarkdown(s) {
6652
7049
  function syncHub(deps) {
6653
7050
  if (!deps.config.hubVault)
6654
7051
  return [];
6655
- const writeFile5 = deps.writeFile ?? ((p, c) => fs15.writeFileSync(p, c));
6656
- const mkdir5 = deps.mkdir ?? ((p) => fs15.mkdirSync(p, { recursive: true }));
6657
- const projectsDir = path19.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");
6658
7055
  mkdir5(projectsDir);
6659
7056
  const out = [];
6660
7057
  for (const s of deps.statuses) {
6661
7058
  if (s.state === "unknown")
6662
7059
  continue;
6663
- const hubPath = path19.join(projectsDir, `${s.project}.md`);
7060
+ const hubPath = path22.join(projectsDir, `${s.project}.md`);
6664
7061
  try {
6665
7062
  writeFile5(hubPath, buildMirrorMarkdown(s));
6666
7063
  out.push({ project: s.project, hubPath });
@@ -6682,9 +7079,9 @@ function mergeSnapshot(daemon, external, now) {
6682
7079
  // packages/web/dist/probes.js
6683
7080
  init_dist();
6684
7081
  init_dist();
6685
- import { join as join17 } from "path";
6686
- import { homedir as homedir11 } from "os";
6687
- 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";
6688
7085
  import { execFile as execFile2 } from "child_process";
6689
7086
  var DEFAULT_TIMEOUT_MS = 2e3;
6690
7087
  var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
@@ -6720,7 +7117,7 @@ function vaultProbe(run, dir) {
6720
7117
  return { state: "unknown", detail: "no vault configured" };
6721
7118
  if (!run.pathExists(dir))
6722
7119
  return { state: "gone", detail: "vault directory missing" };
6723
- if (!run.pathExists(join17(dir, ".obsidian")))
7120
+ if (!run.pathExists(join18(dir, ".obsidian")))
6724
7121
  return { state: "gone", detail: "no .obsidian/ (not a vault)" };
6725
7122
  return { state: "alive" };
6726
7123
  } catch {
@@ -6788,10 +7185,10 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
6788
7185
  const sessions = probeSessions(run);
6789
7186
  return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
6790
7187
  }
6791
- var SESSIONS_PATH = join17(homedir11(), ".config", "squadrant", "sessions.json");
7188
+ var SESSIONS_PATH = join18(homedir12(), ".config", "squadrant", "sessions.json");
6792
7189
  function onPath(cli) {
6793
7190
  const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
6794
- return dirs.some((d) => existsSync9(join17(d, cli)));
7191
+ return dirs.some((d) => existsSync10(join18(d, cli)));
6795
7192
  }
6796
7193
  function readSessionsHashes() {
6797
7194
  const raw = JSON.parse(readFileSync10(SESSIONS_PATH, "utf-8"));
@@ -6808,7 +7205,7 @@ function defaultProbeRunners() {
6808
7205
  }
6809
7206
  }),
6810
7207
  probeOnPath: async (cli) => onPath(cli),
6811
- pathExists: (p) => existsSync9(p),
7208
+ pathExists: (p) => existsSync10(p),
6812
7209
  loadConfig: () => loadConfig(),
6813
7210
  loadSessionsHashes: () => readSessionsHashes()
6814
7211
  };
@@ -7461,7 +7858,7 @@ async function startWebServer(opts) {
7461
7858
 
7462
7859
  // packages/cli/src/commands/dashboard.ts
7463
7860
  init_dist();
7464
- var SOCK3 = join18(homedir12(), ".config", "squadrant", "squadrant.sock");
7861
+ var SOCK3 = join19(homedir13(), ".config", "squadrant", "squadrant.sock");
7465
7862
  function detectCurrentWorkspace2() {
7466
7863
  const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
7467
7864
  const match = out.match(/workspace:\d+/);
@@ -7549,13 +7946,13 @@ init_dist3();
7549
7946
  init_dist2();
7550
7947
  import { Command as Command12 } from "commander";
7551
7948
  import { execSync as execSync11 } from "child_process";
7552
- import fs16 from "fs";
7553
- import path20 from "path";
7554
- import os10 from "os";
7949
+ import fs19 from "fs";
7950
+ import path23 from "path";
7951
+ import os12 from "os";
7555
7952
  import chalk13 from "chalk";
7556
7953
  var CMUX_APP = "/Applications/cmux.app";
7557
- var TEMPLATES_DIR4 = path20.join(os10.homedir(), ".config", "squadrant", "templates");
7558
- var SESSIONS_PATH2 = path20.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");
7559
7956
  function ensureCmuxReady() {
7560
7957
  if (isInsideCmux()) return;
7561
7958
  console.log(chalk13.yellow("\n Not running inside cmux. Opening cmux app...\n"));
@@ -7662,12 +8059,12 @@ var launchCommand = new Command12("launch").description(
7662
8059
  }
7663
8060
  if (opts.all) {
7664
8061
  const hubPath = resolveHome(config.hubVault);
7665
- fs16.mkdirSync(hubPath, { recursive: true });
8062
+ fs19.mkdirSync(hubPath, { recursive: true });
7666
8063
  console.log(chalk13.bold("\nLaunching all captain workspaces\n"));
7667
8064
  for (const [name, proj] of Object.entries(config.projects)) {
7668
8065
  const projPath = resolveHome(proj.path);
7669
8066
  const spokePath = resolveHome(proj.spokeVault);
7670
- if (!fs16.existsSync(spokePath)) {
8067
+ if (!fs19.existsSync(spokePath)) {
7671
8068
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
7672
8069
  await ensureSpokeLayout(spokeDriver);
7673
8070
  console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -7698,7 +8095,7 @@ var launchCommand = new Command12("launch").description(
7698
8095
  const proj = config.projects[project];
7699
8096
  const projPath = resolveHome(proj.path);
7700
8097
  const spokePath = resolveHome(proj.spokeVault);
7701
- if (!fs16.existsSync(spokePath)) {
8098
+ if (!fs19.existsSync(spokePath)) {
7702
8099
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
7703
8100
  await ensureSpokeLayout(spokeDriver);
7704
8101
  console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -7833,24 +8230,24 @@ Shutting down captain workspace for '${project}'...
7833
8230
  // packages/cli/src/commands/feedback.ts
7834
8231
  init_dist();
7835
8232
  import { Command as Command14 } from "commander";
7836
- import fs17 from "fs";
7837
- import os11 from "os";
7838
- import path21 from "path";
8233
+ import fs20 from "fs";
8234
+ import os13 from "os";
8235
+ import path24 from "path";
7839
8236
  import { fileURLToPath as fileURLToPath3 } from "url";
7840
8237
  import { execSync as execSync12 } from "child_process";
7841
8238
  import chalk15 from "chalk";
7842
8239
  var REPO_URL = "https://github.com/tu11aa/squadrant";
7843
8240
  function readPkgVersion() {
7844
8241
  try {
7845
- const pkgPath = path21.join(path21.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
7846
- return JSON.parse(fs17.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";
7847
8244
  } catch {
7848
8245
  return "unknown";
7849
8246
  }
7850
8247
  }
7851
8248
  function readMetrics(metricsPath) {
7852
8249
  try {
7853
- return JSON.parse(fs17.readFileSync(metricsPath, "utf-8"));
8250
+ return JSON.parse(fs20.readFileSync(metricsPath, "utf-8"));
7854
8251
  } catch {
7855
8252
  return {};
7856
8253
  }
@@ -7888,7 +8285,7 @@ function buildIssueUrl(metrics, squadrantVersion) {
7888
8285
  }
7889
8286
  var feedbackCommand = new Command14("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
7890
8287
  const config = loadConfig();
7891
- const metricsPath = config.metrics?.path || path21.join(os11.homedir(), ".config", "squadrant", "metrics.json");
8288
+ const metricsPath = config.metrics?.path || path24.join(os13.homedir(), ".config", "squadrant", "metrics.json");
7892
8289
  const metrics = readMetrics(metricsPath);
7893
8290
  const version = readStamp(config) ?? readPkgVersion();
7894
8291
  const issueUrl = buildIssueUrl(metrics, version);
@@ -7910,8 +8307,8 @@ init_dist();
7910
8307
  init_dist();
7911
8308
  init_dist3();
7912
8309
  import { Command as Command15 } from "commander";
7913
- import fs18 from "fs";
7914
- import path22 from "path";
8310
+ import fs21 from "fs";
8311
+ import path25 from "path";
7915
8312
  import chalk16 from "chalk";
7916
8313
  import matter3 from "gray-matter";
7917
8314
  function getDateStr(yesterday) {
@@ -7920,11 +8317,11 @@ function getDateStr(yesterday) {
7920
8317
  async function getProjectStandup(name, project, dateStr, registry, config) {
7921
8318
  const workspace = registry.forProject(name, config);
7922
8319
  const spokeVault = resolveHome(project.spokeVault);
7923
- const statusFile = path22.join(spokeVault, "status.md");
8320
+ const statusFile = path25.join(spokeVault, "status.md");
7924
8321
  let status = {};
7925
- if (fs18.existsSync(statusFile)) {
8322
+ if (fs21.existsSync(statusFile)) {
7926
8323
  try {
7927
- status = matter3(fs18.readFileSync(statusFile, "utf-8")).data;
8324
+ status = matter3(fs21.readFileSync(statusFile, "utf-8")).data;
7928
8325
  } catch {
7929
8326
  }
7930
8327
  }
@@ -8042,15 +8439,15 @@ init_dist();
8042
8439
  init_dist();
8043
8440
  init_dist3();
8044
8441
  import { Command as Command16 } from "commander";
8045
- import fs19 from "fs";
8046
- import path23 from "path";
8442
+ import fs22 from "fs";
8443
+ import path26 from "path";
8047
8444
  import chalk17 from "chalk";
8048
8445
  import matter4 from "gray-matter";
8049
8446
  function readStatus(spokeVault) {
8050
- const statusFile = path23.join(spokeVault, "status.md");
8051
- if (!fs19.existsSync(statusFile)) return {};
8447
+ const statusFile = path26.join(spokeVault, "status.md");
8448
+ if (!fs22.existsSync(statusFile)) return {};
8052
8449
  try {
8053
- return matter4(fs19.readFileSync(statusFile, "utf-8")).data;
8450
+ return matter4(fs22.readFileSync(statusFile, "utf-8")).data;
8054
8451
  } catch {
8055
8452
  return {};
8056
8453
  }
@@ -8360,9 +8757,9 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
8360
8757
  const config = loadConfig();
8361
8758
  const registry = buildRegistry2();
8362
8759
  try {
8363
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8760
+ const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8364
8761
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8365
- const content = await driver.read(path26);
8762
+ const content = await driver.read(path29);
8366
8763
  process.stdout.write(content);
8367
8764
  } catch (err) {
8368
8765
  console.error(chalk19.red(err.message));
@@ -8374,26 +8771,26 @@ workspaceCommand.command("write").description("Write content to a scope-relative
8374
8771
  const registry = buildRegistry2();
8375
8772
  try {
8376
8773
  let projectTarget;
8377
- let path26;
8774
+ let path29;
8378
8775
  let rawContent;
8379
8776
  if (opts.hub) {
8380
8777
  if (arg3 !== void 0) {
8381
8778
  throw new Error("With --hub, pass only the path and content");
8382
8779
  }
8383
8780
  projectTarget = void 0;
8384
- path26 = arg1;
8781
+ path29 = arg1;
8385
8782
  rawContent = arg2;
8386
8783
  } else {
8387
8784
  if (arg3 === void 0) {
8388
8785
  throw new Error("Missing content \u2014 usage: <project> <path> <content>");
8389
8786
  }
8390
8787
  projectTarget = arg1;
8391
- path26 = arg2;
8788
+ path29 = arg2;
8392
8789
  rawContent = arg3;
8393
8790
  }
8394
8791
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8395
8792
  const payload = rawContent === "-" ? await readStdin() : rawContent;
8396
- await driver.write(path26, payload);
8793
+ await driver.write(path29, payload);
8397
8794
  } catch (err) {
8398
8795
  console.error(chalk19.red(err.message));
8399
8796
  process.exit(1);
@@ -8403,9 +8800,9 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
8403
8800
  const config = loadConfig();
8404
8801
  const registry = buildRegistry2();
8405
8802
  try {
8406
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8803
+ const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8407
8804
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8408
- const entries = await driver.list(path26);
8805
+ const entries = await driver.list(path29);
8409
8806
  for (const entry of entries) console.log(entry);
8410
8807
  } catch (err) {
8411
8808
  console.error(chalk19.red(err.message));
@@ -8416,9 +8813,9 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
8416
8813
  const config = loadConfig();
8417
8814
  const registry = buildRegistry2();
8418
8815
  try {
8419
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8816
+ const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8420
8817
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8421
- const ok = await driver.exists(path26);
8818
+ const ok = await driver.exists(path29);
8422
8819
  process.exit(ok ? 0 : 1);
8423
8820
  } catch (err) {
8424
8821
  console.error(chalk19.red(err.message));
@@ -8429,9 +8826,9 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
8429
8826
  const config = loadConfig();
8430
8827
  const registry = buildRegistry2();
8431
8828
  try {
8432
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8829
+ const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8433
8830
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8434
- await driver.mkdir(path26);
8831
+ await driver.mkdir(path29);
8435
8832
  } catch (err) {
8436
8833
  console.error(chalk19.red(err.message));
8437
8834
  process.exit(1);
@@ -8470,8 +8867,8 @@ init_dist3();
8470
8867
  init_dist();
8471
8868
  import { Command as Command20 } from "commander";
8472
8869
  import chalk21 from "chalk";
8473
- import fs20 from "fs";
8474
- import path24 from "path";
8870
+ import fs23 from "fs";
8871
+ import path27 from "path";
8475
8872
  import { fileURLToPath as fileURLToPath4 } from "url";
8476
8873
  function parseScope(v) {
8477
8874
  if (v !== "user" && v !== "project") {
@@ -8480,10 +8877,10 @@ function parseScope(v) {
8480
8877
  return v;
8481
8878
  }
8482
8879
  function findPackageRoot3() {
8483
- let dir = path24.dirname(fileURLToPath4(import.meta.url));
8880
+ let dir = path27.dirname(fileURLToPath4(import.meta.url));
8484
8881
  while (dir !== "/" && dir !== "") {
8485
- if (fs20.existsSync(path24.join(dir, "package.json"))) return dir;
8486
- dir = path24.dirname(dir);
8882
+ if (fs23.existsSync(path27.join(dir, "package.json"))) return dir;
8883
+ dir = path27.dirname(dir);
8487
8884
  }
8488
8885
  return process.cwd();
8489
8886
  }
@@ -8677,12 +9074,12 @@ init_dist();
8677
9074
  init_dist();
8678
9075
  init_dist();
8679
9076
  import { Command as Command22 } from "commander";
8680
- import fs21 from "fs";
9077
+ import fs24 from "fs";
8681
9078
  import { fileURLToPath as fileURLToPath5 } from "url";
8682
- import { dirname as dirname5, join as join19 } from "path";
9079
+ import { dirname as dirname5, join as join20 } from "path";
8683
9080
  import chalk22 from "chalk";
8684
9081
  function runConfigCheck(opts) {
8685
- const raw = JSON.parse(fs21.readFileSync(opts.configPath, "utf-8"));
9082
+ const raw = JSON.parse(fs24.readFileSync(opts.configPath, "utf-8"));
8686
9083
  const def = getDefaultConfig();
8687
9084
  const items = detectDrift(raw, def);
8688
9085
  let working = raw;
@@ -8699,10 +9096,56 @@ function runConfigCheck(opts) {
8699
9096
  stamped = true;
8700
9097
  }
8701
9098
  if (opts.fix || opts.accept || stamped) {
8702
- fs21.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
9099
+ fs24.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
8703
9100
  }
8704
9101
  return { items, applied, remaining, stamped };
8705
9102
  }
9103
+ function runConfigGet(key, configPath = DEFAULT_CONFIG_PATH) {
9104
+ const config = loadConfig(configPath);
9105
+ const parts = key.split(".");
9106
+ let node = config;
9107
+ for (const p of parts) {
9108
+ if (node === null || typeof node !== "object" || !(p in node)) {
9109
+ throw new Error(`config key not found: ${key}`);
9110
+ }
9111
+ node = node[p];
9112
+ }
9113
+ return node;
9114
+ }
9115
+ function runConfigSet(key, value, configPath = DEFAULT_CONFIG_PATH) {
9116
+ let parsed;
9117
+ try {
9118
+ parsed = JSON.parse(value);
9119
+ } catch {
9120
+ parsed = value;
9121
+ }
9122
+ const config = loadConfig(configPath);
9123
+ const parts = key.split(".");
9124
+ let node = config;
9125
+ for (let i = 0; i < parts.length - 1; i++) {
9126
+ const p = parts[i];
9127
+ if (node[p] === null || typeof node[p] !== "object") node[p] = {};
9128
+ node = node[p];
9129
+ }
9130
+ node[parts[parts.length - 1]] = parsed;
9131
+ saveConfig(config, configPath);
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
+ }
8706
9149
  var SEV_COLOR = {
8707
9150
  info: chalk22.green,
8708
9151
  advisory: chalk22.yellow,
@@ -8724,7 +9167,7 @@ function printItems(items) {
8724
9167
  var configCommand = new Command22("config").description("Inspect and reconcile squadrant config");
8725
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) => {
8726
9169
  const pkgVersion = readPkgVersion2();
8727
- if (!fs21.existsSync(DEFAULT_CONFIG_PATH)) {
9170
+ if (!fs24.existsSync(DEFAULT_CONFIG_PATH)) {
8728
9171
  console.log(chalk22.yellow("No config found \u2014 run `squadrant init` first."));
8729
9172
  return;
8730
9173
  }
@@ -8751,16 +9194,32 @@ ${judgment.length} item(s) need review \u2014 run the config-doctor skill, or \`
8751
9194
  console.log(chalk22.green("\n\u2714 Config reconciled and stamped."));
8752
9195
  }
8753
9196
  });
9197
+ configCommand.command("get").description("Read a config value by dotted key (e.g. defaults.effort)").argument("<key>", "dotted config key").action((key) => {
9198
+ try {
9199
+ const value = runConfigGet(key);
9200
+ console.log(typeof value === "string" ? value : JSON.stringify(value));
9201
+ } catch (e) {
9202
+ console.error(chalk22.red(e.message));
9203
+ process.exit(1);
9204
+ }
9205
+ });
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) => {
9207
+ try {
9208
+ runConfigSetAction({ key, value, noRestart: opts.restart === false });
9209
+ } catch (e) {
9210
+ console.error(chalk22.red(e.message));
9211
+ process.exit(1);
9212
+ }
9213
+ });
8754
9214
  function readPkgVersion2() {
8755
- const pkgPath = join19(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
8756
- return JSON.parse(fs21.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;
8757
9217
  }
8758
9218
 
8759
9219
  // packages/cli/src/commands/heal.ts
8760
9220
  import { Command as Command23 } from "commander";
8761
9221
  import chalk23 from "chalk";
8762
9222
  init_dist2();
8763
- init_dist2();
8764
9223
  function buildHealStatus(components) {
8765
9224
  if (components === null) {
8766
9225
  return { healthy: false, daemonUnreachable: true, components: [] };
@@ -8840,7 +9299,7 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
8840
9299
  ).addCommand(
8841
9300
  new Command23("daemon").description("Restart squadrantd via the idempotent launchd kickstart path").action(async () => {
8842
9301
  const code = await runHealDaemon({
8843
- ensureDaemon,
9302
+ ensureDaemon: () => restartDaemonIfRunning({ reason: "heal", isRunning: () => true }),
8844
9303
  stdout: process.stdout,
8845
9304
  stderr: process.stderr
8846
9305
  });
@@ -8854,10 +9313,10 @@ init_dist2();
8854
9313
  import { Command as Command24 } from "commander";
8855
9314
  import { execSync as execSync13 } from "child_process";
8856
9315
  import { randomUUID as randomUUID4 } from "crypto";
8857
- import { homedir as homedir13 } from "os";
8858
- import { join as join20 } from "path";
9316
+ import { homedir as homedir14 } from "os";
9317
+ import { join as join21 } from "path";
8859
9318
  import chalk24 from "chalk";
8860
- var SOCK4 = join20(homedir13(), ".config", "squadrant", "squadrant.sock");
9319
+ var SOCK4 = join21(homedir14(), ".config", "squadrant", "squadrant.sock");
8861
9320
  var WARMUP_TIMEOUT_MS = 12e4;
8862
9321
  var WARMUP_POLL_MS = 1e3;
8863
9322
  function resolveCurrentProject(config) {
@@ -9019,8 +9478,8 @@ var cmuxCommand = new Command25("cmux").description("cmux integration helpers").
9019
9478
 
9020
9479
  // packages/cli/src/commands/effort.ts
9021
9480
  init_dist();
9022
- import fs22 from "fs";
9023
- import path25 from "path";
9481
+ import fs25 from "fs";
9482
+ import path28 from "path";
9024
9483
  import { Command as Command26 } from "commander";
9025
9484
  import chalk26 from "chalk";
9026
9485
  var VALID_EFFORTS = ["max", "balance", "low"];
@@ -9047,9 +9506,9 @@ function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH) {
9047
9506
  }
9048
9507
  function canonical(p) {
9049
9508
  try {
9050
- return fs22.realpathSync(p);
9509
+ return fs25.realpathSync(p);
9051
9510
  } catch {
9052
- return path25.resolve(p);
9511
+ return path28.resolve(p);
9053
9512
  }
9054
9513
  }
9055
9514
  async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd()) {
@@ -9093,20 +9552,440 @@ var effortCommand = new Command26("effort").description("Get or set the global c
9093
9552
  }
9094
9553
  });
9095
9554
 
9555
+ // packages/cli/src/commands/telegram.ts
9556
+ init_dist();
9557
+ init_dist2();
9558
+ import { join as join22, dirname as dirname6 } from "path";
9559
+ import { emitKeypressEvents } from "readline";
9560
+ import { Command as Command27 } from "commander";
9561
+ import chalk27 from "chalk";
9562
+ function defaultStateRoot() {
9563
+ return join22(dirname6(DEFAULT_CONFIG_PATH), "state");
9564
+ }
9565
+ function runTelegramStatus(opts) {
9566
+ const tg = opts.config.telegram;
9567
+ const env = opts.env ?? process.env;
9568
+ const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
9569
+ const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
9570
+ const sep2 = key.indexOf("::");
9571
+ return { project: key.slice(0, sep2), scope: key.slice(sep2 + 2), topicId };
9572
+ });
9573
+ return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
9574
+ }
9575
+ async function runTelegramSend(opts) {
9576
+ const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
9577
+ if (topicId === void 0) {
9578
+ throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
9579
+ }
9580
+ await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
9581
+ return { chatId: opts.cfg.supergroupId, topicId };
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
+ }
9611
+ async function runTelegramLink(opts) {
9612
+ const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
9613
+ if (existing !== void 0) return { topicId: existing, created: false };
9614
+ const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
9615
+ setTopic(opts.stateRoot, opts.project, topicId);
9616
+ return { topicId, created: true };
9617
+ }
9618
+ async function questionMasked() {
9619
+ return new Promise((resolve3) => {
9620
+ emitKeypressEvents(process.stdin);
9621
+ process.stdin.setRawMode(true);
9622
+ process.stdin.resume();
9623
+ let answer = "";
9624
+ const onKeypress = (_str, key) => {
9625
+ if (key.ctrl && key.name === "c") {
9626
+ process.stdin.removeListener("keypress", onKeypress);
9627
+ process.stdin.setRawMode(false);
9628
+ process.stdin.pause();
9629
+ process.stdout.write("\n");
9630
+ process.exit(130);
9631
+ } else if (key.name === "return" || key.name === "enter") {
9632
+ process.stdin.removeListener("keypress", onKeypress);
9633
+ process.stdin.setRawMode(false);
9634
+ process.stdin.pause();
9635
+ process.stdout.write("\n");
9636
+ resolve3(answer);
9637
+ } else if (key.name === "backspace") {
9638
+ if (answer.length > 0) {
9639
+ answer = answer.slice(0, -1);
9640
+ process.stdout.write("\b \b");
9641
+ }
9642
+ } else if (!key.ctrl && !key.meta && key.sequence) {
9643
+ answer += key.sequence;
9644
+ process.stdout.write("*");
9645
+ }
9646
+ };
9647
+ process.stdin.on("keypress", onKeypress);
9648
+ });
9649
+ }
9650
+ async function questionYesNo(prompt) {
9651
+ const { createInterface: createInterface2 } = await import("readline");
9652
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
9653
+ return new Promise((resolve3) => {
9654
+ rl.question(prompt, (ans) => {
9655
+ rl.close();
9656
+ process.stdin.pause();
9657
+ resolve3(/^y(es)?$/i.test(ans.trim()));
9658
+ });
9659
+ });
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
+ }
9699
+ var telegramCommand = new Command27("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
9700
+ telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
9701
+ const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
9702
+ console.log(`token: ${tokenSet ? chalk27.green("set") : chalk27.yellow("unset")}`);
9703
+ console.log(`supergroup: ${supergroupId ?? chalk27.yellow("unset")}`);
9704
+ if (links.length === 0) {
9705
+ console.log("no projects linked");
9706
+ return;
9707
+ }
9708
+ for (const l of links) console.log(` ${l.project} (${l.scope}) \u2192 topic ${l.topicId}`);
9709
+ });
9710
+ telegramCommand.command("link").argument("<project>", "project to bind to a Telegram topic").description("Create (or reuse) a forum topic for a project and bind it").action(async (project) => {
9711
+ const cfg = loadConfig().telegram;
9712
+ if (!cfg) {
9713
+ console.error(chalk27.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
9714
+ process.exit(1);
9715
+ }
9716
+ const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
9717
+ if (!token) {
9718
+ console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
9719
+ process.exit(1);
9720
+ }
9721
+ const client = createTelegramClient({ token });
9722
+ const { topicId, created } = await runTelegramLink({ project, cfg, client, stateRoot: defaultStateRoot() });
9723
+ console.log(chalk27.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
9724
+ });
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) => {
9726
+ if (!process.stdin.isTTY) {
9727
+ console.error(chalk27.red("setup requires a TTY \u2014 pipe input is not supported"));
9728
+ process.exit(1);
9729
+ }
9730
+ console.log();
9731
+ console.log(chalk27.bold("Telegram setup") + " \u2014 connect squadrant to a Telegram bot for notifications + remote control");
9732
+ console.log();
9733
+ console.log("Before you start you need:");
9734
+ console.log(" 1. A bot token from @BotFather (send /newbot)");
9735
+ console.log(" 2. A forum supergroup with the bot added as an admin (Topics enabled)");
9736
+ console.log(" 3. Bot privacy mode set to OFF (@BotFather \u2192 /setprivacy \u2192 Disable)");
9737
+ console.log();
9738
+ console.log(chalk27.bold("Step 1/3 \u2014 Bot 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;
9744
+ let botUser;
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();
9786
+ }
9787
+ console.log(chalk27.bold("Step 2/3 \u2014 Supergroup"));
9788
+ const groupDecision = resolveSetupGroup(existingCfg?.supergroupId, { redetect: opts.redetect ?? false });
9789
+ let supergroupId;
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();
9807
+ }
9808
+ console.log(chalk27.bold("Step 3/3 \u2014 Remote control + Save"));
9809
+ console.log(chalk27.dim("Remote control enables auto-launching captains and the General command channel"));
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());
9812
+ let users;
9813
+ let remoteControl;
9814
+ let printedRemoteControlState = false;
9815
+ if (finalUserId !== void 0) {
9816
+ const enable = await questionYesNo(
9817
+ `Enable remote control for your user-id ${finalUserId}? [y/N] `
9818
+ );
9819
+ if (enable) {
9820
+ users = [finalUserId];
9821
+ remoteControl = true;
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;
9835
+ }
9836
+ writeTelegramConfig(DEFAULT_CONFIG_PATH, { token, supergroupId, users, remoteControl });
9837
+ console.log(chalk27.green(`Wrote telegram config \u2014 token: ${maskToken(token)} group: ${supergroupId}`));
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)`));
9859
+ } else {
9860
+ console.log(chalk27.dim("No project topics yet \u2014 they're created on first delivery or via: squadrant telegram link <project>"));
9861
+ }
9862
+ runTelegramPostSetup({});
9863
+ console.log();
9864
+ console.log(`Next: ${chalk27.cyan("squadrant telegram link <project>")}`);
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
+ });
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) => {
9934
+ const cfg = loadConfig().telegram;
9935
+ if (!cfg) {
9936
+ console.error(chalk27.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
9937
+ process.exit(1);
9938
+ }
9939
+ const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
9940
+ if (!token) {
9941
+ console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
9942
+ process.exit(1);
9943
+ }
9944
+ if (!capAllowed(project, cfg.notify)) {
9945
+ console.log(chalk27.dim(`${project}: captain messages muted (cap=off) \u2014 not sent`));
9946
+ return;
9947
+ }
9948
+ let message;
9949
+ if (messageParts.length > 0) {
9950
+ message = messageParts.join(" ");
9951
+ } else if (!process.stdin.isTTY) {
9952
+ const { createInterface: createInterface2 } = await import("readline");
9953
+ const lines = [];
9954
+ const rl = createInterface2({ input: process.stdin });
9955
+ for await (const line of rl) lines.push(line);
9956
+ message = lines.join("\n").trimEnd();
9957
+ if (!message) {
9958
+ console.error(chalk27.red("no message provided (stdin was empty)"));
9959
+ process.exit(1);
9960
+ }
9961
+ } else {
9962
+ console.error(chalk27.red("message required \u2014 pass as argument or pipe via stdin"));
9963
+ process.exit(1);
9964
+ }
9965
+ const client = createTelegramClient({ token });
9966
+ try {
9967
+ const { chatId, topicId } = await runTelegramSend({ project, message, cfg, client, stateRoot: defaultStateRoot() });
9968
+ console.log(chalk27.green(`sent to group ${chatId} topic ${topicId}`));
9969
+ } catch (e) {
9970
+ console.error(chalk27.red(e.message));
9971
+ process.exit(1);
9972
+ }
9973
+ });
9974
+
9096
9975
  // packages/cli/src/index.ts
9097
9976
  init_dist();
9098
9977
  init_dist();
9099
9978
  init_dist();
9100
- var __dirname = dirname6(fileURLToPath6(import.meta.url));
9101
- var pkg = JSON.parse(readFileSync11(join21(__dirname, "..", "package.json"), "utf-8"));
9979
+ var __dirname = dirname7(fileURLToPath6(import.meta.url));
9980
+ var pkg = JSON.parse(readFileSync11(join23(__dirname, "..", "package.json"), "utf-8"));
9102
9981
  ensureRuntimeSynced({
9103
- sourceRoot: join21(__dirname, ".."),
9104
- runtimeRoot: join21(homedir14(), ".config", "squadrant")
9982
+ sourceRoot: join23(__dirname, ".."),
9983
+ runtimeRoot: join23(homedir15(), ".config", "squadrant")
9105
9984
  });
9106
9985
  if (process.argv[2] !== "config") {
9107
9986
  try {
9108
- const cfgPath = join21(homedir14(), ".config", "squadrant", "config.json");
9109
- if (existsSync10(cfgPath)) {
9987
+ const cfgPath = join23(homedir15(), ".config", "squadrant", "config.json");
9988
+ if (existsSync11(cfgPath)) {
9110
9989
  const cfg = JSON.parse(readFileSync11(cfgPath, "utf-8"));
9111
9990
  if (needsCheck(cfg, pkg.version)) {
9112
9991
  const items = detectDrift(cfg, getDefaultConfig());
@@ -9130,7 +10009,7 @@ if (process.argv[2] !== "config") {
9130
10009
  if (!process.env.SQUADRANT_DAEMON_SKIP) {
9131
10010
  ensureDaemon();
9132
10011
  }
9133
- var program = new Command27();
10012
+ var program = new Command28();
9134
10013
  program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
9135
10014
  program.addCommand(doctorCommand);
9136
10015
  program.addCommand(initCommand);
@@ -9156,6 +10035,7 @@ program.addCommand(healCommand);
9156
10035
  program.addCommand(groupCommand);
9157
10036
  program.addCommand(cmuxCommand);
9158
10037
  program.addCommand(effortCommand);
10038
+ program.addCommand(telegramCommand);
9159
10039
  program.parseAsync().catch((e) => {
9160
10040
  process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}
9161
10041
  `);