squadrant 0.9.2 → 0.10.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
@@ -155,22 +155,22 @@ function defaultCmuxConfigPath() {
155
155
  return join(homedir(), ".config", "cmux", "cmux.json");
156
156
  }
157
157
  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 };
158
+ const path27 = opts.path ?? defaultCmuxConfigPath();
159
+ if (!existsSync(path27)) {
160
+ mkdirSync(dirname(path27), { recursive: true });
161
+ writeFileSync(path27, MINIMAL_TEMPLATE);
162
+ return { path: path27, changed: true, alreadySet: false };
163
163
  }
164
- const text = readFileSync(path26, "utf-8");
164
+ const text = readFileSync(path27, "utf-8");
165
165
  const current = parse(text)?.automation?.socketControlMode;
166
166
  if (current === AUTOMATION_MODE) {
167
- return { path: path26, changed: false, alreadySet: true };
167
+ return { path: path27, changed: false, alreadySet: true };
168
168
  }
169
169
  const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
170
170
  formattingOptions: { insertSpaces: true, tabSize: 2 }
171
171
  });
172
- writeFileSync(path26, applyEdits(text, edits));
173
- return { path: path26, changed: true, alreadySet: false };
172
+ writeFileSync(path27, applyEdits(text, edits));
173
+ return { path: path27, changed: true, alreadySet: false };
174
174
  }
175
175
  var SOCKET_CONTROL_MODE_PATH, AUTOMATION_MODE, MINIMAL_TEMPLATE;
176
176
  var init_cmux_config = __esm({
@@ -320,15 +320,15 @@ import { dirname as dirname2, join as join4 } from "path";
320
320
  function defaultStatePath() {
321
321
  return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
322
322
  }
323
- function readState(path26) {
323
+ function readState(path27) {
324
324
  try {
325
- return JSON.parse(readFileSync4(path26, "utf-8"));
325
+ return JSON.parse(readFileSync4(path27, "utf-8"));
326
326
  } catch {
327
327
  return {};
328
328
  }
329
329
  }
330
330
  async function ensureCmuxAutoConfig(opts = {}) {
331
- const statePath = opts.statePath ?? defaultStatePath();
331
+ const statePath2 = opts.statePath ?? defaultStatePath();
332
332
  const ensureConfig = opts.ensureConfig ?? ensureSocketAutomation;
333
333
  const probe = opts.probe ?? probeCmuxDaemonDirect;
334
334
  const cfg = ensureConfig({ path: opts.configPath });
@@ -336,15 +336,15 @@ async function ensureCmuxAutoConfig(opts = {}) {
336
336
  const needsRestart = verdict === "denied";
337
337
  let promptedThisRun = false;
338
338
  if (needsRestart) {
339
- const already = readState(statePath).promptedRestart === true;
339
+ const already = readState(statePath2).promptedRestart === true;
340
340
  if (!already) {
341
- mkdirSync2(dirname2(statePath), { recursive: true });
342
- writeFileSync3(statePath, JSON.stringify({ promptedRestart: true }));
341
+ mkdirSync2(dirname2(statePath2), { recursive: true });
342
+ writeFileSync3(statePath2, JSON.stringify({ promptedRestart: true }));
343
343
  promptedThisRun = true;
344
344
  }
345
345
  } else if (verdict === "reachable") {
346
- if (existsSync4(statePath))
347
- rmSync2(statePath, { force: true });
346
+ if (existsSync4(statePath2))
347
+ rmSync2(statePath2, { force: true });
348
348
  }
349
349
  return {
350
350
  configPath: cfg.path,
@@ -1131,10 +1131,10 @@ function daemonEntryPath() {
1131
1131
  function xmlEscape(s) {
1132
1132
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1133
1133
  }
1134
- function sanitizePathForPlist(path26) {
1134
+ function sanitizePathForPlist(path27) {
1135
1135
  const seen = /* @__PURE__ */ new Set();
1136
1136
  const stable = [];
1137
- for (const p of path26.split(":")) {
1137
+ for (const p of path27.split(":")) {
1138
1138
  if (!p)
1139
1139
  continue;
1140
1140
  if (p.includes("/.claude/plugins/"))
@@ -1571,6 +1571,199 @@ var init_crew_lifecycle = __esm({
1571
1571
  }
1572
1572
  });
1573
1573
 
1574
+ // packages/core/dist/telegram/auth.js
1575
+ var init_auth = __esm({
1576
+ "packages/core/dist/telegram/auth.js"() {
1577
+ }
1578
+ });
1579
+
1580
+ // packages/core/dist/telegram/commands.js
1581
+ var init_commands = __esm({
1582
+ "packages/core/dist/telegram/commands.js"() {
1583
+ }
1584
+ });
1585
+
1586
+ // packages/core/dist/telegram/ensure-captain.js
1587
+ var init_ensure_captain = __esm({
1588
+ "packages/core/dist/telegram/ensure-captain.js"() {
1589
+ }
1590
+ });
1591
+
1592
+ // packages/core/dist/telegram/format.js
1593
+ function topicName(project) {
1594
+ return project;
1595
+ }
1596
+ function maskToken(token) {
1597
+ if (token.length <= 4)
1598
+ return token;
1599
+ return "*".repeat(token.length - 4) + token.slice(-4);
1600
+ }
1601
+ var init_format = __esm({
1602
+ "packages/core/dist/telegram/format.js"() {
1603
+ }
1604
+ });
1605
+
1606
+ // packages/core/dist/telegram/state.js
1607
+ import fs8 from "fs";
1608
+ import path7 from "path";
1609
+ function statePath(stateRoot) {
1610
+ return path7.join(stateRoot, "telegram-state.json");
1611
+ }
1612
+ function topicKey(project, scope = "project") {
1613
+ return `${project}::${scope}`;
1614
+ }
1615
+ function loadState(stateRoot) {
1616
+ try {
1617
+ const raw = fs8.readFileSync(statePath(stateRoot), "utf-8");
1618
+ const data = JSON.parse(raw);
1619
+ return {
1620
+ offset: typeof data.offset === "number" ? data.offset : 0,
1621
+ topics: data.topics ?? {}
1622
+ };
1623
+ } catch {
1624
+ return { offset: 0, topics: {} };
1625
+ }
1626
+ }
1627
+ function saveState(stateRoot, s) {
1628
+ fs8.mkdirSync(stateRoot, { recursive: true });
1629
+ fs8.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
1630
+ }
1631
+ function setTopic(stateRoot, project, topicId, scope = "project") {
1632
+ const s = loadState(stateRoot);
1633
+ s.topics[topicKey(project, scope)] = topicId;
1634
+ saveState(stateRoot, s);
1635
+ }
1636
+ var init_state = __esm({
1637
+ "packages/core/dist/telegram/state.js"() {
1638
+ }
1639
+ });
1640
+
1641
+ // packages/core/dist/telegram/client.js
1642
+ function createTelegramClient(opts) {
1643
+ const fetchImpl = opts.fetch ?? fetch;
1644
+ const base = `https://api.telegram.org/bot${opts.token}`;
1645
+ async function call(method, body) {
1646
+ const res = await fetchImpl(`${base}/${method}`, {
1647
+ method: "POST",
1648
+ headers: { "content-type": "application/json" },
1649
+ body: JSON.stringify(body)
1650
+ });
1651
+ const json = await res.json();
1652
+ if (!res.ok || !json.ok) {
1653
+ const code = json.error_code ?? res.status;
1654
+ const desc = json.description ?? "unknown error";
1655
+ throw new Error(`telegram ${method} failed (${code}): ${desc}`);
1656
+ }
1657
+ return json.result;
1658
+ }
1659
+ return {
1660
+ async getMe() {
1661
+ const r = await call("getMe", {});
1662
+ return { id: r.id, username: r.username };
1663
+ },
1664
+ getUpdates(offset, timeoutSec = 50) {
1665
+ return call("getUpdates", { offset, timeout: timeoutSec });
1666
+ },
1667
+ async sendMessage(chatId, threadId, text) {
1668
+ const body = { chat_id: chatId, text };
1669
+ if (threadId !== void 0)
1670
+ body.message_thread_id = threadId;
1671
+ await call("sendMessage", body);
1672
+ },
1673
+ async createForumTopic(chatId, name) {
1674
+ const r = await call("createForumTopic", { chat_id: chatId, name });
1675
+ return r.message_thread_id;
1676
+ }
1677
+ };
1678
+ }
1679
+ var init_client = __esm({
1680
+ "packages/core/dist/telegram/client.js"() {
1681
+ }
1682
+ });
1683
+
1684
+ // packages/core/dist/telegram/bridge.js
1685
+ var init_bridge = __esm({
1686
+ "packages/core/dist/telegram/bridge.js"() {
1687
+ init_auth();
1688
+ init_commands();
1689
+ init_format();
1690
+ init_state();
1691
+ }
1692
+ });
1693
+
1694
+ // packages/core/dist/telegram/setup.js
1695
+ import fs9 from "fs";
1696
+ async function detectGroupAndUser(client, opts = {}) {
1697
+ const timeoutMs = opts.timeoutMs ?? 6e4;
1698
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
1699
+ const deadline = Date.now() + timeoutMs;
1700
+ let offset = 0;
1701
+ while (Date.now() < deadline) {
1702
+ const updates = await client.getUpdates(offset, 10);
1703
+ for (const u of updates) {
1704
+ if (u.update_id >= offset)
1705
+ offset = u.update_id + 1;
1706
+ if (u.message?.chat?.type === "supergroup") {
1707
+ return { supergroupId: u.message.chat.id, userId: u.message.from?.id };
1708
+ }
1709
+ }
1710
+ await sleep2(2e3);
1711
+ }
1712
+ throw new Error("Timed out waiting for the bot to receive a message in a supergroup");
1713
+ }
1714
+ function writeTelegramConfig(configPath, opts) {
1715
+ let config;
1716
+ let raw = null;
1717
+ try {
1718
+ raw = fs9.readFileSync(configPath, "utf-8");
1719
+ } catch (err) {
1720
+ if (err.code !== "ENOENT") {
1721
+ throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);
1722
+ }
1723
+ }
1724
+ if (raw !== null) {
1725
+ try {
1726
+ config = JSON.parse(raw);
1727
+ } catch (err) {
1728
+ throw new Error(`refusing to overwrite corrupt config at ${configPath}: ${String(err)}`);
1729
+ }
1730
+ } else {
1731
+ config = {};
1732
+ }
1733
+ const prev = config.telegram && typeof config.telegram === "object" ? config.telegram : {};
1734
+ const next = {
1735
+ botToken: opts.token,
1736
+ supergroupId: opts.supergroupId,
1737
+ chats: [opts.supergroupId]
1738
+ };
1739
+ const users = opts.users ?? prev.users;
1740
+ const remoteControl = opts.remoteControl ?? prev.remoteControl;
1741
+ if (users !== void 0)
1742
+ next.users = users;
1743
+ if (remoteControl !== void 0)
1744
+ next.remoteControl = remoteControl;
1745
+ config.telegram = next;
1746
+ fs9.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1747
+ }
1748
+ var init_setup = __esm({
1749
+ "packages/core/dist/telegram/setup.js"() {
1750
+ }
1751
+ });
1752
+
1753
+ // packages/core/dist/telegram/index.js
1754
+ var init_telegram = __esm({
1755
+ "packages/core/dist/telegram/index.js"() {
1756
+ init_auth();
1757
+ init_commands();
1758
+ init_ensure_captain();
1759
+ init_format();
1760
+ init_state();
1761
+ init_client();
1762
+ init_bridge();
1763
+ init_setup();
1764
+ }
1765
+ });
1766
+
1574
1767
  // packages/core/dist/index.js
1575
1768
  var init_dist2 = __esm({
1576
1769
  "packages/core/dist/index.js"() {
@@ -1596,6 +1789,7 @@ var init_dist2 = __esm({
1596
1789
  init_session_freshness();
1597
1790
  init_crew_protocol();
1598
1791
  init_crew_lifecycle();
1792
+ init_telegram();
1599
1793
  }
1600
1794
  });
1601
1795
 
@@ -2114,13 +2308,13 @@ var init_notifiers = __esm({
2114
2308
  });
2115
2309
 
2116
2310
  // packages/workspaces/dist/workspaces/obsidian.js
2117
- import fs8 from "fs/promises";
2311
+ import fs10 from "fs/promises";
2118
2312
  import { existsSync as existsSync8 } from "fs";
2119
- import path7 from "path";
2313
+ import path8 from "path";
2120
2314
  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)) {
2315
+ const joined = path8.resolve(root, relative);
2316
+ const normalized = path8.resolve(root) + path8.sep;
2317
+ if (joined !== path8.resolve(root) && !joined.startsWith(normalized)) {
2124
2318
  throw new Error(`Path '${relative}' escapes workspace root`);
2125
2319
  }
2126
2320
  return joined;
@@ -2139,16 +2333,16 @@ function createObsidianDriver(scope) {
2139
2333
  };
2140
2334
  },
2141
2335
  async read(rel) {
2142
- return fs8.readFile(resolveInRoot(root, rel), "utf-8");
2336
+ return fs10.readFile(resolveInRoot(root, rel), "utf-8");
2143
2337
  },
2144
2338
  async write(rel, content) {
2145
2339
  const abs = resolveInRoot(root, rel);
2146
- await fs8.mkdir(path7.dirname(abs), { recursive: true });
2147
- await fs8.writeFile(abs, content);
2340
+ await fs10.mkdir(path8.dirname(abs), { recursive: true });
2341
+ await fs10.writeFile(abs, content);
2148
2342
  },
2149
2343
  async exists(rel) {
2150
2344
  try {
2151
- await fs8.access(resolveInRoot(root, rel));
2345
+ await fs10.access(resolveInRoot(root, rel));
2152
2346
  return true;
2153
2347
  } catch {
2154
2348
  return false;
@@ -2156,13 +2350,13 @@ function createObsidianDriver(scope) {
2156
2350
  },
2157
2351
  async list(rel) {
2158
2352
  try {
2159
- return await fs8.readdir(resolveInRoot(root, rel));
2353
+ return await fs10.readdir(resolveInRoot(root, rel));
2160
2354
  } catch {
2161
2355
  return [];
2162
2356
  }
2163
2357
  },
2164
2358
  async mkdir(rel) {
2165
- await fs8.mkdir(resolveInRoot(root, rel), { recursive: true });
2359
+ await fs10.mkdir(resolveInRoot(root, rel), { recursive: true });
2166
2360
  }
2167
2361
  };
2168
2362
  }
@@ -2831,8 +3025,8 @@ var init_registry4 = __esm({
2831
3025
  });
2832
3026
 
2833
3027
  // packages/agents/dist/drivers/launch-cmd.js
2834
- import fs9 from "fs";
2835
- import path8 from "path";
3028
+ import fs11 from "fs";
3029
+ import path9 from "path";
2836
3030
  function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir) {
2837
3031
  const driver = registry.getDriver(agentName);
2838
3032
  if (driver.name === "claude") {
@@ -2848,27 +3042,27 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
2848
3042
  cmd += ` --model ${model}`;
2849
3043
  }
2850
3044
  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;
3045
+ const roleFile2 = path9.join(templatesDir, `${role}.claude.md`);
3046
+ const legacyRoleFile = path9.join(templatesDir, `${role}.CLAUDE.md`);
3047
+ const actualRoleFile = fs11.existsSync(roleFile2) ? roleFile2 : fs11.existsSync(legacyRoleFile) ? legacyRoleFile : null;
2854
3048
  if (actualRoleFile) {
2855
3049
  cmd += ` --append-system-prompt-file ${actualRoleFile}`;
2856
3050
  }
2857
- const pluginDir = path8.join(templatesDir, "..", "plugin");
2858
- if (fs9.existsSync(pluginDir)) {
3051
+ const pluginDir = path9.join(templatesDir, "..", "plugin");
3052
+ if (fs11.existsSync(pluginDir)) {
2859
3053
  cmd += ` --plugin-dir ${pluginDir}`;
2860
3054
  }
2861
3055
  }
2862
3056
  return cmd;
2863
3057
  }
2864
- const roleFile = templatesDir ? path8.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
3058
+ const roleFile = templatesDir ? path9.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
2865
3059
  return driver.buildCommand({
2866
3060
  prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
2867
3061
  workdir: process.cwd(),
2868
3062
  role,
2869
3063
  model,
2870
3064
  autoApprove: true,
2871
- promptFile: roleFile && fs9.existsSync(roleFile) ? roleFile : void 0
3065
+ promptFile: roleFile && fs11.existsSync(roleFile) ? roleFile : void 0
2872
3066
  });
2873
3067
  }
2874
3068
  var init_launch_cmd = __esm({
@@ -2891,7 +3085,7 @@ var init_drivers = __esm({
2891
3085
 
2892
3086
  // packages/agents/dist/projection/cursor.js
2893
3087
  import { mkdir, readFile, writeFile } from "fs/promises";
2894
- import path9 from "path";
3088
+ import path10 from "path";
2895
3089
  import os2 from "os";
2896
3090
  function renderMdc(source) {
2897
3091
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -2940,7 +3134,7 @@ function createCursorEmitter() {
2940
3134
  if (scope === "user") {
2941
3135
  return [
2942
3136
  {
2943
- path: path9.join(os2.homedir(), ".cursor/rules/squadrant-global.mdc"),
3137
+ path: path10.join(os2.homedir(), ".cursor/rules/squadrant-global.mdc"),
2944
3138
  shared: false,
2945
3139
  format: "mdc"
2946
3140
  }
@@ -2950,7 +3144,7 @@ function createCursorEmitter() {
2950
3144
  return [];
2951
3145
  return [
2952
3146
  {
2953
- path: path9.join(projectRoot, ".cursor/rules/squadrant.mdc"),
3147
+ path: path10.join(projectRoot, ".cursor/rules/squadrant.mdc"),
2954
3148
  shared: false,
2955
3149
  format: "mdc"
2956
3150
  }
@@ -2967,7 +3161,7 @@ function createCursorEmitter() {
2967
3161
  diff: buildDiff(existing, generated)
2968
3162
  };
2969
3163
  }
2970
- await mkdir(path9.dirname(dest.path), { recursive: true });
3164
+ await mkdir(path10.dirname(dest.path), { recursive: true });
2971
3165
  await writeFile(dest.path, generated, "utf-8");
2972
3166
  return {
2973
3167
  written: true,
@@ -3018,7 +3212,7 @@ var init_marker = __esm({
3018
3212
 
3019
3213
  // packages/agents/dist/projection/codex.js
3020
3214
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
3021
- import path10 from "path";
3215
+ import path11 from "path";
3022
3216
  import os3 from "os";
3023
3217
  function renderMarkdown(source) {
3024
3218
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -3043,7 +3237,7 @@ function createCodexEmitter() {
3043
3237
  destinations(scope, projectRoot) {
3044
3238
  if (scope === "user") {
3045
3239
  return [{
3046
- path: path10.join(os3.homedir(), ".codex/AGENTS.md"),
3240
+ path: path11.join(os3.homedir(), ".codex/AGENTS.md"),
3047
3241
  shared: true,
3048
3242
  format: "markdown"
3049
3243
  }];
@@ -3051,7 +3245,7 @@ function createCodexEmitter() {
3051
3245
  if (!projectRoot)
3052
3246
  return [];
3053
3247
  return [{
3054
- path: path10.join(projectRoot, "AGENTS.md"),
3248
+ path: path11.join(projectRoot, "AGENTS.md"),
3055
3249
  shared: true,
3056
3250
  format: "markdown"
3057
3251
  }];
@@ -3072,7 +3266,7 @@ ${existing ?? ""}
3072
3266
  ${generated}`
3073
3267
  };
3074
3268
  }
3075
- await mkdir2(path10.dirname(dest.path), { recursive: true });
3269
+ await mkdir2(path11.dirname(dest.path), { recursive: true });
3076
3270
  await writeFile2(dest.path, generated, "utf-8");
3077
3271
  return {
3078
3272
  written: true,
@@ -3090,7 +3284,7 @@ var init_codex2 = __esm({
3090
3284
 
3091
3285
  // packages/agents/dist/projection/gemini.js
3092
3286
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
3093
- import path11 from "path";
3287
+ import path12 from "path";
3094
3288
  import os4 from "os";
3095
3289
  function renderMarkdown2(source) {
3096
3290
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -3115,7 +3309,7 @@ function createGeminiEmitter() {
3115
3309
  destinations(scope, projectRoot) {
3116
3310
  if (scope === "user") {
3117
3311
  return [{
3118
- path: path11.join(os4.homedir(), ".gemini/GEMINI.md"),
3312
+ path: path12.join(os4.homedir(), ".gemini/GEMINI.md"),
3119
3313
  shared: true,
3120
3314
  format: "markdown"
3121
3315
  }];
@@ -3123,7 +3317,7 @@ function createGeminiEmitter() {
3123
3317
  if (!projectRoot)
3124
3318
  return [];
3125
3319
  return [{
3126
- path: path11.join(projectRoot, "GEMINI.md"),
3320
+ path: path12.join(projectRoot, "GEMINI.md"),
3127
3321
  shared: true,
3128
3322
  format: "markdown"
3129
3323
  }];
@@ -3144,7 +3338,7 @@ ${existing ?? ""}
3144
3338
  ${generated}`
3145
3339
  };
3146
3340
  }
3147
- await mkdir3(path11.dirname(dest.path), { recursive: true });
3341
+ await mkdir3(path12.dirname(dest.path), { recursive: true });
3148
3342
  await writeFile3(dest.path, generated, "utf-8");
3149
3343
  return {
3150
3344
  written: true,
@@ -3162,7 +3356,7 @@ var init_gemini2 = __esm({
3162
3356
 
3163
3357
  // packages/agents/dist/projection/opencode.js
3164
3358
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
3165
- import path12 from "path";
3359
+ import path13 from "path";
3166
3360
  import os5 from "os";
3167
3361
  function renderMarkdown3(source) {
3168
3362
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -3187,7 +3381,7 @@ function createOpencodeEmitter() {
3187
3381
  destinations(scope, projectRoot) {
3188
3382
  if (scope === "user") {
3189
3383
  return [{
3190
- path: path12.join(os5.homedir(), ".config", "opencode", "AGENTS.md"),
3384
+ path: path13.join(os5.homedir(), ".config", "opencode", "AGENTS.md"),
3191
3385
  shared: true,
3192
3386
  format: "markdown"
3193
3387
  }];
@@ -3195,7 +3389,7 @@ function createOpencodeEmitter() {
3195
3389
  if (!projectRoot)
3196
3390
  return [];
3197
3391
  return [{
3198
- path: path12.join(projectRoot, "AGENTS.md"),
3392
+ path: path13.join(projectRoot, "AGENTS.md"),
3199
3393
  shared: true,
3200
3394
  format: "markdown"
3201
3395
  }];
@@ -3216,7 +3410,7 @@ ${existing ?? ""}
3216
3410
  ${generated}`
3217
3411
  };
3218
3412
  }
3219
- await mkdir4(path12.dirname(dest.path), { recursive: true });
3413
+ await mkdir4(path13.dirname(dest.path), { recursive: true });
3220
3414
  await writeFile4(dest.path, generated, "utf-8");
3221
3415
  return {
3222
3416
  written: true,
@@ -4081,8 +4275,8 @@ function resolveLastAssistantText(payload) {
4081
4275
  const derived = deriveTranscriptPath(p?.session_id, cwd);
4082
4276
  if (derived)
4083
4277
  candidates.push(derived);
4084
- for (const path26 of candidates) {
4085
- const text = readLastAssistantText(path26);
4278
+ for (const path27 of candidates) {
4279
+ const text = readLastAssistantText(path27);
4086
4280
  if (text != null)
4087
4281
  return text;
4088
4282
  }
@@ -4481,10 +4675,10 @@ var init_dist4 = __esm({
4481
4675
  // packages/cli/src/index.ts
4482
4676
  init_dist();
4483
4677
  init_dist2();
4484
- import { Command as Command27 } from "commander";
4678
+ import { Command as Command28 } from "commander";
4485
4679
  import { readFileSync as readFileSync11, existsSync as existsSync10, writeFileSync as writeFileSync9 } from "fs";
4486
4680
  import { fileURLToPath as fileURLToPath6 } from "url";
4487
- import { dirname as dirname6, join as join21 } from "path";
4681
+ import { dirname as dirname7, join as join22 } from "path";
4488
4682
  import { homedir as homedir14 } from "os";
4489
4683
 
4490
4684
  // packages/cli/src/commands/doctor.ts
@@ -4494,9 +4688,9 @@ init_dist();
4494
4688
  init_dist3();
4495
4689
  import { Command } from "commander";
4496
4690
  import { execSync as execSync8 } from "child_process";
4497
- import fs10 from "fs";
4691
+ import fs12 from "fs";
4498
4692
  import { stat } from "fs/promises";
4499
- import path13 from "path";
4693
+ import path14 from "path";
4500
4694
  import chalk3 from "chalk";
4501
4695
 
4502
4696
  // packages/cli/src/commands/health-view.ts
@@ -4598,7 +4792,7 @@ function settingsHaveAgentTeams() {
4598
4792
  try {
4599
4793
  const home = process.env.HOME || "";
4600
4794
  const settings = JSON.parse(
4601
- fs10.readFileSync(`${home}/.claude/settings.json`, "utf-8")
4795
+ fs12.readFileSync(`${home}/.claude/settings.json`, "utf-8")
4602
4796
  );
4603
4797
  return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
4604
4798
  } catch {
@@ -4609,7 +4803,7 @@ function pluginInstalled(pluginKey) {
4609
4803
  try {
4610
4804
  const home = process.env.HOME || "";
4611
4805
  const plugins = JSON.parse(
4612
- fs10.readFileSync(
4806
+ fs12.readFileSync(
4613
4807
  `${home}/.claude/plugins/installed_plugins.json`,
4614
4808
  "utf-8"
4615
4809
  )
@@ -4641,7 +4835,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4641
4835
  const results = [];
4642
4836
  results.push(check("Claude Code installed", commandExists("claude")));
4643
4837
  results.push(check(`Claude Code version >= ${compatManifest.tools.claude.min}`, claudeVersionOk()));
4644
- results.push(check("Obsidian installed", commandExists("obsidian") || fs10.existsSync("/Applications/Obsidian.app")));
4838
+ results.push(check("Obsidian installed", commandExists("obsidian") || fs12.existsSync("/Applications/Obsidian.app")));
4645
4839
  results.push(check("Node.js >= 18", nodeVersionOk()));
4646
4840
  results.push(
4647
4841
  check(
@@ -4714,7 +4908,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4714
4908
  const emitter = projectionRegistry.get(name);
4715
4909
  const [userDest] = emitter.destinations("user");
4716
4910
  if (!userDest) continue;
4717
- const dir = path13.dirname(userDest.path);
4911
+ const dir = path14.dirname(userDest.path);
4718
4912
  let status;
4719
4913
  try {
4720
4914
  await stat(dir);
@@ -4727,7 +4921,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4727
4921
  results.push(
4728
4922
  check(
4729
4923
  "Squadrant config exists",
4730
- fs10.existsSync(
4924
+ fs12.existsSync(
4731
4925
  process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
4732
4926
  )
4733
4927
  )
@@ -4800,39 +4994,39 @@ init_dist();
4800
4994
  init_dist3();
4801
4995
  init_dist();
4802
4996
  import { Command as Command2 } from "commander";
4803
- import fs11 from "fs";
4804
- import path14 from "path";
4997
+ import fs13 from "fs";
4998
+ import path15 from "path";
4805
4999
  import os6 from "os";
4806
5000
  import chalk4 from "chalk";
4807
5001
  function findPackageRoot() {
4808
- let dir = path14.dirname(new URL(import.meta.url).pathname);
5002
+ let dir = path15.dirname(new URL(import.meta.url).pathname);
4809
5003
  while (dir !== "/") {
4810
- if (fs11.existsSync(path14.join(dir, "package.json"))) return dir;
4811
- dir = path14.dirname(dir);
5004
+ if (fs13.existsSync(path15.join(dir, "package.json"))) return dir;
5005
+ dir = path15.dirname(dir);
4812
5006
  }
4813
5007
  return process.cwd();
4814
5008
  }
4815
5009
  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);
5010
+ fs13.mkdirSync(dest, { recursive: true });
5011
+ for (const entry of fs13.readdirSync(src, { withFileTypes: true })) {
5012
+ const srcPath = path15.join(src, entry.name);
5013
+ const destPath = path15.join(dest, entry.name);
4820
5014
  if (entry.isDirectory()) {
4821
5015
  copyDirRecursive(srcPath, destPath);
4822
5016
  } else {
4823
- fs11.copyFileSync(srcPath, destPath);
5017
+ fs13.copyFileSync(srcPath, destPath);
4824
5018
  }
4825
5019
  }
4826
5020
  }
4827
5021
  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
5022
  const hubPath = resolveHome(opts.hub);
4829
5023
  const pkgRoot = findPackageRoot();
4830
- const configDir = path14.join(os6.homedir(), ".config", "squadrant");
5024
+ const configDir = path15.join(os6.homedir(), ".config", "squadrant");
4831
5025
  console.log(chalk4.bold("\nSquadrant Init\n"));
4832
5026
  const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
4833
5027
  try {
4834
- if (fs11.existsSync(DEFAULT_CONFIG_PATH)) {
4835
- const existing = JSON.parse(fs11.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
5028
+ if (fs13.existsSync(DEFAULT_CONFIG_PATH)) {
5029
+ const existing = JSON.parse(fs13.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
4836
5030
  const wsName = existing.workspace ?? "obsidian";
4837
5031
  registry.get(wsName);
4838
5032
  }
@@ -4840,7 +5034,7 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4840
5034
  console.log(chalk4.red(` \u2718 ${err.message}`));
4841
5035
  return;
4842
5036
  }
4843
- if (fs11.existsSync(DEFAULT_CONFIG_PATH)) {
5037
+ if (fs13.existsSync(DEFAULT_CONFIG_PATH)) {
4844
5038
  console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
4845
5039
  } else {
4846
5040
  const config = getDefaultConfig();
@@ -4848,37 +5042,37 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4848
5042
  saveConfig(config);
4849
5043
  console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
4850
5044
  }
4851
- const hubTemplate = path14.join(pkgRoot, "obsidian", "hub");
4852
- if (fs11.existsSync(hubPath)) {
5045
+ const hubTemplate = path15.join(pkgRoot, "obsidian", "hub");
5046
+ if (fs13.existsSync(hubPath)) {
4853
5047
  console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}, skipping`));
4854
- } else if (fs11.existsSync(hubTemplate)) {
5048
+ } else if (fs13.existsSync(hubTemplate)) {
4855
5049
  copyDirRecursive(hubTemplate, hubPath);
4856
5050
  console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
4857
5051
  } else {
4858
- fs11.mkdirSync(hubPath, { recursive: true });
5052
+ fs13.mkdirSync(hubPath, { recursive: true });
4859
5053
  console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty dir at ${hubPath}`));
4860
5054
  }
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);
5055
+ const hubDashboardSrc = path15.join(pkgRoot, "obsidian", "hub", "dashboard.md");
5056
+ const hubDashboardDest = path15.join(hubPath, "dashboard.md");
5057
+ if (fs13.existsSync(hubDashboardSrc)) {
5058
+ fs13.copyFileSync(hubDashboardSrc, hubDashboardDest);
4865
5059
  console.log(chalk4.green(` \u2714 Dashboard page refreshed at ${hubDashboardDest}`));
4866
5060
  }
4867
- const projectsDir = path14.join(hubPath, "projects");
4868
- fs11.mkdirSync(projectsDir, { recursive: true });
5061
+ const projectsDir = path15.join(hubPath, "projects");
5062
+ fs13.mkdirSync(projectsDir, { recursive: true });
4869
5063
  ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
4870
5064
  console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
4871
- const settingsPath = path14.join(os6.homedir(), ".claude", "settings.json");
5065
+ const settingsPath = path15.join(os6.homedir(), ".claude", "settings.json");
4872
5066
  try {
4873
5067
  let settings = {};
4874
- if (fs11.existsSync(settingsPath)) {
4875
- settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
5068
+ if (fs13.existsSync(settingsPath)) {
5069
+ settings = JSON.parse(fs13.readFileSync(settingsPath, "utf-8"));
4876
5070
  }
4877
5071
  const env = settings.env || {};
4878
5072
  if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
4879
5073
  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");
5074
+ fs13.mkdirSync(path15.dirname(settingsPath), { recursive: true });
5075
+ fs13.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4882
5076
  console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
4883
5077
  } else {
4884
5078
  console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
@@ -4899,7 +5093,7 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4899
5093
  console.log(chalk4.cyan(` ${hubPath}`));
4900
5094
  console.log("");
4901
5095
  console.log(" 4. Run " + chalk4.cyan("squadrant doctor") + " to verify setup\n");
4902
- if (!fs11.existsSync("/Applications/cmux.app")) {
5096
+ if (!fs13.existsSync("/Applications/cmux.app")) {
4903
5097
  console.log(chalk4.yellow(" \u26A0 cmux not found \u2014 download from https://cmux.dev\n"));
4904
5098
  }
4905
5099
  });
@@ -4907,26 +5101,26 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4907
5101
  // packages/cli/src/commands/projects.ts
4908
5102
  init_dist();
4909
5103
  import { Command as Command3 } from "commander";
4910
- import fs12 from "fs";
4911
- import path15 from "path";
5104
+ import fs14 from "fs";
5105
+ import path16 from "path";
4912
5106
  import chalk5 from "chalk";
4913
5107
  function findPackageRoot2() {
4914
- let dir = path15.dirname(new URL(import.meta.url).pathname);
5108
+ let dir = path16.dirname(new URL(import.meta.url).pathname);
4915
5109
  while (dir !== "/") {
4916
- if (fs12.existsSync(path15.join(dir, "package.json"))) return dir;
4917
- dir = path15.dirname(dir);
5110
+ if (fs14.existsSync(path16.join(dir, "package.json"))) return dir;
5111
+ dir = path16.dirname(dir);
4918
5112
  }
4919
5113
  return process.cwd();
4920
5114
  }
4921
5115
  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);
5116
+ fs14.mkdirSync(dest, { recursive: true });
5117
+ for (const entry of fs14.readdirSync(src, { withFileTypes: true })) {
5118
+ const srcPath = path16.join(src, entry.name);
5119
+ const destPath = path16.join(dest, entry.name);
4926
5120
  if (entry.isDirectory()) {
4927
5121
  copyDirRecursive2(srcPath, destPath);
4928
5122
  } else {
4929
- fs12.copyFileSync(srcPath, destPath);
5123
+ fs14.copyFileSync(srcPath, destPath);
4930
5124
  }
4931
5125
  }
4932
5126
  }
@@ -4962,7 +5156,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
4962
5156
  process.exit(1);
4963
5157
  }
4964
5158
  const resolvedPath = resolveHome(projectPath);
4965
- if (!fs12.existsSync(path15.join(resolvedPath, ".git"))) {
5159
+ if (!fs14.existsSync(path16.join(resolvedPath, ".git"))) {
4966
5160
  console.log(chalk5.yellow(`
4967
5161
  \u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
4968
5162
  `));
@@ -5015,7 +5209,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
5015
5209
  \u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
5016
5210
  }
5017
5211
  }
5018
- const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path15.join(config.hubVault, "spokes", name);
5212
+ const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path16.join(config.hubVault, "spokes", name);
5019
5213
  const project = {
5020
5214
  path: resolvedPath,
5021
5215
  captainName,
@@ -5029,20 +5223,20 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
5029
5223
  console.log(chalk5.green(`
5030
5224
  \u2714 Project '${name}' registered`));
5031
5225
  const pkgRoot = findPackageRoot2();
5032
- const spokeTemplate = path15.join(pkgRoot, "obsidian", "spoke");
5033
- if (fs12.existsSync(spokeVault)) {
5226
+ const spokeTemplate = path16.join(pkgRoot, "obsidian", "spoke");
5227
+ if (fs14.existsSync(spokeVault)) {
5034
5228
  console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
5035
- } else if (fs12.existsSync(spokeTemplate)) {
5229
+ } else if (fs14.existsSync(spokeTemplate)) {
5036
5230
  copyDirRecursive2(spokeTemplate, spokeVault);
5037
- const statusPath = path15.join(spokeVault, "status.md");
5038
- if (fs12.existsSync(statusPath)) {
5039
- const content = fs12.readFileSync(statusPath, "utf-8");
5231
+ const statusPath = path16.join(spokeVault, "status.md");
5232
+ if (fs14.existsSync(statusPath)) {
5233
+ const content = fs14.readFileSync(statusPath, "utf-8");
5040
5234
  const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
5041
- fs12.writeFileSync(statusPath, updated);
5235
+ fs14.writeFileSync(statusPath, updated);
5042
5236
  }
5043
5237
  console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
5044
5238
  } else {
5045
- fs12.mkdirSync(spokeVault, { recursive: true });
5239
+ fs14.mkdirSync(spokeVault, { recursive: true });
5046
5240
  console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
5047
5241
  }
5048
5242
  console.log("");
@@ -5143,10 +5337,10 @@ init_dist4();
5143
5337
  init_dist();
5144
5338
  import { Command as Command5 } from "commander";
5145
5339
  import { execSync as execSync9 } from "child_process";
5146
- import path16 from "path";
5340
+ import path17 from "path";
5147
5341
  import os7 from "os";
5148
5342
  import chalk7 from "chalk";
5149
- var TEMPLATES_DIR = path16.join(os7.homedir(), ".config", "squadrant", "templates");
5343
+ var TEMPLATES_DIR = path17.join(os7.homedir(), ".config", "squadrant", "templates");
5150
5344
  var TASK_PROMPTS = {
5151
5345
  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
5346
  "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 +5373,7 @@ async function runCommandSpawn(input) {
5179
5373
  if (!agent) {
5180
5374
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
5181
5375
  }
5182
- const promptFile = path16.join(TEMPLATES_DIR, `command.${agent.templateSuffix}.md`);
5376
+ const promptFile = path17.join(TEMPLATES_DIR, `command.${agent.templateSuffix}.md`);
5183
5377
  const cliCommand = agent.buildCommand({
5184
5378
  prompt,
5185
5379
  workdir: process.cwd(),
@@ -5206,8 +5400,8 @@ var commandCommand = new Command5("command").description("Spawn a one-shot Comma
5206
5400
  init_dist();
5207
5401
  init_dist();
5208
5402
  import { Command as Command9 } from "commander";
5209
- import fs13 from "fs";
5210
- import path17 from "path";
5403
+ import fs15 from "fs";
5404
+ import path18 from "path";
5211
5405
  import os8 from "os";
5212
5406
  import chalk9 from "chalk";
5213
5407
 
@@ -5875,7 +6069,7 @@ function writePerCrewOpencodeConfig(o) {
5875
6069
 
5876
6070
  // packages/cli/src/commands/crew.ts
5877
6071
  init_dist2();
5878
- var TEMPLATES_DIR2 = path17.join(os8.homedir(), ".config", "squadrant", "templates");
6072
+ var TEMPLATES_DIR2 = path18.join(os8.homedir(), ".config", "squadrant", "templates");
5879
6073
  async function runCrewSpawn(input) {
5880
6074
  const config = loadConfig();
5881
6075
  const proj = config.projects[input.project];
@@ -5923,8 +6117,8 @@ async function runCrewSpawn(input) {
5923
6117
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
5924
6118
  }
5925
6119
  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;
6120
+ const codexRoleFile = path18.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
6121
+ const roleInstructions = fs15.existsSync(codexRoleFile) ? fs15.readFileSync(codexRoleFile, "utf8") : void 0;
5928
6122
  return runCodexInteractiveSpawn({
5929
6123
  project: input.project,
5930
6124
  task: input.task,
@@ -5937,7 +6131,7 @@ async function runCrewSpawn(input) {
5937
6131
  roleInstructions
5938
6132
  });
5939
6133
  }
5940
- const promptFile = path17.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
6134
+ const promptFile = path18.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
5941
6135
  const interactive = agent.name === "claude" || agent.name === "opencode";
5942
6136
  const crewRole = config.defaults.roles?.crew;
5943
6137
  const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
@@ -5994,7 +6188,7 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
5994
6188
  });
5995
6189
  const rec = await squadrantdCall(req);
5996
6190
  const opencodeConfigPath = writePerCrewOpencodeConfig({
5997
- stateRoot: path17.join(os8.homedir(), ".config", "squadrant", "state"),
6191
+ stateRoot: path18.join(os8.homedir(), ".config", "squadrant", "state"),
5998
6192
  project: input.project,
5999
6193
  taskId: rec.id,
6000
6194
  // CP3 opt-in: --approval gates bash so the captain approves shell commands.
@@ -6234,11 +6428,11 @@ init_dist3();
6234
6428
  init_dist();
6235
6429
  init_dist();
6236
6430
  import { Command as Command10 } from "commander";
6237
- import fs14 from "fs";
6238
- import path18 from "path";
6431
+ import fs16 from "fs";
6432
+ import path19 from "path";
6239
6433
  import os9 from "os";
6240
6434
  import chalk10 from "chalk";
6241
- var TEMPLATES_DIR3 = path18.join(os9.homedir(), ".config", "squadrant", "templates");
6435
+ var TEMPLATES_DIR3 = path19.join(os9.homedir(), ".config", "squadrant", "templates");
6242
6436
  var SIDE_ROLES = ["research", "debug"];
6243
6437
  function shellQuote2(p) {
6244
6438
  return "'" + p.replace(/'/g, "'\\''") + "'";
@@ -6329,7 +6523,7 @@ async function runSideSpawn(input) {
6329
6523
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
6330
6524
  }
6331
6525
  const sideModel = sideRole?.model;
6332
- const promptFile = path18.join(
6526
+ const promptFile = path19.join(
6333
6527
  TEMPLATES_DIR3,
6334
6528
  `side.${input.role}.${agent.templateSuffix}.md`
6335
6529
  );
@@ -6340,7 +6534,7 @@ async function runSideSpawn(input) {
6340
6534
  prompt: input.topic,
6341
6535
  workdir: spawnCwd,
6342
6536
  role: "side",
6343
- promptFile: fs14.existsSync(promptFile) ? promptFile : void 0,
6537
+ promptFile: fs16.existsSync(promptFile) ? promptFile : void 0,
6344
6538
  interactive: true,
6345
6539
  permissionMode: config.defaults.permissions?.crew ?? "auto",
6346
6540
  ...sideModel ? { model: sideModel } : {}
@@ -6397,7 +6591,7 @@ async function runSideClose(project, name) {
6397
6591
  project,
6398
6592
  name
6399
6593
  );
6400
- if (fs14.existsSync(wtPath)) {
6594
+ if (fs16.existsSync(wtPath)) {
6401
6595
  try {
6402
6596
  removeWorktree(proj.path, wtPath);
6403
6597
  } catch (e) {
@@ -6625,8 +6819,8 @@ function renderDashboard(rows, opts) {
6625
6819
 
6626
6820
  // packages/web/dist/sync-hub.js
6627
6821
  init_dist();
6628
- import fs15 from "fs";
6629
- import path19 from "path";
6822
+ import fs17 from "fs";
6823
+ import path20 from "path";
6630
6824
  function buildMirrorMarkdown(s) {
6631
6825
  const fenced = "```";
6632
6826
  return [
@@ -6652,15 +6846,15 @@ function buildMirrorMarkdown(s) {
6652
6846
  function syncHub(deps) {
6653
6847
  if (!deps.config.hubVault)
6654
6848
  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");
6849
+ const writeFile5 = deps.writeFile ?? ((p, c) => fs17.writeFileSync(p, c));
6850
+ const mkdir5 = deps.mkdir ?? ((p) => fs17.mkdirSync(p, { recursive: true }));
6851
+ const projectsDir = path20.join(resolveHome(deps.config.hubVault), "projects");
6658
6852
  mkdir5(projectsDir);
6659
6853
  const out = [];
6660
6854
  for (const s of deps.statuses) {
6661
6855
  if (s.state === "unknown")
6662
6856
  continue;
6663
- const hubPath = path19.join(projectsDir, `${s.project}.md`);
6857
+ const hubPath = path20.join(projectsDir, `${s.project}.md`);
6664
6858
  try {
6665
6859
  writeFile5(hubPath, buildMirrorMarkdown(s));
6666
6860
  out.push({ project: s.project, hubPath });
@@ -7549,13 +7743,13 @@ init_dist3();
7549
7743
  init_dist2();
7550
7744
  import { Command as Command12 } from "commander";
7551
7745
  import { execSync as execSync11 } from "child_process";
7552
- import fs16 from "fs";
7553
- import path20 from "path";
7746
+ import fs18 from "fs";
7747
+ import path21 from "path";
7554
7748
  import os10 from "os";
7555
7749
  import chalk13 from "chalk";
7556
7750
  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");
7751
+ var TEMPLATES_DIR4 = path21.join(os10.homedir(), ".config", "squadrant", "templates");
7752
+ var SESSIONS_PATH2 = path21.join(os10.homedir(), ".config", "squadrant", "sessions.json");
7559
7753
  function ensureCmuxReady() {
7560
7754
  if (isInsideCmux()) return;
7561
7755
  console.log(chalk13.yellow("\n Not running inside cmux. Opening cmux app...\n"));
@@ -7662,12 +7856,12 @@ var launchCommand = new Command12("launch").description(
7662
7856
  }
7663
7857
  if (opts.all) {
7664
7858
  const hubPath = resolveHome(config.hubVault);
7665
- fs16.mkdirSync(hubPath, { recursive: true });
7859
+ fs18.mkdirSync(hubPath, { recursive: true });
7666
7860
  console.log(chalk13.bold("\nLaunching all captain workspaces\n"));
7667
7861
  for (const [name, proj] of Object.entries(config.projects)) {
7668
7862
  const projPath = resolveHome(proj.path);
7669
7863
  const spokePath = resolveHome(proj.spokeVault);
7670
- if (!fs16.existsSync(spokePath)) {
7864
+ if (!fs18.existsSync(spokePath)) {
7671
7865
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
7672
7866
  await ensureSpokeLayout(spokeDriver);
7673
7867
  console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -7698,7 +7892,7 @@ var launchCommand = new Command12("launch").description(
7698
7892
  const proj = config.projects[project];
7699
7893
  const projPath = resolveHome(proj.path);
7700
7894
  const spokePath = resolveHome(proj.spokeVault);
7701
- if (!fs16.existsSync(spokePath)) {
7895
+ if (!fs18.existsSync(spokePath)) {
7702
7896
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
7703
7897
  await ensureSpokeLayout(spokeDriver);
7704
7898
  console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -7833,24 +8027,24 @@ Shutting down captain workspace for '${project}'...
7833
8027
  // packages/cli/src/commands/feedback.ts
7834
8028
  init_dist();
7835
8029
  import { Command as Command14 } from "commander";
7836
- import fs17 from "fs";
8030
+ import fs19 from "fs";
7837
8031
  import os11 from "os";
7838
- import path21 from "path";
8032
+ import path22 from "path";
7839
8033
  import { fileURLToPath as fileURLToPath3 } from "url";
7840
8034
  import { execSync as execSync12 } from "child_process";
7841
8035
  import chalk15 from "chalk";
7842
8036
  var REPO_URL = "https://github.com/tu11aa/squadrant";
7843
8037
  function readPkgVersion() {
7844
8038
  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";
8039
+ const pkgPath = path22.join(path22.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
8040
+ return JSON.parse(fs19.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
7847
8041
  } catch {
7848
8042
  return "unknown";
7849
8043
  }
7850
8044
  }
7851
8045
  function readMetrics(metricsPath) {
7852
8046
  try {
7853
- return JSON.parse(fs17.readFileSync(metricsPath, "utf-8"));
8047
+ return JSON.parse(fs19.readFileSync(metricsPath, "utf-8"));
7854
8048
  } catch {
7855
8049
  return {};
7856
8050
  }
@@ -7888,7 +8082,7 @@ function buildIssueUrl(metrics, squadrantVersion) {
7888
8082
  }
7889
8083
  var feedbackCommand = new Command14("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
7890
8084
  const config = loadConfig();
7891
- const metricsPath = config.metrics?.path || path21.join(os11.homedir(), ".config", "squadrant", "metrics.json");
8085
+ const metricsPath = config.metrics?.path || path22.join(os11.homedir(), ".config", "squadrant", "metrics.json");
7892
8086
  const metrics = readMetrics(metricsPath);
7893
8087
  const version = readStamp(config) ?? readPkgVersion();
7894
8088
  const issueUrl = buildIssueUrl(metrics, version);
@@ -7910,8 +8104,8 @@ init_dist();
7910
8104
  init_dist();
7911
8105
  init_dist3();
7912
8106
  import { Command as Command15 } from "commander";
7913
- import fs18 from "fs";
7914
- import path22 from "path";
8107
+ import fs20 from "fs";
8108
+ import path23 from "path";
7915
8109
  import chalk16 from "chalk";
7916
8110
  import matter3 from "gray-matter";
7917
8111
  function getDateStr(yesterday) {
@@ -7920,11 +8114,11 @@ function getDateStr(yesterday) {
7920
8114
  async function getProjectStandup(name, project, dateStr, registry, config) {
7921
8115
  const workspace = registry.forProject(name, config);
7922
8116
  const spokeVault = resolveHome(project.spokeVault);
7923
- const statusFile = path22.join(spokeVault, "status.md");
8117
+ const statusFile = path23.join(spokeVault, "status.md");
7924
8118
  let status = {};
7925
- if (fs18.existsSync(statusFile)) {
8119
+ if (fs20.existsSync(statusFile)) {
7926
8120
  try {
7927
- status = matter3(fs18.readFileSync(statusFile, "utf-8")).data;
8121
+ status = matter3(fs20.readFileSync(statusFile, "utf-8")).data;
7928
8122
  } catch {
7929
8123
  }
7930
8124
  }
@@ -8042,15 +8236,15 @@ init_dist();
8042
8236
  init_dist();
8043
8237
  init_dist3();
8044
8238
  import { Command as Command16 } from "commander";
8045
- import fs19 from "fs";
8046
- import path23 from "path";
8239
+ import fs21 from "fs";
8240
+ import path24 from "path";
8047
8241
  import chalk17 from "chalk";
8048
8242
  import matter4 from "gray-matter";
8049
8243
  function readStatus(spokeVault) {
8050
- const statusFile = path23.join(spokeVault, "status.md");
8051
- if (!fs19.existsSync(statusFile)) return {};
8244
+ const statusFile = path24.join(spokeVault, "status.md");
8245
+ if (!fs21.existsSync(statusFile)) return {};
8052
8246
  try {
8053
- return matter4(fs19.readFileSync(statusFile, "utf-8")).data;
8247
+ return matter4(fs21.readFileSync(statusFile, "utf-8")).data;
8054
8248
  } catch {
8055
8249
  return {};
8056
8250
  }
@@ -8360,9 +8554,9 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
8360
8554
  const config = loadConfig();
8361
8555
  const registry = buildRegistry2();
8362
8556
  try {
8363
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8557
+ const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8364
8558
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8365
- const content = await driver.read(path26);
8559
+ const content = await driver.read(path27);
8366
8560
  process.stdout.write(content);
8367
8561
  } catch (err) {
8368
8562
  console.error(chalk19.red(err.message));
@@ -8374,26 +8568,26 @@ workspaceCommand.command("write").description("Write content to a scope-relative
8374
8568
  const registry = buildRegistry2();
8375
8569
  try {
8376
8570
  let projectTarget;
8377
- let path26;
8571
+ let path27;
8378
8572
  let rawContent;
8379
8573
  if (opts.hub) {
8380
8574
  if (arg3 !== void 0) {
8381
8575
  throw new Error("With --hub, pass only the path and content");
8382
8576
  }
8383
8577
  projectTarget = void 0;
8384
- path26 = arg1;
8578
+ path27 = arg1;
8385
8579
  rawContent = arg2;
8386
8580
  } else {
8387
8581
  if (arg3 === void 0) {
8388
8582
  throw new Error("Missing content \u2014 usage: <project> <path> <content>");
8389
8583
  }
8390
8584
  projectTarget = arg1;
8391
- path26 = arg2;
8585
+ path27 = arg2;
8392
8586
  rawContent = arg3;
8393
8587
  }
8394
8588
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8395
8589
  const payload = rawContent === "-" ? await readStdin() : rawContent;
8396
- await driver.write(path26, payload);
8590
+ await driver.write(path27, payload);
8397
8591
  } catch (err) {
8398
8592
  console.error(chalk19.red(err.message));
8399
8593
  process.exit(1);
@@ -8403,9 +8597,9 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
8403
8597
  const config = loadConfig();
8404
8598
  const registry = buildRegistry2();
8405
8599
  try {
8406
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8600
+ const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8407
8601
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8408
- const entries = await driver.list(path26);
8602
+ const entries = await driver.list(path27);
8409
8603
  for (const entry of entries) console.log(entry);
8410
8604
  } catch (err) {
8411
8605
  console.error(chalk19.red(err.message));
@@ -8416,9 +8610,9 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
8416
8610
  const config = loadConfig();
8417
8611
  const registry = buildRegistry2();
8418
8612
  try {
8419
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8613
+ const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8420
8614
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8421
- const ok = await driver.exists(path26);
8615
+ const ok = await driver.exists(path27);
8422
8616
  process.exit(ok ? 0 : 1);
8423
8617
  } catch (err) {
8424
8618
  console.error(chalk19.red(err.message));
@@ -8429,9 +8623,9 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
8429
8623
  const config = loadConfig();
8430
8624
  const registry = buildRegistry2();
8431
8625
  try {
8432
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8626
+ const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8433
8627
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8434
- await driver.mkdir(path26);
8628
+ await driver.mkdir(path27);
8435
8629
  } catch (err) {
8436
8630
  console.error(chalk19.red(err.message));
8437
8631
  process.exit(1);
@@ -8470,8 +8664,8 @@ init_dist3();
8470
8664
  init_dist();
8471
8665
  import { Command as Command20 } from "commander";
8472
8666
  import chalk21 from "chalk";
8473
- import fs20 from "fs";
8474
- import path24 from "path";
8667
+ import fs22 from "fs";
8668
+ import path25 from "path";
8475
8669
  import { fileURLToPath as fileURLToPath4 } from "url";
8476
8670
  function parseScope(v) {
8477
8671
  if (v !== "user" && v !== "project") {
@@ -8480,10 +8674,10 @@ function parseScope(v) {
8480
8674
  return v;
8481
8675
  }
8482
8676
  function findPackageRoot3() {
8483
- let dir = path24.dirname(fileURLToPath4(import.meta.url));
8677
+ let dir = path25.dirname(fileURLToPath4(import.meta.url));
8484
8678
  while (dir !== "/" && dir !== "") {
8485
- if (fs20.existsSync(path24.join(dir, "package.json"))) return dir;
8486
- dir = path24.dirname(dir);
8679
+ if (fs22.existsSync(path25.join(dir, "package.json"))) return dir;
8680
+ dir = path25.dirname(dir);
8487
8681
  }
8488
8682
  return process.cwd();
8489
8683
  }
@@ -8677,12 +8871,12 @@ init_dist();
8677
8871
  init_dist();
8678
8872
  init_dist();
8679
8873
  import { Command as Command22 } from "commander";
8680
- import fs21 from "fs";
8874
+ import fs23 from "fs";
8681
8875
  import { fileURLToPath as fileURLToPath5 } from "url";
8682
8876
  import { dirname as dirname5, join as join19 } from "path";
8683
8877
  import chalk22 from "chalk";
8684
8878
  function runConfigCheck(opts) {
8685
- const raw = JSON.parse(fs21.readFileSync(opts.configPath, "utf-8"));
8879
+ const raw = JSON.parse(fs23.readFileSync(opts.configPath, "utf-8"));
8686
8880
  const def = getDefaultConfig();
8687
8881
  const items = detectDrift(raw, def);
8688
8882
  let working = raw;
@@ -8699,10 +8893,40 @@ function runConfigCheck(opts) {
8699
8893
  stamped = true;
8700
8894
  }
8701
8895
  if (opts.fix || opts.accept || stamped) {
8702
- fs21.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
8896
+ fs23.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
8703
8897
  }
8704
8898
  return { items, applied, remaining, stamped };
8705
8899
  }
8900
+ function runConfigGet(key, configPath = DEFAULT_CONFIG_PATH) {
8901
+ const config = loadConfig(configPath);
8902
+ const parts = key.split(".");
8903
+ let node = config;
8904
+ for (const p of parts) {
8905
+ if (node === null || typeof node !== "object" || !(p in node)) {
8906
+ throw new Error(`config key not found: ${key}`);
8907
+ }
8908
+ node = node[p];
8909
+ }
8910
+ return node;
8911
+ }
8912
+ function runConfigSet(key, value, configPath = DEFAULT_CONFIG_PATH) {
8913
+ let parsed;
8914
+ try {
8915
+ parsed = JSON.parse(value);
8916
+ } catch {
8917
+ parsed = value;
8918
+ }
8919
+ const config = loadConfig(configPath);
8920
+ const parts = key.split(".");
8921
+ let node = config;
8922
+ for (let i = 0; i < parts.length - 1; i++) {
8923
+ const p = parts[i];
8924
+ if (node[p] === null || typeof node[p] !== "object") node[p] = {};
8925
+ node = node[p];
8926
+ }
8927
+ node[parts[parts.length - 1]] = parsed;
8928
+ saveConfig(config, configPath);
8929
+ }
8706
8930
  var SEV_COLOR = {
8707
8931
  info: chalk22.green,
8708
8932
  advisory: chalk22.yellow,
@@ -8724,7 +8948,7 @@ function printItems(items) {
8724
8948
  var configCommand = new Command22("config").description("Inspect and reconcile squadrant config");
8725
8949
  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
8950
  const pkgVersion = readPkgVersion2();
8727
- if (!fs21.existsSync(DEFAULT_CONFIG_PATH)) {
8951
+ if (!fs23.existsSync(DEFAULT_CONFIG_PATH)) {
8728
8952
  console.log(chalk22.yellow("No config found \u2014 run `squadrant init` first."));
8729
8953
  return;
8730
8954
  }
@@ -8751,9 +8975,27 @@ ${judgment.length} item(s) need review \u2014 run the config-doctor skill, or \`
8751
8975
  console.log(chalk22.green("\n\u2714 Config reconciled and stamped."));
8752
8976
  }
8753
8977
  });
8978
+ configCommand.command("get").description("Read a config value by dotted key (e.g. defaults.effort)").argument("<key>", "dotted config key").action((key) => {
8979
+ try {
8980
+ const value = runConfigGet(key);
8981
+ console.log(typeof value === "string" ? value : JSON.stringify(value));
8982
+ } catch (e) {
8983
+ console.error(chalk22.red(e.message));
8984
+ process.exit(1);
8985
+ }
8986
+ });
8987
+ configCommand.command("set").description("Write a config value by dotted key (e.g. defaults.effort low)").argument("<key>", "dotted config key").argument("<value>", "value (JSON-parsed when possible, else a bare string)").action((key, value) => {
8988
+ try {
8989
+ runConfigSet(key, value);
8990
+ console.log(chalk22.green(`\u2714 set ${key} = ${value}`));
8991
+ } catch (e) {
8992
+ console.error(chalk22.red(e.message));
8993
+ process.exit(1);
8994
+ }
8995
+ });
8754
8996
  function readPkgVersion2() {
8755
8997
  const pkgPath = join19(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
8756
- return JSON.parse(fs21.readFileSync(pkgPath, "utf-8")).version;
8998
+ return JSON.parse(fs23.readFileSync(pkgPath, "utf-8")).version;
8757
8999
  }
8758
9000
 
8759
9001
  // packages/cli/src/commands/heal.ts
@@ -9019,8 +9261,8 @@ var cmuxCommand = new Command25("cmux").description("cmux integration helpers").
9019
9261
 
9020
9262
  // packages/cli/src/commands/effort.ts
9021
9263
  init_dist();
9022
- import fs22 from "fs";
9023
- import path25 from "path";
9264
+ import fs24 from "fs";
9265
+ import path26 from "path";
9024
9266
  import { Command as Command26 } from "commander";
9025
9267
  import chalk26 from "chalk";
9026
9268
  var VALID_EFFORTS = ["max", "balance", "low"];
@@ -9047,9 +9289,9 @@ function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH) {
9047
9289
  }
9048
9290
  function canonical(p) {
9049
9291
  try {
9050
- return fs22.realpathSync(p);
9292
+ return fs24.realpathSync(p);
9051
9293
  } catch {
9052
- return path25.resolve(p);
9294
+ return path26.resolve(p);
9053
9295
  }
9054
9296
  }
9055
9297
  async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd()) {
@@ -9093,19 +9335,232 @@ var effortCommand = new Command26("effort").description("Get or set the global c
9093
9335
  }
9094
9336
  });
9095
9337
 
9338
+ // packages/cli/src/commands/telegram.ts
9339
+ init_dist();
9340
+ init_dist2();
9341
+ import { join as join21, dirname as dirname6 } from "path";
9342
+ import { emitKeypressEvents } from "readline";
9343
+ import { Command as Command27 } from "commander";
9344
+ import chalk27 from "chalk";
9345
+ function defaultStateRoot() {
9346
+ return join21(dirname6(DEFAULT_CONFIG_PATH), "state");
9347
+ }
9348
+ function runTelegramStatus(opts) {
9349
+ const tg = opts.config.telegram;
9350
+ const env = opts.env ?? process.env;
9351
+ const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
9352
+ const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
9353
+ const sep2 = key.indexOf("::");
9354
+ return { project: key.slice(0, sep2), scope: key.slice(sep2 + 2), topicId };
9355
+ });
9356
+ return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
9357
+ }
9358
+ async function runTelegramSend(opts) {
9359
+ const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
9360
+ if (topicId === void 0) {
9361
+ throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
9362
+ }
9363
+ await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
9364
+ return { chatId: opts.cfg.supergroupId, topicId };
9365
+ }
9366
+ async function runTelegramLink(opts) {
9367
+ const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
9368
+ if (existing !== void 0) return { topicId: existing, created: false };
9369
+ const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
9370
+ setTopic(opts.stateRoot, opts.project, topicId);
9371
+ return { topicId, created: true };
9372
+ }
9373
+ async function questionMasked() {
9374
+ return new Promise((resolve3) => {
9375
+ emitKeypressEvents(process.stdin);
9376
+ process.stdin.setRawMode(true);
9377
+ process.stdin.resume();
9378
+ let answer = "";
9379
+ const onKeypress = (_str, key) => {
9380
+ if (key.ctrl && key.name === "c") {
9381
+ process.stdin.removeListener("keypress", onKeypress);
9382
+ process.stdin.setRawMode(false);
9383
+ process.stdin.pause();
9384
+ process.stdout.write("\n");
9385
+ process.exit(130);
9386
+ } else if (key.name === "return" || key.name === "enter") {
9387
+ process.stdin.removeListener("keypress", onKeypress);
9388
+ process.stdin.setRawMode(false);
9389
+ process.stdin.pause();
9390
+ process.stdout.write("\n");
9391
+ resolve3(answer);
9392
+ } else if (key.name === "backspace") {
9393
+ if (answer.length > 0) {
9394
+ answer = answer.slice(0, -1);
9395
+ process.stdout.write("\b \b");
9396
+ }
9397
+ } else if (!key.ctrl && !key.meta && key.sequence) {
9398
+ answer += key.sequence;
9399
+ process.stdout.write("*");
9400
+ }
9401
+ };
9402
+ process.stdin.on("keypress", onKeypress);
9403
+ });
9404
+ }
9405
+ async function questionYesNo(prompt) {
9406
+ const { createInterface: createInterface2 } = await import("readline");
9407
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
9408
+ return new Promise((resolve3) => {
9409
+ rl.question(prompt, (ans) => {
9410
+ rl.close();
9411
+ process.stdin.pause();
9412
+ resolve3(/^y(es)?$/i.test(ans.trim()));
9413
+ });
9414
+ });
9415
+ }
9416
+ var telegramCommand = new Command27("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
9417
+ telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
9418
+ const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
9419
+ console.log(`token: ${tokenSet ? chalk27.green("set") : chalk27.yellow("unset")}`);
9420
+ console.log(`supergroup: ${supergroupId ?? chalk27.yellow("unset")}`);
9421
+ if (links.length === 0) {
9422
+ console.log("no projects linked");
9423
+ return;
9424
+ }
9425
+ for (const l of links) console.log(` ${l.project} (${l.scope}) \u2192 topic ${l.topicId}`);
9426
+ });
9427
+ 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) => {
9428
+ const cfg = loadConfig().telegram;
9429
+ if (!cfg) {
9430
+ console.error(chalk27.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
9431
+ process.exit(1);
9432
+ }
9433
+ const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
9434
+ if (!token) {
9435
+ console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
9436
+ process.exit(1);
9437
+ }
9438
+ const client = createTelegramClient({ token });
9439
+ const { topicId, created } = await runTelegramLink({ project, cfg, client, stateRoot: defaultStateRoot() });
9440
+ console.log(chalk27.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
9441
+ });
9442
+ telegramCommand.command("setup").description("Interactive wizard \u2014 bot token, validate, auto-detect supergroup, write config").action(async () => {
9443
+ if (!process.stdin.isTTY) {
9444
+ console.error(chalk27.red("setup requires a TTY \u2014 pipe input is not supported"));
9445
+ process.exit(1);
9446
+ }
9447
+ console.log();
9448
+ console.log(chalk27.bold("Telegram setup") + " \u2014 connect squadrant to a Telegram bot for notifications + remote control");
9449
+ console.log();
9450
+ console.log("Before you start you need:");
9451
+ console.log(" 1. A bot token from @BotFather (send /newbot)");
9452
+ console.log(" 2. A forum supergroup with the bot added as an admin (Topics enabled)");
9453
+ console.log(" 3. Bot privacy mode set to OFF (@BotFather \u2192 /setprivacy \u2192 Disable)");
9454
+ console.log();
9455
+ console.log(chalk27.bold("Step 1/3 \u2014 Bot token"));
9456
+ console.log("Paste your bot token then press Enter (input is hidden):");
9457
+ const token = await questionMasked();
9458
+ if (!token) {
9459
+ console.error(chalk27.red("token required"));
9460
+ process.exit(1);
9461
+ }
9462
+ const client = createTelegramClient({ token });
9463
+ let botUser;
9464
+ try {
9465
+ botUser = await client.getMe();
9466
+ } catch (e) {
9467
+ console.error(chalk27.red(`token rejected: ${e.message}`));
9468
+ process.exit(1);
9469
+ }
9470
+ console.log(chalk27.green(`Connected as @${botUser.username}`));
9471
+ console.log();
9472
+ console.log(chalk27.bold("Step 2/3 \u2014 Find your group"));
9473
+ console.log("Add the bot to your forum supergroup, then send any message in it.");
9474
+ console.log(chalk27.dim("Waiting for a message (up to 60s)\u2026"));
9475
+ let supergroupId;
9476
+ let userId;
9477
+ try {
9478
+ ({ supergroupId, userId } = await detectGroupAndUser(client, { timeoutMs: 6e4 }));
9479
+ } catch {
9480
+ console.error(chalk27.red("Timed out \u2014 no supergroup message received within 60s."));
9481
+ console.error(chalk27.yellow("Check: bot is an admin in the group \xB7 privacy mode is OFF \xB7 Topics enabled"));
9482
+ process.exit(1);
9483
+ }
9484
+ console.log(chalk27.green(`Found group: ${supergroupId}`));
9485
+ console.log();
9486
+ console.log(chalk27.bold("Step 3/3 \u2014 Remote control + Save"));
9487
+ console.log(chalk27.dim("Remote control enables auto-launching captains and the General command channel"));
9488
+ console.log(chalk27.dim("from your phone \u2014 gated to your Telegram user-id only (fail-closed)."));
9489
+ let users;
9490
+ let remoteControl;
9491
+ if (userId === void 0) {
9492
+ console.log(chalk27.yellow("Could not read your user-id from that message \u2014 skipping remote control."));
9493
+ console.log(chalk27.yellow("Re-run setup once it's available, or edit telegram.users in config manually."));
9494
+ } else {
9495
+ const enable = await questionYesNo(
9496
+ `Enable remote control for your user-id ${userId}? [y/N] `
9497
+ );
9498
+ if (enable) {
9499
+ users = [userId];
9500
+ remoteControl = true;
9501
+ }
9502
+ }
9503
+ writeTelegramConfig(DEFAULT_CONFIG_PATH, { token, supergroupId, users, remoteControl });
9504
+ console.log(chalk27.green(`Wrote telegram config \u2014 token: ${maskToken(token)} group: ${supergroupId}`));
9505
+ if (remoteControl) {
9506
+ console.log(chalk27.green(`Remote control: ON (allowlisted user-id ${users?.[0]})`));
9507
+ } else {
9508
+ console.log(chalk27.dim("Remote control: off (default). Re-run setup to enable later."));
9509
+ }
9510
+ console.log();
9511
+ console.log(`Next: ${chalk27.cyan("squadrant telegram link <project>")}`);
9512
+ });
9513
+ telegramCommand.command("send").argument("<project>", "project whose topic receives the message").argument("[message...]", "message text (omit to read from stdin)").description("Send a message to a project's linked Telegram topic").action(async (project, messageParts) => {
9514
+ const cfg = loadConfig().telegram;
9515
+ if (!cfg) {
9516
+ console.error(chalk27.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
9517
+ process.exit(1);
9518
+ }
9519
+ const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
9520
+ if (!token) {
9521
+ console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
9522
+ process.exit(1);
9523
+ }
9524
+ let message;
9525
+ if (messageParts.length > 0) {
9526
+ message = messageParts.join(" ");
9527
+ } else if (!process.stdin.isTTY) {
9528
+ const { createInterface: createInterface2 } = await import("readline");
9529
+ const lines = [];
9530
+ const rl = createInterface2({ input: process.stdin });
9531
+ for await (const line of rl) lines.push(line);
9532
+ message = lines.join("\n").trimEnd();
9533
+ if (!message) {
9534
+ console.error(chalk27.red("no message provided (stdin was empty)"));
9535
+ process.exit(1);
9536
+ }
9537
+ } else {
9538
+ console.error(chalk27.red("message required \u2014 pass as argument or pipe via stdin"));
9539
+ process.exit(1);
9540
+ }
9541
+ const client = createTelegramClient({ token });
9542
+ try {
9543
+ const { chatId, topicId } = await runTelegramSend({ project, message, cfg, client, stateRoot: defaultStateRoot() });
9544
+ console.log(chalk27.green(`sent to group ${chatId} topic ${topicId}`));
9545
+ } catch (e) {
9546
+ console.error(chalk27.red(e.message));
9547
+ process.exit(1);
9548
+ }
9549
+ });
9550
+
9096
9551
  // packages/cli/src/index.ts
9097
9552
  init_dist();
9098
9553
  init_dist();
9099
9554
  init_dist();
9100
- var __dirname = dirname6(fileURLToPath6(import.meta.url));
9101
- var pkg = JSON.parse(readFileSync11(join21(__dirname, "..", "package.json"), "utf-8"));
9555
+ var __dirname = dirname7(fileURLToPath6(import.meta.url));
9556
+ var pkg = JSON.parse(readFileSync11(join22(__dirname, "..", "package.json"), "utf-8"));
9102
9557
  ensureRuntimeSynced({
9103
- sourceRoot: join21(__dirname, ".."),
9104
- runtimeRoot: join21(homedir14(), ".config", "squadrant")
9558
+ sourceRoot: join22(__dirname, ".."),
9559
+ runtimeRoot: join22(homedir14(), ".config", "squadrant")
9105
9560
  });
9106
9561
  if (process.argv[2] !== "config") {
9107
9562
  try {
9108
- const cfgPath = join21(homedir14(), ".config", "squadrant", "config.json");
9563
+ const cfgPath = join22(homedir14(), ".config", "squadrant", "config.json");
9109
9564
  if (existsSync10(cfgPath)) {
9110
9565
  const cfg = JSON.parse(readFileSync11(cfgPath, "utf-8"));
9111
9566
  if (needsCheck(cfg, pkg.version)) {
@@ -9130,7 +9585,7 @@ if (process.argv[2] !== "config") {
9130
9585
  if (!process.env.SQUADRANT_DAEMON_SKIP) {
9131
9586
  ensureDaemon();
9132
9587
  }
9133
- var program = new Command27();
9588
+ var program = new Command28();
9134
9589
  program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
9135
9590
  program.addCommand(doctorCommand);
9136
9591
  program.addCommand(initCommand);
@@ -9156,6 +9611,7 @@ program.addCommand(healCommand);
9156
9611
  program.addCommand(groupCommand);
9157
9612
  program.addCommand(cmuxCommand);
9158
9613
  program.addCommand(effortCommand);
9614
+ program.addCommand(telegramCommand);
9159
9615
  program.parseAsync().catch((e) => {
9160
9616
  process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}
9161
9617
  `);