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 +168 -113
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +202 -40
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/captain-ops/SKILL.md +10 -7
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
|
-
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
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
|
|
10701
|
-
import
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
10742
|
-
new
|
|
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
|
|
10759
|
-
import
|
|
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
|
|
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(
|
|
10807
|
-
console.log(
|
|
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(
|
|
10866
|
+
console.error(chalk28.red(err.message));
|
|
10814
10867
|
process.exit(1);
|
|
10815
10868
|
}
|
|
10816
10869
|
const effort = value;
|
|
10817
|
-
console.log(
|
|
10818
|
-
console.log(
|
|
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(
|
|
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
|
|
10836
|
-
import
|
|
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
|
|
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 ?
|
|
10887
|
-
console.log(`supergroup: ${supergroupId ??
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
10987
|
+
console.log(chalk29.green(`Using existing bot token (@${botUser.username})`));
|
|
10935
10988
|
console.log();
|
|
10936
10989
|
} catch {
|
|
10937
|
-
console.log(
|
|
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(
|
|
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(
|
|
11001
|
+
console.error(chalk29.red(`token rejected: ${e.message}`));
|
|
10949
11002
|
process.exit(1);
|
|
10950
11003
|
}
|
|
10951
|
-
console.log(
|
|
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(
|
|
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(
|
|
11018
|
+
console.error(chalk29.red(`token rejected: ${e.message}`));
|
|
10966
11019
|
process.exit(1);
|
|
10967
11020
|
}
|
|
10968
|
-
console.log(
|
|
11021
|
+
console.log(chalk29.green(`Connected as @${botUser.username}`));
|
|
10969
11022
|
console.log();
|
|
10970
11023
|
}
|
|
10971
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
10986
|
-
console.error(
|
|
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(
|
|
11042
|
+
console.log(chalk29.green(`Found group: ${supergroupId}`));
|
|
10990
11043
|
console.log();
|
|
10991
11044
|
}
|
|
10992
|
-
console.log(
|
|
10993
|
-
console.log(
|
|
10994
|
-
console.log(
|
|
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(
|
|
11009
|
-
console.log(
|
|
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(
|
|
11067
|
+
console.log(chalk29.dim(`Remote control: already configured (user-id ${existingUsers[0]}). Use --user-id to update.`));
|
|
11015
11068
|
} else {
|
|
11016
|
-
console.log(
|
|
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(
|
|
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(
|
|
11077
|
+
console.log(chalk29.green(`Remote control: ON (allowlisted user-id ${users[0]})`));
|
|
11025
11078
|
} else {
|
|
11026
|
-
console.log(
|
|
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(
|
|
11084
|
+
console.log(chalk29.dim("Registered the /command menu."));
|
|
11032
11085
|
} catch (e) {
|
|
11033
|
-
console.log(
|
|
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(
|
|
11095
|
+
console.log(chalk29.dim(`Existing topics: ${summary} (already created \u2014 not recreated)`));
|
|
11043
11096
|
} else {
|
|
11044
|
-
console.log(
|
|
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: ${
|
|
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(
|
|
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(
|
|
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(
|
|
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 ?
|
|
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(
|
|
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(
|
|
11143
|
+
console.error(chalk29.red(res.message));
|
|
11091
11144
|
process.exit(1);
|
|
11092
11145
|
}
|
|
11093
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
11195
|
+
console.error(chalk29.red("no message provided (stdin was empty)"));
|
|
11143
11196
|
process.exit(1);
|
|
11144
11197
|
}
|
|
11145
11198
|
} else {
|
|
11146
|
-
console.error(
|
|
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(
|
|
11205
|
+
console.log(chalk29.green(`sent to group ${chatId} topic ${topicId}`));
|
|
11153
11206
|
} catch (e) {
|
|
11154
|
-
console.error(
|
|
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
|
|
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
|
|
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
|
|
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);
|