squadrant 0.16.3 → 0.16.5
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 +662 -310
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +180 -17
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/captain-ops/SKILL.md +17 -0
- package/plugin/skills/explainer-reel/SKILL.md +160 -0
- package/plugin/skills/explainer-reel/assets/scene-kit.js +196 -0
- package/plugin/skills/explainer-reel/assets/swimlane-preset.html +391 -0
- package/plugin/skills/explainer-reel/assets/theme.css +99 -0
- package/plugin/skills/explainer-reel/examples/jwt-reel.gif +0 -0
- package/plugin/skills/explainer-reel/examples/jwt-reel.html +233 -0
- package/plugin/skills/explainer-reel/references/component-library.md +75 -0
- package/plugin/skills/explainer-reel/references/design-tokens.md +72 -0
- package/plugin/skills/explainer-reel/references/interactive-mode.md +62 -0
- package/plugin/skills/explainer-reel/scripts/README.md +30 -0
- package/plugin/skills/explainer-reel/scripts/contact-sheet.sh +20 -0
- package/plugin/skills/explainer-reel/scripts/render-gif.sh +44 -0
- package/plugin/skills/explainer-reel/scripts/seek-shot.sh +24 -0
package/dist/index.js
CHANGED
|
@@ -1313,6 +1313,9 @@ function stampAttempt(rec, patch, now) {
|
|
|
1313
1313
|
attempts.push(last);
|
|
1314
1314
|
return { ...rec, attempts };
|
|
1315
1315
|
}
|
|
1316
|
+
function isStickyAttention(state) {
|
|
1317
|
+
return state === "blocked" || state === "review";
|
|
1318
|
+
}
|
|
1316
1319
|
function nextPendingTool(current, ev, now) {
|
|
1317
1320
|
if (ev.note === "agent.hook.PreToolUse")
|
|
1318
1321
|
return { name: ev.tool ?? "tool", since: now };
|
|
@@ -1341,7 +1344,7 @@ function reduce(rec, ev, now) {
|
|
|
1341
1344
|
};
|
|
1342
1345
|
case "task.progress": {
|
|
1343
1346
|
const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
|
|
1344
|
-
if (rec.state
|
|
1347
|
+
if (isStickyAttention(rec.state))
|
|
1345
1348
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool };
|
|
1346
1349
|
const b = { ...base, pendingTool };
|
|
1347
1350
|
if (rec.state === "awaiting-input" || rec.state === "stalled")
|
|
@@ -1349,7 +1352,7 @@ function reduce(rec, ev, now) {
|
|
|
1349
1352
|
return stampAttempt(b, {}, now);
|
|
1350
1353
|
}
|
|
1351
1354
|
case "heartbeat":
|
|
1352
|
-
if (rec.state
|
|
1355
|
+
if (isStickyAttention(rec.state))
|
|
1353
1356
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1354
1357
|
if (rec.state === "awaiting-input")
|
|
1355
1358
|
return { ...base, state: "working" };
|
|
@@ -1358,7 +1361,12 @@ function reduce(rec, ev, now) {
|
|
|
1358
1361
|
if (rec.state === "blocked")
|
|
1359
1362
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1360
1363
|
return { ...base, state: "blocked", question: ev.question, pendingTool: void 0 };
|
|
1364
|
+
case "task.review":
|
|
1365
|
+
return { ...base, state: "review", reviewNote: ev.message, pendingTool: void 0 };
|
|
1361
1366
|
case "task.done":
|
|
1367
|
+
if (rec.state === "review" && ev.source !== "approve") {
|
|
1368
|
+
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1369
|
+
}
|
|
1362
1370
|
return { ...base, state: "done", resultRef: ev.resultRef, parseWarning: ev.parseWarning };
|
|
1363
1371
|
case "task.failed":
|
|
1364
1372
|
return { ...base, state: "failed", error: ev.error, exitCode: ev.exitCode };
|
|
@@ -1371,7 +1379,7 @@ function reduce(rec, ev, now) {
|
|
|
1371
1379
|
case "task.turn.started":
|
|
1372
1380
|
return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0 };
|
|
1373
1381
|
case "task.turn.completed":
|
|
1374
|
-
if (rec.state
|
|
1382
|
+
if (isStickyAttention(rec.state))
|
|
1375
1383
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1376
1384
|
if (rec.pendingTool)
|
|
1377
1385
|
return stampAttempt(base, {}, now);
|
|
@@ -1454,6 +1462,10 @@ function formatMessage(rec, event) {
|
|
|
1454
1462
|
}
|
|
1455
1463
|
case "blocked":
|
|
1456
1464
|
return `CREW BLOCKED ${tag}: ${(rec.question ?? "(no question)").trim()}`;
|
|
1465
|
+
case "review": {
|
|
1466
|
+
const note = (rec.reviewNote ?? "").trim();
|
|
1467
|
+
return `CREW REVIEW ${tag}: ${note || "ready for review"} \u2014 run 'squadrant diff ${rec.project} ${rec.name ?? rec.id}' then 'squadrant crew approve' or send feedback.`;
|
|
1468
|
+
}
|
|
1457
1469
|
case "failed":
|
|
1458
1470
|
return `CREW FAILED ${tag}: ${(rec.error ?? "(no error)").trim()}`;
|
|
1459
1471
|
case "stalled": {
|
|
@@ -1829,14 +1841,15 @@ var init_reduce = __esm({
|
|
|
1829
1841
|
DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1e3;
|
|
1830
1842
|
TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1831
1843
|
TERMINAL_RECORD_KEEP_PER_PROJECT = 20;
|
|
1832
|
-
ATTENTION_STATES = /* @__PURE__ */ new Set(["done", "blocked", "failed", "stalled", "awaiting-input"]);
|
|
1833
|
-
REAPABLE_SURFACE_STATES = /* @__PURE__ */ new Set(["working", "stalled", "awaiting-input", "blocked"]);
|
|
1844
|
+
ATTENTION_STATES = /* @__PURE__ */ new Set(["done", "blocked", "review", "failed", "stalled", "awaiting-input"]);
|
|
1845
|
+
REAPABLE_SURFACE_STATES = /* @__PURE__ */ new Set(["working", "stalled", "awaiting-input", "blocked", "review"]);
|
|
1834
1846
|
IDLE_DEBOUNCE_MS = 12e3;
|
|
1835
1847
|
KNOWN_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1836
1848
|
"task.started",
|
|
1837
1849
|
"task.progress",
|
|
1838
1850
|
"heartbeat",
|
|
1839
1851
|
"task.blocked",
|
|
1852
|
+
"task.review",
|
|
1840
1853
|
"task.done",
|
|
1841
1854
|
"task.failed",
|
|
1842
1855
|
"task.session",
|
|
@@ -2332,13 +2345,14 @@ function projectHealth(input) {
|
|
|
2332
2345
|
const { project, now, captainName, captainStopped, commandPresent, crews } = input;
|
|
2333
2346
|
const out = [];
|
|
2334
2347
|
const captainState = input.captainState ?? (captainStopped === true ? "stopped" : captainStopped === false ? "alive" : "unknown");
|
|
2348
|
+
const deferral = input.captainDeferral;
|
|
2335
2349
|
out.push({
|
|
2336
2350
|
kind: "captain",
|
|
2337
2351
|
project,
|
|
2338
2352
|
ref: captainName,
|
|
2339
2353
|
state: captainState,
|
|
2340
2354
|
lastSeenMs: null,
|
|
2341
|
-
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : captainState === "gone" ? "captain process died (crash) \u2014 crews reaped" : void 0
|
|
2355
|
+
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : captainState === "gone" ? "captain process died (crash) \u2014 crews reaped" : deferral?.stuck ? `\u26A0\uFE0F delivery stuck (${deferral.maxDeferCount}+ retries) \u2014 draft/ghost text blocking captain pane; input never touched, delivers automatically once cleared` : void 0
|
|
2342
2356
|
});
|
|
2343
2357
|
if (commandPresent !== null) {
|
|
2344
2358
|
out.push({
|
|
@@ -3005,6 +3019,8 @@ function buildContext(opts) {
|
|
|
3005
3019
|
opencodeBridge: null,
|
|
3006
3020
|
cmuxEventsBridge: null,
|
|
3007
3021
|
telegramBridge: void 0,
|
|
3022
|
+
notifyFault: opts.notifyFault ?? (() => {
|
|
3023
|
+
}),
|
|
3008
3024
|
lifecycleSources: opts.lifecycleSources ?? [],
|
|
3009
3025
|
broadcast: () => {
|
|
3010
3026
|
},
|
|
@@ -3345,7 +3361,7 @@ var init_captain_delivery = __esm({
|
|
|
3345
3361
|
const seq = entry.seq;
|
|
3346
3362
|
const deferCount = this.deferCounts.get(seq) ?? 0;
|
|
3347
3363
|
const stable = (this.stableCounts.get(seq) ?? 0) >= this.opts.stableProbePolls;
|
|
3348
|
-
const probe = stable
|
|
3364
|
+
const probe = stable;
|
|
3349
3365
|
try {
|
|
3350
3366
|
await send(msg, probe ? { probe: true } : void 0);
|
|
3351
3367
|
this.deferCounts.delete(seq);
|
|
@@ -3469,7 +3485,9 @@ async function runLivenessTick(deps) {
|
|
|
3469
3485
|
}
|
|
3470
3486
|
}
|
|
3471
3487
|
function createDelivery(ctx, daemonCmux) {
|
|
3472
|
-
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts } = ctx;
|
|
3488
|
+
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts, telegramBridge } = ctx;
|
|
3489
|
+
const notifyFault = ctx.notifyFault ?? (() => {
|
|
3490
|
+
});
|
|
3473
3491
|
const defaultNotify = async (args) => {
|
|
3474
3492
|
try {
|
|
3475
3493
|
await appendToMailbox({
|
|
@@ -3492,6 +3510,7 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3492
3510
|
const cfg = loadConfig();
|
|
3493
3511
|
const deliveries = /* @__PURE__ */ new Map();
|
|
3494
3512
|
const deliveryStats = (project) => deliveries.get(project)?.stats();
|
|
3513
|
+
const stuckNotified = /* @__PURE__ */ new Set();
|
|
3495
3514
|
const sessionStartMs = Date.now();
|
|
3496
3515
|
let delivering = false;
|
|
3497
3516
|
const deliveryCore = async () => {
|
|
@@ -3563,6 +3582,18 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3563
3582
|
break;
|
|
3564
3583
|
}
|
|
3565
3584
|
}
|
|
3585
|
+
const stuck = d.stats().stuck;
|
|
3586
|
+
if (stuck && !stuckNotified.has(project)) {
|
|
3587
|
+
stuckNotified.add(project);
|
|
3588
|
+
const { maxDeferCount } = d.stats();
|
|
3589
|
+
log(`delivery stuck project=${project} deferCount=${maxDeferCount}`);
|
|
3590
|
+
const text = `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
|
|
3591
|
+
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`delivery stuck alert failed project=${project}: ${e.message}`));
|
|
3592
|
+
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
3593
|
+
telegramBridge?.pushRaw(project, text);
|
|
3594
|
+
} else if (!stuck && stuckNotified.has(project)) {
|
|
3595
|
+
stuckNotified.delete(project);
|
|
3596
|
+
}
|
|
3566
3597
|
}
|
|
3567
3598
|
};
|
|
3568
3599
|
const deliveryTick = async () => {
|
|
@@ -3839,7 +3870,12 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
3839
3870
|
captainStopped: null,
|
|
3840
3871
|
captainState: deriveCaptainState(capEntry),
|
|
3841
3872
|
commandPresent: null,
|
|
3842
|
-
crews: store.list(project)
|
|
3873
|
+
crews: store.list(project),
|
|
3874
|
+
// #579/#484 Gap 3: surface the same deferral stats already exposed to
|
|
3875
|
+
// the snapshot (line ~135 below) on the health row too, so `squadrant
|
|
3876
|
+
// doctor` / `squadrant status --detailed` show a stuck delivery with
|
|
3877
|
+
// zero configuration.
|
|
3878
|
+
captainDeferral: deliveryStats(project)
|
|
3843
3879
|
}));
|
|
3844
3880
|
}
|
|
3845
3881
|
return out;
|
|
@@ -4286,8 +4322,12 @@ var init_commands = __esm({
|
|
|
4286
4322
|
build: (a) => a[0] ? ok("crews", ["crew", "list", a[0]]) : usage("crews", "usage: /crews <project>")
|
|
4287
4323
|
},
|
|
4288
4324
|
launch: {
|
|
4325
|
+
// --headless (#586, same reason as #520 on the boot-if-down path): runCommand
|
|
4326
|
+
// execs this argv from the daemon, which has no CMUX_WORKSPACE_ID and no
|
|
4327
|
+
// terminal — a plain `launch` would open the cmux GUI app and exit 0 before
|
|
4328
|
+
// the workspace is ever launched.
|
|
4289
4329
|
usage: "/launch <project>",
|
|
4290
|
-
build: (a) => a[0] ? ok("launch", ["launch", a[0]]) : usage("launch", "usage: /launch <project>")
|
|
4330
|
+
build: (a) => a[0] ? ok("launch", ["launch", a[0], "--headless"]) : usage("launch", "usage: /launch <project>")
|
|
4291
4331
|
},
|
|
4292
4332
|
effort: {
|
|
4293
4333
|
usage: "/effort [max|balance|low]",
|
|
@@ -4461,6 +4501,9 @@ ${ev.message}` : "");
|
|
|
4461
4501
|
case "task.blocked":
|
|
4462
4502
|
return `\u{1F6A7} [${project}] CREW BLOCKED \xB7 ${ev.id}
|
|
4463
4503
|
${ev.question}`;
|
|
4504
|
+
case "task.review":
|
|
4505
|
+
return `\u{1F440} [${project}] CREW REVIEW \xB7 ${ev.id}` + (ev.message ? `
|
|
4506
|
+
${ev.message}` : "");
|
|
4464
4507
|
case "task.idle":
|
|
4465
4508
|
return `\u{1F4A4} [${project}] CREW IDLE \xB7 ${ev.id}`;
|
|
4466
4509
|
case "task.failed":
|
|
@@ -4693,6 +4736,7 @@ var init_tiers = __esm({
|
|
|
4693
4736
|
ALERTS = /* @__PURE__ */ new Set([
|
|
4694
4737
|
...DONE_ONLY,
|
|
4695
4738
|
"task.blocked",
|
|
4739
|
+
"task.review",
|
|
4696
4740
|
"task.approval.requested",
|
|
4697
4741
|
"task.input.requested",
|
|
4698
4742
|
"task.timeout"
|
|
@@ -4740,6 +4784,14 @@ function createTelegramBridge(opts) {
|
|
|
4740
4784
|
s.offset = next;
|
|
4741
4785
|
saveState(stateRoot, s);
|
|
4742
4786
|
}
|
|
4787
|
+
async function sendToTopic(project, text) {
|
|
4788
|
+
let threadId = loadState(stateRoot).topics[topicKey(project)];
|
|
4789
|
+
if (threadId === void 0) {
|
|
4790
|
+
threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
|
|
4791
|
+
setTopic(stateRoot, project, threadId);
|
|
4792
|
+
}
|
|
4793
|
+
await client.sendMessage(cfg.supergroupId, threadId, text);
|
|
4794
|
+
}
|
|
4743
4795
|
async function deliverOutbound(project, ev) {
|
|
4744
4796
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
4745
4797
|
const live = loadState(stateRoot).notify[project];
|
|
@@ -4748,12 +4800,10 @@ function createTelegramBridge(opts) {
|
|
|
4748
4800
|
return;
|
|
4749
4801
|
if (!tierIncludes(resolved.crew, ev.type))
|
|
4750
4802
|
return;
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
}
|
|
4756
|
-
await client.sendMessage(cfg.supergroupId, threadId, formatLifecycle(project, ev));
|
|
4803
|
+
await sendToTopic(project, formatLifecycle(project, ev));
|
|
4804
|
+
}
|
|
4805
|
+
async function deliverRawOutbound(project, text) {
|
|
4806
|
+
await sendToTopic(project, text);
|
|
4757
4807
|
}
|
|
4758
4808
|
function resolveLiveNotify(project) {
|
|
4759
4809
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
@@ -5061,6 +5111,11 @@ function createTelegramBridge(opts) {
|
|
|
5061
5111
|
log(`telegram outbound failed project=${project}: ${e.message}`);
|
|
5062
5112
|
});
|
|
5063
5113
|
},
|
|
5114
|
+
pushRaw(project, text) {
|
|
5115
|
+
void deliverRawOutbound(project, text).catch((e) => {
|
|
5116
|
+
log(`telegram raw push failed project=${project}: ${e.message}`);
|
|
5117
|
+
});
|
|
5118
|
+
},
|
|
5064
5119
|
health() {
|
|
5065
5120
|
return { polling: running, lastSuccessfulPollAt, lastError, lastErrorAt };
|
|
5066
5121
|
}
|
|
@@ -5898,7 +5953,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
|
5898
5953
|
if (task) {
|
|
5899
5954
|
if (TERMINAL_STATES.has(task.state)) {
|
|
5900
5955
|
await deps.emitEvent(project, { type: "task.reopened", id: task.id });
|
|
5901
|
-
} else if (task.state === "blocked" || task.state === "awaiting-input") {
|
|
5956
|
+
} else if (task.state === "blocked" || task.state === "awaiting-input" || task.state === "review") {
|
|
5902
5957
|
await deps.emitEvent(project, { type: "task.started", id: task.id });
|
|
5903
5958
|
}
|
|
5904
5959
|
}
|
|
@@ -6243,6 +6298,18 @@ function cmux(args) {
|
|
|
6243
6298
|
);
|
|
6244
6299
|
});
|
|
6245
6300
|
}
|
|
6301
|
+
function cmuxStdin(args, input) {
|
|
6302
|
+
return new Promise((resolve3, reject) => {
|
|
6303
|
+
const child = execFile2(resolveCmuxBin(), args, { encoding: "utf-8", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: "1" } }, (err, stdout) => {
|
|
6304
|
+
if (err) {
|
|
6305
|
+
reject(err.code === "ETIMEDOUT" ? new CmuxTimeoutError(args.join(" ")) : err);
|
|
6306
|
+
return;
|
|
6307
|
+
}
|
|
6308
|
+
resolve3(stdout.trim());
|
|
6309
|
+
});
|
|
6310
|
+
child.stdin.end(input);
|
|
6311
|
+
});
|
|
6312
|
+
}
|
|
6246
6313
|
function parseList(output) {
|
|
6247
6314
|
let parsed;
|
|
6248
6315
|
try {
|
|
@@ -6642,6 +6709,37 @@ function createCmuxDriver() {
|
|
|
6642
6709
|
}
|
|
6643
6710
|
throw new DeferDelivery(draft);
|
|
6644
6711
|
},
|
|
6712
|
+
async showDiff(opts) {
|
|
6713
|
+
const source = opts.source ?? "branch";
|
|
6714
|
+
const args = ["diff"];
|
|
6715
|
+
if (source === "staged") {
|
|
6716
|
+
args.push("--staged");
|
|
6717
|
+
} else if (source === "unstaged") {
|
|
6718
|
+
args.push("--unstaged");
|
|
6719
|
+
} else {
|
|
6720
|
+
args.push("--branch", "--base", opts.base);
|
|
6721
|
+
if (opts.lastTurn)
|
|
6722
|
+
args.push("--last-turn");
|
|
6723
|
+
}
|
|
6724
|
+
args.push("--cwd", opts.cwd, "--workspace", opts.workspaceId, "--layout", opts.layout ?? "split");
|
|
6725
|
+
if (opts.title)
|
|
6726
|
+
args.push("--title", opts.title);
|
|
6727
|
+
if (opts.focus === false)
|
|
6728
|
+
args.push("--no-focus");
|
|
6729
|
+
else
|
|
6730
|
+
args.push("--focus", "true");
|
|
6731
|
+
await cmux(args);
|
|
6732
|
+
},
|
|
6733
|
+
async showPatch(opts) {
|
|
6734
|
+
const args = ["diff", "-", "--workspace", opts.workspaceId, "--layout", opts.layout ?? "split"];
|
|
6735
|
+
if (opts.title)
|
|
6736
|
+
args.push("--title", opts.title);
|
|
6737
|
+
if (opts.focus === false)
|
|
6738
|
+
args.push("--no-focus");
|
|
6739
|
+
else
|
|
6740
|
+
args.push("--focus", "true");
|
|
6741
|
+
await cmuxStdin(args, opts.patch);
|
|
6742
|
+
},
|
|
6645
6743
|
async listSurfaces(workspaceId) {
|
|
6646
6744
|
let output;
|
|
6647
6745
|
try {
|
|
@@ -6738,7 +6836,8 @@ var init_runtimes = __esm({
|
|
|
6738
6836
|
});
|
|
6739
6837
|
|
|
6740
6838
|
// packages/workspaces/dist/notifiers/cmux.js
|
|
6741
|
-
import {
|
|
6839
|
+
import { execFile as execFileCb, execSync as execSync2 } from "child_process";
|
|
6840
|
+
import { promisify as promisify2 } from "util";
|
|
6742
6841
|
function createCmuxNotifier(_scope) {
|
|
6743
6842
|
return {
|
|
6744
6843
|
name: "cmux",
|
|
@@ -6755,13 +6854,15 @@ function createCmuxNotifier(_scope) {
|
|
|
6755
6854
|
}
|
|
6756
6855
|
},
|
|
6757
6856
|
async notify(message) {
|
|
6758
|
-
|
|
6857
|
+
await execFile3("squadrant", ["runtime", "send", "--command", message], { encoding: "utf-8", timeout: CMUX_TIMEOUT });
|
|
6759
6858
|
}
|
|
6760
6859
|
};
|
|
6761
6860
|
}
|
|
6861
|
+
var execFile3;
|
|
6762
6862
|
var init_cmux2 = __esm({
|
|
6763
6863
|
"packages/workspaces/dist/notifiers/cmux.js"() {
|
|
6764
6864
|
init_cmux();
|
|
6865
|
+
execFile3 = promisify2(execFileCb);
|
|
6765
6866
|
}
|
|
6766
6867
|
});
|
|
6767
6868
|
|
|
@@ -9907,7 +10008,7 @@ var init_require_daemon = __esm({
|
|
|
9907
10008
|
// packages/cli/src/index.ts
|
|
9908
10009
|
init_dist();
|
|
9909
10010
|
init_dist2();
|
|
9910
|
-
import { Command as
|
|
10011
|
+
import { Command as Command32 } from "commander";
|
|
9911
10012
|
import { readFileSync as readFileSync15, existsSync as existsSync12, writeFileSync as writeFileSync11 } from "fs";
|
|
9912
10013
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
9913
10014
|
import { dirname as dirname9, join as join29 } from "path";
|
|
@@ -10799,9 +10900,18 @@ function progressBar(completed, total) {
|
|
|
10799
10900
|
}
|
|
10800
10901
|
function captainIndicator(state) {
|
|
10801
10902
|
if (state === "alive" || state === "stale") return chalk6.green("\u25CF");
|
|
10903
|
+
if (state === "stopped") return chalk6.magenta("\u23FB");
|
|
10802
10904
|
if (state === void 0 || state === "unknown") return chalk6.dim("?");
|
|
10803
10905
|
return chalk6.dim("\u25CB");
|
|
10804
10906
|
}
|
|
10907
|
+
function formatProjectRow(name, captainName, fm, statusMdState, captainState) {
|
|
10908
|
+
const sessionIndicator = captainIndicator(captainState);
|
|
10909
|
+
const captainDisplay = `${captainName.padEnd(11)} ${sessionIndicator}`;
|
|
10910
|
+
const crew = statusMdState === "ok" ? String(fm.active_crew ?? 0).padEnd(6) : chalk6.dim("?").padEnd(6);
|
|
10911
|
+
const progress = statusMdState === "ok" ? progressBar(fm.tasks_completed ?? 0, fm.tasks_total ?? 0).padEnd(25) : statusMdState === "unreadable" ? chalk6.red("status.md unreadable").padEnd(25) : chalk6.dim("no notes").padEnd(25);
|
|
10912
|
+
const updated = statusMdState === "ok" ? timeAgo(fm.last_updated) : chalk6.dim("\u2014");
|
|
10913
|
+
return ` ${name.padEnd(18)} ${captainDisplay} ${crew} ${progress} ${updated}`;
|
|
10914
|
+
}
|
|
10805
10915
|
var statusCommand = new Command4("status").description("Show status of all projects from spoke vault status files").option("--detailed", "also show live per-component service health from the daemon (#77)").action(async (opts) => {
|
|
10806
10916
|
const config = loadConfig();
|
|
10807
10917
|
const projects = Object.entries(config.projects);
|
|
@@ -10826,28 +10936,19 @@ var statusCommand = new Command4("status").description("Show status of all proje
|
|
|
10826
10936
|
console.log(chalk6.dim(" " + "\u2500".repeat(85)));
|
|
10827
10937
|
for (const [name, project] of projects) {
|
|
10828
10938
|
const workspace = registry.forProject(name, config);
|
|
10829
|
-
if (!await workspace.exists("status.md")) {
|
|
10830
|
-
console.log(` ${name.padEnd(18)} ${chalk6.dim("no status.md")}`);
|
|
10831
|
-
continue;
|
|
10832
|
-
}
|
|
10833
10939
|
let fm = {};
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
|
|
10940
|
+
let statusMdState = "missing";
|
|
10941
|
+
if (await workspace.exists("status.md")) {
|
|
10942
|
+
try {
|
|
10943
|
+
const raw = await workspace.read("status.md");
|
|
10944
|
+
fm = matter2(raw).data;
|
|
10945
|
+
statusMdState = "ok";
|
|
10946
|
+
} catch {
|
|
10947
|
+
statusMdState = "unreadable";
|
|
10948
|
+
}
|
|
10840
10949
|
}
|
|
10841
|
-
const sessionIndicator = captainIndicator(captainStateByProject.get(name));
|
|
10842
|
-
const captainDisplay = `${project.captainName.padEnd(11)} ${sessionIndicator}`;
|
|
10843
|
-
const crew = String(fm.active_crew ?? 0).padEnd(6);
|
|
10844
|
-
const progress = progressBar(
|
|
10845
|
-
fm.tasks_completed ?? 0,
|
|
10846
|
-
fm.tasks_total ?? 0
|
|
10847
|
-
).padEnd(25);
|
|
10848
|
-
const updated = timeAgo(fm.last_updated);
|
|
10849
10950
|
console.log(
|
|
10850
|
-
|
|
10951
|
+
formatProjectRow(name, project.captainName, fm, statusMdState, captainStateByProject.get(name))
|
|
10851
10952
|
);
|
|
10852
10953
|
}
|
|
10853
10954
|
console.log("");
|
|
@@ -10938,6 +11039,7 @@ init_dist4();
|
|
|
10938
11039
|
import { Command as Command8 } from "commander";
|
|
10939
11040
|
import { createConnection as createConnection3 } from "net";
|
|
10940
11041
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
11042
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
10941
11043
|
import { homedir as homedir16 } from "os";
|
|
10942
11044
|
import { join as join21 } from "path";
|
|
10943
11045
|
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "fs";
|
|
@@ -11337,6 +11439,8 @@ function buildSignalRequest(signal, o) {
|
|
|
11337
11439
|
};
|
|
11338
11440
|
} else if (signal === "blocked") {
|
|
11339
11441
|
event = { type: "task.blocked", id: taskId, reason: "crew signaled blocked", question: o.question ?? "" };
|
|
11442
|
+
} else if (signal === "review") {
|
|
11443
|
+
event = { type: "task.review", id: taskId, ...o.message !== void 0 ? { message: o.message } : {} };
|
|
11340
11444
|
} else {
|
|
11341
11445
|
event = { type: "task.failed", id: taskId, error: o.error ?? "crew signaled failed" };
|
|
11342
11446
|
}
|
|
@@ -11365,6 +11469,49 @@ async function runCrewSignal(signal, o, deps) {
|
|
|
11365
11469
|
const req = buildSignalRequest(signal, { ...o, writeResult: o.writeResult ?? defaultWriteResult });
|
|
11366
11470
|
await deps.call(req);
|
|
11367
11471
|
}
|
|
11472
|
+
function resolveApproveTarget(tasks, crew) {
|
|
11473
|
+
const matches = tasks.filter((t) => t.name === crew);
|
|
11474
|
+
if (matches.length === 0) return null;
|
|
11475
|
+
return matches.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
|
|
11476
|
+
}
|
|
11477
|
+
function defaultPushBranch(cwd, branch) {
|
|
11478
|
+
execFileSync6("git", ["-C", cwd, "push", "-u", "origin", branch], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
11479
|
+
}
|
|
11480
|
+
function defaultCreatePr(cwd, o) {
|
|
11481
|
+
return execFileSync6(
|
|
11482
|
+
"gh",
|
|
11483
|
+
["pr", "create", "--base", o.base, "--head", o.branch, "--title", o.title, "--body", o.body],
|
|
11484
|
+
{ cwd, encoding: "utf-8" }
|
|
11485
|
+
).trim();
|
|
11486
|
+
}
|
|
11487
|
+
async function runCrewApprove(project, crew, deps) {
|
|
11488
|
+
const config = loadConfig();
|
|
11489
|
+
const proj = config.projects[project];
|
|
11490
|
+
if (!proj) throw new Error(`Project '${project}' not found. Run 'squadrant projects list'.`);
|
|
11491
|
+
const tasks = await deps.call({ kind: "list", project });
|
|
11492
|
+
const task = resolveApproveTarget(tasks, crew);
|
|
11493
|
+
if (!task) throw new Error(`Crew '${crew}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
11494
|
+
if (task.state !== "review") {
|
|
11495
|
+
throw new Error(
|
|
11496
|
+
`Crew '${crew}' is not awaiting review (state=${task.state}). Only a crew that signaled 'review' can be approved.`
|
|
11497
|
+
);
|
|
11498
|
+
}
|
|
11499
|
+
const cwd = task.cwd ?? proj.path;
|
|
11500
|
+
const base = resolveWorktreeBase(proj.path);
|
|
11501
|
+
const branch = crewBranch(crew);
|
|
11502
|
+
const title = (task.task ?? branch).split(/\r?\n/)[0].trim().slice(0, 100);
|
|
11503
|
+
const body = (task.reviewNote ?? task.task ?? "").trim();
|
|
11504
|
+
const pushBranch = deps.pushBranch ?? defaultPushBranch;
|
|
11505
|
+
const createPr = deps.createPr ?? defaultCreatePr;
|
|
11506
|
+
pushBranch(cwd, branch);
|
|
11507
|
+
const prUrl = createPr(cwd, { base, branch, title, body });
|
|
11508
|
+
await deps.call({
|
|
11509
|
+
kind: "event",
|
|
11510
|
+
project,
|
|
11511
|
+
event: { type: "task.done", id: task.id, resultRef: "", message: `Approved \u2014 PR opened: ${prUrl}`, source: "approve" }
|
|
11512
|
+
});
|
|
11513
|
+
return prUrl;
|
|
11514
|
+
}
|
|
11368
11515
|
function addControlPlaneCrewCommands(crew) {
|
|
11369
11516
|
crew.command("dispatch <project> <task>").description("Dispatch a crew task via the control-plane daemon").requiredOption("--provider <p>", "claude|opencode|codex (gemini: experimental, headless not supported)").option("--mode <m>", "headless|interactive", "interactive").option("--cwd <dir>", "working dir for the crew (project/worktree); required for codex to edit code").action(async (project, task, opts) => {
|
|
11370
11517
|
const req = buildDispatchRequest({ project, task, provider: opts.provider, mode: opts.mode, cwd: opts.cwd });
|
|
@@ -11439,9 +11586,9 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11439
11586
|
}
|
|
11440
11587
|
process.exit(0);
|
|
11441
11588
|
});
|
|
11442
|
-
crew.command("signal <state>").description("Emit explicit terminal signal from a crew session: done|blocked|failed (reads SQUADRANT_CREW_* env, or --task-id/--project for codex)").option("--message <m>", "Summary written to resultRef (done)").option("--question <q>", "Question to surface to captain (blocked)").option("--error <e>", "Error message (failed)").option("--task-id <id>", "Explicit task id (codex; overrides SQUADRANT_CREW_TASK_ID env)").option("--project <p>", "Explicit project (codex; overrides SQUADRANT_CREW_PROJECT env)").action(async (state, opts) => {
|
|
11443
|
-
if (state !== "done" && state !== "blocked" && state !== "failed") {
|
|
11444
|
-
process.stderr.write(`unknown signal '${state}' (expected: done|blocked|failed)
|
|
11589
|
+
crew.command("signal <state>").description("Emit explicit terminal/review signal from a crew session: done|blocked|failed|review (reads SQUADRANT_CREW_* env, or --task-id/--project for codex)").option("--message <m>", "Summary written to resultRef (done), or review summary (review)").option("--question <q>", "Question to surface to captain (blocked)").option("--error <e>", "Error message (failed)").option("--task-id <id>", "Explicit task id (codex; overrides SQUADRANT_CREW_TASK_ID env)").option("--project <p>", "Explicit project (codex; overrides SQUADRANT_CREW_PROJECT env)").action(async (state, opts) => {
|
|
11590
|
+
if (state !== "done" && state !== "blocked" && state !== "failed" && state !== "review") {
|
|
11591
|
+
process.stderr.write(`unknown signal '${state}' (expected: done|blocked|failed|review)
|
|
11445
11592
|
`);
|
|
11446
11593
|
process.exit(2);
|
|
11447
11594
|
}
|
|
@@ -11457,6 +11604,17 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11457
11604
|
process.exit(0);
|
|
11458
11605
|
} catch (e) {
|
|
11459
11606
|
process.stderr.write(`${e.message}
|
|
11607
|
+
`);
|
|
11608
|
+
process.exit(1);
|
|
11609
|
+
}
|
|
11610
|
+
});
|
|
11611
|
+
crew.command("approve <project> <crew>").description("Approve a crew's reviewed work: push crew/<name> + open a PR, then terminalize DONE (#599)").action(async (project, crewName) => {
|
|
11612
|
+
try {
|
|
11613
|
+
const prUrl = await runCrewApprove(project, crewName, { call: squadrantdCall });
|
|
11614
|
+
process.stdout.write(`\u2714 Approved ${crewBranch(crewName)} \u2014 pushed + PR opened: ${prUrl}
|
|
11615
|
+
`);
|
|
11616
|
+
} catch (e) {
|
|
11617
|
+
process.stderr.write(`${e.message}
|
|
11460
11618
|
`);
|
|
11461
11619
|
process.exit(1);
|
|
11462
11620
|
}
|
|
@@ -11614,6 +11772,199 @@ crewCommand.command("close").description("Shutdown a crew session (closes its ta
|
|
|
11614
11772
|
}
|
|
11615
11773
|
});
|
|
11616
11774
|
|
|
11775
|
+
// packages/cli/src/commands/diff.ts
|
|
11776
|
+
init_dist();
|
|
11777
|
+
init_dist3();
|
|
11778
|
+
import { Command as Command10 } from "commander";
|
|
11779
|
+
import chalk10 from "chalk";
|
|
11780
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
11781
|
+
import readline2 from "readline";
|
|
11782
|
+
function resolveDiffTarget(tasks, crew, projectPath) {
|
|
11783
|
+
const matches = tasks.filter((t) => t.name === crew);
|
|
11784
|
+
if (matches.length === 0) return null;
|
|
11785
|
+
const task = matches.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
|
|
11786
|
+
const cwd = task.cwd ?? projectPath;
|
|
11787
|
+
return { cwd, isShared: cwd === projectPath };
|
|
11788
|
+
}
|
|
11789
|
+
function resolveDiffSources(opts) {
|
|
11790
|
+
if (opts.working) return ["unstaged", "staged"];
|
|
11791
|
+
if (opts.staged) return ["staged"];
|
|
11792
|
+
if (opts.unstaged) return ["unstaged"];
|
|
11793
|
+
return [];
|
|
11794
|
+
}
|
|
11795
|
+
function resolveDiffMode(crew, opts) {
|
|
11796
|
+
const requestedModes = [crew !== void 0, opts.pr !== void 0, opts.base !== void 0 || opts.head !== void 0 || opts.against !== void 0];
|
|
11797
|
+
if (requestedModes.filter(Boolean).length > 1) {
|
|
11798
|
+
throw new Error(
|
|
11799
|
+
"squadrant diff: a crew argument, --pr, and --base/--head/--against are mutually exclusive."
|
|
11800
|
+
);
|
|
11801
|
+
}
|
|
11802
|
+
if (opts.against !== void 0 && (opts.base !== void 0 || opts.head !== void 0)) {
|
|
11803
|
+
throw new Error("squadrant diff: --against cannot be combined with --base/--head.");
|
|
11804
|
+
}
|
|
11805
|
+
if (crew !== void 0) return { mode: "crew", crew };
|
|
11806
|
+
if (opts.pr !== void 0) return { mode: "pr", pr: opts.pr };
|
|
11807
|
+
if (opts.against !== void 0) return { mode: "refs", base: opts.against, head: "HEAD" };
|
|
11808
|
+
if (opts.base !== void 0 || opts.head !== void 0) {
|
|
11809
|
+
if (opts.base === void 0 || opts.head === void 0) {
|
|
11810
|
+
throw new Error(
|
|
11811
|
+
"squadrant diff: --base and --head must be used together (or use --against <ref> to diff against HEAD)."
|
|
11812
|
+
);
|
|
11813
|
+
}
|
|
11814
|
+
return { mode: "refs", base: opts.base, head: opts.head };
|
|
11815
|
+
}
|
|
11816
|
+
return { mode: "pick" };
|
|
11817
|
+
}
|
|
11818
|
+
function buildCrewDiffStats(tasks, projectPath, base, getStat) {
|
|
11819
|
+
const live = /* @__PURE__ */ new Map();
|
|
11820
|
+
for (const t of tasks) {
|
|
11821
|
+
if (!t.name || TERMINAL_STATES.has(t.state)) continue;
|
|
11822
|
+
const prev = live.get(t.name);
|
|
11823
|
+
if (!prev || (t.createdAt ?? 0) > (prev.createdAt ?? 0)) live.set(t.name, t);
|
|
11824
|
+
}
|
|
11825
|
+
return [...live.values()].map((t) => {
|
|
11826
|
+
const cwd = t.cwd ?? projectPath;
|
|
11827
|
+
return { name: t.name, cwd, stat: getStat(cwd, base).trim() };
|
|
11828
|
+
});
|
|
11829
|
+
}
|
|
11830
|
+
function parseCrewPick(raw, stats) {
|
|
11831
|
+
const trimmed = raw.trim();
|
|
11832
|
+
const idx = Number(trimmed);
|
|
11833
|
+
if (Number.isInteger(idx) && idx >= 1 && idx <= stats.length) {
|
|
11834
|
+
return stats[idx - 1].name;
|
|
11835
|
+
}
|
|
11836
|
+
const byName = stats.find((s) => s.name === trimmed);
|
|
11837
|
+
if (byName) return byName.name;
|
|
11838
|
+
throw new Error(`Invalid selection '${raw}'. Enter a number 1-${stats.length} or a crew name.`);
|
|
11839
|
+
}
|
|
11840
|
+
function promptLine2(question) {
|
|
11841
|
+
return new Promise((resolve3) => {
|
|
11842
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
11843
|
+
rl.question(question, (answer) => {
|
|
11844
|
+
rl.close();
|
|
11845
|
+
resolve3(answer);
|
|
11846
|
+
});
|
|
11847
|
+
});
|
|
11848
|
+
}
|
|
11849
|
+
function getPrDiff(projectPath, pr) {
|
|
11850
|
+
try {
|
|
11851
|
+
return execFileSync7("gh", ["pr", "diff", pr], { cwd: projectPath, encoding: "utf-8" });
|
|
11852
|
+
} catch (e) {
|
|
11853
|
+
throw new Error(`Could not fetch PR #${pr} diff (gh pr diff failed): ${e.message}`);
|
|
11854
|
+
}
|
|
11855
|
+
}
|
|
11856
|
+
function getRefsDiff(projectPath, base, head) {
|
|
11857
|
+
try {
|
|
11858
|
+
return execFileSync7("git", ["-C", projectPath, "diff", `${base}...${head}`], { encoding: "utf-8" });
|
|
11859
|
+
} catch (e) {
|
|
11860
|
+
throw new Error(`Could not diff ${base}...${head}: ${e.message}`);
|
|
11861
|
+
}
|
|
11862
|
+
}
|
|
11863
|
+
async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
|
|
11864
|
+
const tasks = await squadrantdCall({ kind: "list", project });
|
|
11865
|
+
const target = resolveDiffTarget(tasks, crew, proj.path);
|
|
11866
|
+
if (!target) {
|
|
11867
|
+
throw new Error(`Crew '${crew}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
11868
|
+
}
|
|
11869
|
+
const base = resolveWorktreeBase(proj.path);
|
|
11870
|
+
const branchLabel = crewBranch(crew);
|
|
11871
|
+
if (!runtime.showDiff) {
|
|
11872
|
+
throw new Error(`Runtime '${runtime.name}' has no native diff viewer yet \u2014 Phase 1 supports cmux only.`);
|
|
11873
|
+
}
|
|
11874
|
+
const sources = resolveDiffSources(opts);
|
|
11875
|
+
if (sources.length > 0) {
|
|
11876
|
+
let opened = 0;
|
|
11877
|
+
for (const source of sources) {
|
|
11878
|
+
const statArgs = source === "staged" ? ["diff", "--stat", "--cached"] : ["diff", "--stat"];
|
|
11879
|
+
const stat2 = execFileSync7("git", ["-C", target.cwd, ...statArgs], { encoding: "utf-8" }).trim();
|
|
11880
|
+
if (!stat2) continue;
|
|
11881
|
+
await runtime.showDiff({
|
|
11882
|
+
workspaceId,
|
|
11883
|
+
cwd: target.cwd,
|
|
11884
|
+
base,
|
|
11885
|
+
title: `${branchLabel} \u2014 ${source}`,
|
|
11886
|
+
layout: opts.layout,
|
|
11887
|
+
focus: opts.focus,
|
|
11888
|
+
source
|
|
11889
|
+
});
|
|
11890
|
+
opened++;
|
|
11891
|
+
}
|
|
11892
|
+
if (opened === 0) {
|
|
11893
|
+
const label = sources.length > 1 ? "staged or unstaged" : sources[0];
|
|
11894
|
+
console.log(`No ${label} changes on ${branchLabel}.`);
|
|
11895
|
+
} else {
|
|
11896
|
+
console.log(chalk10.dim(`Opened ${opened} working-tree diff(s) (${sources.join(", ")}) for ${branchLabel}.`));
|
|
11897
|
+
}
|
|
11898
|
+
return;
|
|
11899
|
+
}
|
|
11900
|
+
const diffStat = execFileSync7(
|
|
11901
|
+
"git",
|
|
11902
|
+
["-C", target.cwd, "diff", "--stat", `${base}...HEAD`],
|
|
11903
|
+
{ encoding: "utf-8" }
|
|
11904
|
+
).trim();
|
|
11905
|
+
if (!diffStat) {
|
|
11906
|
+
console.log(`No changes on ${branchLabel} vs ${base}.`);
|
|
11907
|
+
return;
|
|
11908
|
+
}
|
|
11909
|
+
await runtime.showDiff({
|
|
11910
|
+
workspaceId,
|
|
11911
|
+
cwd: target.cwd,
|
|
11912
|
+
base,
|
|
11913
|
+
title: `${branchLabel} vs ${base}`,
|
|
11914
|
+
layout: opts.layout,
|
|
11915
|
+
focus: opts.focus,
|
|
11916
|
+
lastTurn: opts.lastTurn,
|
|
11917
|
+
source: "branch"
|
|
11918
|
+
});
|
|
11919
|
+
console.log(chalk10.dim(`Opened ${branchLabel} vs ${base} in cmux diff.`));
|
|
11920
|
+
}
|
|
11921
|
+
async function runDiff(project, crewArg, opts) {
|
|
11922
|
+
const config = loadConfig();
|
|
11923
|
+
const proj = config.projects[project];
|
|
11924
|
+
if (!proj) {
|
|
11925
|
+
throw new Error(`Project '${project}' not found. Run 'squadrant projects list'.`);
|
|
11926
|
+
}
|
|
11927
|
+
const mode = resolveDiffMode(crewArg, opts);
|
|
11928
|
+
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
11929
|
+
if (mode.mode === "pr" || mode.mode === "refs") {
|
|
11930
|
+
if (!runtime.showPatch) {
|
|
11931
|
+
throw new Error(`Runtime '${runtime.name}' has no native patch viewer yet \u2014 Phase 1 supports cmux only.`);
|
|
11932
|
+
}
|
|
11933
|
+
const title = mode.mode === "pr" ? `PR #${mode.pr}` : `${mode.base}...${mode.head}`;
|
|
11934
|
+
const patch = mode.mode === "pr" ? getPrDiff(proj.path, mode.pr) : getRefsDiff(proj.path, mode.base, mode.head);
|
|
11935
|
+
if (!patch.trim()) {
|
|
11936
|
+
console.log(`No changes in ${title}.`);
|
|
11937
|
+
return;
|
|
11938
|
+
}
|
|
11939
|
+
await runtime.showPatch({ workspaceId, patch, title, layout: opts.layout, focus: opts.focus });
|
|
11940
|
+
console.log(chalk10.dim(`Opened ${title} in cmux diff.`));
|
|
11941
|
+
return;
|
|
11942
|
+
}
|
|
11943
|
+
let crew;
|
|
11944
|
+
if (mode.mode === "pick") {
|
|
11945
|
+
const tasks = await squadrantdCall({ kind: "list", project });
|
|
11946
|
+
const base = resolveWorktreeBase(proj.path);
|
|
11947
|
+
const stats = buildCrewDiffStats(tasks, proj.path, base, (cwd, b) => {
|
|
11948
|
+
try {
|
|
11949
|
+
return execFileSync7("git", ["-C", cwd, "diff", "--stat", `${b}...HEAD`], { encoding: "utf-8" });
|
|
11950
|
+
} catch {
|
|
11951
|
+
return "";
|
|
11952
|
+
}
|
|
11953
|
+
});
|
|
11954
|
+
if (stats.length === 0) {
|
|
11955
|
+
throw new Error(`No live crews for ${project}. Run 'squadrant crew list ${project}' to check, or 'squadrant crew spawn' one.`);
|
|
11956
|
+
}
|
|
11957
|
+
console.log(`Live crews for ${project} (vs ${base}):`);
|
|
11958
|
+
stats.forEach((s, i) => console.log(` ${i + 1}. ${s.name} \u2014 ${s.stat || "no changes"}`));
|
|
11959
|
+
const raw = await promptLine2("Pick a crew to diff (number or name): ");
|
|
11960
|
+
crew = parseCrewPick(raw, stats);
|
|
11961
|
+
} else {
|
|
11962
|
+
crew = mode.crew;
|
|
11963
|
+
}
|
|
11964
|
+
await openCrewDiff(project, proj, crew, opts, runtime, workspaceId);
|
|
11965
|
+
}
|
|
11966
|
+
var diffCommand = new Command10("diff").description("Open a crew's branch diff, a PR, or a ref comparison in cmux's native diff viewer \u2014 no VSCode required (#596/#604)").argument("<project>", "Project name (must be registered)").argument("[crew]", "Crew name (e.g. crew-1); omit with no other flags to pick from live crews").option("--pr <n>", "Review a PR: wraps `gh pr diff <n>` (mutually exclusive with crew/--base/--head)").option("--base <ref>", "Base ref for a --head ref comparison (merge-base diff)").option("--head <ref>", "Head ref for a --base ref comparison").option("--against <ref>", "Alias: diff <ref>...HEAD (mutually exclusive with --base/--head)").option("--layout <mode>", "split (default) or unified", "split").option("--last-turn", "diff only changes since the crew's last agent turn", false).option("--no-focus", "open the diff pane without stealing focus").option("--staged", "show only staged (index) changes \u2014 VSCode's 'Staged Changes' panel (#599)", false).option("--unstaged", "show only unstaged working-tree changes \u2014 VSCode's 'Changes' panel (#599)", false).option("--working", "show both staged and unstaged changes (mid-task working-tree review, #599)", false).action(runDiff);
|
|
11967
|
+
|
|
11617
11968
|
// packages/cli/src/commands/side.ts
|
|
11618
11969
|
init_dist();
|
|
11619
11970
|
init_dist3();
|
|
@@ -11621,11 +11972,11 @@ init_dist4();
|
|
|
11621
11972
|
init_dist3();
|
|
11622
11973
|
init_dist();
|
|
11623
11974
|
init_dist2();
|
|
11624
|
-
import { Command as
|
|
11975
|
+
import { Command as Command11 } from "commander";
|
|
11625
11976
|
import fs20 from "fs";
|
|
11626
11977
|
import path22 from "path";
|
|
11627
11978
|
import os12 from "os";
|
|
11628
|
-
import
|
|
11979
|
+
import chalk11 from "chalk";
|
|
11629
11980
|
var TEMPLATES_DIR3 = path22.join(os12.homedir(), ".config", "squadrant", "templates");
|
|
11630
11981
|
async function runSideSpawn2(input) {
|
|
11631
11982
|
const config = loadConfig();
|
|
@@ -11687,7 +12038,7 @@ async function runSideClose2(project, name) {
|
|
|
11687
12038
|
config.defaults.worktreeDir ?? ".worktrees"
|
|
11688
12039
|
);
|
|
11689
12040
|
}
|
|
11690
|
-
var sideCommand = new
|
|
12041
|
+
var sideCommand = new Command11("side").description(
|
|
11691
12042
|
"Spawn and manage side-sessions (research/debug) \u2014 fresh-context tabs off the daemon lifecycle"
|
|
11692
12043
|
);
|
|
11693
12044
|
sideCommand.command("spawn").description(
|
|
@@ -11712,9 +12063,9 @@ sideCommand.command("spawn").description(
|
|
|
11712
12063
|
direction: opts.direction,
|
|
11713
12064
|
agent: opts.agent
|
|
11714
12065
|
});
|
|
11715
|
-
console.log(
|
|
12066
|
+
console.log(chalk11.green(`\u2714 Side session '${pane.title}' spawned (${pane.surfaceId})`));
|
|
11716
12067
|
} catch (err) {
|
|
11717
|
-
console.error(
|
|
12068
|
+
console.error(chalk11.red(err.message));
|
|
11718
12069
|
process.exit(1);
|
|
11719
12070
|
}
|
|
11720
12071
|
}
|
|
@@ -11723,14 +12074,14 @@ sideCommand.command("list").description("List live side-sessions for a project")
|
|
|
11723
12074
|
try {
|
|
11724
12075
|
const sessions = await runSideList2(project);
|
|
11725
12076
|
if (sessions.length === 0) {
|
|
11726
|
-
console.log(
|
|
12077
|
+
console.log(chalk11.yellow(`No live side-sessions for ${project}.`));
|
|
11727
12078
|
return;
|
|
11728
12079
|
}
|
|
11729
12080
|
for (const s of sessions) {
|
|
11730
12081
|
console.log(` ${s.name} (${s.surfaceId})`);
|
|
11731
12082
|
}
|
|
11732
12083
|
} catch (err) {
|
|
11733
|
-
console.error(
|
|
12084
|
+
console.error(chalk11.red(err.message));
|
|
11734
12085
|
process.exit(1);
|
|
11735
12086
|
}
|
|
11736
12087
|
});
|
|
@@ -11743,9 +12094,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
11743
12094
|
label: "message"
|
|
11744
12095
|
});
|
|
11745
12096
|
await runSideSend2(project, name, resolvedMessage);
|
|
11746
|
-
console.log(
|
|
12097
|
+
console.log(chalk11.green(`\u2714 Sent to ${project}:${name}`));
|
|
11747
12098
|
} catch (err) {
|
|
11748
|
-
console.error(
|
|
12099
|
+
console.error(chalk11.red(err.message));
|
|
11749
12100
|
process.exit(1);
|
|
11750
12101
|
}
|
|
11751
12102
|
}
|
|
@@ -11753,9 +12104,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
11753
12104
|
sideCommand.command("close").description("Close a side-session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Session name").action(async (project, name) => {
|
|
11754
12105
|
try {
|
|
11755
12106
|
await runSideClose2(project, name);
|
|
11756
|
-
console.log(
|
|
12107
|
+
console.log(chalk11.green(`\u2714 Closed ${project}:${name}`));
|
|
11757
12108
|
} catch (err) {
|
|
11758
|
-
console.error(
|
|
12109
|
+
console.error(chalk11.red(err.message));
|
|
11759
12110
|
process.exit(1);
|
|
11760
12111
|
}
|
|
11761
12112
|
});
|
|
@@ -11763,15 +12114,15 @@ sideCommand.command("close").description("Close a side-session (closes its tab)"
|
|
|
11763
12114
|
// packages/cli/src/commands/dashboard.ts
|
|
11764
12115
|
init_dist();
|
|
11765
12116
|
init_dist3();
|
|
11766
|
-
import { Command as
|
|
12117
|
+
import { Command as Command12 } from "commander";
|
|
11767
12118
|
import { execSync as execSync10 } from "child_process";
|
|
11768
12119
|
import { homedir as homedir18 } from "os";
|
|
11769
12120
|
import { join as join23 } from "path";
|
|
11770
|
-
import
|
|
12121
|
+
import chalk13 from "chalk";
|
|
11771
12122
|
|
|
11772
12123
|
// packages/web/dist/read-status.js
|
|
11773
12124
|
function deriveState(tasks) {
|
|
11774
|
-
if (tasks.some((t) => t.state === "blocked" || t.state === "awaiting-input"))
|
|
12125
|
+
if (tasks.some((t) => t.state === "blocked" || t.state === "awaiting-input" || t.state === "review"))
|
|
11775
12126
|
return "blocked";
|
|
11776
12127
|
if (tasks.some((t) => t.state === "failed" || t.state === "stalled"))
|
|
11777
12128
|
return "errored";
|
|
@@ -11786,14 +12137,14 @@ function deriveRowState(tasks, captainState) {
|
|
|
11786
12137
|
}
|
|
11787
12138
|
function buildExcerpt(tasks) {
|
|
11788
12139
|
const working = tasks.filter((t) => t.state === "working").length;
|
|
11789
|
-
const blocked = tasks.filter((t) => t.state === "blocked" || t.state === "awaiting-input").length;
|
|
12140
|
+
const blocked = tasks.filter((t) => t.state === "blocked" || t.state === "awaiting-input" || t.state === "review").length;
|
|
11790
12141
|
const parts = [];
|
|
11791
12142
|
if (working > 0)
|
|
11792
12143
|
parts.push(`${working} working`);
|
|
11793
12144
|
if (blocked > 0)
|
|
11794
12145
|
parts.push(`${blocked} blocked`);
|
|
11795
12146
|
const summary = parts.length > 0 ? parts.join(", ") : "idle";
|
|
11796
|
-
const active = tasks.filter((t) => ["working", "blocked", "awaiting-input", "submitted"].includes(t.state));
|
|
12147
|
+
const active = tasks.filter((t) => ["working", "blocked", "awaiting-input", "review", "submitted"].includes(t.state));
|
|
11797
12148
|
const titles = active.slice(0, 3).map((t) => {
|
|
11798
12149
|
const firstLine2 = t.task ? t.task.split("\n")[0] : "";
|
|
11799
12150
|
return t.name ?? (firstLine2 || t.id.slice(0, 8));
|
|
@@ -11844,14 +12195,14 @@ async function readAllStatuses(deps) {
|
|
|
11844
12195
|
}
|
|
11845
12196
|
|
|
11846
12197
|
// packages/web/dist/render.js
|
|
11847
|
-
import
|
|
12198
|
+
import chalk12 from "chalk";
|
|
11848
12199
|
var ICON = {
|
|
11849
|
-
idle:
|
|
11850
|
-
busy:
|
|
11851
|
-
blocked:
|
|
11852
|
-
errored:
|
|
11853
|
-
offline:
|
|
11854
|
-
unknown:
|
|
12200
|
+
idle: chalk12.green,
|
|
12201
|
+
busy: chalk12.cyan,
|
|
12202
|
+
blocked: chalk12.yellow,
|
|
12203
|
+
errored: chalk12.red,
|
|
12204
|
+
offline: chalk12.dim,
|
|
12205
|
+
unknown: chalk12.gray
|
|
11855
12206
|
};
|
|
11856
12207
|
var ICON_CHAR = {
|
|
11857
12208
|
idle: "\u25CF",
|
|
@@ -11894,10 +12245,10 @@ function renderDashboard(rows, opts) {
|
|
|
11894
12245
|
const width = opts.width ?? 100;
|
|
11895
12246
|
const lines = [];
|
|
11896
12247
|
lines.push("");
|
|
11897
|
-
lines.push(" " +
|
|
12248
|
+
lines.push(" " + chalk12.bold("\u{1F4CA} Squadrant Dashboard") + " " + chalk12.dim(opts.now));
|
|
11898
12249
|
lines.push("");
|
|
11899
12250
|
if (rows.length === 0) {
|
|
11900
|
-
lines.push(" " +
|
|
12251
|
+
lines.push(" " + chalk12.yellow("No projects registered. Add one with: squadrant projects add <name> <path>"));
|
|
11901
12252
|
lines.push("");
|
|
11902
12253
|
return lines.join("\n");
|
|
11903
12254
|
}
|
|
@@ -11908,14 +12259,14 @@ function renderDashboard(rows, opts) {
|
|
|
11908
12259
|
const excerptW = Math.max(20, width - FIXED);
|
|
11909
12260
|
for (const r of rows) {
|
|
11910
12261
|
const icon = ICON[r.state](ICON_CHAR[r.state]);
|
|
11911
|
-
const name =
|
|
12262
|
+
const name = chalk12.cyan(pad(r.project, NAME_W));
|
|
11912
12263
|
const state = ICON[r.state](pad(r.state, STATE_W));
|
|
11913
12264
|
const age = pad(formatAge(r.lastChecked, opts.now), AGE_W);
|
|
11914
|
-
const excerpt =
|
|
12265
|
+
const excerpt = chalk12.dim(truncate(firstLine(r.excerpt), excerptW));
|
|
11915
12266
|
lines.push(` ${icon} ${name} ${state} ${age} \u2502 ${excerpt}`);
|
|
11916
12267
|
}
|
|
11917
12268
|
lines.push("");
|
|
11918
|
-
lines.push(
|
|
12269
|
+
lines.push(chalk12.dim(" Refreshes every 10s \xB7 Ctrl+C to exit"));
|
|
11919
12270
|
lines.push("");
|
|
11920
12271
|
return lines.join("\n");
|
|
11921
12272
|
}
|
|
@@ -11982,7 +12333,7 @@ init_dist();
|
|
|
11982
12333
|
import { join as join22 } from "path";
|
|
11983
12334
|
import { homedir as homedir17 } from "os";
|
|
11984
12335
|
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
|
|
11985
|
-
import { execFile as
|
|
12336
|
+
import { execFile as execFile4 } from "child_process";
|
|
11986
12337
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
11987
12338
|
var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
|
|
11988
12339
|
function withTimeout2(p, ms) {
|
|
@@ -12099,7 +12450,7 @@ function defaultProbeRunners() {
|
|
|
12099
12450
|
return {
|
|
12100
12451
|
probeCmuxBin: () => new Promise((resolve3) => {
|
|
12101
12452
|
try {
|
|
12102
|
-
|
|
12453
|
+
execFile4(resolveCmuxBin(), ["--version"], { timeout: 1500 }, (err) => resolve3(!err));
|
|
12103
12454
|
} catch {
|
|
12104
12455
|
resolve3(false);
|
|
12105
12456
|
}
|
|
@@ -13038,10 +13389,10 @@ async function runDashboardWeb(input) {
|
|
|
13038
13389
|
sockPath: SOCK3,
|
|
13039
13390
|
runners: defaultProbeRunners()
|
|
13040
13391
|
});
|
|
13041
|
-
console.log(
|
|
13042
|
-
console.log(
|
|
13392
|
+
console.log(chalk13.green(`\u2714 Squadrant system dashboard \u2192 http://127.0.0.1:${handle.port}`));
|
|
13393
|
+
console.log(chalk13.dim(` polling the daemon every ${input.interval}s \xB7 localhost only \xB7 read-only \xB7 Ctrl-C to stop`));
|
|
13043
13394
|
}
|
|
13044
|
-
var dashboardCommand = new
|
|
13395
|
+
var dashboardCommand = new Command12("dashboard").description("Live status grid of all projects (derived from daemon task state)").option("--once", "Print one snapshot and exit (used by --pane's refresh loop)").option("--pane", "Open a refreshing sidebar pane in the current cmux workspace").option("--web", "Serve the live system-health web dashboard on 127.0.0.1 (HTTP + SSE)").option("--port <port>", "Port for --web (default 7878)", (v) => parseInt(v, 10), 7878).option("--direction <dir>", "Pane split direction (right|left|up|down)", "right").option("--interval <seconds>", "Daemon poll interval for --web (default 5); refresh interval for --pane (default 10)", (v) => parseInt(v, 10)).action(async (opts) => {
|
|
13045
13396
|
try {
|
|
13046
13397
|
if (opts.web) {
|
|
13047
13398
|
await runDashboardWeb({ port: opts.port, interval: opts.interval ?? 5 });
|
|
@@ -13049,12 +13400,12 @@ var dashboardCommand = new Command11("dashboard").description("Live status grid
|
|
|
13049
13400
|
}
|
|
13050
13401
|
if (opts.pane) {
|
|
13051
13402
|
const pane = await runDashboardPane({ direction: opts.direction, interval: opts.interval ?? 10 });
|
|
13052
|
-
console.log(
|
|
13403
|
+
console.log(chalk13.green(`\u2714 Dashboard pane opened in ${pane.workspaceId} ${pane.surfaceId}`));
|
|
13053
13404
|
return;
|
|
13054
13405
|
}
|
|
13055
13406
|
await runDashboardOnce();
|
|
13056
13407
|
} catch (err) {
|
|
13057
|
-
console.error(
|
|
13408
|
+
console.error(chalk13.red(err.message));
|
|
13058
13409
|
process.exit(1);
|
|
13059
13410
|
}
|
|
13060
13411
|
});
|
|
@@ -13065,12 +13416,12 @@ dashboardCommand.command("sync-hub").description("Mirror each spoke status.md in
|
|
|
13065
13416
|
return;
|
|
13066
13417
|
}
|
|
13067
13418
|
if (results.length === 0) {
|
|
13068
|
-
console.log(
|
|
13419
|
+
console.log(chalk13.dim("\n No mirrors written (no projects with usable status.md, or hubVault unset).\n"));
|
|
13069
13420
|
return;
|
|
13070
13421
|
}
|
|
13071
|
-
console.log(
|
|
13422
|
+
console.log(chalk13.bold("\n \u{1F4CA} Hub mirror sync\n"));
|
|
13072
13423
|
for (const r of results) {
|
|
13073
|
-
console.log(` ${
|
|
13424
|
+
console.log(` ${chalk13.green("\u2714")} ${chalk13.cyan(r.project.padEnd(16))} \u2192 ${chalk13.dim(r.hubPath)}`);
|
|
13074
13425
|
}
|
|
13075
13426
|
console.log("");
|
|
13076
13427
|
});
|
|
@@ -13080,12 +13431,12 @@ init_dist();
|
|
|
13080
13431
|
init_dist4();
|
|
13081
13432
|
init_dist3();
|
|
13082
13433
|
init_dist2();
|
|
13083
|
-
import { Command as
|
|
13434
|
+
import { Command as Command13 } from "commander";
|
|
13084
13435
|
import { execSync as execSync11 } from "child_process";
|
|
13085
13436
|
import fs22 from "fs";
|
|
13086
13437
|
import path24 from "path";
|
|
13087
13438
|
import os13 from "os";
|
|
13088
|
-
import
|
|
13439
|
+
import chalk14 from "chalk";
|
|
13089
13440
|
|
|
13090
13441
|
// packages/cli/src/commands/launch-interactive.ts
|
|
13091
13442
|
import checkbox, { Separator } from "@inquirer/checkbox";
|
|
@@ -13166,16 +13517,16 @@ var TEMPLATES_DIR4 = path24.join(os13.homedir(), ".config", "squadrant", "templa
|
|
|
13166
13517
|
var SESSIONS_PATH2 = path24.join(os13.homedir(), ".config", "squadrant", "sessions.json");
|
|
13167
13518
|
function ensureCmuxReady(headless) {
|
|
13168
13519
|
if (headless || isInsideCmux()) return;
|
|
13169
|
-
console.log(
|
|
13520
|
+
console.log(chalk14.yellow("\n Not running inside cmux. Opening cmux app...\n"));
|
|
13170
13521
|
execSync11(`open "${CMUX_APP}"`, { stdio: "inherit" });
|
|
13171
|
-
console.log(
|
|
13522
|
+
console.log(chalk14.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
|
|
13172
13523
|
process.exit(0);
|
|
13173
13524
|
}
|
|
13174
|
-
var launchCommand = new
|
|
13525
|
+
var launchCommand = new Command13("launch").description(
|
|
13175
13526
|
"Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
|
|
13176
13527
|
).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").option("--keep", "Resume the latest session even on a new day / after a template change").option("--all", "Launch all captain workspaces").option("--headless", "Skip the interactive cmux-app requirement (used by the daemon to boot captains without a terminal)").action(async (project, opts) => {
|
|
13177
13528
|
if (opts.fresh && opts.keep) {
|
|
13178
|
-
console.error(
|
|
13529
|
+
console.error(chalk14.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
|
|
13179
13530
|
process.exit(1);
|
|
13180
13531
|
}
|
|
13181
13532
|
const config = loadConfig();
|
|
@@ -13223,29 +13574,29 @@ var launchCommand = new Command12("launch").description(
|
|
|
13223
13574
|
return null;
|
|
13224
13575
|
}
|
|
13225
13576
|
},
|
|
13226
|
-
onFreshReason: (reason) => console.log(
|
|
13227
|
-
onStoppingStale: (name) => console.log(
|
|
13228
|
-
onAlreadyExists: (name) => console.log(
|
|
13229
|
-
onCreated: (name) => console.log(
|
|
13577
|
+
onFreshReason: (reason) => console.log(chalk14.cyan(` \u21BB ${reason}`)),
|
|
13578
|
+
onStoppingStale: (name) => console.log(chalk14.yellow(` Closing stale workspace '${name}' for fresh start`)),
|
|
13579
|
+
onAlreadyExists: (name) => console.log(chalk14.yellow(` Workspace '${name}' already exists \u2014 switching to it`)),
|
|
13580
|
+
onCreated: (name) => console.log(chalk14.green(` \u2714 Workspace '${name}' created`))
|
|
13230
13581
|
});
|
|
13231
13582
|
} catch (err) {
|
|
13232
|
-
console.error(
|
|
13583
|
+
console.error(chalk14.red(` \u2718 Failed: ${err.message}`));
|
|
13233
13584
|
hadFailure = true;
|
|
13234
13585
|
}
|
|
13235
13586
|
}
|
|
13236
13587
|
if (opts.all) {
|
|
13237
13588
|
const hubPath = resolveHome(config.hubVault);
|
|
13238
13589
|
fs22.mkdirSync(hubPath, { recursive: true });
|
|
13239
|
-
console.log(
|
|
13590
|
+
console.log(chalk14.bold("\nLaunching all captain workspaces\n"));
|
|
13240
13591
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
13241
13592
|
const projPath = resolveHome(proj.path);
|
|
13242
13593
|
const spokePath = resolveHome(proj.spokeVault);
|
|
13243
13594
|
if (!fs22.existsSync(spokePath)) {
|
|
13244
13595
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
13245
13596
|
await ensureSpokeLayout(spokeDriver);
|
|
13246
|
-
console.log(
|
|
13597
|
+
console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13247
13598
|
}
|
|
13248
|
-
console.log(
|
|
13599
|
+
console.log(chalk14.bold(`
|
|
13249
13600
|
Captain: ${proj.captainName} (${name})`));
|
|
13250
13601
|
await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
|
|
13251
13602
|
}
|
|
@@ -13253,7 +13604,7 @@ var launchCommand = new Command12("launch").description(
|
|
|
13253
13604
|
} else if (!project) {
|
|
13254
13605
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
13255
13606
|
console.error(
|
|
13256
|
-
|
|
13607
|
+
chalk14.red(
|
|
13257
13608
|
"\n \u2718 Specify a project name, or pass --all to launch every captain.\n For one-shot Command tasks, use `squadrant command --task <briefing|learnings-review|wiki-aggregate>`.\n"
|
|
13258
13609
|
)
|
|
13259
13610
|
);
|
|
@@ -13268,10 +13619,10 @@ var launchCommand = new Command12("launch").description(
|
|
|
13268
13619
|
}));
|
|
13269
13620
|
const selected = await selectCaptainsInteractive(entries);
|
|
13270
13621
|
if (selected.length === 0) {
|
|
13271
|
-
console.log(
|
|
13622
|
+
console.log(chalk14.yellow("\n No captains selected.\n"));
|
|
13272
13623
|
return;
|
|
13273
13624
|
}
|
|
13274
|
-
console.log(
|
|
13625
|
+
console.log(chalk14.bold(`
|
|
13275
13626
|
Launching ${selected.length} captain workspace(s) in parallel
|
|
13276
13627
|
`));
|
|
13277
13628
|
await Promise.all(selected.map(async (name) => {
|
|
@@ -13281,9 +13632,9 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13281
13632
|
if (!fs22.existsSync(spokePath)) {
|
|
13282
13633
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
13283
13634
|
await ensureSpokeLayout(spokeDriver);
|
|
13284
|
-
console.log(
|
|
13635
|
+
console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13285
13636
|
}
|
|
13286
|
-
console.log(
|
|
13637
|
+
console.log(chalk14.bold(`
|
|
13287
13638
|
Captain: ${proj.captainName} (${name})`));
|
|
13288
13639
|
await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
|
|
13289
13640
|
}));
|
|
@@ -13291,7 +13642,7 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13291
13642
|
} else {
|
|
13292
13643
|
if (!config.projects[project]) {
|
|
13293
13644
|
console.error(
|
|
13294
|
-
|
|
13645
|
+
chalk14.red(
|
|
13295
13646
|
`
|
|
13296
13647
|
\u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
|
|
13297
13648
|
`
|
|
@@ -13305,10 +13656,10 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13305
13656
|
if (!fs22.existsSync(spokePath)) {
|
|
13306
13657
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
|
|
13307
13658
|
await ensureSpokeLayout(spokeDriver);
|
|
13308
|
-
console.log(
|
|
13659
|
+
console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13309
13660
|
}
|
|
13310
13661
|
console.log(
|
|
13311
|
-
|
|
13662
|
+
chalk14.bold(
|
|
13312
13663
|
`
|
|
13313
13664
|
Launching captain workspace for '${project}' (${proj.captainName})
|
|
13314
13665
|
`
|
|
@@ -13322,8 +13673,8 @@ Launching captain workspace for '${project}' (${proj.captainName})
|
|
|
13322
13673
|
// packages/cli/src/commands/shutdown.ts
|
|
13323
13674
|
init_dist();
|
|
13324
13675
|
init_dist3();
|
|
13325
|
-
import { Command as
|
|
13326
|
-
import
|
|
13676
|
+
import { Command as Command14 } from "commander";
|
|
13677
|
+
import chalk15 from "chalk";
|
|
13327
13678
|
init_dist();
|
|
13328
13679
|
function nameVariants(name) {
|
|
13329
13680
|
const stripped = name.replace(/^⚓\s+/, "").trim();
|
|
@@ -13336,23 +13687,23 @@ async function closeMatching(runtime, variants, label) {
|
|
|
13336
13687
|
const failed = [];
|
|
13337
13688
|
if (matches.length === 0) {
|
|
13338
13689
|
console.log(
|
|
13339
|
-
|
|
13690
|
+
chalk15.yellow(` \u26A0 Workspace '${label}' not found \u2014 already closed?`)
|
|
13340
13691
|
);
|
|
13341
13692
|
return { closed, failed };
|
|
13342
13693
|
}
|
|
13343
13694
|
for (const ws of matches) {
|
|
13344
13695
|
try {
|
|
13345
13696
|
await runtime.stop(ws.id);
|
|
13346
|
-
console.log(
|
|
13697
|
+
console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
|
|
13347
13698
|
closed.push(ws.name);
|
|
13348
13699
|
} catch {
|
|
13349
|
-
console.log(
|
|
13700
|
+
console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
|
|
13350
13701
|
failed.push(ws.name);
|
|
13351
13702
|
}
|
|
13352
13703
|
}
|
|
13353
13704
|
return { closed, failed };
|
|
13354
13705
|
}
|
|
13355
|
-
var shutdownCommand = new
|
|
13706
|
+
var shutdownCommand = new Command14("shutdown").description(
|
|
13356
13707
|
"Shutdown command + all captain workspaces (no args) or one captain workspace"
|
|
13357
13708
|
).argument("[project]", "Project name to shut down captain for").action(async (project) => {
|
|
13358
13709
|
const config = loadConfig();
|
|
@@ -13368,11 +13719,11 @@ var shutdownCommand = new Command13("shutdown").description(
|
|
|
13368
13719
|
const allVariants = /* @__PURE__ */ new Set([...captainVariants, ...commandVariants]);
|
|
13369
13720
|
const squadrantWorkspaces = workspaces.filter((w) => allVariants.has(w.name));
|
|
13370
13721
|
if (squadrantWorkspaces.length === 0) {
|
|
13371
|
-
console.log(
|
|
13722
|
+
console.log(chalk15.yellow("\nNo squadrant workspaces found to close.\n"));
|
|
13372
13723
|
return;
|
|
13373
13724
|
}
|
|
13374
13725
|
console.log(
|
|
13375
|
-
|
|
13726
|
+
chalk15.bold(
|
|
13376
13727
|
`
|
|
13377
13728
|
Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
13378
13729
|
`
|
|
@@ -13392,9 +13743,9 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
13392
13743
|
for (const ws of squadrantWorkspaces) {
|
|
13393
13744
|
try {
|
|
13394
13745
|
await globalRuntime.stop(ws.id);
|
|
13395
|
-
console.log(
|
|
13746
|
+
console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
|
|
13396
13747
|
} catch {
|
|
13397
|
-
console.log(
|
|
13748
|
+
console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
|
|
13398
13749
|
}
|
|
13399
13750
|
}
|
|
13400
13751
|
console.log("");
|
|
@@ -13402,7 +13753,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
13402
13753
|
}
|
|
13403
13754
|
if (!config.projects[project]) {
|
|
13404
13755
|
console.error(
|
|
13405
|
-
|
|
13756
|
+
chalk15.red(
|
|
13406
13757
|
`
|
|
13407
13758
|
\u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
|
|
13408
13759
|
`
|
|
@@ -13413,7 +13764,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
13413
13764
|
const captainName = config.projects[project].captainName;
|
|
13414
13765
|
const runtime = runtimes.forProject(project, config);
|
|
13415
13766
|
console.log(
|
|
13416
|
-
|
|
13767
|
+
chalk15.bold(`
|
|
13417
13768
|
Shutting down captain workspace for '${project}'...
|
|
13418
13769
|
`)
|
|
13419
13770
|
);
|
|
@@ -13437,13 +13788,13 @@ Shutting down captain workspace for '${project}'...
|
|
|
13437
13788
|
|
|
13438
13789
|
// packages/cli/src/commands/feedback.ts
|
|
13439
13790
|
init_dist();
|
|
13440
|
-
import { Command as
|
|
13791
|
+
import { Command as Command15 } from "commander";
|
|
13441
13792
|
import fs23 from "fs";
|
|
13442
13793
|
import os14 from "os";
|
|
13443
13794
|
import path25 from "path";
|
|
13444
13795
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13445
13796
|
import { execSync as execSync12 } from "child_process";
|
|
13446
|
-
import
|
|
13797
|
+
import chalk16 from "chalk";
|
|
13447
13798
|
var REPO_URL = "https://github.com/tu11aa/squadrant";
|
|
13448
13799
|
function readPkgVersion() {
|
|
13449
13800
|
try {
|
|
@@ -13491,21 +13842,21 @@ function buildIssueUrl(metrics, squadrantVersion) {
|
|
|
13491
13842
|
});
|
|
13492
13843
|
return `${REPO_URL}/issues/new?${params.toString()}`;
|
|
13493
13844
|
}
|
|
13494
|
-
var feedbackCommand = new
|
|
13845
|
+
var feedbackCommand = new Command15("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
|
|
13495
13846
|
const config = loadConfig();
|
|
13496
13847
|
const metricsPath = config.metrics?.path || path25.join(os14.homedir(), ".config", "squadrant", "metrics.json");
|
|
13497
13848
|
const metrics = readMetrics(metricsPath);
|
|
13498
13849
|
const version = readStamp(config) ?? readPkgVersion();
|
|
13499
13850
|
const issueUrl = buildIssueUrl(metrics, version);
|
|
13500
|
-
console.log(
|
|
13501
|
-
console.log(
|
|
13851
|
+
console.log(chalk16.bold("\nOpening feedback issue in browser...\n"));
|
|
13852
|
+
console.log(chalk16.dim(` URL: ${issueUrl.substring(0, 80)}...
|
|
13502
13853
|
`));
|
|
13503
13854
|
try {
|
|
13504
13855
|
execSync12(`open "${issueUrl}"`, { stdio: "ignore" });
|
|
13505
|
-
console.log(
|
|
13856
|
+
console.log(chalk16.green(" \u2714 Browser opened\n"));
|
|
13506
13857
|
} catch {
|
|
13507
|
-
console.log(
|
|
13508
|
-
console.log(` Open manually: ${
|
|
13858
|
+
console.log(chalk16.yellow(" \u26A0 Could not open browser automatically."));
|
|
13859
|
+
console.log(` Open manually: ${chalk16.cyan(issueUrl)}
|
|
13509
13860
|
`);
|
|
13510
13861
|
}
|
|
13511
13862
|
});
|
|
@@ -13514,10 +13865,10 @@ var feedbackCommand = new Command14("feedback").description("Open a pre-filled G
|
|
|
13514
13865
|
init_dist();
|
|
13515
13866
|
init_dist();
|
|
13516
13867
|
init_dist3();
|
|
13517
|
-
import { Command as
|
|
13868
|
+
import { Command as Command16 } from "commander";
|
|
13518
13869
|
import fs24 from "fs";
|
|
13519
13870
|
import path26 from "path";
|
|
13520
|
-
import
|
|
13871
|
+
import chalk17 from "chalk";
|
|
13521
13872
|
import matter3 from "gray-matter";
|
|
13522
13873
|
function getDateStr(yesterday) {
|
|
13523
13874
|
return iso(daysAgo(yesterday ? 1 : 0));
|
|
@@ -13547,7 +13898,7 @@ function formatStandup(standups, dateStr, raw) {
|
|
|
13547
13898
|
const lines = [];
|
|
13548
13899
|
const header = `Standup \u2014 ${dateStr}`;
|
|
13549
13900
|
if (!raw) {
|
|
13550
|
-
lines.push(
|
|
13901
|
+
lines.push(chalk17.bold(`
|
|
13551
13902
|
${header}
|
|
13552
13903
|
`));
|
|
13553
13904
|
} else {
|
|
@@ -13560,12 +13911,12 @@ ${header}
|
|
|
13560
13911
|
const tasksTotal = s.status.tasks_total ?? 0;
|
|
13561
13912
|
const tasksInProgress = s.status.tasks_in_progress ?? 0;
|
|
13562
13913
|
if (!raw) {
|
|
13563
|
-
lines.push(
|
|
13914
|
+
lines.push(chalk17.cyan.bold(`## ${s.name}`));
|
|
13564
13915
|
} else {
|
|
13565
13916
|
lines.push(`## ${s.name}`);
|
|
13566
13917
|
}
|
|
13567
13918
|
if (s.gitCommits.length > 0 || tasksDone > 0) {
|
|
13568
|
-
lines.push(!raw ?
|
|
13919
|
+
lines.push(!raw ? chalk17.green("Done:") : "**Done:**");
|
|
13569
13920
|
for (const commit of s.gitCommits) {
|
|
13570
13921
|
lines.push(` - ${commit}`);
|
|
13571
13922
|
}
|
|
@@ -13574,7 +13925,7 @@ ${header}
|
|
|
13574
13925
|
}
|
|
13575
13926
|
}
|
|
13576
13927
|
if (tasksInProgress > 0) {
|
|
13577
|
-
lines.push(!raw ?
|
|
13928
|
+
lines.push(!raw ? chalk17.yellow("In Progress:") : "**In Progress:**");
|
|
13578
13929
|
lines.push(` - ${tasksInProgress} task(s) active`);
|
|
13579
13930
|
}
|
|
13580
13931
|
if (s.dailyLog) {
|
|
@@ -13584,7 +13935,7 @@ ${header}
|
|
|
13584
13935
|
if (match) {
|
|
13585
13936
|
const items = match[1].trim().split("\n").filter((l) => l.trim().startsWith("-"));
|
|
13586
13937
|
if (items.length > 0 && section === "Tomorrow") {
|
|
13587
|
-
lines.push(!raw ?
|
|
13938
|
+
lines.push(!raw ? chalk17.blue("Next:") : "**Next:**");
|
|
13588
13939
|
for (const item of items) lines.push(` ${item.trim()}`);
|
|
13589
13940
|
}
|
|
13590
13941
|
}
|
|
@@ -13592,20 +13943,20 @@ ${header}
|
|
|
13592
13943
|
}
|
|
13593
13944
|
if (s.blockers.length > 0) {
|
|
13594
13945
|
hasBlockers = true;
|
|
13595
|
-
lines.push(!raw ?
|
|
13946
|
+
lines.push(!raw ? chalk17.red("Blocked:") : "**Blocked:**");
|
|
13596
13947
|
for (const b of s.blockers) {
|
|
13597
13948
|
lines.push(` - ${b}`);
|
|
13598
13949
|
}
|
|
13599
13950
|
}
|
|
13600
13951
|
if (s.gitCommits.length === 0 && tasksDone === 0 && !s.dailyLog) {
|
|
13601
|
-
lines.push(!raw ?
|
|
13952
|
+
lines.push(!raw ? chalk17.dim(" (no activity)") : " (no activity)");
|
|
13602
13953
|
}
|
|
13603
13954
|
lines.push("");
|
|
13604
13955
|
}
|
|
13605
13956
|
const totalCommits = standups.reduce((sum, s) => sum + s.gitCommits.length, 0);
|
|
13606
13957
|
const totalDone = standups.reduce((sum, s) => sum + (s.status.tasks_completed ?? 0), 0);
|
|
13607
13958
|
if (!raw) {
|
|
13608
|
-
lines.push(
|
|
13959
|
+
lines.push(chalk17.dim(`--- ${totalCommits} commits, ${totalDone} tasks done${hasBlockers ? ", HAS BLOCKERS" : ""} ---
|
|
13609
13960
|
`));
|
|
13610
13961
|
} else {
|
|
13611
13962
|
lines.push(`---
|
|
@@ -13614,12 +13965,12 @@ ${header}
|
|
|
13614
13965
|
}
|
|
13615
13966
|
return lines.join("\n");
|
|
13616
13967
|
}
|
|
13617
|
-
var standupCommand = new
|
|
13968
|
+
var standupCommand = new Command16("standup").description("Generate daily standup report from spoke vault data and git logs (zero tokens)").option("-p, --project <name>", "Show standup for a specific project only").option("-a, --all", "Show all projects (default)").option("-y, --yesterday", "Show yesterday's standup instead of today").option("-r, --raw", "Output raw markdown (for pasting into Slack/chat)").action(async (opts) => {
|
|
13618
13969
|
const config = loadConfig();
|
|
13619
13970
|
const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
13620
13971
|
const projects = Object.entries(config.projects);
|
|
13621
13972
|
if (projects.length === 0) {
|
|
13622
|
-
console.log(
|
|
13973
|
+
console.log(chalk17.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
|
|
13623
13974
|
return;
|
|
13624
13975
|
}
|
|
13625
13976
|
const dateStr = getDateStr(!!opts.yesterday);
|
|
@@ -13628,7 +13979,7 @@ var standupCommand = new Command15("standup").description("Generate daily standu
|
|
|
13628
13979
|
if (opts.project) {
|
|
13629
13980
|
const match = projects.find(([name]) => name === opts.project);
|
|
13630
13981
|
if (!match) {
|
|
13631
|
-
console.error(
|
|
13982
|
+
console.error(chalk17.red(`Project "${opts.project}" not found.`));
|
|
13632
13983
|
process.exit(1);
|
|
13633
13984
|
}
|
|
13634
13985
|
targets = [match];
|
|
@@ -13646,10 +13997,10 @@ var standupCommand = new Command15("standup").description("Generate daily standu
|
|
|
13646
13997
|
init_dist();
|
|
13647
13998
|
init_dist();
|
|
13648
13999
|
init_dist3();
|
|
13649
|
-
import { Command as
|
|
14000
|
+
import { Command as Command17 } from "commander";
|
|
13650
14001
|
import fs25 from "fs";
|
|
13651
14002
|
import path27 from "path";
|
|
13652
|
-
import
|
|
14003
|
+
import chalk18 from "chalk";
|
|
13653
14004
|
import matter4 from "gray-matter";
|
|
13654
14005
|
function readStatus(spokeVault) {
|
|
13655
14006
|
const statusFile = path27.join(spokeVault, "status.md");
|
|
@@ -13719,7 +14070,7 @@ function formatRetro(retros, fromStr, toStr, raw) {
|
|
|
13719
14070
|
const lines = [];
|
|
13720
14071
|
const header = `Retro \u2014 ${fromStr} \u2192 ${toStr}`;
|
|
13721
14072
|
lines.push(raw ? `# ${header}
|
|
13722
|
-
` :
|
|
14073
|
+
` : chalk18.bold(`
|
|
13723
14074
|
${header}
|
|
13724
14075
|
`));
|
|
13725
14076
|
let totalCommits = 0;
|
|
@@ -13729,39 +14080,39 @@ ${header}
|
|
|
13729
14080
|
totalCommits += r.commits.length;
|
|
13730
14081
|
totalPRs += r.mergedPRs.length;
|
|
13731
14082
|
totalShipped += r.shipped.length;
|
|
13732
|
-
lines.push(raw ? `## ${r.name}` :
|
|
13733
|
-
renderList(lines, r.shipped, raw, "Shipped",
|
|
14083
|
+
lines.push(raw ? `## ${r.name}` : chalk18.cyan.bold(`## ${r.name}`));
|
|
14084
|
+
renderList(lines, r.shipped, raw, "Shipped", chalk18.green);
|
|
13734
14085
|
if (r.mergedPRs.length > 0) {
|
|
13735
|
-
lines.push(raw ? `**PRs merged:**` :
|
|
14086
|
+
lines.push(raw ? `**PRs merged:**` : chalk18.green("PRs merged:"));
|
|
13736
14087
|
for (const pr of r.mergedPRs) lines.push(` - ${pr}`);
|
|
13737
14088
|
}
|
|
13738
|
-
renderList(lines, r.inProgress, raw, "In Progress",
|
|
13739
|
-
renderList(lines, r.blocked, raw, "Blocked",
|
|
13740
|
-
renderList(lines, r.decisions, raw, "Key Decisions",
|
|
14089
|
+
renderList(lines, r.inProgress, raw, "In Progress", chalk18.yellow);
|
|
14090
|
+
renderList(lines, r.blocked, raw, "Blocked", chalk18.red);
|
|
14091
|
+
renderList(lines, r.decisions, raw, "Key Decisions", chalk18.magenta);
|
|
13741
14092
|
const metricBits = [
|
|
13742
14093
|
`${r.commits.length} commits`,
|
|
13743
14094
|
`${r.mergedPRs.length} PRs merged`,
|
|
13744
14095
|
`${r.shipped.length} shipped`
|
|
13745
14096
|
];
|
|
13746
|
-
lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` :
|
|
14097
|
+
lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` : chalk18.dim(` ${metricBits.join(" \xB7 ")}`));
|
|
13747
14098
|
if (r.shipped.length === 0 && r.commits.length === 0 && r.mergedPRs.length === 0 && r.inProgress.length === 0 && r.blocked.length === 0) {
|
|
13748
|
-
lines.push(raw ? "_(no activity in this window)_" :
|
|
14099
|
+
lines.push(raw ? "_(no activity in this window)_" : chalk18.dim(" (no activity in this window)"));
|
|
13749
14100
|
}
|
|
13750
14101
|
lines.push("");
|
|
13751
14102
|
}
|
|
13752
14103
|
const summary = `${totalShipped} items shipped \xB7 ${totalCommits} commits \xB7 ${totalPRs} PRs merged`;
|
|
13753
14104
|
lines.push(raw ? `---
|
|
13754
14105
|
*${summary}*
|
|
13755
|
-
` :
|
|
14106
|
+
` : chalk18.dim(`--- ${summary} ---
|
|
13756
14107
|
`));
|
|
13757
14108
|
return lines.join("\n");
|
|
13758
14109
|
}
|
|
13759
|
-
var retroCommand = new
|
|
14110
|
+
var retroCommand = new Command17("retro").description("Generate a retro (weekly/sprint summary) from daily logs and git (zero tokens)").option("-w, --week", "Trailing 7 days (default)").option("-s, --sprint [days]", "Custom window of N days (default 14 if N omitted)").option("-p, --project <name>", "Retro for a single project").option("-a, --all", "All projects (default)").option("-r, --raw", "Raw markdown output (for pasting into Slack/Obsidian)").action(async (opts) => {
|
|
13760
14111
|
const config = loadConfig();
|
|
13761
14112
|
const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
13762
14113
|
const projects = Object.entries(config.projects);
|
|
13763
14114
|
if (projects.length === 0) {
|
|
13764
|
-
console.log(
|
|
14115
|
+
console.log(chalk18.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
|
|
13765
14116
|
return;
|
|
13766
14117
|
}
|
|
13767
14118
|
let windowDays = 7;
|
|
@@ -13778,7 +14129,7 @@ var retroCommand = new Command16("retro").description("Generate a retro (weekly/
|
|
|
13778
14129
|
if (opts.project) {
|
|
13779
14130
|
const match = projects.find(([name]) => name === opts.project);
|
|
13780
14131
|
if (!match) {
|
|
13781
|
-
console.error(
|
|
14132
|
+
console.error(chalk18.red(`Project "${opts.project}" not found.`));
|
|
13782
14133
|
process.exit(1);
|
|
13783
14134
|
}
|
|
13784
14135
|
targets = [match];
|
|
@@ -13794,8 +14145,8 @@ var retroCommand = new Command16("retro").description("Generate a retro (weekly/
|
|
|
13794
14145
|
// packages/cli/src/commands/runtime.ts
|
|
13795
14146
|
init_dist();
|
|
13796
14147
|
init_dist3();
|
|
13797
|
-
import { Command as
|
|
13798
|
-
import
|
|
14148
|
+
import { Command as Command18 } from "commander";
|
|
14149
|
+
import chalk19 from "chalk";
|
|
13799
14150
|
function buildRegistry() {
|
|
13800
14151
|
return new RuntimeRegistry({
|
|
13801
14152
|
cmux: createCmuxDriver()
|
|
@@ -13827,7 +14178,7 @@ async function needRef(resolved) {
|
|
|
13827
14178
|
}
|
|
13828
14179
|
return ref.id;
|
|
13829
14180
|
}
|
|
13830
|
-
var runtimeCommand = new
|
|
14181
|
+
var runtimeCommand = new Command18("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
|
|
13831
14182
|
runtimeCommand.command("status").description("Print 'running' or 'stopped' for a target; exit 0 if running, 1 if not").argument("[target]", "Project name").option("--command", "Target the command workspace instead of a project captain").action(async (target, opts) => {
|
|
13832
14183
|
const config = loadConfig();
|
|
13833
14184
|
const registry = buildRegistry();
|
|
@@ -13842,7 +14193,7 @@ runtimeCommand.command("status").description("Print 'running' or 'stopped' for a
|
|
|
13842
14193
|
process.exit(1);
|
|
13843
14194
|
}
|
|
13844
14195
|
} catch (err) {
|
|
13845
|
-
console.error(
|
|
14196
|
+
console.error(chalk19.red(err.message));
|
|
13846
14197
|
process.exit(2);
|
|
13847
14198
|
}
|
|
13848
14199
|
});
|
|
@@ -13889,9 +14240,9 @@ async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
|
|
|
13889
14240
|
runtimeCommand.command("send").description("Send a message to a target workspace AND commit with Enter. With --command, the first positional is the message.").argument("<arg1>", "Project name, or the message when --command is used").argument("[arg2]", "Message (when target is a project). Omit when using --command.").option("--command", "Target the command workspace").action(async (arg1, arg2, opts) => {
|
|
13890
14241
|
try {
|
|
13891
14242
|
await runRuntimeSend(arg1, arg2, opts);
|
|
13892
|
-
console.log(
|
|
14243
|
+
console.log(chalk19.green("\u2714 Delivered (confirmed)"));
|
|
13893
14244
|
} catch (err) {
|
|
13894
|
-
console.error(
|
|
14245
|
+
console.error(chalk19.red(err.message));
|
|
13895
14246
|
process.exit(1);
|
|
13896
14247
|
}
|
|
13897
14248
|
});
|
|
@@ -13917,7 +14268,7 @@ runtimeCommand.command("read-screen").description("Print a terminal snapshot of
|
|
|
13917
14268
|
const screen = await resolved.driver.readScreen(ref);
|
|
13918
14269
|
process.stdout.write(screen);
|
|
13919
14270
|
} catch (err) {
|
|
13920
|
-
console.error(
|
|
14271
|
+
console.error(chalk19.red(err.message));
|
|
13921
14272
|
process.exit(1);
|
|
13922
14273
|
}
|
|
13923
14274
|
});
|
|
@@ -13928,13 +14279,13 @@ runtimeCommand.command("stop").description("Stop a target workspace").argument("
|
|
|
13928
14279
|
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
13929
14280
|
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
13930
14281
|
if (!ref) {
|
|
13931
|
-
console.log(
|
|
14282
|
+
console.log(chalk19.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
|
|
13932
14283
|
return;
|
|
13933
14284
|
}
|
|
13934
14285
|
await resolved.driver.stop(ref.id);
|
|
13935
|
-
console.log(
|
|
14286
|
+
console.log(chalk19.green(`\u2714 Stopped ${resolved.workspaceName}`));
|
|
13936
14287
|
} catch (err) {
|
|
13937
|
-
console.error(
|
|
14288
|
+
console.error(chalk19.red(err.message));
|
|
13938
14289
|
process.exit(1);
|
|
13939
14290
|
}
|
|
13940
14291
|
});
|
|
@@ -13942,8 +14293,8 @@ runtimeCommand.command("stop").description("Stop a target workspace").argument("
|
|
|
13942
14293
|
// packages/cli/src/commands/workspace.ts
|
|
13943
14294
|
init_dist();
|
|
13944
14295
|
init_dist3();
|
|
13945
|
-
import { Command as
|
|
13946
|
-
import
|
|
14296
|
+
import { Command as Command19 } from "commander";
|
|
14297
|
+
import chalk20 from "chalk";
|
|
13947
14298
|
function buildRegistry2() {
|
|
13948
14299
|
return new WorkspaceRegistry({
|
|
13949
14300
|
obsidian: createObsidianDriver
|
|
@@ -13961,7 +14312,7 @@ async function readStdin() {
|
|
|
13961
14312
|
}
|
|
13962
14313
|
return Buffer.concat(chunks).toString("utf-8");
|
|
13963
14314
|
}
|
|
13964
|
-
var workspaceCommand = new
|
|
14315
|
+
var workspaceCommand = new Command19("workspace").description("Interact with the workspace layer (vault storage). Bridges bash scripts to the WorkspaceDriver.");
|
|
13965
14316
|
function resolveTargetAndPath(arg1, arg2, useHub) {
|
|
13966
14317
|
if (useHub) {
|
|
13967
14318
|
if (arg2 !== void 0) {
|
|
@@ -13983,7 +14334,7 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
|
|
|
13983
14334
|
const content = await driver.read(path30);
|
|
13984
14335
|
process.stdout.write(content);
|
|
13985
14336
|
} catch (err) {
|
|
13986
|
-
console.error(
|
|
14337
|
+
console.error(chalk20.red(err.message));
|
|
13987
14338
|
process.exit(1);
|
|
13988
14339
|
}
|
|
13989
14340
|
});
|
|
@@ -14013,7 +14364,7 @@ workspaceCommand.command("write").description("Write content to a scope-relative
|
|
|
14013
14364
|
const payload = rawContent === "-" ? await readStdin() : rawContent;
|
|
14014
14365
|
await driver.write(path30, payload);
|
|
14015
14366
|
} catch (err) {
|
|
14016
|
-
console.error(
|
|
14367
|
+
console.error(chalk20.red(err.message));
|
|
14017
14368
|
process.exit(1);
|
|
14018
14369
|
}
|
|
14019
14370
|
});
|
|
@@ -14026,7 +14377,7 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
|
|
|
14026
14377
|
const entries = await driver.list(path30);
|
|
14027
14378
|
for (const entry of entries) console.log(entry);
|
|
14028
14379
|
} catch (err) {
|
|
14029
|
-
console.error(
|
|
14380
|
+
console.error(chalk20.red(err.message));
|
|
14030
14381
|
process.exit(1);
|
|
14031
14382
|
}
|
|
14032
14383
|
});
|
|
@@ -14039,7 +14390,7 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
|
|
|
14039
14390
|
const ok2 = await driver.exists(path30);
|
|
14040
14391
|
process.exit(ok2 ? 0 : 1);
|
|
14041
14392
|
} catch (err) {
|
|
14042
|
-
console.error(
|
|
14393
|
+
console.error(chalk20.red(err.message));
|
|
14043
14394
|
process.exit(2);
|
|
14044
14395
|
}
|
|
14045
14396
|
});
|
|
@@ -14051,7 +14402,7 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
|
|
|
14051
14402
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
14052
14403
|
await driver.mkdir(path30);
|
|
14053
14404
|
} catch (err) {
|
|
14054
|
-
console.error(
|
|
14405
|
+
console.error(chalk20.red(err.message));
|
|
14055
14406
|
process.exit(1);
|
|
14056
14407
|
}
|
|
14057
14408
|
});
|
|
@@ -14059,8 +14410,8 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
|
|
|
14059
14410
|
// packages/cli/src/commands/notify.ts
|
|
14060
14411
|
init_dist();
|
|
14061
14412
|
init_dist3();
|
|
14062
|
-
import { Command as
|
|
14063
|
-
import
|
|
14413
|
+
import { Command as Command20 } from "commander";
|
|
14414
|
+
import chalk21 from "chalk";
|
|
14064
14415
|
async function readStdin2() {
|
|
14065
14416
|
const chunks = [];
|
|
14066
14417
|
for await (const chunk of process.stdin) {
|
|
@@ -14068,7 +14419,7 @@ async function readStdin2() {
|
|
|
14068
14419
|
}
|
|
14069
14420
|
return Buffer.concat(chunks).toString("utf-8");
|
|
14070
14421
|
}
|
|
14071
|
-
var notifyCommand = new
|
|
14422
|
+
var notifyCommand = new Command20("notify").description("Send a message to the user via the configured notifier").argument("<message>", "Message to send (use '-' to read from stdin)").action(async (message) => {
|
|
14072
14423
|
const config = loadConfig();
|
|
14073
14424
|
const registry = new NotifierRegistry({ cmux: createCmuxNotifier });
|
|
14074
14425
|
try {
|
|
@@ -14076,7 +14427,7 @@ var notifyCommand = new Command19("notify").description("Send a message to the u
|
|
|
14076
14427
|
if (!payload) throw new Error("Empty message");
|
|
14077
14428
|
await registry.get(config).notify(payload);
|
|
14078
14429
|
} catch (err) {
|
|
14079
|
-
console.error(
|
|
14430
|
+
console.error(chalk21.red(err.message));
|
|
14080
14431
|
process.exit(1);
|
|
14081
14432
|
}
|
|
14082
14433
|
});
|
|
@@ -14086,8 +14437,8 @@ init_dist();
|
|
|
14086
14437
|
init_dist4();
|
|
14087
14438
|
init_dist3();
|
|
14088
14439
|
init_dist();
|
|
14089
|
-
import { Command as
|
|
14090
|
-
import
|
|
14440
|
+
import { Command as Command21 } from "commander";
|
|
14441
|
+
import chalk22 from "chalk";
|
|
14091
14442
|
import fs26 from "fs";
|
|
14092
14443
|
import path28 from "path";
|
|
14093
14444
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -14134,17 +14485,17 @@ async function runEmit(opts) {
|
|
|
14134
14485
|
for (const dest of emitter.destinations(scope, projectRoot)) {
|
|
14135
14486
|
const result = await emitter.emit(source, dest, { dryRun: opts.dryRun });
|
|
14136
14487
|
if (opts.dryRun) {
|
|
14137
|
-
console.log(
|
|
14488
|
+
console.log(chalk22.cyan(`[${emitter.name}] ${dest.path}`));
|
|
14138
14489
|
console.log(result.diff ?? "(no diff)");
|
|
14139
14490
|
} else if (result.written) {
|
|
14140
14491
|
console.log(
|
|
14141
|
-
|
|
14492
|
+
chalk22.green(
|
|
14142
14493
|
`\u2714 ${emitter.name} \u2192 ${dest.path} (${result.bytesWritten} bytes)`
|
|
14143
14494
|
)
|
|
14144
14495
|
);
|
|
14145
14496
|
emittedCount.written++;
|
|
14146
14497
|
} else {
|
|
14147
|
-
console.log(
|
|
14498
|
+
console.log(chalk22.gray(`- ${emitter.name} \u2192 ${dest.path} (skipped)`));
|
|
14148
14499
|
emittedCount.skipped++;
|
|
14149
14500
|
}
|
|
14150
14501
|
}
|
|
@@ -14167,14 +14518,14 @@ async function runEmit(opts) {
|
|
|
14167
14518
|
`Unknown project '${projectName}'. Available: ${Object.keys(cfg.projects).join(", ") || "(none)"}`
|
|
14168
14519
|
);
|
|
14169
14520
|
}
|
|
14170
|
-
console.error(
|
|
14521
|
+
console.error(chalk22.yellow(`\u26A0 unknown project: ${projectName}`));
|
|
14171
14522
|
continue;
|
|
14172
14523
|
}
|
|
14173
14524
|
const source = await readProjectLevelSource(
|
|
14174
14525
|
createObsidianDriver({ root: proj.path })
|
|
14175
14526
|
);
|
|
14176
14527
|
if (!source) {
|
|
14177
|
-
console.log(
|
|
14528
|
+
console.log(chalk22.gray(`- ${projectName}: no AGENTS.md, skipping`));
|
|
14178
14529
|
continue;
|
|
14179
14530
|
}
|
|
14180
14531
|
for (const name of targets) {
|
|
@@ -14184,21 +14535,21 @@ async function runEmit(opts) {
|
|
|
14184
14535
|
}
|
|
14185
14536
|
if (!opts.dryRun) {
|
|
14186
14537
|
console.log(
|
|
14187
|
-
|
|
14538
|
+
chalk22.bold(
|
|
14188
14539
|
`
|
|
14189
14540
|
Projection complete \u2014 ${emittedCount.written} written, ${emittedCount.skipped} skipped.`
|
|
14190
14541
|
)
|
|
14191
14542
|
);
|
|
14192
14543
|
}
|
|
14193
14544
|
}
|
|
14194
|
-
var projectionCommand = new
|
|
14545
|
+
var projectionCommand = new Command21("projection").description(
|
|
14195
14546
|
"Project squadrant instructions and skills to supported agent formats"
|
|
14196
14547
|
);
|
|
14197
14548
|
projectionCommand.command("emit").description("Emit projections to disk").option("--scope <scope>", "user or project", parseScope).option("--project <name>", "managed project name").option("--target <name>", "single target (cursor, codex, gemini, opencode)").option("--all", "emit user-level + every managed project").action(async (opts) => {
|
|
14198
14549
|
try {
|
|
14199
14550
|
await runEmit({ ...opts, dryRun: false });
|
|
14200
14551
|
} catch (err) {
|
|
14201
|
-
console.error(
|
|
14552
|
+
console.error(chalk22.red(err.message));
|
|
14202
14553
|
process.exit(1);
|
|
14203
14554
|
}
|
|
14204
14555
|
});
|
|
@@ -14206,7 +14557,7 @@ projectionCommand.command("diff").description("Preview changes without writing")
|
|
|
14206
14557
|
try {
|
|
14207
14558
|
await runEmit({ ...opts, dryRun: true });
|
|
14208
14559
|
} catch (err) {
|
|
14209
|
-
console.error(
|
|
14560
|
+
console.error(chalk22.red(err.message));
|
|
14210
14561
|
process.exit(1);
|
|
14211
14562
|
}
|
|
14212
14563
|
});
|
|
@@ -14216,7 +14567,7 @@ projectionCommand.command("list").description("List registered projection target
|
|
|
14216
14567
|
const emitter = registry.get(name);
|
|
14217
14568
|
const userDests = emitter.destinations("user").map((d) => d.path);
|
|
14218
14569
|
const projectDests = emitter.destinations("project", "<project>").map((d) => d.path);
|
|
14219
|
-
console.log(
|
|
14570
|
+
console.log(chalk22.bold(name));
|
|
14220
14571
|
console.log(` user: ${userDests.join(", ") || "(none)"}`);
|
|
14221
14572
|
console.log(` project: ${projectDests.join(", ") || "(none)"}`);
|
|
14222
14573
|
}
|
|
@@ -14224,9 +14575,9 @@ projectionCommand.command("list").description("List registered projection target
|
|
|
14224
14575
|
|
|
14225
14576
|
// packages/cli/src/commands/codex-chat-smoke.ts
|
|
14226
14577
|
init_dist4();
|
|
14227
|
-
import { Command as
|
|
14578
|
+
import { Command as Command22 } from "commander";
|
|
14228
14579
|
import { resolve as resolve2 } from "path";
|
|
14229
|
-
var codexChatSmokeCommand = new
|
|
14580
|
+
var codexChatSmokeCommand = new Command22("codex-chat-smoke").description("Phase 1 gate: prove the codex app-server JSON-RPC path works end-to-end.").option("--cwd <dir>", "working dir for the codex thread", process.cwd()).option("--model <m>", "model id (optional)").option(
|
|
14230
14581
|
"--approval",
|
|
14231
14582
|
"include the approval round-trip (Phase 1 PASS requires this)",
|
|
14232
14583
|
false
|
|
@@ -14295,11 +14646,11 @@ init_dist();
|
|
|
14295
14646
|
init_dist();
|
|
14296
14647
|
init_dist();
|
|
14297
14648
|
init_dist2();
|
|
14298
|
-
import { Command as
|
|
14649
|
+
import { Command as Command23 } from "commander";
|
|
14299
14650
|
import fs27 from "fs";
|
|
14300
14651
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
14301
14652
|
import { dirname as dirname6, join as join25 } from "path";
|
|
14302
|
-
import
|
|
14653
|
+
import chalk23 from "chalk";
|
|
14303
14654
|
function runConfigCheck(opts) {
|
|
14304
14655
|
const raw = JSON.parse(fs27.readFileSync(opts.configPath, "utf-8"));
|
|
14305
14656
|
const def = getDefaultConfig();
|
|
@@ -14354,14 +14705,14 @@ function runConfigSet(key, value, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
14354
14705
|
}
|
|
14355
14706
|
function printRestartOutcome(outcome) {
|
|
14356
14707
|
if (outcome === "skipped-not-running") {
|
|
14357
|
-
console.log(
|
|
14708
|
+
console.log(chalk23.dim("(daemon not running \u2014 change applies on next start)"));
|
|
14358
14709
|
} else if (outcome === "skipped-opt-out") {
|
|
14359
|
-
console.log(
|
|
14710
|
+
console.log(chalk23.dim("(run 'squadrant heal daemon' to apply)"));
|
|
14360
14711
|
}
|
|
14361
14712
|
}
|
|
14362
14713
|
function runConfigSetAction(opts) {
|
|
14363
14714
|
runConfigSet(opts.key, opts.value, opts.configPath);
|
|
14364
|
-
console.log(
|
|
14715
|
+
console.log(chalk23.green(`\u2714 set ${opts.key} = ${opts.value}`));
|
|
14365
14716
|
if (isDaemonCachedKey(opts.key)) {
|
|
14366
14717
|
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
14367
14718
|
const outcome = doRestart({ reason: `config ${opts.key}`, noRestart: opts.noRestart });
|
|
@@ -14369,9 +14720,9 @@ function runConfigSetAction(opts) {
|
|
|
14369
14720
|
}
|
|
14370
14721
|
}
|
|
14371
14722
|
var SEV_COLOR = {
|
|
14372
|
-
info:
|
|
14373
|
-
advisory:
|
|
14374
|
-
warn:
|
|
14723
|
+
info: chalk23.green,
|
|
14724
|
+
advisory: chalk23.yellow,
|
|
14725
|
+
warn: chalk23.red
|
|
14375
14726
|
};
|
|
14376
14727
|
var KIND_GLYPH = {
|
|
14377
14728
|
missing: "+",
|
|
@@ -14383,14 +14734,14 @@ function printItems(items) {
|
|
|
14383
14734
|
for (const i of items) {
|
|
14384
14735
|
const color = SEV_COLOR[i.severity] ?? ((s) => s);
|
|
14385
14736
|
const detail = i.note ? ` (${i.note})` : i.suggested !== void 0 ? ` \u2192 ${JSON.stringify(i.suggested)}` : "";
|
|
14386
|
-
console.log(" " + color(`${KIND_GLYPH[i.kind]} ${i.kind}: ${i.path}`) +
|
|
14737
|
+
console.log(" " + color(`${KIND_GLYPH[i.kind]} ${i.kind}: ${i.path}`) + chalk23.dim(detail));
|
|
14387
14738
|
}
|
|
14388
14739
|
}
|
|
14389
|
-
var configCommand = new
|
|
14740
|
+
var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
|
|
14390
14741
|
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) => {
|
|
14391
14742
|
const pkgVersion = readPkgVersion2();
|
|
14392
14743
|
if (!fs27.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
14393
|
-
console.log(
|
|
14744
|
+
console.log(chalk23.yellow("No config found \u2014 run `squadrant init` first."));
|
|
14394
14745
|
return;
|
|
14395
14746
|
}
|
|
14396
14747
|
const res = runConfigCheck({ configPath: DEFAULT_CONFIG_PATH, pkgVersion, fix: opts.fix, accept: opts.accept });
|
|
@@ -14399,21 +14750,21 @@ configCommand.command("check").description("Detect config drift vs the current d
|
|
|
14399
14750
|
return;
|
|
14400
14751
|
}
|
|
14401
14752
|
if (res.items.length === 0) {
|
|
14402
|
-
console.log(
|
|
14753
|
+
console.log(chalk23.green("\u2714 Config is in sync with the current schema."));
|
|
14403
14754
|
return;
|
|
14404
14755
|
}
|
|
14405
|
-
console.log(
|
|
14756
|
+
console.log(chalk23.bold("\nConfig drift:\n"));
|
|
14406
14757
|
printItems(res.items);
|
|
14407
14758
|
if (opts.fix && res.applied.length) {
|
|
14408
|
-
console.log(
|
|
14759
|
+
console.log(chalk23.green(`
|
|
14409
14760
|
\u2714 Applied ${res.applied.length} safe item(s): ${res.applied.join(", ")}`));
|
|
14410
14761
|
}
|
|
14411
14762
|
const judgment = res.remaining.filter((i) => i.kind === "changed-default" || i.kind === "invalid");
|
|
14412
14763
|
if (judgment.length) {
|
|
14413
|
-
console.log(
|
|
14764
|
+
console.log(chalk23.yellow(`
|
|
14414
14765
|
${judgment.length} item(s) need review \u2014 run the config-doctor skill, or \`squadrant config check --accept\` to keep your values.`));
|
|
14415
14766
|
} else if (res.stamped) {
|
|
14416
|
-
console.log(
|
|
14767
|
+
console.log(chalk23.green("\n\u2714 Config reconciled and stamped."));
|
|
14417
14768
|
}
|
|
14418
14769
|
});
|
|
14419
14770
|
configCommand.command("get").description("Read a config value by dotted key (e.g. defaults.effort)").argument("<key>", "dotted config key").action((key) => {
|
|
@@ -14421,7 +14772,7 @@ configCommand.command("get").description("Read a config value by dotted key (e.g
|
|
|
14421
14772
|
const value = runConfigGet(key);
|
|
14422
14773
|
console.log(typeof value === "string" ? value : JSON.stringify(value));
|
|
14423
14774
|
} catch (e) {
|
|
14424
|
-
console.error(
|
|
14775
|
+
console.error(chalk23.red(e.message));
|
|
14425
14776
|
process.exit(1);
|
|
14426
14777
|
}
|
|
14427
14778
|
});
|
|
@@ -14429,7 +14780,7 @@ configCommand.command("set").description("Write a config value by dotted key (e.
|
|
|
14429
14780
|
try {
|
|
14430
14781
|
runConfigSetAction({ key, value, noRestart: opts.restart === false });
|
|
14431
14782
|
} catch (e) {
|
|
14432
|
-
console.error(
|
|
14783
|
+
console.error(chalk23.red(e.message));
|
|
14433
14784
|
process.exit(1);
|
|
14434
14785
|
}
|
|
14435
14786
|
});
|
|
@@ -14439,8 +14790,8 @@ function readPkgVersion2() {
|
|
|
14439
14790
|
}
|
|
14440
14791
|
|
|
14441
14792
|
// packages/cli/src/commands/heal.ts
|
|
14442
|
-
import { Command as
|
|
14443
|
-
import
|
|
14793
|
+
import { Command as Command24 } from "commander";
|
|
14794
|
+
import chalk24 from "chalk";
|
|
14444
14795
|
init_dist2();
|
|
14445
14796
|
init_dist2();
|
|
14446
14797
|
function buildHealStatus(components) {
|
|
@@ -14481,15 +14832,15 @@ async function runHealStatus(opts) {
|
|
|
14481
14832
|
return result.healthy ? 0 : 2;
|
|
14482
14833
|
}
|
|
14483
14834
|
if (result.healthy) {
|
|
14484
|
-
stdout.write(
|
|
14835
|
+
stdout.write(chalk24.green("\u2714 all components healthy\n"));
|
|
14485
14836
|
return 0;
|
|
14486
14837
|
}
|
|
14487
|
-
stdout.write(
|
|
14838
|
+
stdout.write(chalk24.bold("Unhealthy components:\n\n"));
|
|
14488
14839
|
for (const c of result.components) {
|
|
14489
14840
|
if (c.healCmd) {
|
|
14490
|
-
stdout.write(` ${
|
|
14841
|
+
stdout.write(` ${chalk24.red("\u2718")} ${c.kind.padEnd(8)} ${c.ref.padEnd(16)} ${chalk24.red(c.state.padEnd(8))} ${c.project}
|
|
14491
14842
|
`);
|
|
14492
|
-
stdout.write(` heal: ${
|
|
14843
|
+
stdout.write(` heal: ${chalk24.cyan(c.healCmd)}
|
|
14493
14844
|
`);
|
|
14494
14845
|
}
|
|
14495
14846
|
}
|
|
@@ -14500,7 +14851,7 @@ async function runHealDaemon(opts) {
|
|
|
14500
14851
|
stdout.write("restarting squadrantd via launchd kickstart...\n");
|
|
14501
14852
|
try {
|
|
14502
14853
|
opts.ensureDaemon();
|
|
14503
|
-
stdout.write(
|
|
14854
|
+
stdout.write(chalk24.green("\u2714 daemon kickstart complete\n"));
|
|
14504
14855
|
return 0;
|
|
14505
14856
|
} catch (e) {
|
|
14506
14857
|
stderr.write(`heal daemon failed: ${e.message}
|
|
@@ -14508,8 +14859,8 @@ async function runHealDaemon(opts) {
|
|
|
14508
14859
|
return 1;
|
|
14509
14860
|
}
|
|
14510
14861
|
}
|
|
14511
|
-
var healCommand = new
|
|
14512
|
-
new
|
|
14862
|
+
var healCommand = new Command24("heal").description("Targeted, idempotent remediation for squadrant components (daemon, health)").addHelpText("after", "\nDeferred: 'squadrant heal crew <id>' (re-attach) \u2014 see issue #100.").addCommand(
|
|
14863
|
+
new Command24("status").description("Dry-run: print unhealthy components and the exact heal command for each").option("-p, --project <project>", "scope to one project").option("--json", "output machine-readable JSON (exit 0=healthy, 1=error, 2=unhealthy)").action(async (opts) => {
|
|
14513
14864
|
const code = await runHealStatus({
|
|
14514
14865
|
project: opts.project,
|
|
14515
14866
|
json: opts.json ?? false,
|
|
@@ -14520,7 +14871,7 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
|
|
|
14520
14871
|
process.exit(code);
|
|
14521
14872
|
})
|
|
14522
14873
|
).addCommand(
|
|
14523
|
-
new
|
|
14874
|
+
new Command24("daemon").description("Restart squadrantd via the idempotent launchd kickstart path").action(async () => {
|
|
14524
14875
|
const code = await runHealDaemon({
|
|
14525
14876
|
ensureDaemon: () => restartDaemonIfRunning({ reason: "heal", isRunning: () => true }),
|
|
14526
14877
|
stdout: process.stdout,
|
|
@@ -14531,15 +14882,15 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
|
|
|
14531
14882
|
);
|
|
14532
14883
|
|
|
14533
14884
|
// packages/cli/src/commands/group.ts
|
|
14534
|
-
import { Command as
|
|
14535
|
-
import
|
|
14885
|
+
import { Command as Command26 } from "commander";
|
|
14886
|
+
import chalk26 from "chalk";
|
|
14536
14887
|
|
|
14537
14888
|
// packages/cli/src/commands/dispatch.ts
|
|
14538
14889
|
init_dist();
|
|
14539
14890
|
init_dist2();
|
|
14540
|
-
import { Command as
|
|
14891
|
+
import { Command as Command25 } from "commander";
|
|
14541
14892
|
import { execSync as execSync13 } from "child_process";
|
|
14542
|
-
import
|
|
14893
|
+
import chalk25 from "chalk";
|
|
14543
14894
|
async function runDispatch(toProject, task, opts) {
|
|
14544
14895
|
const fromProject = resolveCurrentProject(loadConfig());
|
|
14545
14896
|
if (!fromProject) {
|
|
@@ -14564,21 +14915,21 @@ async function runDispatch(toProject, task, opts) {
|
|
|
14564
14915
|
async function dispatchAction(toProject, task, opts) {
|
|
14565
14916
|
try {
|
|
14566
14917
|
const result = await runDispatch(toProject, task, opts);
|
|
14567
|
-
console.log(
|
|
14568
|
-
console.log(
|
|
14569
|
-
console.log(
|
|
14918
|
+
console.log(chalk25.green(`\u2714 Dispatched to '${toProject}' (task ${result.id.slice(0, 8)})`));
|
|
14919
|
+
console.log(chalk25.dim(` originProject: ${result.originProject ?? "none"}`));
|
|
14920
|
+
console.log(chalk25.dim(" You will be notified when the task settles (done/blocked/failed)."));
|
|
14570
14921
|
} catch (e) {
|
|
14571
|
-
console.error(
|
|
14922
|
+
console.error(chalk25.red(`\u2718 ${e.message}`));
|
|
14572
14923
|
process.exit(1);
|
|
14573
14924
|
}
|
|
14574
14925
|
}
|
|
14575
|
-
var dispatchCommand = new
|
|
14926
|
+
var dispatchCommand = new Command25("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);
|
|
14576
14927
|
|
|
14577
14928
|
// packages/cli/src/commands/group.ts
|
|
14578
14929
|
init_dist2();
|
|
14579
|
-
var groupCommand = new
|
|
14580
|
-
new
|
|
14581
|
-
console.error(
|
|
14930
|
+
var groupCommand = new Command26("group").description("Cross-project intra-group operations (Phase 1: dispatch)").addCommand(
|
|
14931
|
+
new Command26("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) => {
|
|
14932
|
+
console.error(chalk26.yellow(
|
|
14582
14933
|
`\u26A0 'squadrant group dispatch' is deprecated \u2014 use 'squadrant dispatch <project> "<task>"' instead.`
|
|
14583
14934
|
));
|
|
14584
14935
|
await dispatchAction(toProject, task, opts);
|
|
@@ -14589,8 +14940,8 @@ var groupCommand = new Command25("group").description("Cross-project intra-group
|
|
|
14589
14940
|
init_dist();
|
|
14590
14941
|
init_dist2();
|
|
14591
14942
|
import { join as join26, dirname as dirname7 } from "path";
|
|
14592
|
-
import { Command as
|
|
14593
|
-
import
|
|
14943
|
+
import { Command as Command27 } from "commander";
|
|
14944
|
+
import chalk27 from "chalk";
|
|
14594
14945
|
init_require_daemon();
|
|
14595
14946
|
async function runPing(project, message) {
|
|
14596
14947
|
const config = loadConfig();
|
|
@@ -14606,20 +14957,20 @@ async function runPing(project, message) {
|
|
|
14606
14957
|
source: "cli"
|
|
14607
14958
|
});
|
|
14608
14959
|
}
|
|
14609
|
-
var pingCommand = new
|
|
14960
|
+
var pingCommand = new Command27("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) => {
|
|
14610
14961
|
try {
|
|
14611
14962
|
await runPing(project, message);
|
|
14612
|
-
console.log(
|
|
14963
|
+
console.log(chalk27.green(`\u2714 Pinged '${project}'`));
|
|
14613
14964
|
} catch (err) {
|
|
14614
|
-
console.error(
|
|
14965
|
+
console.error(chalk27.red(err.message));
|
|
14615
14966
|
process.exit(1);
|
|
14616
14967
|
}
|
|
14617
14968
|
});
|
|
14618
14969
|
|
|
14619
14970
|
// packages/cli/src/commands/cmux.ts
|
|
14620
14971
|
init_dist();
|
|
14621
|
-
import { Command as
|
|
14622
|
-
import
|
|
14972
|
+
import { Command as Command28 } from "commander";
|
|
14973
|
+
import chalk28 from "chalk";
|
|
14623
14974
|
async function runCmuxAutoconfig(opts) {
|
|
14624
14975
|
const { json, stdout, stderr } = opts;
|
|
14625
14976
|
let r;
|
|
@@ -14638,19 +14989,19 @@ async function runCmuxAutoconfig(opts) {
|
|
|
14638
14989
|
stdout.write(`wrote cmux automation config \u2192 ${r.configPath}
|
|
14639
14990
|
`);
|
|
14640
14991
|
} else {
|
|
14641
|
-
stdout.write(
|
|
14992
|
+
stdout.write(chalk28.dim(`cmux automation config already in place (${r.configPath})
|
|
14642
14993
|
`));
|
|
14643
14994
|
}
|
|
14644
14995
|
if (r.verdict === "reachable") {
|
|
14645
|
-
stdout.write(
|
|
14996
|
+
stdout.write(chalk28.green("\u2714 daemon-direct delivery is reachable \u2014 cmux control socket accepts the daemon\n"));
|
|
14646
14997
|
return 0;
|
|
14647
14998
|
}
|
|
14648
14999
|
if (r.needsRestart) {
|
|
14649
15000
|
stdout.write(
|
|
14650
|
-
|
|
15001
|
+
chalk28.yellow("\u26A0 cmux is still on the old socket mode \u2014 restart cmux to enable daemon-direct delivery.\n")
|
|
14651
15002
|
);
|
|
14652
15003
|
if (r.promptedThisRun) {
|
|
14653
|
-
stdout.write(
|
|
15004
|
+
stdout.write(chalk28.dim(" (one-time prompt \u2014 you won't be nagged again)\n"));
|
|
14654
15005
|
}
|
|
14655
15006
|
return 2;
|
|
14656
15007
|
}
|
|
@@ -14659,8 +15010,8 @@ async function runCmuxAutoconfig(opts) {
|
|
|
14659
15010
|
);
|
|
14660
15011
|
return 1;
|
|
14661
15012
|
}
|
|
14662
|
-
var cmuxCommand = new
|
|
14663
|
-
new
|
|
15013
|
+
var cmuxCommand = new Command28("cmux").description("cmux integration helpers").addCommand(
|
|
15014
|
+
new Command28("autoconfig").description(
|
|
14664
15015
|
"Write the cmux automation socket config and probe whether daemon-direct\ndelivery is reachable. Idempotent; prompts once if a cmux restart is needed."
|
|
14665
15016
|
).option("--json", "output machine-readable JSON (exit 0=reachable, 1=unknown, 2=restart-needed)").action(async (opts) => {
|
|
14666
15017
|
const code = await runCmuxAutoconfig({
|
|
@@ -14677,8 +15028,8 @@ init_dist();
|
|
|
14677
15028
|
init_dist2();
|
|
14678
15029
|
import fs28 from "fs";
|
|
14679
15030
|
import path29 from "path";
|
|
14680
|
-
import { Command as
|
|
14681
|
-
import
|
|
15031
|
+
import { Command as Command29 } from "commander";
|
|
15032
|
+
import chalk29 from "chalk";
|
|
14682
15033
|
var VALID_EFFORTS = ["max", "balance", "low"];
|
|
14683
15034
|
var EFFORT_MEANING = {
|
|
14684
15035
|
max: "tokens are plentiful \u2014 bias crew spawns toward claude/opus",
|
|
@@ -14743,29 +15094,29 @@ async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(
|
|
|
14743
15094
|
}
|
|
14744
15095
|
}
|
|
14745
15096
|
}
|
|
14746
|
-
var effortCommand = new
|
|
15097
|
+
var effortCommand = new Command29("effort").description("Get or set the crew tokenomics dial (max | balance | low) \u2014 global by default, or per-project with --project").argument("[value]", "effort level to set: max | balance | low").option("--project <name>", "target a specific project (get: show its resolved effort; set: write a per-project override)").action(async (value, options) => {
|
|
14747
15098
|
if (value === void 0) {
|
|
14748
15099
|
let result;
|
|
14749
15100
|
try {
|
|
14750
15101
|
result = runEffortGet(void 0, options.project);
|
|
14751
15102
|
} catch (err) {
|
|
14752
|
-
console.error(
|
|
15103
|
+
console.error(chalk29.red(err.message));
|
|
14753
15104
|
process.exit(1);
|
|
14754
15105
|
}
|
|
14755
15106
|
const label = options.project ? `${options.project} project` : "global";
|
|
14756
|
-
console.log(
|
|
14757
|
-
console.log(
|
|
15107
|
+
console.log(chalk29.bold(`Current effort (${label}):`), chalk29.cyan(result.effort));
|
|
15108
|
+
console.log(chalk29.dim(EFFORT_MEANING[result.effort]));
|
|
14758
15109
|
return;
|
|
14759
15110
|
}
|
|
14760
15111
|
try {
|
|
14761
15112
|
runEffortSet(value, void 0, options.project);
|
|
14762
15113
|
} catch (err) {
|
|
14763
|
-
console.error(
|
|
15114
|
+
console.error(chalk29.red(err.message));
|
|
14764
15115
|
process.exit(1);
|
|
14765
15116
|
}
|
|
14766
15117
|
const effort = value;
|
|
14767
|
-
console.log(
|
|
14768
|
-
console.log(
|
|
15118
|
+
console.log(chalk29.green(`\u2714 effort \u2192 ${effort} (${effortScopeLabel(options.project)})`));
|
|
15119
|
+
console.log(chalk29.dim(EFFORT_MEANING[effort]));
|
|
14769
15120
|
try {
|
|
14770
15121
|
const { createCmuxDriver: createCmuxDriver2, RuntimeRegistry: RuntimeRegistry2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports3));
|
|
14771
15122
|
const config = loadConfig();
|
|
@@ -14775,7 +15126,7 @@ var effortCommand = new Command28("effort").description("Get or set the crew tok
|
|
|
14775
15126
|
const append = (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
|
|
14776
15127
|
await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append, options.project);
|
|
14777
15128
|
} catch {
|
|
14778
|
-
console.log(
|
|
15129
|
+
console.log(chalk29.dim("(no running captain detected \u2014 change applies on next launch)"));
|
|
14779
15130
|
}
|
|
14780
15131
|
});
|
|
14781
15132
|
|
|
@@ -14784,8 +15135,8 @@ init_dist();
|
|
|
14784
15135
|
init_dist2();
|
|
14785
15136
|
import { join as join27, dirname as dirname8 } from "path";
|
|
14786
15137
|
import { emitKeypressEvents } from "readline";
|
|
14787
|
-
import { Command as
|
|
14788
|
-
import
|
|
15138
|
+
import { Command as Command30 } from "commander";
|
|
15139
|
+
import chalk30 from "chalk";
|
|
14789
15140
|
function defaultStateRoot() {
|
|
14790
15141
|
return join27(dirname8(DEFAULT_CONFIG_PATH), "state");
|
|
14791
15142
|
}
|
|
@@ -14832,11 +15183,11 @@ async function questionYesNo(prompt) {
|
|
|
14832
15183
|
});
|
|
14833
15184
|
});
|
|
14834
15185
|
}
|
|
14835
|
-
var telegramCommand = new
|
|
15186
|
+
var telegramCommand = new Command30("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
|
|
14836
15187
|
telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
|
|
14837
15188
|
const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
|
|
14838
|
-
console.log(`token: ${tokenSet ?
|
|
14839
|
-
console.log(`supergroup: ${supergroupId ??
|
|
15189
|
+
console.log(`token: ${tokenSet ? chalk30.green("set") : chalk30.yellow("unset")}`);
|
|
15190
|
+
console.log(`supergroup: ${supergroupId ?? chalk30.yellow("unset")}`);
|
|
14840
15191
|
if (links.length === 0) {
|
|
14841
15192
|
console.log("no projects linked");
|
|
14842
15193
|
return;
|
|
@@ -14846,32 +15197,32 @@ telegramCommand.command("status").description("Show Telegram config and linked p
|
|
|
14846
15197
|
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) => {
|
|
14847
15198
|
const cfg = loadConfig().telegram;
|
|
14848
15199
|
if (!cfg) {
|
|
14849
|
-
console.error(
|
|
15200
|
+
console.error(chalk30.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
|
|
14850
15201
|
process.exit(1);
|
|
14851
15202
|
}
|
|
14852
15203
|
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
14853
15204
|
if (!token) {
|
|
14854
|
-
console.error(
|
|
15205
|
+
console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
14855
15206
|
process.exit(1);
|
|
14856
15207
|
}
|
|
14857
15208
|
const client = createTelegramClient({ token });
|
|
14858
15209
|
const { topicId, created } = await runTelegramLink({ project, cfg, client, stateRoot: defaultStateRoot() });
|
|
14859
|
-
console.log(
|
|
15210
|
+
console.log(chalk30.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
|
|
14860
15211
|
});
|
|
14861
15212
|
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) => {
|
|
14862
15213
|
if (!process.stdin.isTTY) {
|
|
14863
|
-
console.error(
|
|
15214
|
+
console.error(chalk30.red("setup requires a TTY \u2014 pipe input is not supported"));
|
|
14864
15215
|
process.exit(1);
|
|
14865
15216
|
}
|
|
14866
15217
|
console.log();
|
|
14867
|
-
console.log(
|
|
15218
|
+
console.log(chalk30.bold("Telegram setup") + " \u2014 connect squadrant to a Telegram bot for notifications + remote control");
|
|
14868
15219
|
console.log();
|
|
14869
15220
|
console.log("Before you start you need:");
|
|
14870
15221
|
console.log(" 1. A bot token from @BotFather (send /newbot)");
|
|
14871
15222
|
console.log(" 2. A forum supergroup with the bot added as an admin (Topics enabled)");
|
|
14872
15223
|
console.log(" 3. Bot privacy mode set to OFF (@BotFather \u2192 /setprivacy \u2192 Disable)");
|
|
14873
15224
|
console.log();
|
|
14874
|
-
console.log(
|
|
15225
|
+
console.log(chalk30.bold("Step 1/3 \u2014 Bot token"));
|
|
14875
15226
|
const existingCfg = loadConfig().telegram;
|
|
14876
15227
|
const existingToken = existingCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
14877
15228
|
const decision = resolveSetupToken(existingToken, { resetToken: opts.resetToken ?? false });
|
|
@@ -14883,67 +15234,67 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
|
|
|
14883
15234
|
try {
|
|
14884
15235
|
botUser = await client.getMe();
|
|
14885
15236
|
token = existingToken;
|
|
14886
|
-
console.log(
|
|
15237
|
+
console.log(chalk30.green(`Using existing bot token (@${botUser.username})`));
|
|
14887
15238
|
console.log();
|
|
14888
15239
|
} catch {
|
|
14889
|
-
console.log(
|
|
15240
|
+
console.log(chalk30.yellow("Existing token is invalid \u2014 please enter a new one."));
|
|
14890
15241
|
console.log("Paste your bot token then press Enter (input is hidden):");
|
|
14891
15242
|
token = await questionMasked();
|
|
14892
15243
|
if (!token) {
|
|
14893
|
-
console.error(
|
|
15244
|
+
console.error(chalk30.red("token required"));
|
|
14894
15245
|
process.exit(1);
|
|
14895
15246
|
}
|
|
14896
15247
|
client = createTelegramClient({ token });
|
|
14897
15248
|
try {
|
|
14898
15249
|
botUser = await client.getMe();
|
|
14899
15250
|
} catch (e) {
|
|
14900
|
-
console.error(
|
|
15251
|
+
console.error(chalk30.red(`token rejected: ${e.message}`));
|
|
14901
15252
|
process.exit(1);
|
|
14902
15253
|
}
|
|
14903
|
-
console.log(
|
|
15254
|
+
console.log(chalk30.green(`Connected as @${botUser.username}`));
|
|
14904
15255
|
console.log();
|
|
14905
15256
|
}
|
|
14906
15257
|
} else {
|
|
14907
15258
|
console.log("Paste your bot token then press Enter (input is hidden):");
|
|
14908
15259
|
token = await questionMasked();
|
|
14909
15260
|
if (!token) {
|
|
14910
|
-
console.error(
|
|
15261
|
+
console.error(chalk30.red("token required"));
|
|
14911
15262
|
process.exit(1);
|
|
14912
15263
|
}
|
|
14913
15264
|
client = createTelegramClient({ token });
|
|
14914
15265
|
try {
|
|
14915
15266
|
botUser = await client.getMe();
|
|
14916
15267
|
} catch (e) {
|
|
14917
|
-
console.error(
|
|
15268
|
+
console.error(chalk30.red(`token rejected: ${e.message}`));
|
|
14918
15269
|
process.exit(1);
|
|
14919
15270
|
}
|
|
14920
|
-
console.log(
|
|
15271
|
+
console.log(chalk30.green(`Connected as @${botUser.username}`));
|
|
14921
15272
|
console.log();
|
|
14922
15273
|
}
|
|
14923
|
-
console.log(
|
|
15274
|
+
console.log(chalk30.bold("Step 2/3 \u2014 Supergroup"));
|
|
14924
15275
|
const groupDecision = resolveSetupGroup(existingCfg?.supergroupId, { redetect: opts.redetect ?? false });
|
|
14925
15276
|
let supergroupId;
|
|
14926
15277
|
let detectedUserId;
|
|
14927
15278
|
if (groupDecision === "reuse") {
|
|
14928
15279
|
supergroupId = existingCfg.supergroupId;
|
|
14929
|
-
console.log(
|
|
15280
|
+
console.log(chalk30.green(`Using existing group: ${supergroupId}`));
|
|
14930
15281
|
console.log();
|
|
14931
15282
|
} else {
|
|
14932
15283
|
console.log("Add the bot to your forum supergroup, then send any message in it.");
|
|
14933
|
-
console.log(
|
|
15284
|
+
console.log(chalk30.dim("Waiting for a message (up to 60s)\u2026"));
|
|
14934
15285
|
try {
|
|
14935
15286
|
({ supergroupId, userId: detectedUserId } = await detectGroupAndUser(client, { timeoutMs: 6e4 }));
|
|
14936
15287
|
} catch {
|
|
14937
|
-
console.error(
|
|
14938
|
-
console.error(
|
|
15288
|
+
console.error(chalk30.red("Timed out \u2014 no supergroup message received within 60s."));
|
|
15289
|
+
console.error(chalk30.yellow("Check: bot is an admin in the group \xB7 privacy mode is OFF \xB7 Topics enabled"));
|
|
14939
15290
|
process.exit(1);
|
|
14940
15291
|
}
|
|
14941
|
-
console.log(
|
|
15292
|
+
console.log(chalk30.green(`Found group: ${supergroupId}`));
|
|
14942
15293
|
console.log();
|
|
14943
15294
|
}
|
|
14944
|
-
console.log(
|
|
14945
|
-
console.log(
|
|
14946
|
-
console.log(
|
|
15295
|
+
console.log(chalk30.bold("Step 3/3 \u2014 Remote control + Save"));
|
|
15296
|
+
console.log(chalk30.dim("Remote control enables auto-launching captains and the General command channel"));
|
|
15297
|
+
console.log(chalk30.dim("from your phone \u2014 gated to your Telegram user-id only (fail-closed)."));
|
|
14947
15298
|
const finalUserId = resolveSetupUserId(opts.userId, detectedUserId, defaultStateRoot());
|
|
14948
15299
|
let users;
|
|
14949
15300
|
let remoteControl;
|
|
@@ -14957,32 +15308,32 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
|
|
|
14957
15308
|
remoteControl = true;
|
|
14958
15309
|
}
|
|
14959
15310
|
} else if (groupDecision === "detect") {
|
|
14960
|
-
console.log(
|
|
14961
|
-
console.log(
|
|
15311
|
+
console.log(chalk30.yellow("Could not read your user-id from that message \u2014 skipping remote control."));
|
|
15312
|
+
console.log(chalk30.yellow("Re-run with --user-id <id> to enable, or edit telegram.users in config manually."));
|
|
14962
15313
|
printedRemoteControlState = true;
|
|
14963
15314
|
} else {
|
|
14964
15315
|
const existingUsers = existingCfg?.users;
|
|
14965
15316
|
if (existingUsers && existingUsers.length > 0) {
|
|
14966
|
-
console.log(
|
|
15317
|
+
console.log(chalk30.dim(`Remote control: already configured (user-id ${existingUsers[0]}). Use --user-id to update.`));
|
|
14967
15318
|
} else {
|
|
14968
|
-
console.log(
|
|
15319
|
+
console.log(chalk30.dim("Remote control: off. Re-run with --user-id <id> to enable."));
|
|
14969
15320
|
}
|
|
14970
15321
|
printedRemoteControlState = true;
|
|
14971
15322
|
}
|
|
14972
15323
|
writeTelegramConfig(DEFAULT_CONFIG_PATH, { token, supergroupId, users, remoteControl });
|
|
14973
|
-
console.log(
|
|
15324
|
+
console.log(chalk30.green(`Wrote telegram config \u2014 token: ${maskToken(token)} group: ${supergroupId}`));
|
|
14974
15325
|
if (!printedRemoteControlState) {
|
|
14975
15326
|
if (remoteControl) {
|
|
14976
|
-
console.log(
|
|
15327
|
+
console.log(chalk30.green(`Remote control: ON (allowlisted user-id ${users[0]})`));
|
|
14977
15328
|
} else {
|
|
14978
|
-
console.log(
|
|
15329
|
+
console.log(chalk30.dim("Remote control: off (default). Re-run with --user-id <id> to enable."));
|
|
14979
15330
|
}
|
|
14980
15331
|
}
|
|
14981
15332
|
try {
|
|
14982
15333
|
await runRegisterCommands({ client });
|
|
14983
|
-
console.log(
|
|
15334
|
+
console.log(chalk30.dim("Registered the /command menu."));
|
|
14984
15335
|
} catch (e) {
|
|
14985
|
-
console.log(
|
|
15336
|
+
console.log(chalk30.yellow(`command-menu registration skipped: ${e.message}`));
|
|
14986
15337
|
}
|
|
14987
15338
|
const topics = loadState(defaultStateRoot()).topics;
|
|
14988
15339
|
const topicEntries = Object.entries(topics);
|
|
@@ -14991,28 +15342,28 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
|
|
|
14991
15342
|
const project = key.slice(0, key.indexOf("::"));
|
|
14992
15343
|
return `${project}\u2192${id}`;
|
|
14993
15344
|
}).join(", ");
|
|
14994
|
-
console.log(
|
|
15345
|
+
console.log(chalk30.dim(`Existing topics: ${summary} (already created \u2014 not recreated)`));
|
|
14995
15346
|
} else {
|
|
14996
|
-
console.log(
|
|
15347
|
+
console.log(chalk30.dim("No project topics yet \u2014 they're created on first delivery or via: squadrant telegram link <project>"));
|
|
14997
15348
|
}
|
|
14998
15349
|
runTelegramPostSetup({});
|
|
14999
15350
|
console.log();
|
|
15000
|
-
console.log(`Next: ${
|
|
15351
|
+
console.log(`Next: ${chalk30.cyan("squadrant telegram link <project>")}`);
|
|
15001
15352
|
});
|
|
15002
15353
|
telegramCommand.command("register-commands").description("Register (or re-register) the bot's / command menu with Telegram").action(async () => {
|
|
15003
15354
|
const cfg = loadConfig().telegram;
|
|
15004
15355
|
if (!cfg) {
|
|
15005
|
-
console.error(
|
|
15356
|
+
console.error(chalk30.red("telegram config absent \u2014 run: squadrant telegram setup"));
|
|
15006
15357
|
process.exit(1);
|
|
15007
15358
|
}
|
|
15008
15359
|
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
15009
15360
|
if (!token) {
|
|
15010
|
-
console.error(
|
|
15361
|
+
console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
15011
15362
|
process.exit(1);
|
|
15012
15363
|
}
|
|
15013
15364
|
const client = createTelegramClient({ token });
|
|
15014
15365
|
await runRegisterCommands({ client });
|
|
15015
|
-
console.log(
|
|
15366
|
+
console.log(chalk30.green(`registered ${BOT_COMMANDS.length} bot commands`));
|
|
15016
15367
|
});
|
|
15017
15368
|
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) => {
|
|
15018
15369
|
const stateRoot = defaultStateRoot();
|
|
@@ -15023,7 +15374,7 @@ telegramCommand.command("notify").argument("[project]", "project to toggle").arg
|
|
|
15023
15374
|
return;
|
|
15024
15375
|
}
|
|
15025
15376
|
for (const r of rows) {
|
|
15026
|
-
console.log(` ${r.project}: ${r.active ?
|
|
15377
|
+
console.log(` ${r.project}: ${r.active ? chalk30.green("on") : chalk30.dim("off (muted)")}`);
|
|
15027
15378
|
}
|
|
15028
15379
|
return;
|
|
15029
15380
|
}
|
|
@@ -15032,53 +15383,53 @@ telegramCommand.command("notify").argument("[project]", "project to toggle").arg
|
|
|
15032
15383
|
const token = tgCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
15033
15384
|
if (state === "crew" || state === "cap") {
|
|
15034
15385
|
if (value === void 0) {
|
|
15035
|
-
console.error(
|
|
15386
|
+
console.error(chalk30.red(`usage: squadrant telegram notify <project> ${state} <value>`));
|
|
15036
15387
|
process.exit(1);
|
|
15037
15388
|
}
|
|
15038
15389
|
const resolved2 = resolveNotify(globalNotify, loadProjectOverride(project));
|
|
15039
15390
|
const before2 = { ...resolved2, active: isNotifyActive(stateRoot, project) };
|
|
15040
15391
|
const res = runTelegramNotifyPref({ project, dimension: state, value });
|
|
15041
15392
|
if (!res.ok) {
|
|
15042
|
-
console.error(
|
|
15393
|
+
console.error(chalk30.red(res.message));
|
|
15043
15394
|
process.exit(1);
|
|
15044
15395
|
}
|
|
15045
|
-
console.log(
|
|
15396
|
+
console.log(chalk30.green(`${project} ${state} = ${value}`));
|
|
15046
15397
|
const after2 = state === "crew" ? { ...before2, crew: value } : { ...before2, cap: value === "on" };
|
|
15047
15398
|
if (tgCfg && token) {
|
|
15048
15399
|
const client = createTelegramClient({ token });
|
|
15049
15400
|
const sent = await runNotifyConfirmation({ project, before: before2, after: after2, cfg: tgCfg, client, stateRoot });
|
|
15050
|
-
if (sent) console.log(
|
|
15401
|
+
if (sent) console.log(chalk30.dim(`\u2192 notified ${project} topic`));
|
|
15051
15402
|
}
|
|
15052
15403
|
return;
|
|
15053
15404
|
}
|
|
15054
15405
|
if (state !== "on" && state !== "off") {
|
|
15055
|
-
console.error(
|
|
15406
|
+
console.error(chalk30.red("usage: squadrant telegram notify <project> <on|off|crew <tier>|cap <on|off>>"));
|
|
15056
15407
|
process.exit(1);
|
|
15057
15408
|
}
|
|
15058
15409
|
const resolved = resolveNotify(globalNotify, loadProjectOverride(project));
|
|
15059
15410
|
const before = { ...resolved, active: isNotifyActive(stateRoot, project) };
|
|
15060
15411
|
const after = { ...before, active: state === "on" };
|
|
15061
15412
|
runTelegramNotifySet({ project, active: state === "on", stateRoot });
|
|
15062
|
-
console.log(
|
|
15413
|
+
console.log(chalk30.green(`${project} notifications ${state === "on" ? "ON" : "OFF"}`));
|
|
15063
15414
|
if (tgCfg && token) {
|
|
15064
15415
|
const client = createTelegramClient({ token });
|
|
15065
15416
|
const sent = await runNotifyConfirmation({ project, before, after, cfg: tgCfg, client, stateRoot });
|
|
15066
|
-
if (sent) console.log(
|
|
15417
|
+
if (sent) console.log(chalk30.dim(`\u2192 notified ${project} topic`));
|
|
15067
15418
|
}
|
|
15068
15419
|
});
|
|
15069
15420
|
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) => {
|
|
15070
15421
|
const cfg = loadConfig().telegram;
|
|
15071
15422
|
if (!cfg) {
|
|
15072
|
-
console.error(
|
|
15423
|
+
console.error(chalk30.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
|
|
15073
15424
|
process.exit(1);
|
|
15074
15425
|
}
|
|
15075
15426
|
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
15076
15427
|
if (!token) {
|
|
15077
|
-
console.error(
|
|
15428
|
+
console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
15078
15429
|
process.exit(1);
|
|
15079
15430
|
}
|
|
15080
15431
|
if (!capAllowed(project, cfg.notify)) {
|
|
15081
|
-
console.log(
|
|
15432
|
+
console.log(chalk30.dim(`${project}: captain messages muted (cap=off) \u2014 not sent`));
|
|
15082
15433
|
return;
|
|
15083
15434
|
}
|
|
15084
15435
|
let message;
|
|
@@ -15091,19 +15442,19 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
|
|
|
15091
15442
|
for await (const line of rl) lines.push(line);
|
|
15092
15443
|
message = lines.join("\n").trimEnd();
|
|
15093
15444
|
if (!message) {
|
|
15094
|
-
console.error(
|
|
15445
|
+
console.error(chalk30.red("no message provided (stdin was empty)"));
|
|
15095
15446
|
process.exit(1);
|
|
15096
15447
|
}
|
|
15097
15448
|
} else {
|
|
15098
|
-
console.error(
|
|
15449
|
+
console.error(chalk30.red("message required \u2014 pass as argument or pipe via stdin"));
|
|
15099
15450
|
process.exit(1);
|
|
15100
15451
|
}
|
|
15101
15452
|
const client = createTelegramClient({ token });
|
|
15102
15453
|
try {
|
|
15103
15454
|
const { chatId, topicId } = await runTelegramSend({ project, message, cfg, client, stateRoot: defaultStateRoot() });
|
|
15104
|
-
console.log(
|
|
15455
|
+
console.log(chalk30.green(`sent to group ${chatId} topic ${topicId}`));
|
|
15105
15456
|
} catch (e) {
|
|
15106
|
-
console.error(
|
|
15457
|
+
console.error(chalk30.red(e.message));
|
|
15107
15458
|
process.exit(1);
|
|
15108
15459
|
}
|
|
15109
15460
|
});
|
|
@@ -15111,7 +15462,7 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
|
|
|
15111
15462
|
// packages/cli/src/commands/hooks.ts
|
|
15112
15463
|
init_dist2();
|
|
15113
15464
|
init_dist4();
|
|
15114
|
-
import { Command as
|
|
15465
|
+
import { Command as Command31 } from "commander";
|
|
15115
15466
|
import { join as join28 } from "path";
|
|
15116
15467
|
import { homedir as homedir20 } from "os";
|
|
15117
15468
|
var SOCK4 = join28(homedir20(), ".config", "squadrant", "squadrant.sock");
|
|
@@ -15138,7 +15489,7 @@ function mapHookSub(sub, payload, taskId) {
|
|
|
15138
15489
|
}
|
|
15139
15490
|
}
|
|
15140
15491
|
function hooksCommand() {
|
|
15141
|
-
const hooks = new
|
|
15492
|
+
const hooks = new Command31("hooks").description("(internal) receive lifecycle hook events from agent processes");
|
|
15142
15493
|
hooks.command("claude <sub>", { hidden: true }).description("internal: bridge a NativeHookSource claude hook to squadrantd").action(async (sub) => {
|
|
15143
15494
|
const taskId = process.env.SQUADRANT_CREW_TASK_ID;
|
|
15144
15495
|
const project = process.env.SQUADRANT_CREW_PROJECT;
|
|
@@ -15209,7 +15560,7 @@ if (process.argv[2] !== "config") {
|
|
|
15209
15560
|
if (!process.env.SQUADRANT_DAEMON_SKIP) {
|
|
15210
15561
|
ensureDaemon();
|
|
15211
15562
|
}
|
|
15212
|
-
var program = new
|
|
15563
|
+
var program = new Command32();
|
|
15213
15564
|
program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
|
|
15214
15565
|
program.addCommand(doctorCommand);
|
|
15215
15566
|
program.addCommand(initCommand);
|
|
@@ -15218,6 +15569,7 @@ program.addCommand(statusCommand);
|
|
|
15218
15569
|
addControlPlaneCrewCommands(crewCommand);
|
|
15219
15570
|
program.addCommand(crewCommand);
|
|
15220
15571
|
program.addCommand(sideCommand);
|
|
15572
|
+
program.addCommand(diffCommand);
|
|
15221
15573
|
program.addCommand(commandCommand);
|
|
15222
15574
|
program.addCommand(dashboardCommand);
|
|
15223
15575
|
program.addCommand(launchCommand);
|