squadrant 0.9.1 → 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/"))
@@ -1499,7 +1499,8 @@ function buildCompletionProtocol(taskId, project) {
1499
1499
  "COMPLETION PROTOCOL (required): When this task is fully complete, your FINAL action MUST be to run exactly:",
1500
1500
  ` squadrant crew signal done --task-id ${taskId} --project ${project} --message "<one-line summary>"`,
1501
1501
  "Run it as a discrete final step AFTER you report your results. If you are blocked or need a decision, instead run:",
1502
- ` squadrant crew signal blocked --task-id ${taskId} --project ${project} --question "<your question>"`
1502
+ ` squadrant crew signal blocked --task-id ${taskId} --project ${project} --question "<your question>"`,
1503
+ "If this task failed because of a defect in squadrant itself (not an API/infra blip, a config/user error, or an expected failure), say so in your signal done/blocked message so the captain can check tu11aa/squadrant and file it. Don't file issues from the crew."
1503
1504
  ].join("\n");
1504
1505
  }
1505
1506
  function shellQuote(p) {
@@ -1570,6 +1571,199 @@ var init_crew_lifecycle = __esm({
1570
1571
  }
1571
1572
  });
1572
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
+
1573
1767
  // packages/core/dist/index.js
1574
1768
  var init_dist2 = __esm({
1575
1769
  "packages/core/dist/index.js"() {
@@ -1595,6 +1789,7 @@ var init_dist2 = __esm({
1595
1789
  init_session_freshness();
1596
1790
  init_crew_protocol();
1597
1791
  init_crew_lifecycle();
1792
+ init_telegram();
1598
1793
  }
1599
1794
  });
1600
1795
 
@@ -2113,13 +2308,13 @@ var init_notifiers = __esm({
2113
2308
  });
2114
2309
 
2115
2310
  // packages/workspaces/dist/workspaces/obsidian.js
2116
- import fs8 from "fs/promises";
2311
+ import fs10 from "fs/promises";
2117
2312
  import { existsSync as existsSync8 } from "fs";
2118
- import path7 from "path";
2313
+ import path8 from "path";
2119
2314
  function resolveInRoot(root, relative) {
2120
- const joined = path7.resolve(root, relative);
2121
- const normalized = path7.resolve(root) + path7.sep;
2122
- 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)) {
2123
2318
  throw new Error(`Path '${relative}' escapes workspace root`);
2124
2319
  }
2125
2320
  return joined;
@@ -2138,16 +2333,16 @@ function createObsidianDriver(scope) {
2138
2333
  };
2139
2334
  },
2140
2335
  async read(rel) {
2141
- return fs8.readFile(resolveInRoot(root, rel), "utf-8");
2336
+ return fs10.readFile(resolveInRoot(root, rel), "utf-8");
2142
2337
  },
2143
2338
  async write(rel, content) {
2144
2339
  const abs = resolveInRoot(root, rel);
2145
- await fs8.mkdir(path7.dirname(abs), { recursive: true });
2146
- await fs8.writeFile(abs, content);
2340
+ await fs10.mkdir(path8.dirname(abs), { recursive: true });
2341
+ await fs10.writeFile(abs, content);
2147
2342
  },
2148
2343
  async exists(rel) {
2149
2344
  try {
2150
- await fs8.access(resolveInRoot(root, rel));
2345
+ await fs10.access(resolveInRoot(root, rel));
2151
2346
  return true;
2152
2347
  } catch {
2153
2348
  return false;
@@ -2155,13 +2350,13 @@ function createObsidianDriver(scope) {
2155
2350
  },
2156
2351
  async list(rel) {
2157
2352
  try {
2158
- return await fs8.readdir(resolveInRoot(root, rel));
2353
+ return await fs10.readdir(resolveInRoot(root, rel));
2159
2354
  } catch {
2160
2355
  return [];
2161
2356
  }
2162
2357
  },
2163
2358
  async mkdir(rel) {
2164
- await fs8.mkdir(resolveInRoot(root, rel), { recursive: true });
2359
+ await fs10.mkdir(resolveInRoot(root, rel), { recursive: true });
2165
2360
  }
2166
2361
  };
2167
2362
  }
@@ -2830,8 +3025,8 @@ var init_registry4 = __esm({
2830
3025
  });
2831
3026
 
2832
3027
  // packages/agents/dist/drivers/launch-cmd.js
2833
- import fs9 from "fs";
2834
- import path8 from "path";
3028
+ import fs11 from "fs";
3029
+ import path9 from "path";
2835
3030
  function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir) {
2836
3031
  const driver = registry.getDriver(agentName);
2837
3032
  if (driver.name === "claude") {
@@ -2847,27 +3042,27 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
2847
3042
  cmd += ` --model ${model}`;
2848
3043
  }
2849
3044
  if (templatesDir) {
2850
- const roleFile2 = path8.join(templatesDir, `${role}.claude.md`);
2851
- const legacyRoleFile = path8.join(templatesDir, `${role}.CLAUDE.md`);
2852
- 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;
2853
3048
  if (actualRoleFile) {
2854
3049
  cmd += ` --append-system-prompt-file ${actualRoleFile}`;
2855
3050
  }
2856
- const pluginDir = path8.join(templatesDir, "..", "plugin");
2857
- if (fs9.existsSync(pluginDir)) {
3051
+ const pluginDir = path9.join(templatesDir, "..", "plugin");
3052
+ if (fs11.existsSync(pluginDir)) {
2858
3053
  cmd += ` --plugin-dir ${pluginDir}`;
2859
3054
  }
2860
3055
  }
2861
3056
  return cmd;
2862
3057
  }
2863
- 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;
2864
3059
  return driver.buildCommand({
2865
3060
  prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
2866
3061
  workdir: process.cwd(),
2867
3062
  role,
2868
3063
  model,
2869
3064
  autoApprove: true,
2870
- promptFile: roleFile && fs9.existsSync(roleFile) ? roleFile : void 0
3065
+ promptFile: roleFile && fs11.existsSync(roleFile) ? roleFile : void 0
2871
3066
  });
2872
3067
  }
2873
3068
  var init_launch_cmd = __esm({
@@ -2890,7 +3085,7 @@ var init_drivers = __esm({
2890
3085
 
2891
3086
  // packages/agents/dist/projection/cursor.js
2892
3087
  import { mkdir, readFile, writeFile } from "fs/promises";
2893
- import path9 from "path";
3088
+ import path10 from "path";
2894
3089
  import os2 from "os";
2895
3090
  function renderMdc(source) {
2896
3091
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -2939,7 +3134,7 @@ function createCursorEmitter() {
2939
3134
  if (scope === "user") {
2940
3135
  return [
2941
3136
  {
2942
- path: path9.join(os2.homedir(), ".cursor/rules/squadrant-global.mdc"),
3137
+ path: path10.join(os2.homedir(), ".cursor/rules/squadrant-global.mdc"),
2943
3138
  shared: false,
2944
3139
  format: "mdc"
2945
3140
  }
@@ -2949,7 +3144,7 @@ function createCursorEmitter() {
2949
3144
  return [];
2950
3145
  return [
2951
3146
  {
2952
- path: path9.join(projectRoot, ".cursor/rules/squadrant.mdc"),
3147
+ path: path10.join(projectRoot, ".cursor/rules/squadrant.mdc"),
2953
3148
  shared: false,
2954
3149
  format: "mdc"
2955
3150
  }
@@ -2966,7 +3161,7 @@ function createCursorEmitter() {
2966
3161
  diff: buildDiff(existing, generated)
2967
3162
  };
2968
3163
  }
2969
- await mkdir(path9.dirname(dest.path), { recursive: true });
3164
+ await mkdir(path10.dirname(dest.path), { recursive: true });
2970
3165
  await writeFile(dest.path, generated, "utf-8");
2971
3166
  return {
2972
3167
  written: true,
@@ -3017,7 +3212,7 @@ var init_marker = __esm({
3017
3212
 
3018
3213
  // packages/agents/dist/projection/codex.js
3019
3214
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
3020
- import path10 from "path";
3215
+ import path11 from "path";
3021
3216
  import os3 from "os";
3022
3217
  function renderMarkdown(source) {
3023
3218
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -3042,7 +3237,7 @@ function createCodexEmitter() {
3042
3237
  destinations(scope, projectRoot) {
3043
3238
  if (scope === "user") {
3044
3239
  return [{
3045
- path: path10.join(os3.homedir(), ".codex/AGENTS.md"),
3240
+ path: path11.join(os3.homedir(), ".codex/AGENTS.md"),
3046
3241
  shared: true,
3047
3242
  format: "markdown"
3048
3243
  }];
@@ -3050,7 +3245,7 @@ function createCodexEmitter() {
3050
3245
  if (!projectRoot)
3051
3246
  return [];
3052
3247
  return [{
3053
- path: path10.join(projectRoot, "AGENTS.md"),
3248
+ path: path11.join(projectRoot, "AGENTS.md"),
3054
3249
  shared: true,
3055
3250
  format: "markdown"
3056
3251
  }];
@@ -3071,7 +3266,7 @@ ${existing ?? ""}
3071
3266
  ${generated}`
3072
3267
  };
3073
3268
  }
3074
- await mkdir2(path10.dirname(dest.path), { recursive: true });
3269
+ await mkdir2(path11.dirname(dest.path), { recursive: true });
3075
3270
  await writeFile2(dest.path, generated, "utf-8");
3076
3271
  return {
3077
3272
  written: true,
@@ -3089,7 +3284,7 @@ var init_codex2 = __esm({
3089
3284
 
3090
3285
  // packages/agents/dist/projection/gemini.js
3091
3286
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
3092
- import path11 from "path";
3287
+ import path12 from "path";
3093
3288
  import os4 from "os";
3094
3289
  function renderMarkdown2(source) {
3095
3290
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -3114,7 +3309,7 @@ function createGeminiEmitter() {
3114
3309
  destinations(scope, projectRoot) {
3115
3310
  if (scope === "user") {
3116
3311
  return [{
3117
- path: path11.join(os4.homedir(), ".gemini/GEMINI.md"),
3312
+ path: path12.join(os4.homedir(), ".gemini/GEMINI.md"),
3118
3313
  shared: true,
3119
3314
  format: "markdown"
3120
3315
  }];
@@ -3122,7 +3317,7 @@ function createGeminiEmitter() {
3122
3317
  if (!projectRoot)
3123
3318
  return [];
3124
3319
  return [{
3125
- path: path11.join(projectRoot, "GEMINI.md"),
3320
+ path: path12.join(projectRoot, "GEMINI.md"),
3126
3321
  shared: true,
3127
3322
  format: "markdown"
3128
3323
  }];
@@ -3143,7 +3338,7 @@ ${existing ?? ""}
3143
3338
  ${generated}`
3144
3339
  };
3145
3340
  }
3146
- await mkdir3(path11.dirname(dest.path), { recursive: true });
3341
+ await mkdir3(path12.dirname(dest.path), { recursive: true });
3147
3342
  await writeFile3(dest.path, generated, "utf-8");
3148
3343
  return {
3149
3344
  written: true,
@@ -3161,7 +3356,7 @@ var init_gemini2 = __esm({
3161
3356
 
3162
3357
  // packages/agents/dist/projection/opencode.js
3163
3358
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
3164
- import path12 from "path";
3359
+ import path13 from "path";
3165
3360
  import os5 from "os";
3166
3361
  function renderMarkdown3(source) {
3167
3362
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -3186,7 +3381,7 @@ function createOpencodeEmitter() {
3186
3381
  destinations(scope, projectRoot) {
3187
3382
  if (scope === "user") {
3188
3383
  return [{
3189
- path: path12.join(os5.homedir(), ".config", "opencode", "AGENTS.md"),
3384
+ path: path13.join(os5.homedir(), ".config", "opencode", "AGENTS.md"),
3190
3385
  shared: true,
3191
3386
  format: "markdown"
3192
3387
  }];
@@ -3194,7 +3389,7 @@ function createOpencodeEmitter() {
3194
3389
  if (!projectRoot)
3195
3390
  return [];
3196
3391
  return [{
3197
- path: path12.join(projectRoot, "AGENTS.md"),
3392
+ path: path13.join(projectRoot, "AGENTS.md"),
3198
3393
  shared: true,
3199
3394
  format: "markdown"
3200
3395
  }];
@@ -3215,7 +3410,7 @@ ${existing ?? ""}
3215
3410
  ${generated}`
3216
3411
  };
3217
3412
  }
3218
- await mkdir4(path12.dirname(dest.path), { recursive: true });
3413
+ await mkdir4(path13.dirname(dest.path), { recursive: true });
3219
3414
  await writeFile4(dest.path, generated, "utf-8");
3220
3415
  return {
3221
3416
  written: true,
@@ -4080,8 +4275,8 @@ function resolveLastAssistantText(payload) {
4080
4275
  const derived = deriveTranscriptPath(p?.session_id, cwd);
4081
4276
  if (derived)
4082
4277
  candidates.push(derived);
4083
- for (const path26 of candidates) {
4084
- const text = readLastAssistantText(path26);
4278
+ for (const path27 of candidates) {
4279
+ const text = readLastAssistantText(path27);
4085
4280
  if (text != null)
4086
4281
  return text;
4087
4282
  }
@@ -4480,10 +4675,10 @@ var init_dist4 = __esm({
4480
4675
  // packages/cli/src/index.ts
4481
4676
  init_dist();
4482
4677
  init_dist2();
4483
- import { Command as Command27 } from "commander";
4678
+ import { Command as Command28 } from "commander";
4484
4679
  import { readFileSync as readFileSync11, existsSync as existsSync10, writeFileSync as writeFileSync9 } from "fs";
4485
- import { fileURLToPath as fileURLToPath5 } from "url";
4486
- import { dirname as dirname6, join as join21 } from "path";
4680
+ import { fileURLToPath as fileURLToPath6 } from "url";
4681
+ import { dirname as dirname7, join as join22 } from "path";
4487
4682
  import { homedir as homedir14 } from "os";
4488
4683
 
4489
4684
  // packages/cli/src/commands/doctor.ts
@@ -4493,9 +4688,9 @@ init_dist();
4493
4688
  init_dist3();
4494
4689
  import { Command } from "commander";
4495
4690
  import { execSync as execSync8 } from "child_process";
4496
- import fs10 from "fs";
4691
+ import fs12 from "fs";
4497
4692
  import { stat } from "fs/promises";
4498
- import path13 from "path";
4693
+ import path14 from "path";
4499
4694
  import chalk3 from "chalk";
4500
4695
 
4501
4696
  // packages/cli/src/commands/health-view.ts
@@ -4597,7 +4792,7 @@ function settingsHaveAgentTeams() {
4597
4792
  try {
4598
4793
  const home = process.env.HOME || "";
4599
4794
  const settings = JSON.parse(
4600
- fs10.readFileSync(`${home}/.claude/settings.json`, "utf-8")
4795
+ fs12.readFileSync(`${home}/.claude/settings.json`, "utf-8")
4601
4796
  );
4602
4797
  return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
4603
4798
  } catch {
@@ -4608,7 +4803,7 @@ function pluginInstalled(pluginKey) {
4608
4803
  try {
4609
4804
  const home = process.env.HOME || "";
4610
4805
  const plugins = JSON.parse(
4611
- fs10.readFileSync(
4806
+ fs12.readFileSync(
4612
4807
  `${home}/.claude/plugins/installed_plugins.json`,
4613
4808
  "utf-8"
4614
4809
  )
@@ -4640,7 +4835,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4640
4835
  const results = [];
4641
4836
  results.push(check("Claude Code installed", commandExists("claude")));
4642
4837
  results.push(check(`Claude Code version >= ${compatManifest.tools.claude.min}`, claudeVersionOk()));
4643
- 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")));
4644
4839
  results.push(check("Node.js >= 18", nodeVersionOk()));
4645
4840
  results.push(
4646
4841
  check(
@@ -4713,7 +4908,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4713
4908
  const emitter = projectionRegistry.get(name);
4714
4909
  const [userDest] = emitter.destinations("user");
4715
4910
  if (!userDest) continue;
4716
- const dir = path13.dirname(userDest.path);
4911
+ const dir = path14.dirname(userDest.path);
4717
4912
  let status;
4718
4913
  try {
4719
4914
  await stat(dir);
@@ -4726,7 +4921,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
4726
4921
  results.push(
4727
4922
  check(
4728
4923
  "Squadrant config exists",
4729
- fs10.existsSync(
4924
+ fs12.existsSync(
4730
4925
  process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
4731
4926
  )
4732
4927
  )
@@ -4799,39 +4994,39 @@ init_dist();
4799
4994
  init_dist3();
4800
4995
  init_dist();
4801
4996
  import { Command as Command2 } from "commander";
4802
- import fs11 from "fs";
4803
- import path14 from "path";
4997
+ import fs13 from "fs";
4998
+ import path15 from "path";
4804
4999
  import os6 from "os";
4805
5000
  import chalk4 from "chalk";
4806
5001
  function findPackageRoot() {
4807
- let dir = path14.dirname(new URL(import.meta.url).pathname);
5002
+ let dir = path15.dirname(new URL(import.meta.url).pathname);
4808
5003
  while (dir !== "/") {
4809
- if (fs11.existsSync(path14.join(dir, "package.json"))) return dir;
4810
- dir = path14.dirname(dir);
5004
+ if (fs13.existsSync(path15.join(dir, "package.json"))) return dir;
5005
+ dir = path15.dirname(dir);
4811
5006
  }
4812
5007
  return process.cwd();
4813
5008
  }
4814
5009
  function copyDirRecursive(src, dest) {
4815
- fs11.mkdirSync(dest, { recursive: true });
4816
- for (const entry of fs11.readdirSync(src, { withFileTypes: true })) {
4817
- const srcPath = path14.join(src, entry.name);
4818
- 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);
4819
5014
  if (entry.isDirectory()) {
4820
5015
  copyDirRecursive(srcPath, destPath);
4821
5016
  } else {
4822
- fs11.copyFileSync(srcPath, destPath);
5017
+ fs13.copyFileSync(srcPath, destPath);
4823
5018
  }
4824
5019
  }
4825
5020
  }
4826
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) => {
4827
5022
  const hubPath = resolveHome(opts.hub);
4828
5023
  const pkgRoot = findPackageRoot();
4829
- const configDir = path14.join(os6.homedir(), ".config", "squadrant");
5024
+ const configDir = path15.join(os6.homedir(), ".config", "squadrant");
4830
5025
  console.log(chalk4.bold("\nSquadrant Init\n"));
4831
5026
  const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
4832
5027
  try {
4833
- if (fs11.existsSync(DEFAULT_CONFIG_PATH)) {
4834
- 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"));
4835
5030
  const wsName = existing.workspace ?? "obsidian";
4836
5031
  registry.get(wsName);
4837
5032
  }
@@ -4839,7 +5034,7 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4839
5034
  console.log(chalk4.red(` \u2718 ${err.message}`));
4840
5035
  return;
4841
5036
  }
4842
- if (fs11.existsSync(DEFAULT_CONFIG_PATH)) {
5037
+ if (fs13.existsSync(DEFAULT_CONFIG_PATH)) {
4843
5038
  console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
4844
5039
  } else {
4845
5040
  const config = getDefaultConfig();
@@ -4847,37 +5042,37 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4847
5042
  saveConfig(config);
4848
5043
  console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
4849
5044
  }
4850
- const hubTemplate = path14.join(pkgRoot, "obsidian", "hub");
4851
- if (fs11.existsSync(hubPath)) {
5045
+ const hubTemplate = path15.join(pkgRoot, "obsidian", "hub");
5046
+ if (fs13.existsSync(hubPath)) {
4852
5047
  console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}, skipping`));
4853
- } else if (fs11.existsSync(hubTemplate)) {
5048
+ } else if (fs13.existsSync(hubTemplate)) {
4854
5049
  copyDirRecursive(hubTemplate, hubPath);
4855
5050
  console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
4856
5051
  } else {
4857
- fs11.mkdirSync(hubPath, { recursive: true });
5052
+ fs13.mkdirSync(hubPath, { recursive: true });
4858
5053
  console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty dir at ${hubPath}`));
4859
5054
  }
4860
- const hubDashboardSrc = path14.join(pkgRoot, "obsidian", "hub", "dashboard.md");
4861
- const hubDashboardDest = path14.join(hubPath, "dashboard.md");
4862
- if (fs11.existsSync(hubDashboardSrc)) {
4863
- 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);
4864
5059
  console.log(chalk4.green(` \u2714 Dashboard page refreshed at ${hubDashboardDest}`));
4865
5060
  }
4866
- const projectsDir = path14.join(hubPath, "projects");
4867
- fs11.mkdirSync(projectsDir, { recursive: true });
5061
+ const projectsDir = path15.join(hubPath, "projects");
5062
+ fs13.mkdirSync(projectsDir, { recursive: true });
4868
5063
  ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
4869
5064
  console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
4870
- const settingsPath = path14.join(os6.homedir(), ".claude", "settings.json");
5065
+ const settingsPath = path15.join(os6.homedir(), ".claude", "settings.json");
4871
5066
  try {
4872
5067
  let settings = {};
4873
- if (fs11.existsSync(settingsPath)) {
4874
- settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
5068
+ if (fs13.existsSync(settingsPath)) {
5069
+ settings = JSON.parse(fs13.readFileSync(settingsPath, "utf-8"));
4875
5070
  }
4876
5071
  const env = settings.env || {};
4877
5072
  if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
4878
5073
  settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
4879
- fs11.mkdirSync(path14.dirname(settingsPath), { recursive: true });
4880
- 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");
4881
5076
  console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
4882
5077
  } else {
4883
5078
  console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
@@ -4898,7 +5093,7 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4898
5093
  console.log(chalk4.cyan(` ${hubPath}`));
4899
5094
  console.log("");
4900
5095
  console.log(" 4. Run " + chalk4.cyan("squadrant doctor") + " to verify setup\n");
4901
- if (!fs11.existsSync("/Applications/cmux.app")) {
5096
+ if (!fs13.existsSync("/Applications/cmux.app")) {
4902
5097
  console.log(chalk4.yellow(" \u26A0 cmux not found \u2014 download from https://cmux.dev\n"));
4903
5098
  }
4904
5099
  });
@@ -4906,26 +5101,26 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
4906
5101
  // packages/cli/src/commands/projects.ts
4907
5102
  init_dist();
4908
5103
  import { Command as Command3 } from "commander";
4909
- import fs12 from "fs";
4910
- import path15 from "path";
5104
+ import fs14 from "fs";
5105
+ import path16 from "path";
4911
5106
  import chalk5 from "chalk";
4912
5107
  function findPackageRoot2() {
4913
- let dir = path15.dirname(new URL(import.meta.url).pathname);
5108
+ let dir = path16.dirname(new URL(import.meta.url).pathname);
4914
5109
  while (dir !== "/") {
4915
- if (fs12.existsSync(path15.join(dir, "package.json"))) return dir;
4916
- dir = path15.dirname(dir);
5110
+ if (fs14.existsSync(path16.join(dir, "package.json"))) return dir;
5111
+ dir = path16.dirname(dir);
4917
5112
  }
4918
5113
  return process.cwd();
4919
5114
  }
4920
5115
  function copyDirRecursive2(src, dest) {
4921
- fs12.mkdirSync(dest, { recursive: true });
4922
- for (const entry of fs12.readdirSync(src, { withFileTypes: true })) {
4923
- const srcPath = path15.join(src, entry.name);
4924
- 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);
4925
5120
  if (entry.isDirectory()) {
4926
5121
  copyDirRecursive2(srcPath, destPath);
4927
5122
  } else {
4928
- fs12.copyFileSync(srcPath, destPath);
5123
+ fs14.copyFileSync(srcPath, destPath);
4929
5124
  }
4930
5125
  }
4931
5126
  }
@@ -4961,7 +5156,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
4961
5156
  process.exit(1);
4962
5157
  }
4963
5158
  const resolvedPath = resolveHome(projectPath);
4964
- if (!fs12.existsSync(path15.join(resolvedPath, ".git"))) {
5159
+ if (!fs14.existsSync(path16.join(resolvedPath, ".git"))) {
4965
5160
  console.log(chalk5.yellow(`
4966
5161
  \u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
4967
5162
  `));
@@ -5014,7 +5209,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
5014
5209
  \u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
5015
5210
  }
5016
5211
  }
5017
- 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);
5018
5213
  const project = {
5019
5214
  path: resolvedPath,
5020
5215
  captainName,
@@ -5028,20 +5223,20 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
5028
5223
  console.log(chalk5.green(`
5029
5224
  \u2714 Project '${name}' registered`));
5030
5225
  const pkgRoot = findPackageRoot2();
5031
- const spokeTemplate = path15.join(pkgRoot, "obsidian", "spoke");
5032
- if (fs12.existsSync(spokeVault)) {
5226
+ const spokeTemplate = path16.join(pkgRoot, "obsidian", "spoke");
5227
+ if (fs14.existsSync(spokeVault)) {
5033
5228
  console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
5034
- } else if (fs12.existsSync(spokeTemplate)) {
5229
+ } else if (fs14.existsSync(spokeTemplate)) {
5035
5230
  copyDirRecursive2(spokeTemplate, spokeVault);
5036
- const statusPath = path15.join(spokeVault, "status.md");
5037
- if (fs12.existsSync(statusPath)) {
5038
- 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");
5039
5234
  const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
5040
- fs12.writeFileSync(statusPath, updated);
5235
+ fs14.writeFileSync(statusPath, updated);
5041
5236
  }
5042
5237
  console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
5043
5238
  } else {
5044
- fs12.mkdirSync(spokeVault, { recursive: true });
5239
+ fs14.mkdirSync(spokeVault, { recursive: true });
5045
5240
  console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
5046
5241
  }
5047
5242
  console.log("");
@@ -5142,10 +5337,10 @@ init_dist4();
5142
5337
  init_dist();
5143
5338
  import { Command as Command5 } from "commander";
5144
5339
  import { execSync as execSync9 } from "child_process";
5145
- import path16 from "path";
5340
+ import path17 from "path";
5146
5341
  import os7 from "os";
5147
5342
  import chalk7 from "chalk";
5148
- var TEMPLATES_DIR = path16.join(os7.homedir(), ".config", "squadrant", "templates");
5343
+ var TEMPLATES_DIR = path17.join(os7.homedir(), ".config", "squadrant", "templates");
5149
5344
  var TASK_PROMPTS = {
5150
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.",
5151
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.",
@@ -5178,7 +5373,7 @@ async function runCommandSpawn(input) {
5178
5373
  if (!agent) {
5179
5374
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
5180
5375
  }
5181
- const promptFile = path16.join(TEMPLATES_DIR, `command.${agent.templateSuffix}.md`);
5376
+ const promptFile = path17.join(TEMPLATES_DIR, `command.${agent.templateSuffix}.md`);
5182
5377
  const cliCommand = agent.buildCommand({
5183
5378
  prompt,
5184
5379
  workdir: process.cwd(),
@@ -5205,8 +5400,8 @@ var commandCommand = new Command5("command").description("Spawn a one-shot Comma
5205
5400
  init_dist();
5206
5401
  init_dist();
5207
5402
  import { Command as Command9 } from "commander";
5208
- import fs13 from "fs";
5209
- import path17 from "path";
5403
+ import fs15 from "fs";
5404
+ import path18 from "path";
5210
5405
  import os8 from "os";
5211
5406
  import chalk9 from "chalk";
5212
5407
 
@@ -5874,7 +6069,7 @@ function writePerCrewOpencodeConfig(o) {
5874
6069
 
5875
6070
  // packages/cli/src/commands/crew.ts
5876
6071
  init_dist2();
5877
- var TEMPLATES_DIR2 = path17.join(os8.homedir(), ".config", "squadrant", "templates");
6072
+ var TEMPLATES_DIR2 = path18.join(os8.homedir(), ".config", "squadrant", "templates");
5878
6073
  async function runCrewSpawn(input) {
5879
6074
  const config = loadConfig();
5880
6075
  const proj = config.projects[input.project];
@@ -5922,8 +6117,8 @@ async function runCrewSpawn(input) {
5922
6117
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
5923
6118
  }
5924
6119
  if (agentName === "codex") {
5925
- const codexRoleFile = path17.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
5926
- 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;
5927
6122
  return runCodexInteractiveSpawn({
5928
6123
  project: input.project,
5929
6124
  task: input.task,
@@ -5936,7 +6131,7 @@ async function runCrewSpawn(input) {
5936
6131
  roleInstructions
5937
6132
  });
5938
6133
  }
5939
- const promptFile = path17.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
6134
+ const promptFile = path18.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
5940
6135
  const interactive = agent.name === "claude" || agent.name === "opencode";
5941
6136
  const crewRole = config.defaults.roles?.crew;
5942
6137
  const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
@@ -5993,7 +6188,7 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
5993
6188
  });
5994
6189
  const rec = await squadrantdCall(req);
5995
6190
  const opencodeConfigPath = writePerCrewOpencodeConfig({
5996
- stateRoot: path17.join(os8.homedir(), ".config", "squadrant", "state"),
6191
+ stateRoot: path18.join(os8.homedir(), ".config", "squadrant", "state"),
5997
6192
  project: input.project,
5998
6193
  taskId: rec.id,
5999
6194
  // CP3 opt-in: --approval gates bash so the captain approves shell commands.
@@ -6233,11 +6428,11 @@ init_dist3();
6233
6428
  init_dist();
6234
6429
  init_dist();
6235
6430
  import { Command as Command10 } from "commander";
6236
- import fs14 from "fs";
6237
- import path18 from "path";
6431
+ import fs16 from "fs";
6432
+ import path19 from "path";
6238
6433
  import os9 from "os";
6239
6434
  import chalk10 from "chalk";
6240
- var TEMPLATES_DIR3 = path18.join(os9.homedir(), ".config", "squadrant", "templates");
6435
+ var TEMPLATES_DIR3 = path19.join(os9.homedir(), ".config", "squadrant", "templates");
6241
6436
  var SIDE_ROLES = ["research", "debug"];
6242
6437
  function shellQuote2(p) {
6243
6438
  return "'" + p.replace(/'/g, "'\\''") + "'";
@@ -6328,7 +6523,7 @@ async function runSideSpawn(input) {
6328
6523
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
6329
6524
  }
6330
6525
  const sideModel = sideRole?.model;
6331
- const promptFile = path18.join(
6526
+ const promptFile = path19.join(
6332
6527
  TEMPLATES_DIR3,
6333
6528
  `side.${input.role}.${agent.templateSuffix}.md`
6334
6529
  );
@@ -6339,7 +6534,7 @@ async function runSideSpawn(input) {
6339
6534
  prompt: input.topic,
6340
6535
  workdir: spawnCwd,
6341
6536
  role: "side",
6342
- promptFile: fs14.existsSync(promptFile) ? promptFile : void 0,
6537
+ promptFile: fs16.existsSync(promptFile) ? promptFile : void 0,
6343
6538
  interactive: true,
6344
6539
  permissionMode: config.defaults.permissions?.crew ?? "auto",
6345
6540
  ...sideModel ? { model: sideModel } : {}
@@ -6396,7 +6591,7 @@ async function runSideClose(project, name) {
6396
6591
  project,
6397
6592
  name
6398
6593
  );
6399
- if (fs14.existsSync(wtPath)) {
6594
+ if (fs16.existsSync(wtPath)) {
6400
6595
  try {
6401
6596
  removeWorktree(proj.path, wtPath);
6402
6597
  } catch (e) {
@@ -6624,8 +6819,8 @@ function renderDashboard(rows, opts) {
6624
6819
 
6625
6820
  // packages/web/dist/sync-hub.js
6626
6821
  init_dist();
6627
- import fs15 from "fs";
6628
- import path19 from "path";
6822
+ import fs17 from "fs";
6823
+ import path20 from "path";
6629
6824
  function buildMirrorMarkdown(s) {
6630
6825
  const fenced = "```";
6631
6826
  return [
@@ -6651,15 +6846,15 @@ function buildMirrorMarkdown(s) {
6651
6846
  function syncHub(deps) {
6652
6847
  if (!deps.config.hubVault)
6653
6848
  return [];
6654
- const writeFile5 = deps.writeFile ?? ((p, c) => fs15.writeFileSync(p, c));
6655
- const mkdir5 = deps.mkdir ?? ((p) => fs15.mkdirSync(p, { recursive: true }));
6656
- 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");
6657
6852
  mkdir5(projectsDir);
6658
6853
  const out = [];
6659
6854
  for (const s of deps.statuses) {
6660
6855
  if (s.state === "unknown")
6661
6856
  continue;
6662
- const hubPath = path19.join(projectsDir, `${s.project}.md`);
6857
+ const hubPath = path20.join(projectsDir, `${s.project}.md`);
6663
6858
  try {
6664
6859
  writeFile5(hubPath, buildMirrorMarkdown(s));
6665
6860
  out.push({ project: s.project, hubPath });
@@ -7548,13 +7743,13 @@ init_dist3();
7548
7743
  init_dist2();
7549
7744
  import { Command as Command12 } from "commander";
7550
7745
  import { execSync as execSync11 } from "child_process";
7551
- import fs16 from "fs";
7552
- import path20 from "path";
7746
+ import fs18 from "fs";
7747
+ import path21 from "path";
7553
7748
  import os10 from "os";
7554
7749
  import chalk13 from "chalk";
7555
7750
  var CMUX_APP = "/Applications/cmux.app";
7556
- var TEMPLATES_DIR4 = path20.join(os10.homedir(), ".config", "squadrant", "templates");
7557
- 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");
7558
7753
  function ensureCmuxReady() {
7559
7754
  if (isInsideCmux()) return;
7560
7755
  console.log(chalk13.yellow("\n Not running inside cmux. Opening cmux app...\n"));
@@ -7661,12 +7856,12 @@ var launchCommand = new Command12("launch").description(
7661
7856
  }
7662
7857
  if (opts.all) {
7663
7858
  const hubPath = resolveHome(config.hubVault);
7664
- fs16.mkdirSync(hubPath, { recursive: true });
7859
+ fs18.mkdirSync(hubPath, { recursive: true });
7665
7860
  console.log(chalk13.bold("\nLaunching all captain workspaces\n"));
7666
7861
  for (const [name, proj] of Object.entries(config.projects)) {
7667
7862
  const projPath = resolveHome(proj.path);
7668
7863
  const spokePath = resolveHome(proj.spokeVault);
7669
- if (!fs16.existsSync(spokePath)) {
7864
+ if (!fs18.existsSync(spokePath)) {
7670
7865
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
7671
7866
  await ensureSpokeLayout(spokeDriver);
7672
7867
  console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -7697,7 +7892,7 @@ var launchCommand = new Command12("launch").description(
7697
7892
  const proj = config.projects[project];
7698
7893
  const projPath = resolveHome(proj.path);
7699
7894
  const spokePath = resolveHome(proj.spokeVault);
7700
- if (!fs16.existsSync(spokePath)) {
7895
+ if (!fs18.existsSync(spokePath)) {
7701
7896
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
7702
7897
  await ensureSpokeLayout(spokeDriver);
7703
7898
  console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -7832,23 +8027,31 @@ Shutting down captain workspace for '${project}'...
7832
8027
  // packages/cli/src/commands/feedback.ts
7833
8028
  init_dist();
7834
8029
  import { Command as Command14 } from "commander";
7835
- import fs17 from "fs";
8030
+ import fs19 from "fs";
7836
8031
  import os11 from "os";
7837
- import path21 from "path";
8032
+ import path22 from "path";
8033
+ import { fileURLToPath as fileURLToPath3 } from "url";
7838
8034
  import { execSync as execSync12 } from "child_process";
7839
8035
  import chalk15 from "chalk";
7840
8036
  var REPO_URL = "https://github.com/tu11aa/squadrant";
8037
+ function readPkgVersion() {
8038
+ try {
8039
+ const pkgPath = path22.join(path22.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
8040
+ return JSON.parse(fs19.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
8041
+ } catch {
8042
+ return "unknown";
8043
+ }
8044
+ }
7841
8045
  function readMetrics(metricsPath) {
7842
8046
  try {
7843
- return JSON.parse(fs17.readFileSync(metricsPath, "utf-8"));
8047
+ return JSON.parse(fs19.readFileSync(metricsPath, "utf-8"));
7844
8048
  } catch {
7845
8049
  return {};
7846
8050
  }
7847
8051
  }
7848
- function buildIssueUrl(metrics) {
8052
+ function buildIssueUrl(metrics, squadrantVersion) {
7849
8053
  const nodeVersion = process.versions.node;
7850
8054
  const platform = process.platform;
7851
- const squadrantVersion = "0.1.0";
7852
8055
  const body = [
7853
8056
  "## Feedback / Bug Report",
7854
8057
  "",
@@ -7879,9 +8082,10 @@ function buildIssueUrl(metrics) {
7879
8082
  }
7880
8083
  var feedbackCommand = new Command14("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
7881
8084
  const config = loadConfig();
7882
- 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");
7883
8086
  const metrics = readMetrics(metricsPath);
7884
- const issueUrl = buildIssueUrl(metrics);
8087
+ const version = readStamp(config) ?? readPkgVersion();
8088
+ const issueUrl = buildIssueUrl(metrics, version);
7885
8089
  console.log(chalk15.bold("\nOpening feedback issue in browser...\n"));
7886
8090
  console.log(chalk15.dim(` URL: ${issueUrl.substring(0, 80)}...
7887
8091
  `));
@@ -7900,8 +8104,8 @@ init_dist();
7900
8104
  init_dist();
7901
8105
  init_dist3();
7902
8106
  import { Command as Command15 } from "commander";
7903
- import fs18 from "fs";
7904
- import path22 from "path";
8107
+ import fs20 from "fs";
8108
+ import path23 from "path";
7905
8109
  import chalk16 from "chalk";
7906
8110
  import matter3 from "gray-matter";
7907
8111
  function getDateStr(yesterday) {
@@ -7910,11 +8114,11 @@ function getDateStr(yesterday) {
7910
8114
  async function getProjectStandup(name, project, dateStr, registry, config) {
7911
8115
  const workspace = registry.forProject(name, config);
7912
8116
  const spokeVault = resolveHome(project.spokeVault);
7913
- const statusFile = path22.join(spokeVault, "status.md");
8117
+ const statusFile = path23.join(spokeVault, "status.md");
7914
8118
  let status = {};
7915
- if (fs18.existsSync(statusFile)) {
8119
+ if (fs20.existsSync(statusFile)) {
7916
8120
  try {
7917
- status = matter3(fs18.readFileSync(statusFile, "utf-8")).data;
8121
+ status = matter3(fs20.readFileSync(statusFile, "utf-8")).data;
7918
8122
  } catch {
7919
8123
  }
7920
8124
  }
@@ -8032,15 +8236,15 @@ init_dist();
8032
8236
  init_dist();
8033
8237
  init_dist3();
8034
8238
  import { Command as Command16 } from "commander";
8035
- import fs19 from "fs";
8036
- import path23 from "path";
8239
+ import fs21 from "fs";
8240
+ import path24 from "path";
8037
8241
  import chalk17 from "chalk";
8038
8242
  import matter4 from "gray-matter";
8039
8243
  function readStatus(spokeVault) {
8040
- const statusFile = path23.join(spokeVault, "status.md");
8041
- if (!fs19.existsSync(statusFile)) return {};
8244
+ const statusFile = path24.join(spokeVault, "status.md");
8245
+ if (!fs21.existsSync(statusFile)) return {};
8042
8246
  try {
8043
- return matter4(fs19.readFileSync(statusFile, "utf-8")).data;
8247
+ return matter4(fs21.readFileSync(statusFile, "utf-8")).data;
8044
8248
  } catch {
8045
8249
  return {};
8046
8250
  }
@@ -8350,9 +8554,9 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
8350
8554
  const config = loadConfig();
8351
8555
  const registry = buildRegistry2();
8352
8556
  try {
8353
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8557
+ const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8354
8558
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8355
- const content = await driver.read(path26);
8559
+ const content = await driver.read(path27);
8356
8560
  process.stdout.write(content);
8357
8561
  } catch (err) {
8358
8562
  console.error(chalk19.red(err.message));
@@ -8364,26 +8568,26 @@ workspaceCommand.command("write").description("Write content to a scope-relative
8364
8568
  const registry = buildRegistry2();
8365
8569
  try {
8366
8570
  let projectTarget;
8367
- let path26;
8571
+ let path27;
8368
8572
  let rawContent;
8369
8573
  if (opts.hub) {
8370
8574
  if (arg3 !== void 0) {
8371
8575
  throw new Error("With --hub, pass only the path and content");
8372
8576
  }
8373
8577
  projectTarget = void 0;
8374
- path26 = arg1;
8578
+ path27 = arg1;
8375
8579
  rawContent = arg2;
8376
8580
  } else {
8377
8581
  if (arg3 === void 0) {
8378
8582
  throw new Error("Missing content \u2014 usage: <project> <path> <content>");
8379
8583
  }
8380
8584
  projectTarget = arg1;
8381
- path26 = arg2;
8585
+ path27 = arg2;
8382
8586
  rawContent = arg3;
8383
8587
  }
8384
8588
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8385
8589
  const payload = rawContent === "-" ? await readStdin() : rawContent;
8386
- await driver.write(path26, payload);
8590
+ await driver.write(path27, payload);
8387
8591
  } catch (err) {
8388
8592
  console.error(chalk19.red(err.message));
8389
8593
  process.exit(1);
@@ -8393,9 +8597,9 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
8393
8597
  const config = loadConfig();
8394
8598
  const registry = buildRegistry2();
8395
8599
  try {
8396
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8600
+ const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8397
8601
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8398
- const entries = await driver.list(path26);
8602
+ const entries = await driver.list(path27);
8399
8603
  for (const entry of entries) console.log(entry);
8400
8604
  } catch (err) {
8401
8605
  console.error(chalk19.red(err.message));
@@ -8406,9 +8610,9 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
8406
8610
  const config = loadConfig();
8407
8611
  const registry = buildRegistry2();
8408
8612
  try {
8409
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8613
+ const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8410
8614
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8411
- const ok = await driver.exists(path26);
8615
+ const ok = await driver.exists(path27);
8412
8616
  process.exit(ok ? 0 : 1);
8413
8617
  } catch (err) {
8414
8618
  console.error(chalk19.red(err.message));
@@ -8419,9 +8623,9 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
8419
8623
  const config = loadConfig();
8420
8624
  const registry = buildRegistry2();
8421
8625
  try {
8422
- const { projectTarget, path: path26 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8626
+ const { projectTarget, path: path27 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
8423
8627
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
8424
- await driver.mkdir(path26);
8628
+ await driver.mkdir(path27);
8425
8629
  } catch (err) {
8426
8630
  console.error(chalk19.red(err.message));
8427
8631
  process.exit(1);
@@ -8460,9 +8664,9 @@ init_dist3();
8460
8664
  init_dist();
8461
8665
  import { Command as Command20 } from "commander";
8462
8666
  import chalk21 from "chalk";
8463
- import fs20 from "fs";
8464
- import path24 from "path";
8465
- import { fileURLToPath as fileURLToPath3 } from "url";
8667
+ import fs22 from "fs";
8668
+ import path25 from "path";
8669
+ import { fileURLToPath as fileURLToPath4 } from "url";
8466
8670
  function parseScope(v) {
8467
8671
  if (v !== "user" && v !== "project") {
8468
8672
  throw new Error("--scope must be 'user' or 'project'");
@@ -8470,10 +8674,10 @@ function parseScope(v) {
8470
8674
  return v;
8471
8675
  }
8472
8676
  function findPackageRoot3() {
8473
- let dir = path24.dirname(fileURLToPath3(import.meta.url));
8677
+ let dir = path25.dirname(fileURLToPath4(import.meta.url));
8474
8678
  while (dir !== "/" && dir !== "") {
8475
- if (fs20.existsSync(path24.join(dir, "package.json"))) return dir;
8476
- dir = path24.dirname(dir);
8679
+ if (fs22.existsSync(path25.join(dir, "package.json"))) return dir;
8680
+ dir = path25.dirname(dir);
8477
8681
  }
8478
8682
  return process.cwd();
8479
8683
  }
@@ -8667,12 +8871,12 @@ init_dist();
8667
8871
  init_dist();
8668
8872
  init_dist();
8669
8873
  import { Command as Command22 } from "commander";
8670
- import fs21 from "fs";
8671
- import { fileURLToPath as fileURLToPath4 } from "url";
8874
+ import fs23 from "fs";
8875
+ import { fileURLToPath as fileURLToPath5 } from "url";
8672
8876
  import { dirname as dirname5, join as join19 } from "path";
8673
8877
  import chalk22 from "chalk";
8674
8878
  function runConfigCheck(opts) {
8675
- const raw = JSON.parse(fs21.readFileSync(opts.configPath, "utf-8"));
8879
+ const raw = JSON.parse(fs23.readFileSync(opts.configPath, "utf-8"));
8676
8880
  const def = getDefaultConfig();
8677
8881
  const items = detectDrift(raw, def);
8678
8882
  let working = raw;
@@ -8689,10 +8893,40 @@ function runConfigCheck(opts) {
8689
8893
  stamped = true;
8690
8894
  }
8691
8895
  if (opts.fix || opts.accept || stamped) {
8692
- fs21.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
8896
+ fs23.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
8693
8897
  }
8694
8898
  return { items, applied, remaining, stamped };
8695
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
+ }
8696
8930
  var SEV_COLOR = {
8697
8931
  info: chalk22.green,
8698
8932
  advisory: chalk22.yellow,
@@ -8713,8 +8947,8 @@ function printItems(items) {
8713
8947
  }
8714
8948
  var configCommand = new Command22("config").description("Inspect and reconcile squadrant config");
8715
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) => {
8716
- const pkgVersion = readPkgVersion();
8717
- if (!fs21.existsSync(DEFAULT_CONFIG_PATH)) {
8950
+ const pkgVersion = readPkgVersion2();
8951
+ if (!fs23.existsSync(DEFAULT_CONFIG_PATH)) {
8718
8952
  console.log(chalk22.yellow("No config found \u2014 run `squadrant init` first."));
8719
8953
  return;
8720
8954
  }
@@ -8741,9 +8975,27 @@ ${judgment.length} item(s) need review \u2014 run the config-doctor skill, or \`
8741
8975
  console.log(chalk22.green("\n\u2714 Config reconciled and stamped."));
8742
8976
  }
8743
8977
  });
8744
- function readPkgVersion() {
8745
- const pkgPath = join19(dirname5(fileURLToPath4(import.meta.url)), "..", "package.json");
8746
- return JSON.parse(fs21.readFileSync(pkgPath, "utf-8")).version;
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
+ });
8996
+ function readPkgVersion2() {
8997
+ const pkgPath = join19(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
8998
+ return JSON.parse(fs23.readFileSync(pkgPath, "utf-8")).version;
8747
8999
  }
8748
9000
 
8749
9001
  // packages/cli/src/commands/heal.ts
@@ -9009,8 +9261,8 @@ var cmuxCommand = new Command25("cmux").description("cmux integration helpers").
9009
9261
 
9010
9262
  // packages/cli/src/commands/effort.ts
9011
9263
  init_dist();
9012
- import fs22 from "fs";
9013
- import path25 from "path";
9264
+ import fs24 from "fs";
9265
+ import path26 from "path";
9014
9266
  import { Command as Command26 } from "commander";
9015
9267
  import chalk26 from "chalk";
9016
9268
  var VALID_EFFORTS = ["max", "balance", "low"];
@@ -9037,9 +9289,9 @@ function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH) {
9037
9289
  }
9038
9290
  function canonical(p) {
9039
9291
  try {
9040
- return fs22.realpathSync(p);
9292
+ return fs24.realpathSync(p);
9041
9293
  } catch {
9042
- return path25.resolve(p);
9294
+ return path26.resolve(p);
9043
9295
  }
9044
9296
  }
9045
9297
  async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd()) {
@@ -9083,19 +9335,232 @@ var effortCommand = new Command26("effort").description("Get or set the global c
9083
9335
  }
9084
9336
  });
9085
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
+
9086
9551
  // packages/cli/src/index.ts
9087
9552
  init_dist();
9088
9553
  init_dist();
9089
9554
  init_dist();
9090
- var __dirname = dirname6(fileURLToPath5(import.meta.url));
9091
- 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"));
9092
9557
  ensureRuntimeSynced({
9093
- sourceRoot: join21(__dirname, ".."),
9094
- runtimeRoot: join21(homedir14(), ".config", "squadrant")
9558
+ sourceRoot: join22(__dirname, ".."),
9559
+ runtimeRoot: join22(homedir14(), ".config", "squadrant")
9095
9560
  });
9096
9561
  if (process.argv[2] !== "config") {
9097
9562
  try {
9098
- const cfgPath = join21(homedir14(), ".config", "squadrant", "config.json");
9563
+ const cfgPath = join22(homedir14(), ".config", "squadrant", "config.json");
9099
9564
  if (existsSync10(cfgPath)) {
9100
9565
  const cfg = JSON.parse(readFileSync11(cfgPath, "utf-8"));
9101
9566
  if (needsCheck(cfg, pkg.version)) {
@@ -9120,7 +9585,7 @@ if (process.argv[2] !== "config") {
9120
9585
  if (!process.env.SQUADRANT_DAEMON_SKIP) {
9121
9586
  ensureDaemon();
9122
9587
  }
9123
- var program = new Command27();
9588
+ var program = new Command28();
9124
9589
  program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
9125
9590
  program.addCommand(doctorCommand);
9126
9591
  program.addCommand(initCommand);
@@ -9146,6 +9611,7 @@ program.addCommand(healCommand);
9146
9611
  program.addCommand(groupCommand);
9147
9612
  program.addCommand(cmuxCommand);
9148
9613
  program.addCommand(effortCommand);
9614
+ program.addCommand(telegramCommand);
9149
9615
  program.parseAsync().catch((e) => {
9150
9616
  process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}
9151
9617
  `);