squadrant 0.14.2 → 0.14.3

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
@@ -2186,15 +2186,16 @@ async function dispatchToSibling(opts) {
2186
2186
  if (!toCfg) {
2187
2187
  throw new Error(`target project '${opts.toProject}' not found in config`);
2188
2188
  }
2189
- if (!fromCfg.group || !toCfg.group || fromCfg.group !== toCfg.group) {
2190
- throw new Error(`cannot dispatch: '${opts.toProject}' (group: ${toCfg.group ?? "none"}) is not in the same group as '${opts.fromProject}' (group: ${fromCfg.group ?? "none"})`);
2191
- }
2189
+ const sameGroup = !!fromCfg?.group && !!toCfg.group && fromCfg.group === toCfg.group;
2192
2190
  if (toCfg.acceptDelegations === false) {
2193
2191
  throw new Error(`cannot dispatch to '${opts.toProject}': project has acceptDelegations set to false`);
2194
2192
  }
2195
2193
  const sockPath = opts.sockPath ?? DEFAULT_SOCK_PATH2;
2196
2194
  const alive = await isCaptainAlive(opts.toProject, sockPath);
2197
2195
  if (!alive) {
2196
+ if (!sameGroup) {
2197
+ throw new Error(`cannot dispatch to '${opts.toProject}': captain is not running and cross-group dispatch does not auto-boot it. Use 'squadrant ping ${opts.toProject} "<msg>"' or start it manually with 'squadrant launch ${opts.toProject}', then retry.`);
2198
+ }
2198
2199
  if (opts.bootCaptain) {
2199
2200
  await opts.bootCaptain(opts.toProject);
2200
2201
  }
@@ -4152,6 +4153,21 @@ async function confirmedSendToPane(runtime, pane, message) {
4152
4153
  }
4153
4154
  return { delivered: false };
4154
4155
  }
4156
+ async function resendCrewFirstTurn(runtime, captainName, project, name, message) {
4157
+ const captain = await runtime.status(captainName);
4158
+ if (!captain)
4159
+ return { delivered: false };
4160
+ const surfaces = await runtime.listSurfaces(captain.id);
4161
+ const want = titleFor(project, name);
4162
+ const pane = surfaces.find((s) => s.title === want);
4163
+ if (!pane)
4164
+ return { delivered: false };
4165
+ const screen = await runtime.readPaneScreen(pane) ?? "";
4166
+ if (!hasCCInputBox(screen) || classifyStartupSurface(screen) !== "idle") {
4167
+ return { delivered: false };
4168
+ }
4169
+ return confirmedSendToPane(runtime, pane, message);
4170
+ }
4155
4171
  async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acceptanceConfig) {
4156
4172
  await new Promise((r) => setTimeout(r, SEND_FIRST_TURN_FLOOR_MS));
4157
4173
  const maxPolls = Math.floor((SEND_FIRST_TURN_TIMEOUT_MS - SEND_FIRST_TURN_FLOOR_MS) / POLL_INTERVAL_MS);
@@ -4260,6 +4276,7 @@ __export(dist_exports, {
4260
4276
  isInsideCmux: () => isInsideCmux,
4261
4277
  listProjectCrews: () => listProjectCrews,
4262
4278
  mapSubToLifecycle: () => mapSubToLifecycle,
4279
+ resendCrewFirstTurn: () => resendCrewFirstTurn,
4263
4280
  resolveCaptainWorkspace: () => resolveCaptainWorkspace,
4264
4281
  sendFirstTurnWhenReady: () => sendFirstTurnWhenReady
4265
4282
  });
@@ -5722,7 +5739,7 @@ var init_sse_bridge = __esm({
5722
5739
  const fetchImpl = this.deps.fetchImpl ?? fetch;
5723
5740
  const sleep2 = this.deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
5724
5741
  const reconnectMs = this.deps.reconnectMs ?? 500;
5725
- const maxBoot = this.deps.maxBootAttempts ?? 60;
5742
+ const maxBoot = this.deps.maxBootAttempts ?? 240;
5726
5743
  const url = `http://127.0.0.1:${port}/event`;
5727
5744
  let booted = false;
5728
5745
  let bootAttempts = 0;
@@ -6344,7 +6361,7 @@ var init_dist4 = __esm({
6344
6361
  // packages/cli/src/index.ts
6345
6362
  init_dist();
6346
6363
  init_dist2();
6347
- import { Command as Command29 } from "commander";
6364
+ import { Command as Command31 } from "commander";
6348
6365
  import { readFileSync as readFileSync13, existsSync as existsSync12, writeFileSync as writeFileSync10 } from "fs";
6349
6366
  import { fileURLToPath as fileURLToPath6 } from "url";
6350
6367
  import { dirname as dirname7, join as join26 } from "path";
@@ -10656,49 +10673,85 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
10656
10673
  );
10657
10674
 
10658
10675
  // packages/cli/src/commands/group.ts
10676
+ import { Command as Command25 } from "commander";
10677
+ import chalk25 from "chalk";
10678
+
10679
+ // packages/cli/src/commands/dispatch.ts
10659
10680
  init_dist();
10660
10681
  init_dist2();
10661
- init_dist2();
10662
10682
  import { Command as Command24 } from "commander";
10663
10683
  import { execSync as execSync13 } from "child_process";
10664
10684
  import chalk24 from "chalk";
10665
- var groupCommand = new Command24("group").description("Cross-project intra-group operations (Phase 1: dispatch)").addCommand(
10666
- new Command24("dispatch").description("Dispatch a task to a sibling project in the same group").argument("<to-project>", "Target project name (must be in the same group)").argument("<task>", "Task description to dispatch").option("--provider <p>", "claude|opencode|codex", "claude").option("--mode <m>", "headless|interactive", "headless").option("--warmup-timeout <s>", "seconds to wait for target captain relay to boot (default: 120)", (v) => parseInt(v, 10) * 1e3).action(async (toProject, task, opts) => {
10667
- const fromProject = resolveCurrentProject(loadConfig());
10668
- if (!fromProject) {
10669
- console.error(chalk24.red("Could not determine current project from cwd. Run from inside a registered project directory."));
10670
- process.exit(1);
10671
- }
10672
- try {
10673
- const result = await dispatchToSibling({
10674
- fromProject,
10675
- toProject,
10676
- task,
10677
- provider: opts.provider,
10678
- mode: opts.mode,
10679
- warmupTimeoutMs: opts.warmupTimeout,
10680
- bootCaptain: async (project) => {
10681
- try {
10682
- execSync13(`squadrant launch ${project}`, { stdio: "ignore", timeout: 15e3 });
10683
- } catch {
10684
- throw new Error(`failed to launch captain for '${project}' \u2014 is squadrant installed?`);
10685
- }
10686
- }
10687
- });
10688
- console.log(chalk24.green(`\u2714 Dispatched to '${toProject}' (task ${result.id.slice(0, 8)})`));
10689
- console.log(chalk24.dim(` originProject: ${result.originProject ?? "none"}`));
10690
- console.log(chalk24.dim(" You will be notified when the task settles (done/blocked/failed)."));
10691
- } catch (e) {
10692
- console.error(chalk24.red(`\u2718 ${e.message}`));
10693
- process.exit(1);
10685
+ async function runDispatch(toProject, task, opts) {
10686
+ const fromProject = resolveCurrentProject(loadConfig());
10687
+ if (!fromProject) {
10688
+ throw new Error("Could not determine current project from cwd. Run from inside a registered project directory.");
10689
+ }
10690
+ return dispatchToSibling({
10691
+ fromProject,
10692
+ toProject,
10693
+ task,
10694
+ provider: opts.provider,
10695
+ mode: opts.mode,
10696
+ warmupTimeoutMs: opts.warmupTimeout,
10697
+ bootCaptain: async (project) => {
10698
+ try {
10699
+ execSync13(`squadrant launch ${project}`, { stdio: "ignore", timeout: 15e3 });
10700
+ } catch {
10701
+ throw new Error(`failed to launch captain for '${project}' \u2014 is squadrant installed?`);
10702
+ }
10694
10703
  }
10704
+ });
10705
+ }
10706
+ async function dispatchAction(toProject, task, opts) {
10707
+ try {
10708
+ const result = await runDispatch(toProject, task, opts);
10709
+ console.log(chalk24.green(`\u2714 Dispatched to '${toProject}' (task ${result.id.slice(0, 8)})`));
10710
+ console.log(chalk24.dim(` originProject: ${result.originProject ?? "none"}`));
10711
+ console.log(chalk24.dim(" You will be notified when the task settles (done/blocked/failed)."));
10712
+ } catch (e) {
10713
+ console.error(chalk24.red(`\u2718 ${e.message}`));
10714
+ process.exit(1);
10715
+ }
10716
+ }
10717
+ var dispatchCommand = new Command24("dispatch").description("Dispatch a task to any registered project (tracked, reports back on settle)").argument("<project>", "Target project name").argument("<task>", "Task description to dispatch").option("--provider <p>", "claude|opencode|codex", "claude").option("--mode <m>", "headless|interactive", "headless").option("--warmup-timeout <s>", "seconds to wait for target captain to boot (same-group only; default: 120)", (v) => parseInt(v, 10) * 1e3).action(dispatchAction);
10718
+
10719
+ // packages/cli/src/commands/group.ts
10720
+ init_dist2();
10721
+ var groupCommand = new Command25("group").description("Cross-project intra-group operations (Phase 1: dispatch)").addCommand(
10722
+ new Command25("dispatch").description("[DEPRECATED \u2014 use 'squadrant dispatch'] Dispatch a task to a sibling project in the same group").argument("<to-project>", "Target project name").argument("<task>", "Task description to dispatch").option("--provider <p>", "claude|opencode|codex", "claude").option("--mode <m>", "headless|interactive", "headless").option("--warmup-timeout <s>", "seconds to wait for target captain to boot (default: 120)", (v) => parseInt(v, 10) * 1e3).action(async (toProject, task, opts) => {
10723
+ console.error(chalk25.yellow(
10724
+ `\u26A0 'squadrant group dispatch' is deprecated \u2014 use 'squadrant dispatch <project> "<task>"' instead.`
10725
+ ));
10726
+ await dispatchAction(toProject, task, opts);
10695
10727
  })
10696
10728
  );
10697
10729
 
10730
+ // packages/cli/src/commands/ping.ts
10731
+ init_dist();
10732
+ import { Command as Command26 } from "commander";
10733
+ import chalk26 from "chalk";
10734
+ async function runPing(project, message) {
10735
+ const config = loadConfig();
10736
+ const registry = buildRegistry();
10737
+ const resolved = resolveTarget(registry, config, project, false);
10738
+ const ref = await needRef(resolved);
10739
+ await resolved.driver.send(ref, message);
10740
+ }
10741
+ var pingCommand = new Command26("ping").description("Fire-and-forget: deliver a message into a registered project's captain pane (no tracked task, no report-back)").argument("<project>", "Target project name (must be registered)").argument("<message>", "Message to deliver").action(async (project, message) => {
10742
+ try {
10743
+ await runPing(project, message);
10744
+ console.log(chalk26.green(`\u2714 Pinged '${project}'`));
10745
+ } catch (err) {
10746
+ console.error(chalk26.red(err.message));
10747
+ process.exit(1);
10748
+ }
10749
+ });
10750
+
10698
10751
  // packages/cli/src/commands/cmux.ts
10699
10752
  init_dist();
10700
- import { Command as Command25 } from "commander";
10701
- import chalk25 from "chalk";
10753
+ import { Command as Command27 } from "commander";
10754
+ import chalk27 from "chalk";
10702
10755
  async function runCmuxAutoconfig(opts) {
10703
10756
  const { json, stdout, stderr } = opts;
10704
10757
  let r;
@@ -10717,19 +10770,19 @@ async function runCmuxAutoconfig(opts) {
10717
10770
  stdout.write(`wrote cmux automation config \u2192 ${r.configPath}
10718
10771
  `);
10719
10772
  } else {
10720
- stdout.write(chalk25.dim(`cmux automation config already in place (${r.configPath})
10773
+ stdout.write(chalk27.dim(`cmux automation config already in place (${r.configPath})
10721
10774
  `));
10722
10775
  }
10723
10776
  if (r.verdict === "reachable") {
10724
- stdout.write(chalk25.green("\u2714 daemon-direct delivery is reachable \u2014 cmux control socket accepts the daemon\n"));
10777
+ stdout.write(chalk27.green("\u2714 daemon-direct delivery is reachable \u2014 cmux control socket accepts the daemon\n"));
10725
10778
  return 0;
10726
10779
  }
10727
10780
  if (r.needsRestart) {
10728
10781
  stdout.write(
10729
- chalk25.yellow("\u26A0 cmux is still on the old socket mode \u2014 restart cmux to enable daemon-direct delivery.\n")
10782
+ chalk27.yellow("\u26A0 cmux is still on the old socket mode \u2014 restart cmux to enable daemon-direct delivery.\n")
10730
10783
  );
10731
10784
  if (r.promptedThisRun) {
10732
- stdout.write(chalk25.dim(" (one-time prompt \u2014 you won't be nagged again)\n"));
10785
+ stdout.write(chalk27.dim(" (one-time prompt \u2014 you won't be nagged again)\n"));
10733
10786
  }
10734
10787
  return 2;
10735
10788
  }
@@ -10738,8 +10791,8 @@ async function runCmuxAutoconfig(opts) {
10738
10791
  );
10739
10792
  return 1;
10740
10793
  }
10741
- var cmuxCommand = new Command25("cmux").description("cmux integration helpers").addCommand(
10742
- new Command25("autoconfig").description(
10794
+ var cmuxCommand = new Command27("cmux").description("cmux integration helpers").addCommand(
10795
+ new Command27("autoconfig").description(
10743
10796
  "Write the cmux automation socket config and probe whether daemon-direct\ndelivery is reachable. Idempotent; prompts once if a cmux restart is needed."
10744
10797
  ).option("--json", "output machine-readable JSON (exit 0=reachable, 1=unknown, 2=restart-needed)").action(async (opts) => {
10745
10798
  const code = await runCmuxAutoconfig({
@@ -10755,8 +10808,8 @@ var cmuxCommand = new Command25("cmux").description("cmux integration helpers").
10755
10808
  init_dist();
10756
10809
  import fs26 from "fs";
10757
10810
  import path28 from "path";
10758
- import { Command as Command26 } from "commander";
10759
- import chalk26 from "chalk";
10811
+ import { Command as Command28 } from "commander";
10812
+ import chalk28 from "chalk";
10760
10813
  var VALID_EFFORTS = ["max", "balance", "low"];
10761
10814
  var EFFORT_MEANING = {
10762
10815
  max: "tokens are plentiful \u2014 bias crew spawns toward claude/opus",
@@ -10800,22 +10853,22 @@ async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(
10800
10853
  }
10801
10854
  }
10802
10855
  }
10803
- var effortCommand = new Command26("effort").description("Get or set the global crew tokenomics dial (max | balance | low)").argument("[value]", "effort level to set: max | balance | low").action(async (value) => {
10856
+ var effortCommand = new Command28("effort").description("Get or set the global crew tokenomics dial (max | balance | low)").argument("[value]", "effort level to set: max | balance | low").action(async (value) => {
10804
10857
  if (value === void 0) {
10805
10858
  const { effort: effort2, description } = runEffortGet();
10806
- console.log(chalk26.bold("Current effort:"), chalk26.cyan(effort2));
10807
- console.log(chalk26.dim(EFFORT_MEANING[effort2]));
10859
+ console.log(chalk28.bold("Current effort:"), chalk28.cyan(effort2));
10860
+ console.log(chalk28.dim(EFFORT_MEANING[effort2]));
10808
10861
  return;
10809
10862
  }
10810
10863
  try {
10811
10864
  runEffortSet(value);
10812
10865
  } catch (err) {
10813
- console.error(chalk26.red(err.message));
10866
+ console.error(chalk28.red(err.message));
10814
10867
  process.exit(1);
10815
10868
  }
10816
10869
  const effort = value;
10817
- console.log(chalk26.green(`\u2714 effort \u2192 ${effort}`));
10818
- console.log(chalk26.dim(EFFORT_MEANING[effort]));
10870
+ console.log(chalk28.green(`\u2714 effort \u2192 ${effort}`));
10871
+ console.log(chalk28.dim(EFFORT_MEANING[effort]));
10819
10872
  try {
10820
10873
  const { createCmuxDriver: createCmuxDriver2, RuntimeRegistry: RuntimeRegistry2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports));
10821
10874
  const config = loadConfig();
@@ -10823,7 +10876,7 @@ var effortCommand = new Command26("effort").description("Get or set the global c
10823
10876
  const driver = registry.global(config);
10824
10877
  await notifyCaptainsOfEffort(effort, config, driver);
10825
10878
  } catch {
10826
- console.log(chalk26.dim("(no running captain detected \u2014 change applies on next launch)"));
10879
+ console.log(chalk28.dim("(no running captain detected \u2014 change applies on next launch)"));
10827
10880
  }
10828
10881
  });
10829
10882
 
@@ -10832,8 +10885,8 @@ init_dist();
10832
10885
  init_dist2();
10833
10886
  import { join as join24, dirname as dirname6 } from "path";
10834
10887
  import { emitKeypressEvents } from "readline";
10835
- import { Command as Command27 } from "commander";
10836
- import chalk27 from "chalk";
10888
+ import { Command as Command29 } from "commander";
10889
+ import chalk29 from "chalk";
10837
10890
  function defaultStateRoot() {
10838
10891
  return join24(dirname6(DEFAULT_CONFIG_PATH), "state");
10839
10892
  }
@@ -10880,11 +10933,11 @@ async function questionYesNo(prompt) {
10880
10933
  });
10881
10934
  });
10882
10935
  }
10883
- var telegramCommand = new Command27("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
10936
+ var telegramCommand = new Command29("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
10884
10937
  telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
10885
10938
  const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
10886
- console.log(`token: ${tokenSet ? chalk27.green("set") : chalk27.yellow("unset")}`);
10887
- console.log(`supergroup: ${supergroupId ?? chalk27.yellow("unset")}`);
10939
+ console.log(`token: ${tokenSet ? chalk29.green("set") : chalk29.yellow("unset")}`);
10940
+ console.log(`supergroup: ${supergroupId ?? chalk29.yellow("unset")}`);
10888
10941
  if (links.length === 0) {
10889
10942
  console.log("no projects linked");
10890
10943
  return;
@@ -10894,32 +10947,32 @@ telegramCommand.command("status").description("Show Telegram config and linked p
10894
10947
  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) => {
10895
10948
  const cfg = loadConfig().telegram;
10896
10949
  if (!cfg) {
10897
- console.error(chalk27.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
10950
+ console.error(chalk29.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
10898
10951
  process.exit(1);
10899
10952
  }
10900
10953
  const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
10901
10954
  if (!token) {
10902
- console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
10955
+ console.error(chalk29.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
10903
10956
  process.exit(1);
10904
10957
  }
10905
10958
  const client = createTelegramClient({ token });
10906
10959
  const { topicId, created } = await runTelegramLink({ project, cfg, client, stateRoot: defaultStateRoot() });
10907
- console.log(chalk27.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
10960
+ console.log(chalk29.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
10908
10961
  });
10909
10962
  telegramCommand.command("setup").description("Interactive wizard \u2014 bot token, validate, auto-detect supergroup, write config").option("--reset-token", "force re-entry of the bot token even if one already exists").option("--redetect", "force group re-detection even when a supergroup is already configured").option("--user-id <id>", "allowlist user-id \u2014 enables remote control on a re-run without getUpdates detection", (v) => parseInt(v, 10)).action(async (opts) => {
10910
10963
  if (!process.stdin.isTTY) {
10911
- console.error(chalk27.red("setup requires a TTY \u2014 pipe input is not supported"));
10964
+ console.error(chalk29.red("setup requires a TTY \u2014 pipe input is not supported"));
10912
10965
  process.exit(1);
10913
10966
  }
10914
10967
  console.log();
10915
- console.log(chalk27.bold("Telegram setup") + " \u2014 connect squadrant to a Telegram bot for notifications + remote control");
10968
+ console.log(chalk29.bold("Telegram setup") + " \u2014 connect squadrant to a Telegram bot for notifications + remote control");
10916
10969
  console.log();
10917
10970
  console.log("Before you start you need:");
10918
10971
  console.log(" 1. A bot token from @BotFather (send /newbot)");
10919
10972
  console.log(" 2. A forum supergroup with the bot added as an admin (Topics enabled)");
10920
10973
  console.log(" 3. Bot privacy mode set to OFF (@BotFather \u2192 /setprivacy \u2192 Disable)");
10921
10974
  console.log();
10922
- console.log(chalk27.bold("Step 1/3 \u2014 Bot token"));
10975
+ console.log(chalk29.bold("Step 1/3 \u2014 Bot token"));
10923
10976
  const existingCfg = loadConfig().telegram;
10924
10977
  const existingToken = existingCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
10925
10978
  const decision = resolveSetupToken(existingToken, { resetToken: opts.resetToken ?? false });
@@ -10931,67 +10984,67 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
10931
10984
  try {
10932
10985
  botUser = await client.getMe();
10933
10986
  token = existingToken;
10934
- console.log(chalk27.green(`Using existing bot token (@${botUser.username})`));
10987
+ console.log(chalk29.green(`Using existing bot token (@${botUser.username})`));
10935
10988
  console.log();
10936
10989
  } catch {
10937
- console.log(chalk27.yellow("Existing token is invalid \u2014 please enter a new one."));
10990
+ console.log(chalk29.yellow("Existing token is invalid \u2014 please enter a new one."));
10938
10991
  console.log("Paste your bot token then press Enter (input is hidden):");
10939
10992
  token = await questionMasked();
10940
10993
  if (!token) {
10941
- console.error(chalk27.red("token required"));
10994
+ console.error(chalk29.red("token required"));
10942
10995
  process.exit(1);
10943
10996
  }
10944
10997
  client = createTelegramClient({ token });
10945
10998
  try {
10946
10999
  botUser = await client.getMe();
10947
11000
  } catch (e) {
10948
- console.error(chalk27.red(`token rejected: ${e.message}`));
11001
+ console.error(chalk29.red(`token rejected: ${e.message}`));
10949
11002
  process.exit(1);
10950
11003
  }
10951
- console.log(chalk27.green(`Connected as @${botUser.username}`));
11004
+ console.log(chalk29.green(`Connected as @${botUser.username}`));
10952
11005
  console.log();
10953
11006
  }
10954
11007
  } else {
10955
11008
  console.log("Paste your bot token then press Enter (input is hidden):");
10956
11009
  token = await questionMasked();
10957
11010
  if (!token) {
10958
- console.error(chalk27.red("token required"));
11011
+ console.error(chalk29.red("token required"));
10959
11012
  process.exit(1);
10960
11013
  }
10961
11014
  client = createTelegramClient({ token });
10962
11015
  try {
10963
11016
  botUser = await client.getMe();
10964
11017
  } catch (e) {
10965
- console.error(chalk27.red(`token rejected: ${e.message}`));
11018
+ console.error(chalk29.red(`token rejected: ${e.message}`));
10966
11019
  process.exit(1);
10967
11020
  }
10968
- console.log(chalk27.green(`Connected as @${botUser.username}`));
11021
+ console.log(chalk29.green(`Connected as @${botUser.username}`));
10969
11022
  console.log();
10970
11023
  }
10971
- console.log(chalk27.bold("Step 2/3 \u2014 Supergroup"));
11024
+ console.log(chalk29.bold("Step 2/3 \u2014 Supergroup"));
10972
11025
  const groupDecision = resolveSetupGroup(existingCfg?.supergroupId, { redetect: opts.redetect ?? false });
10973
11026
  let supergroupId;
10974
11027
  let detectedUserId;
10975
11028
  if (groupDecision === "reuse") {
10976
11029
  supergroupId = existingCfg.supergroupId;
10977
- console.log(chalk27.green(`Using existing group: ${supergroupId}`));
11030
+ console.log(chalk29.green(`Using existing group: ${supergroupId}`));
10978
11031
  console.log();
10979
11032
  } else {
10980
11033
  console.log("Add the bot to your forum supergroup, then send any message in it.");
10981
- console.log(chalk27.dim("Waiting for a message (up to 60s)\u2026"));
11034
+ console.log(chalk29.dim("Waiting for a message (up to 60s)\u2026"));
10982
11035
  try {
10983
11036
  ({ supergroupId, userId: detectedUserId } = await detectGroupAndUser(client, { timeoutMs: 6e4 }));
10984
11037
  } catch {
10985
- console.error(chalk27.red("Timed out \u2014 no supergroup message received within 60s."));
10986
- console.error(chalk27.yellow("Check: bot is an admin in the group \xB7 privacy mode is OFF \xB7 Topics enabled"));
11038
+ console.error(chalk29.red("Timed out \u2014 no supergroup message received within 60s."));
11039
+ console.error(chalk29.yellow("Check: bot is an admin in the group \xB7 privacy mode is OFF \xB7 Topics enabled"));
10987
11040
  process.exit(1);
10988
11041
  }
10989
- console.log(chalk27.green(`Found group: ${supergroupId}`));
11042
+ console.log(chalk29.green(`Found group: ${supergroupId}`));
10990
11043
  console.log();
10991
11044
  }
10992
- console.log(chalk27.bold("Step 3/3 \u2014 Remote control + Save"));
10993
- console.log(chalk27.dim("Remote control enables auto-launching captains and the General command channel"));
10994
- console.log(chalk27.dim("from your phone \u2014 gated to your Telegram user-id only (fail-closed)."));
11045
+ console.log(chalk29.bold("Step 3/3 \u2014 Remote control + Save"));
11046
+ console.log(chalk29.dim("Remote control enables auto-launching captains and the General command channel"));
11047
+ console.log(chalk29.dim("from your phone \u2014 gated to your Telegram user-id only (fail-closed)."));
10995
11048
  const finalUserId = resolveSetupUserId(opts.userId, detectedUserId, defaultStateRoot());
10996
11049
  let users;
10997
11050
  let remoteControl;
@@ -11005,32 +11058,32 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
11005
11058
  remoteControl = true;
11006
11059
  }
11007
11060
  } else if (groupDecision === "detect") {
11008
- console.log(chalk27.yellow("Could not read your user-id from that message \u2014 skipping remote control."));
11009
- console.log(chalk27.yellow("Re-run with --user-id <id> to enable, or edit telegram.users in config manually."));
11061
+ console.log(chalk29.yellow("Could not read your user-id from that message \u2014 skipping remote control."));
11062
+ console.log(chalk29.yellow("Re-run with --user-id <id> to enable, or edit telegram.users in config manually."));
11010
11063
  printedRemoteControlState = true;
11011
11064
  } else {
11012
11065
  const existingUsers = existingCfg?.users;
11013
11066
  if (existingUsers && existingUsers.length > 0) {
11014
- console.log(chalk27.dim(`Remote control: already configured (user-id ${existingUsers[0]}). Use --user-id to update.`));
11067
+ console.log(chalk29.dim(`Remote control: already configured (user-id ${existingUsers[0]}). Use --user-id to update.`));
11015
11068
  } else {
11016
- console.log(chalk27.dim("Remote control: off. Re-run with --user-id <id> to enable."));
11069
+ console.log(chalk29.dim("Remote control: off. Re-run with --user-id <id> to enable."));
11017
11070
  }
11018
11071
  printedRemoteControlState = true;
11019
11072
  }
11020
11073
  writeTelegramConfig(DEFAULT_CONFIG_PATH, { token, supergroupId, users, remoteControl });
11021
- console.log(chalk27.green(`Wrote telegram config \u2014 token: ${maskToken(token)} group: ${supergroupId}`));
11074
+ console.log(chalk29.green(`Wrote telegram config \u2014 token: ${maskToken(token)} group: ${supergroupId}`));
11022
11075
  if (!printedRemoteControlState) {
11023
11076
  if (remoteControl) {
11024
- console.log(chalk27.green(`Remote control: ON (allowlisted user-id ${users[0]})`));
11077
+ console.log(chalk29.green(`Remote control: ON (allowlisted user-id ${users[0]})`));
11025
11078
  } else {
11026
- console.log(chalk27.dim("Remote control: off (default). Re-run with --user-id <id> to enable."));
11079
+ console.log(chalk29.dim("Remote control: off (default). Re-run with --user-id <id> to enable."));
11027
11080
  }
11028
11081
  }
11029
11082
  try {
11030
11083
  await runRegisterCommands({ client });
11031
- console.log(chalk27.dim("Registered the /command menu."));
11084
+ console.log(chalk29.dim("Registered the /command menu."));
11032
11085
  } catch (e) {
11033
- console.log(chalk27.yellow(`command-menu registration skipped: ${e.message}`));
11086
+ console.log(chalk29.yellow(`command-menu registration skipped: ${e.message}`));
11034
11087
  }
11035
11088
  const topics = loadState(defaultStateRoot()).topics;
11036
11089
  const topicEntries = Object.entries(topics);
@@ -11039,28 +11092,28 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
11039
11092
  const project = key.slice(0, key.indexOf("::"));
11040
11093
  return `${project}\u2192${id}`;
11041
11094
  }).join(", ");
11042
- console.log(chalk27.dim(`Existing topics: ${summary} (already created \u2014 not recreated)`));
11095
+ console.log(chalk29.dim(`Existing topics: ${summary} (already created \u2014 not recreated)`));
11043
11096
  } else {
11044
- console.log(chalk27.dim("No project topics yet \u2014 they're created on first delivery or via: squadrant telegram link <project>"));
11097
+ console.log(chalk29.dim("No project topics yet \u2014 they're created on first delivery or via: squadrant telegram link <project>"));
11045
11098
  }
11046
11099
  runTelegramPostSetup({});
11047
11100
  console.log();
11048
- console.log(`Next: ${chalk27.cyan("squadrant telegram link <project>")}`);
11101
+ console.log(`Next: ${chalk29.cyan("squadrant telegram link <project>")}`);
11049
11102
  });
11050
11103
  telegramCommand.command("register-commands").description("Register (or re-register) the bot's / command menu with Telegram").action(async () => {
11051
11104
  const cfg = loadConfig().telegram;
11052
11105
  if (!cfg) {
11053
- console.error(chalk27.red("telegram config absent \u2014 run: squadrant telegram setup"));
11106
+ console.error(chalk29.red("telegram config absent \u2014 run: squadrant telegram setup"));
11054
11107
  process.exit(1);
11055
11108
  }
11056
11109
  const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
11057
11110
  if (!token) {
11058
- console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
11111
+ console.error(chalk29.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
11059
11112
  process.exit(1);
11060
11113
  }
11061
11114
  const client = createTelegramClient({ token });
11062
11115
  await runRegisterCommands({ client });
11063
- console.log(chalk27.green(`registered ${BOT_COMMANDS.length} bot commands`));
11116
+ console.log(chalk29.green(`registered ${BOT_COMMANDS.length} bot commands`));
11064
11117
  });
11065
11118
  telegramCommand.command("notify").argument("[project]", "project to toggle").argument("[state]", "on | off | crew | cap").argument("[value]", "tier for crew (all|alert_only|done_only|none) or on|off for cap").option("--status", "list notification state for all projects").description("Live on|off (state), or crew <tier> / cap <on|off> preference (per-project config)").action(async (project, state, value, opts) => {
11066
11119
  const stateRoot = defaultStateRoot();
@@ -11071,7 +11124,7 @@ telegramCommand.command("notify").argument("[project]", "project to toggle").arg
11071
11124
  return;
11072
11125
  }
11073
11126
  for (const r of rows) {
11074
- console.log(` ${r.project}: ${r.active ? chalk27.green("on") : chalk27.dim("off (muted)")}`);
11127
+ console.log(` ${r.project}: ${r.active ? chalk29.green("on") : chalk29.dim("off (muted)")}`);
11075
11128
  }
11076
11129
  return;
11077
11130
  }
@@ -11080,53 +11133,53 @@ telegramCommand.command("notify").argument("[project]", "project to toggle").arg
11080
11133
  const token = tgCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
11081
11134
  if (state === "crew" || state === "cap") {
11082
11135
  if (value === void 0) {
11083
- console.error(chalk27.red(`usage: squadrant telegram notify <project> ${state} <value>`));
11136
+ console.error(chalk29.red(`usage: squadrant telegram notify <project> ${state} <value>`));
11084
11137
  process.exit(1);
11085
11138
  }
11086
11139
  const resolved2 = resolveNotify(globalNotify, loadProjectOverride(project));
11087
11140
  const before2 = { ...resolved2, active: isNotifyActive(stateRoot, project) };
11088
11141
  const res = runTelegramNotifyPref({ project, dimension: state, value });
11089
11142
  if (!res.ok) {
11090
- console.error(chalk27.red(res.message));
11143
+ console.error(chalk29.red(res.message));
11091
11144
  process.exit(1);
11092
11145
  }
11093
- console.log(chalk27.green(`${project} ${state} = ${value}`));
11146
+ console.log(chalk29.green(`${project} ${state} = ${value}`));
11094
11147
  const after2 = state === "crew" ? { ...before2, crew: value } : { ...before2, cap: value === "on" };
11095
11148
  if (tgCfg && token) {
11096
11149
  const client = createTelegramClient({ token });
11097
11150
  const sent = await runNotifyConfirmation({ project, before: before2, after: after2, cfg: tgCfg, client, stateRoot });
11098
- if (sent) console.log(chalk27.dim(`\u2192 notified ${project} topic`));
11151
+ if (sent) console.log(chalk29.dim(`\u2192 notified ${project} topic`));
11099
11152
  }
11100
11153
  return;
11101
11154
  }
11102
11155
  if (state !== "on" && state !== "off") {
11103
- console.error(chalk27.red("usage: squadrant telegram notify <project> <on|off|crew <tier>|cap <on|off>>"));
11156
+ console.error(chalk29.red("usage: squadrant telegram notify <project> <on|off|crew <tier>|cap <on|off>>"));
11104
11157
  process.exit(1);
11105
11158
  }
11106
11159
  const resolved = resolveNotify(globalNotify, loadProjectOverride(project));
11107
11160
  const before = { ...resolved, active: isNotifyActive(stateRoot, project) };
11108
11161
  const after = { ...before, active: state === "on" };
11109
11162
  runTelegramNotifySet({ project, active: state === "on", stateRoot });
11110
- console.log(chalk27.green(`${project} notifications ${state === "on" ? "ON" : "OFF"}`));
11163
+ console.log(chalk29.green(`${project} notifications ${state === "on" ? "ON" : "OFF"}`));
11111
11164
  if (tgCfg && token) {
11112
11165
  const client = createTelegramClient({ token });
11113
11166
  const sent = await runNotifyConfirmation({ project, before, after, cfg: tgCfg, client, stateRoot });
11114
- if (sent) console.log(chalk27.dim(`\u2192 notified ${project} topic`));
11167
+ if (sent) console.log(chalk29.dim(`\u2192 notified ${project} topic`));
11115
11168
  }
11116
11169
  });
11117
11170
  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) => {
11118
11171
  const cfg = loadConfig().telegram;
11119
11172
  if (!cfg) {
11120
- console.error(chalk27.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
11173
+ console.error(chalk29.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
11121
11174
  process.exit(1);
11122
11175
  }
11123
11176
  const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
11124
11177
  if (!token) {
11125
- console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
11178
+ console.error(chalk29.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
11126
11179
  process.exit(1);
11127
11180
  }
11128
11181
  if (!capAllowed(project, cfg.notify)) {
11129
- console.log(chalk27.dim(`${project}: captain messages muted (cap=off) \u2014 not sent`));
11182
+ console.log(chalk29.dim(`${project}: captain messages muted (cap=off) \u2014 not sent`));
11130
11183
  return;
11131
11184
  }
11132
11185
  let message;
@@ -11139,19 +11192,19 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
11139
11192
  for await (const line of rl) lines.push(line);
11140
11193
  message = lines.join("\n").trimEnd();
11141
11194
  if (!message) {
11142
- console.error(chalk27.red("no message provided (stdin was empty)"));
11195
+ console.error(chalk29.red("no message provided (stdin was empty)"));
11143
11196
  process.exit(1);
11144
11197
  }
11145
11198
  } else {
11146
- console.error(chalk27.red("message required \u2014 pass as argument or pipe via stdin"));
11199
+ console.error(chalk29.red("message required \u2014 pass as argument or pipe via stdin"));
11147
11200
  process.exit(1);
11148
11201
  }
11149
11202
  const client = createTelegramClient({ token });
11150
11203
  try {
11151
11204
  const { chatId, topicId } = await runTelegramSend({ project, message, cfg, client, stateRoot: defaultStateRoot() });
11152
- console.log(chalk27.green(`sent to group ${chatId} topic ${topicId}`));
11205
+ console.log(chalk29.green(`sent to group ${chatId} topic ${topicId}`));
11153
11206
  } catch (e) {
11154
- console.error(chalk27.red(e.message));
11207
+ console.error(chalk29.red(e.message));
11155
11208
  process.exit(1);
11156
11209
  }
11157
11210
  });
@@ -11159,7 +11212,7 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
11159
11212
  // packages/cli/src/commands/hooks.ts
11160
11213
  init_dist2();
11161
11214
  init_dist4();
11162
- import { Command as Command28 } from "commander";
11215
+ import { Command as Command30 } from "commander";
11163
11216
  import { join as join25 } from "path";
11164
11217
  import { homedir as homedir17 } from "os";
11165
11218
  var SOCK4 = join25(homedir17(), ".config", "squadrant", "squadrant.sock");
@@ -11188,7 +11241,7 @@ function mapHookSub(sub, payload, taskId) {
11188
11241
  }
11189
11242
  }
11190
11243
  function hooksCommand() {
11191
- const hooks = new Command28("hooks").description("(internal) receive lifecycle hook events from agent processes");
11244
+ const hooks = new Command30("hooks").description("(internal) receive lifecycle hook events from agent processes");
11192
11245
  hooks.command("claude <sub>", { hidden: true }).description("internal: bridge a NativeHookSource claude hook to squadrantd").action(async (sub) => {
11193
11246
  const taskId = process.env.SQUADRANT_CREW_TASK_ID;
11194
11247
  const project = process.env.SQUADRANT_CREW_PROJECT;
@@ -11257,7 +11310,7 @@ if (process.argv[2] !== "config") {
11257
11310
  if (!process.env.SQUADRANT_DAEMON_SKIP) {
11258
11311
  ensureDaemon();
11259
11312
  }
11260
- var program = new Command29();
11313
+ var program = new Command31();
11261
11314
  program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
11262
11315
  program.addCommand(doctorCommand);
11263
11316
  program.addCommand(initCommand);
@@ -11281,6 +11334,8 @@ program.addCommand(codexChatSmokeCommand);
11281
11334
  program.addCommand(configCommand);
11282
11335
  program.addCommand(healCommand);
11283
11336
  program.addCommand(groupCommand);
11337
+ program.addCommand(pingCommand);
11338
+ program.addCommand(dispatchCommand);
11284
11339
  program.addCommand(cmuxCommand);
11285
11340
  program.addCommand(effortCommand);
11286
11341
  program.addCommand(telegramCommand);