squadrant 0.16.4 → 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 +592 -277
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +70 -6
- 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",
|
|
@@ -4309,8 +4322,12 @@ var init_commands = __esm({
|
|
|
4309
4322
|
build: (a) => a[0] ? ok("crews", ["crew", "list", a[0]]) : usage("crews", "usage: /crews <project>")
|
|
4310
4323
|
},
|
|
4311
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.
|
|
4312
4329
|
usage: "/launch <project>",
|
|
4313
|
-
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>")
|
|
4314
4331
|
},
|
|
4315
4332
|
effort: {
|
|
4316
4333
|
usage: "/effort [max|balance|low]",
|
|
@@ -4484,6 +4501,9 @@ ${ev.message}` : "");
|
|
|
4484
4501
|
case "task.blocked":
|
|
4485
4502
|
return `\u{1F6A7} [${project}] CREW BLOCKED \xB7 ${ev.id}
|
|
4486
4503
|
${ev.question}`;
|
|
4504
|
+
case "task.review":
|
|
4505
|
+
return `\u{1F440} [${project}] CREW REVIEW \xB7 ${ev.id}` + (ev.message ? `
|
|
4506
|
+
${ev.message}` : "");
|
|
4487
4507
|
case "task.idle":
|
|
4488
4508
|
return `\u{1F4A4} [${project}] CREW IDLE \xB7 ${ev.id}`;
|
|
4489
4509
|
case "task.failed":
|
|
@@ -4716,6 +4736,7 @@ var init_tiers = __esm({
|
|
|
4716
4736
|
ALERTS = /* @__PURE__ */ new Set([
|
|
4717
4737
|
...DONE_ONLY,
|
|
4718
4738
|
"task.blocked",
|
|
4739
|
+
"task.review",
|
|
4719
4740
|
"task.approval.requested",
|
|
4720
4741
|
"task.input.requested",
|
|
4721
4742
|
"task.timeout"
|
|
@@ -5932,7 +5953,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
|
5932
5953
|
if (task) {
|
|
5933
5954
|
if (TERMINAL_STATES.has(task.state)) {
|
|
5934
5955
|
await deps.emitEvent(project, { type: "task.reopened", id: task.id });
|
|
5935
|
-
} else if (task.state === "blocked" || task.state === "awaiting-input") {
|
|
5956
|
+
} else if (task.state === "blocked" || task.state === "awaiting-input" || task.state === "review") {
|
|
5936
5957
|
await deps.emitEvent(project, { type: "task.started", id: task.id });
|
|
5937
5958
|
}
|
|
5938
5959
|
}
|
|
@@ -6277,6 +6298,18 @@ function cmux(args) {
|
|
|
6277
6298
|
);
|
|
6278
6299
|
});
|
|
6279
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
|
+
}
|
|
6280
6313
|
function parseList(output) {
|
|
6281
6314
|
let parsed;
|
|
6282
6315
|
try {
|
|
@@ -6676,6 +6709,37 @@ function createCmuxDriver() {
|
|
|
6676
6709
|
}
|
|
6677
6710
|
throw new DeferDelivery(draft);
|
|
6678
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
|
+
},
|
|
6679
6743
|
async listSurfaces(workspaceId) {
|
|
6680
6744
|
let output;
|
|
6681
6745
|
try {
|
|
@@ -9944,7 +10008,7 @@ var init_require_daemon = __esm({
|
|
|
9944
10008
|
// packages/cli/src/index.ts
|
|
9945
10009
|
init_dist();
|
|
9946
10010
|
init_dist2();
|
|
9947
|
-
import { Command as
|
|
10011
|
+
import { Command as Command32 } from "commander";
|
|
9948
10012
|
import { readFileSync as readFileSync15, existsSync as existsSync12, writeFileSync as writeFileSync11 } from "fs";
|
|
9949
10013
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
9950
10014
|
import { dirname as dirname9, join as join29 } from "path";
|
|
@@ -10975,6 +11039,7 @@ init_dist4();
|
|
|
10975
11039
|
import { Command as Command8 } from "commander";
|
|
10976
11040
|
import { createConnection as createConnection3 } from "net";
|
|
10977
11041
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
11042
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
10978
11043
|
import { homedir as homedir16 } from "os";
|
|
10979
11044
|
import { join as join21 } from "path";
|
|
10980
11045
|
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "fs";
|
|
@@ -11374,6 +11439,8 @@ function buildSignalRequest(signal, o) {
|
|
|
11374
11439
|
};
|
|
11375
11440
|
} else if (signal === "blocked") {
|
|
11376
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 } : {} };
|
|
11377
11444
|
} else {
|
|
11378
11445
|
event = { type: "task.failed", id: taskId, error: o.error ?? "crew signaled failed" };
|
|
11379
11446
|
}
|
|
@@ -11402,6 +11469,49 @@ async function runCrewSignal(signal, o, deps) {
|
|
|
11402
11469
|
const req = buildSignalRequest(signal, { ...o, writeResult: o.writeResult ?? defaultWriteResult });
|
|
11403
11470
|
await deps.call(req);
|
|
11404
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
|
+
}
|
|
11405
11515
|
function addControlPlaneCrewCommands(crew) {
|
|
11406
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) => {
|
|
11407
11517
|
const req = buildDispatchRequest({ project, task, provider: opts.provider, mode: opts.mode, cwd: opts.cwd });
|
|
@@ -11476,9 +11586,9 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11476
11586
|
}
|
|
11477
11587
|
process.exit(0);
|
|
11478
11588
|
});
|
|
11479
|
-
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) => {
|
|
11480
|
-
if (state !== "done" && state !== "blocked" && state !== "failed") {
|
|
11481
|
-
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)
|
|
11482
11592
|
`);
|
|
11483
11593
|
process.exit(2);
|
|
11484
11594
|
}
|
|
@@ -11494,6 +11604,17 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11494
11604
|
process.exit(0);
|
|
11495
11605
|
} catch (e) {
|
|
11496
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}
|
|
11497
11618
|
`);
|
|
11498
11619
|
process.exit(1);
|
|
11499
11620
|
}
|
|
@@ -11651,6 +11772,199 @@ crewCommand.command("close").description("Shutdown a crew session (closes its ta
|
|
|
11651
11772
|
}
|
|
11652
11773
|
});
|
|
11653
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
|
+
|
|
11654
11968
|
// packages/cli/src/commands/side.ts
|
|
11655
11969
|
init_dist();
|
|
11656
11970
|
init_dist3();
|
|
@@ -11658,11 +11972,11 @@ init_dist4();
|
|
|
11658
11972
|
init_dist3();
|
|
11659
11973
|
init_dist();
|
|
11660
11974
|
init_dist2();
|
|
11661
|
-
import { Command as
|
|
11975
|
+
import { Command as Command11 } from "commander";
|
|
11662
11976
|
import fs20 from "fs";
|
|
11663
11977
|
import path22 from "path";
|
|
11664
11978
|
import os12 from "os";
|
|
11665
|
-
import
|
|
11979
|
+
import chalk11 from "chalk";
|
|
11666
11980
|
var TEMPLATES_DIR3 = path22.join(os12.homedir(), ".config", "squadrant", "templates");
|
|
11667
11981
|
async function runSideSpawn2(input) {
|
|
11668
11982
|
const config = loadConfig();
|
|
@@ -11724,7 +12038,7 @@ async function runSideClose2(project, name) {
|
|
|
11724
12038
|
config.defaults.worktreeDir ?? ".worktrees"
|
|
11725
12039
|
);
|
|
11726
12040
|
}
|
|
11727
|
-
var sideCommand = new
|
|
12041
|
+
var sideCommand = new Command11("side").description(
|
|
11728
12042
|
"Spawn and manage side-sessions (research/debug) \u2014 fresh-context tabs off the daemon lifecycle"
|
|
11729
12043
|
);
|
|
11730
12044
|
sideCommand.command("spawn").description(
|
|
@@ -11749,9 +12063,9 @@ sideCommand.command("spawn").description(
|
|
|
11749
12063
|
direction: opts.direction,
|
|
11750
12064
|
agent: opts.agent
|
|
11751
12065
|
});
|
|
11752
|
-
console.log(
|
|
12066
|
+
console.log(chalk11.green(`\u2714 Side session '${pane.title}' spawned (${pane.surfaceId})`));
|
|
11753
12067
|
} catch (err) {
|
|
11754
|
-
console.error(
|
|
12068
|
+
console.error(chalk11.red(err.message));
|
|
11755
12069
|
process.exit(1);
|
|
11756
12070
|
}
|
|
11757
12071
|
}
|
|
@@ -11760,14 +12074,14 @@ sideCommand.command("list").description("List live side-sessions for a project")
|
|
|
11760
12074
|
try {
|
|
11761
12075
|
const sessions = await runSideList2(project);
|
|
11762
12076
|
if (sessions.length === 0) {
|
|
11763
|
-
console.log(
|
|
12077
|
+
console.log(chalk11.yellow(`No live side-sessions for ${project}.`));
|
|
11764
12078
|
return;
|
|
11765
12079
|
}
|
|
11766
12080
|
for (const s of sessions) {
|
|
11767
12081
|
console.log(` ${s.name} (${s.surfaceId})`);
|
|
11768
12082
|
}
|
|
11769
12083
|
} catch (err) {
|
|
11770
|
-
console.error(
|
|
12084
|
+
console.error(chalk11.red(err.message));
|
|
11771
12085
|
process.exit(1);
|
|
11772
12086
|
}
|
|
11773
12087
|
});
|
|
@@ -11780,9 +12094,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
11780
12094
|
label: "message"
|
|
11781
12095
|
});
|
|
11782
12096
|
await runSideSend2(project, name, resolvedMessage);
|
|
11783
|
-
console.log(
|
|
12097
|
+
console.log(chalk11.green(`\u2714 Sent to ${project}:${name}`));
|
|
11784
12098
|
} catch (err) {
|
|
11785
|
-
console.error(
|
|
12099
|
+
console.error(chalk11.red(err.message));
|
|
11786
12100
|
process.exit(1);
|
|
11787
12101
|
}
|
|
11788
12102
|
}
|
|
@@ -11790,9 +12104,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
11790
12104
|
sideCommand.command("close").description("Close a side-session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Session name").action(async (project, name) => {
|
|
11791
12105
|
try {
|
|
11792
12106
|
await runSideClose2(project, name);
|
|
11793
|
-
console.log(
|
|
12107
|
+
console.log(chalk11.green(`\u2714 Closed ${project}:${name}`));
|
|
11794
12108
|
} catch (err) {
|
|
11795
|
-
console.error(
|
|
12109
|
+
console.error(chalk11.red(err.message));
|
|
11796
12110
|
process.exit(1);
|
|
11797
12111
|
}
|
|
11798
12112
|
});
|
|
@@ -11800,15 +12114,15 @@ sideCommand.command("close").description("Close a side-session (closes its tab)"
|
|
|
11800
12114
|
// packages/cli/src/commands/dashboard.ts
|
|
11801
12115
|
init_dist();
|
|
11802
12116
|
init_dist3();
|
|
11803
|
-
import { Command as
|
|
12117
|
+
import { Command as Command12 } from "commander";
|
|
11804
12118
|
import { execSync as execSync10 } from "child_process";
|
|
11805
12119
|
import { homedir as homedir18 } from "os";
|
|
11806
12120
|
import { join as join23 } from "path";
|
|
11807
|
-
import
|
|
12121
|
+
import chalk13 from "chalk";
|
|
11808
12122
|
|
|
11809
12123
|
// packages/web/dist/read-status.js
|
|
11810
12124
|
function deriveState(tasks) {
|
|
11811
|
-
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"))
|
|
11812
12126
|
return "blocked";
|
|
11813
12127
|
if (tasks.some((t) => t.state === "failed" || t.state === "stalled"))
|
|
11814
12128
|
return "errored";
|
|
@@ -11823,14 +12137,14 @@ function deriveRowState(tasks, captainState) {
|
|
|
11823
12137
|
}
|
|
11824
12138
|
function buildExcerpt(tasks) {
|
|
11825
12139
|
const working = tasks.filter((t) => t.state === "working").length;
|
|
11826
|
-
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;
|
|
11827
12141
|
const parts = [];
|
|
11828
12142
|
if (working > 0)
|
|
11829
12143
|
parts.push(`${working} working`);
|
|
11830
12144
|
if (blocked > 0)
|
|
11831
12145
|
parts.push(`${blocked} blocked`);
|
|
11832
12146
|
const summary = parts.length > 0 ? parts.join(", ") : "idle";
|
|
11833
|
-
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));
|
|
11834
12148
|
const titles = active.slice(0, 3).map((t) => {
|
|
11835
12149
|
const firstLine2 = t.task ? t.task.split("\n")[0] : "";
|
|
11836
12150
|
return t.name ?? (firstLine2 || t.id.slice(0, 8));
|
|
@@ -11881,14 +12195,14 @@ async function readAllStatuses(deps) {
|
|
|
11881
12195
|
}
|
|
11882
12196
|
|
|
11883
12197
|
// packages/web/dist/render.js
|
|
11884
|
-
import
|
|
12198
|
+
import chalk12 from "chalk";
|
|
11885
12199
|
var ICON = {
|
|
11886
|
-
idle:
|
|
11887
|
-
busy:
|
|
11888
|
-
blocked:
|
|
11889
|
-
errored:
|
|
11890
|
-
offline:
|
|
11891
|
-
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
|
|
11892
12206
|
};
|
|
11893
12207
|
var ICON_CHAR = {
|
|
11894
12208
|
idle: "\u25CF",
|
|
@@ -11931,10 +12245,10 @@ function renderDashboard(rows, opts) {
|
|
|
11931
12245
|
const width = opts.width ?? 100;
|
|
11932
12246
|
const lines = [];
|
|
11933
12247
|
lines.push("");
|
|
11934
|
-
lines.push(" " +
|
|
12248
|
+
lines.push(" " + chalk12.bold("\u{1F4CA} Squadrant Dashboard") + " " + chalk12.dim(opts.now));
|
|
11935
12249
|
lines.push("");
|
|
11936
12250
|
if (rows.length === 0) {
|
|
11937
|
-
lines.push(" " +
|
|
12251
|
+
lines.push(" " + chalk12.yellow("No projects registered. Add one with: squadrant projects add <name> <path>"));
|
|
11938
12252
|
lines.push("");
|
|
11939
12253
|
return lines.join("\n");
|
|
11940
12254
|
}
|
|
@@ -11945,14 +12259,14 @@ function renderDashboard(rows, opts) {
|
|
|
11945
12259
|
const excerptW = Math.max(20, width - FIXED);
|
|
11946
12260
|
for (const r of rows) {
|
|
11947
12261
|
const icon = ICON[r.state](ICON_CHAR[r.state]);
|
|
11948
|
-
const name =
|
|
12262
|
+
const name = chalk12.cyan(pad(r.project, NAME_W));
|
|
11949
12263
|
const state = ICON[r.state](pad(r.state, STATE_W));
|
|
11950
12264
|
const age = pad(formatAge(r.lastChecked, opts.now), AGE_W);
|
|
11951
|
-
const excerpt =
|
|
12265
|
+
const excerpt = chalk12.dim(truncate(firstLine(r.excerpt), excerptW));
|
|
11952
12266
|
lines.push(` ${icon} ${name} ${state} ${age} \u2502 ${excerpt}`);
|
|
11953
12267
|
}
|
|
11954
12268
|
lines.push("");
|
|
11955
|
-
lines.push(
|
|
12269
|
+
lines.push(chalk12.dim(" Refreshes every 10s \xB7 Ctrl+C to exit"));
|
|
11956
12270
|
lines.push("");
|
|
11957
12271
|
return lines.join("\n");
|
|
11958
12272
|
}
|
|
@@ -13075,10 +13389,10 @@ async function runDashboardWeb(input) {
|
|
|
13075
13389
|
sockPath: SOCK3,
|
|
13076
13390
|
runners: defaultProbeRunners()
|
|
13077
13391
|
});
|
|
13078
|
-
console.log(
|
|
13079
|
-
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`));
|
|
13080
13394
|
}
|
|
13081
|
-
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) => {
|
|
13082
13396
|
try {
|
|
13083
13397
|
if (opts.web) {
|
|
13084
13398
|
await runDashboardWeb({ port: opts.port, interval: opts.interval ?? 5 });
|
|
@@ -13086,12 +13400,12 @@ var dashboardCommand = new Command11("dashboard").description("Live status grid
|
|
|
13086
13400
|
}
|
|
13087
13401
|
if (opts.pane) {
|
|
13088
13402
|
const pane = await runDashboardPane({ direction: opts.direction, interval: opts.interval ?? 10 });
|
|
13089
|
-
console.log(
|
|
13403
|
+
console.log(chalk13.green(`\u2714 Dashboard pane opened in ${pane.workspaceId} ${pane.surfaceId}`));
|
|
13090
13404
|
return;
|
|
13091
13405
|
}
|
|
13092
13406
|
await runDashboardOnce();
|
|
13093
13407
|
} catch (err) {
|
|
13094
|
-
console.error(
|
|
13408
|
+
console.error(chalk13.red(err.message));
|
|
13095
13409
|
process.exit(1);
|
|
13096
13410
|
}
|
|
13097
13411
|
});
|
|
@@ -13102,12 +13416,12 @@ dashboardCommand.command("sync-hub").description("Mirror each spoke status.md in
|
|
|
13102
13416
|
return;
|
|
13103
13417
|
}
|
|
13104
13418
|
if (results.length === 0) {
|
|
13105
|
-
console.log(
|
|
13419
|
+
console.log(chalk13.dim("\n No mirrors written (no projects with usable status.md, or hubVault unset).\n"));
|
|
13106
13420
|
return;
|
|
13107
13421
|
}
|
|
13108
|
-
console.log(
|
|
13422
|
+
console.log(chalk13.bold("\n \u{1F4CA} Hub mirror sync\n"));
|
|
13109
13423
|
for (const r of results) {
|
|
13110
|
-
console.log(` ${
|
|
13424
|
+
console.log(` ${chalk13.green("\u2714")} ${chalk13.cyan(r.project.padEnd(16))} \u2192 ${chalk13.dim(r.hubPath)}`);
|
|
13111
13425
|
}
|
|
13112
13426
|
console.log("");
|
|
13113
13427
|
});
|
|
@@ -13117,12 +13431,12 @@ init_dist();
|
|
|
13117
13431
|
init_dist4();
|
|
13118
13432
|
init_dist3();
|
|
13119
13433
|
init_dist2();
|
|
13120
|
-
import { Command as
|
|
13434
|
+
import { Command as Command13 } from "commander";
|
|
13121
13435
|
import { execSync as execSync11 } from "child_process";
|
|
13122
13436
|
import fs22 from "fs";
|
|
13123
13437
|
import path24 from "path";
|
|
13124
13438
|
import os13 from "os";
|
|
13125
|
-
import
|
|
13439
|
+
import chalk14 from "chalk";
|
|
13126
13440
|
|
|
13127
13441
|
// packages/cli/src/commands/launch-interactive.ts
|
|
13128
13442
|
import checkbox, { Separator } from "@inquirer/checkbox";
|
|
@@ -13203,16 +13517,16 @@ var TEMPLATES_DIR4 = path24.join(os13.homedir(), ".config", "squadrant", "templa
|
|
|
13203
13517
|
var SESSIONS_PATH2 = path24.join(os13.homedir(), ".config", "squadrant", "sessions.json");
|
|
13204
13518
|
function ensureCmuxReady(headless) {
|
|
13205
13519
|
if (headless || isInsideCmux()) return;
|
|
13206
|
-
console.log(
|
|
13520
|
+
console.log(chalk14.yellow("\n Not running inside cmux. Opening cmux app...\n"));
|
|
13207
13521
|
execSync11(`open "${CMUX_APP}"`, { stdio: "inherit" });
|
|
13208
|
-
console.log(
|
|
13522
|
+
console.log(chalk14.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
|
|
13209
13523
|
process.exit(0);
|
|
13210
13524
|
}
|
|
13211
|
-
var launchCommand = new
|
|
13525
|
+
var launchCommand = new Command13("launch").description(
|
|
13212
13526
|
"Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
|
|
13213
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) => {
|
|
13214
13528
|
if (opts.fresh && opts.keep) {
|
|
13215
|
-
console.error(
|
|
13529
|
+
console.error(chalk14.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
|
|
13216
13530
|
process.exit(1);
|
|
13217
13531
|
}
|
|
13218
13532
|
const config = loadConfig();
|
|
@@ -13260,29 +13574,29 @@ var launchCommand = new Command12("launch").description(
|
|
|
13260
13574
|
return null;
|
|
13261
13575
|
}
|
|
13262
13576
|
},
|
|
13263
|
-
onFreshReason: (reason) => console.log(
|
|
13264
|
-
onStoppingStale: (name) => console.log(
|
|
13265
|
-
onAlreadyExists: (name) => console.log(
|
|
13266
|
-
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`))
|
|
13267
13581
|
});
|
|
13268
13582
|
} catch (err) {
|
|
13269
|
-
console.error(
|
|
13583
|
+
console.error(chalk14.red(` \u2718 Failed: ${err.message}`));
|
|
13270
13584
|
hadFailure = true;
|
|
13271
13585
|
}
|
|
13272
13586
|
}
|
|
13273
13587
|
if (opts.all) {
|
|
13274
13588
|
const hubPath = resolveHome(config.hubVault);
|
|
13275
13589
|
fs22.mkdirSync(hubPath, { recursive: true });
|
|
13276
|
-
console.log(
|
|
13590
|
+
console.log(chalk14.bold("\nLaunching all captain workspaces\n"));
|
|
13277
13591
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
13278
13592
|
const projPath = resolveHome(proj.path);
|
|
13279
13593
|
const spokePath = resolveHome(proj.spokeVault);
|
|
13280
13594
|
if (!fs22.existsSync(spokePath)) {
|
|
13281
13595
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
13282
13596
|
await ensureSpokeLayout(spokeDriver);
|
|
13283
|
-
console.log(
|
|
13597
|
+
console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13284
13598
|
}
|
|
13285
|
-
console.log(
|
|
13599
|
+
console.log(chalk14.bold(`
|
|
13286
13600
|
Captain: ${proj.captainName} (${name})`));
|
|
13287
13601
|
await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
|
|
13288
13602
|
}
|
|
@@ -13290,7 +13604,7 @@ var launchCommand = new Command12("launch").description(
|
|
|
13290
13604
|
} else if (!project) {
|
|
13291
13605
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
13292
13606
|
console.error(
|
|
13293
|
-
|
|
13607
|
+
chalk14.red(
|
|
13294
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"
|
|
13295
13609
|
)
|
|
13296
13610
|
);
|
|
@@ -13305,10 +13619,10 @@ var launchCommand = new Command12("launch").description(
|
|
|
13305
13619
|
}));
|
|
13306
13620
|
const selected = await selectCaptainsInteractive(entries);
|
|
13307
13621
|
if (selected.length === 0) {
|
|
13308
|
-
console.log(
|
|
13622
|
+
console.log(chalk14.yellow("\n No captains selected.\n"));
|
|
13309
13623
|
return;
|
|
13310
13624
|
}
|
|
13311
|
-
console.log(
|
|
13625
|
+
console.log(chalk14.bold(`
|
|
13312
13626
|
Launching ${selected.length} captain workspace(s) in parallel
|
|
13313
13627
|
`));
|
|
13314
13628
|
await Promise.all(selected.map(async (name) => {
|
|
@@ -13318,9 +13632,9 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13318
13632
|
if (!fs22.existsSync(spokePath)) {
|
|
13319
13633
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
13320
13634
|
await ensureSpokeLayout(spokeDriver);
|
|
13321
|
-
console.log(
|
|
13635
|
+
console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13322
13636
|
}
|
|
13323
|
-
console.log(
|
|
13637
|
+
console.log(chalk14.bold(`
|
|
13324
13638
|
Captain: ${proj.captainName} (${name})`));
|
|
13325
13639
|
await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
|
|
13326
13640
|
}));
|
|
@@ -13328,7 +13642,7 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13328
13642
|
} else {
|
|
13329
13643
|
if (!config.projects[project]) {
|
|
13330
13644
|
console.error(
|
|
13331
|
-
|
|
13645
|
+
chalk14.red(
|
|
13332
13646
|
`
|
|
13333
13647
|
\u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
|
|
13334
13648
|
`
|
|
@@ -13342,10 +13656,10 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13342
13656
|
if (!fs22.existsSync(spokePath)) {
|
|
13343
13657
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
|
|
13344
13658
|
await ensureSpokeLayout(spokeDriver);
|
|
13345
|
-
console.log(
|
|
13659
|
+
console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13346
13660
|
}
|
|
13347
13661
|
console.log(
|
|
13348
|
-
|
|
13662
|
+
chalk14.bold(
|
|
13349
13663
|
`
|
|
13350
13664
|
Launching captain workspace for '${project}' (${proj.captainName})
|
|
13351
13665
|
`
|
|
@@ -13359,8 +13673,8 @@ Launching captain workspace for '${project}' (${proj.captainName})
|
|
|
13359
13673
|
// packages/cli/src/commands/shutdown.ts
|
|
13360
13674
|
init_dist();
|
|
13361
13675
|
init_dist3();
|
|
13362
|
-
import { Command as
|
|
13363
|
-
import
|
|
13676
|
+
import { Command as Command14 } from "commander";
|
|
13677
|
+
import chalk15 from "chalk";
|
|
13364
13678
|
init_dist();
|
|
13365
13679
|
function nameVariants(name) {
|
|
13366
13680
|
const stripped = name.replace(/^⚓\s+/, "").trim();
|
|
@@ -13373,23 +13687,23 @@ async function closeMatching(runtime, variants, label) {
|
|
|
13373
13687
|
const failed = [];
|
|
13374
13688
|
if (matches.length === 0) {
|
|
13375
13689
|
console.log(
|
|
13376
|
-
|
|
13690
|
+
chalk15.yellow(` \u26A0 Workspace '${label}' not found \u2014 already closed?`)
|
|
13377
13691
|
);
|
|
13378
13692
|
return { closed, failed };
|
|
13379
13693
|
}
|
|
13380
13694
|
for (const ws of matches) {
|
|
13381
13695
|
try {
|
|
13382
13696
|
await runtime.stop(ws.id);
|
|
13383
|
-
console.log(
|
|
13697
|
+
console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
|
|
13384
13698
|
closed.push(ws.name);
|
|
13385
13699
|
} catch {
|
|
13386
|
-
console.log(
|
|
13700
|
+
console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
|
|
13387
13701
|
failed.push(ws.name);
|
|
13388
13702
|
}
|
|
13389
13703
|
}
|
|
13390
13704
|
return { closed, failed };
|
|
13391
13705
|
}
|
|
13392
|
-
var shutdownCommand = new
|
|
13706
|
+
var shutdownCommand = new Command14("shutdown").description(
|
|
13393
13707
|
"Shutdown command + all captain workspaces (no args) or one captain workspace"
|
|
13394
13708
|
).argument("[project]", "Project name to shut down captain for").action(async (project) => {
|
|
13395
13709
|
const config = loadConfig();
|
|
@@ -13405,11 +13719,11 @@ var shutdownCommand = new Command13("shutdown").description(
|
|
|
13405
13719
|
const allVariants = /* @__PURE__ */ new Set([...captainVariants, ...commandVariants]);
|
|
13406
13720
|
const squadrantWorkspaces = workspaces.filter((w) => allVariants.has(w.name));
|
|
13407
13721
|
if (squadrantWorkspaces.length === 0) {
|
|
13408
|
-
console.log(
|
|
13722
|
+
console.log(chalk15.yellow("\nNo squadrant workspaces found to close.\n"));
|
|
13409
13723
|
return;
|
|
13410
13724
|
}
|
|
13411
13725
|
console.log(
|
|
13412
|
-
|
|
13726
|
+
chalk15.bold(
|
|
13413
13727
|
`
|
|
13414
13728
|
Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
13415
13729
|
`
|
|
@@ -13429,9 +13743,9 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
13429
13743
|
for (const ws of squadrantWorkspaces) {
|
|
13430
13744
|
try {
|
|
13431
13745
|
await globalRuntime.stop(ws.id);
|
|
13432
|
-
console.log(
|
|
13746
|
+
console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
|
|
13433
13747
|
} catch {
|
|
13434
|
-
console.log(
|
|
13748
|
+
console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
|
|
13435
13749
|
}
|
|
13436
13750
|
}
|
|
13437
13751
|
console.log("");
|
|
@@ -13439,7 +13753,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
13439
13753
|
}
|
|
13440
13754
|
if (!config.projects[project]) {
|
|
13441
13755
|
console.error(
|
|
13442
|
-
|
|
13756
|
+
chalk15.red(
|
|
13443
13757
|
`
|
|
13444
13758
|
\u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
|
|
13445
13759
|
`
|
|
@@ -13450,7 +13764,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
13450
13764
|
const captainName = config.projects[project].captainName;
|
|
13451
13765
|
const runtime = runtimes.forProject(project, config);
|
|
13452
13766
|
console.log(
|
|
13453
|
-
|
|
13767
|
+
chalk15.bold(`
|
|
13454
13768
|
Shutting down captain workspace for '${project}'...
|
|
13455
13769
|
`)
|
|
13456
13770
|
);
|
|
@@ -13474,13 +13788,13 @@ Shutting down captain workspace for '${project}'...
|
|
|
13474
13788
|
|
|
13475
13789
|
// packages/cli/src/commands/feedback.ts
|
|
13476
13790
|
init_dist();
|
|
13477
|
-
import { Command as
|
|
13791
|
+
import { Command as Command15 } from "commander";
|
|
13478
13792
|
import fs23 from "fs";
|
|
13479
13793
|
import os14 from "os";
|
|
13480
13794
|
import path25 from "path";
|
|
13481
13795
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13482
13796
|
import { execSync as execSync12 } from "child_process";
|
|
13483
|
-
import
|
|
13797
|
+
import chalk16 from "chalk";
|
|
13484
13798
|
var REPO_URL = "https://github.com/tu11aa/squadrant";
|
|
13485
13799
|
function readPkgVersion() {
|
|
13486
13800
|
try {
|
|
@@ -13528,21 +13842,21 @@ function buildIssueUrl(metrics, squadrantVersion) {
|
|
|
13528
13842
|
});
|
|
13529
13843
|
return `${REPO_URL}/issues/new?${params.toString()}`;
|
|
13530
13844
|
}
|
|
13531
|
-
var feedbackCommand = new
|
|
13845
|
+
var feedbackCommand = new Command15("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
|
|
13532
13846
|
const config = loadConfig();
|
|
13533
13847
|
const metricsPath = config.metrics?.path || path25.join(os14.homedir(), ".config", "squadrant", "metrics.json");
|
|
13534
13848
|
const metrics = readMetrics(metricsPath);
|
|
13535
13849
|
const version = readStamp(config) ?? readPkgVersion();
|
|
13536
13850
|
const issueUrl = buildIssueUrl(metrics, version);
|
|
13537
|
-
console.log(
|
|
13538
|
-
console.log(
|
|
13851
|
+
console.log(chalk16.bold("\nOpening feedback issue in browser...\n"));
|
|
13852
|
+
console.log(chalk16.dim(` URL: ${issueUrl.substring(0, 80)}...
|
|
13539
13853
|
`));
|
|
13540
13854
|
try {
|
|
13541
13855
|
execSync12(`open "${issueUrl}"`, { stdio: "ignore" });
|
|
13542
|
-
console.log(
|
|
13856
|
+
console.log(chalk16.green(" \u2714 Browser opened\n"));
|
|
13543
13857
|
} catch {
|
|
13544
|
-
console.log(
|
|
13545
|
-
console.log(` Open manually: ${
|
|
13858
|
+
console.log(chalk16.yellow(" \u26A0 Could not open browser automatically."));
|
|
13859
|
+
console.log(` Open manually: ${chalk16.cyan(issueUrl)}
|
|
13546
13860
|
`);
|
|
13547
13861
|
}
|
|
13548
13862
|
});
|
|
@@ -13551,10 +13865,10 @@ var feedbackCommand = new Command14("feedback").description("Open a pre-filled G
|
|
|
13551
13865
|
init_dist();
|
|
13552
13866
|
init_dist();
|
|
13553
13867
|
init_dist3();
|
|
13554
|
-
import { Command as
|
|
13868
|
+
import { Command as Command16 } from "commander";
|
|
13555
13869
|
import fs24 from "fs";
|
|
13556
13870
|
import path26 from "path";
|
|
13557
|
-
import
|
|
13871
|
+
import chalk17 from "chalk";
|
|
13558
13872
|
import matter3 from "gray-matter";
|
|
13559
13873
|
function getDateStr(yesterday) {
|
|
13560
13874
|
return iso(daysAgo(yesterday ? 1 : 0));
|
|
@@ -13584,7 +13898,7 @@ function formatStandup(standups, dateStr, raw) {
|
|
|
13584
13898
|
const lines = [];
|
|
13585
13899
|
const header = `Standup \u2014 ${dateStr}`;
|
|
13586
13900
|
if (!raw) {
|
|
13587
|
-
lines.push(
|
|
13901
|
+
lines.push(chalk17.bold(`
|
|
13588
13902
|
${header}
|
|
13589
13903
|
`));
|
|
13590
13904
|
} else {
|
|
@@ -13597,12 +13911,12 @@ ${header}
|
|
|
13597
13911
|
const tasksTotal = s.status.tasks_total ?? 0;
|
|
13598
13912
|
const tasksInProgress = s.status.tasks_in_progress ?? 0;
|
|
13599
13913
|
if (!raw) {
|
|
13600
|
-
lines.push(
|
|
13914
|
+
lines.push(chalk17.cyan.bold(`## ${s.name}`));
|
|
13601
13915
|
} else {
|
|
13602
13916
|
lines.push(`## ${s.name}`);
|
|
13603
13917
|
}
|
|
13604
13918
|
if (s.gitCommits.length > 0 || tasksDone > 0) {
|
|
13605
|
-
lines.push(!raw ?
|
|
13919
|
+
lines.push(!raw ? chalk17.green("Done:") : "**Done:**");
|
|
13606
13920
|
for (const commit of s.gitCommits) {
|
|
13607
13921
|
lines.push(` - ${commit}`);
|
|
13608
13922
|
}
|
|
@@ -13611,7 +13925,7 @@ ${header}
|
|
|
13611
13925
|
}
|
|
13612
13926
|
}
|
|
13613
13927
|
if (tasksInProgress > 0) {
|
|
13614
|
-
lines.push(!raw ?
|
|
13928
|
+
lines.push(!raw ? chalk17.yellow("In Progress:") : "**In Progress:**");
|
|
13615
13929
|
lines.push(` - ${tasksInProgress} task(s) active`);
|
|
13616
13930
|
}
|
|
13617
13931
|
if (s.dailyLog) {
|
|
@@ -13621,7 +13935,7 @@ ${header}
|
|
|
13621
13935
|
if (match) {
|
|
13622
13936
|
const items = match[1].trim().split("\n").filter((l) => l.trim().startsWith("-"));
|
|
13623
13937
|
if (items.length > 0 && section === "Tomorrow") {
|
|
13624
|
-
lines.push(!raw ?
|
|
13938
|
+
lines.push(!raw ? chalk17.blue("Next:") : "**Next:**");
|
|
13625
13939
|
for (const item of items) lines.push(` ${item.trim()}`);
|
|
13626
13940
|
}
|
|
13627
13941
|
}
|
|
@@ -13629,20 +13943,20 @@ ${header}
|
|
|
13629
13943
|
}
|
|
13630
13944
|
if (s.blockers.length > 0) {
|
|
13631
13945
|
hasBlockers = true;
|
|
13632
|
-
lines.push(!raw ?
|
|
13946
|
+
lines.push(!raw ? chalk17.red("Blocked:") : "**Blocked:**");
|
|
13633
13947
|
for (const b of s.blockers) {
|
|
13634
13948
|
lines.push(` - ${b}`);
|
|
13635
13949
|
}
|
|
13636
13950
|
}
|
|
13637
13951
|
if (s.gitCommits.length === 0 && tasksDone === 0 && !s.dailyLog) {
|
|
13638
|
-
lines.push(!raw ?
|
|
13952
|
+
lines.push(!raw ? chalk17.dim(" (no activity)") : " (no activity)");
|
|
13639
13953
|
}
|
|
13640
13954
|
lines.push("");
|
|
13641
13955
|
}
|
|
13642
13956
|
const totalCommits = standups.reduce((sum, s) => sum + s.gitCommits.length, 0);
|
|
13643
13957
|
const totalDone = standups.reduce((sum, s) => sum + (s.status.tasks_completed ?? 0), 0);
|
|
13644
13958
|
if (!raw) {
|
|
13645
|
-
lines.push(
|
|
13959
|
+
lines.push(chalk17.dim(`--- ${totalCommits} commits, ${totalDone} tasks done${hasBlockers ? ", HAS BLOCKERS" : ""} ---
|
|
13646
13960
|
`));
|
|
13647
13961
|
} else {
|
|
13648
13962
|
lines.push(`---
|
|
@@ -13651,12 +13965,12 @@ ${header}
|
|
|
13651
13965
|
}
|
|
13652
13966
|
return lines.join("\n");
|
|
13653
13967
|
}
|
|
13654
|
-
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) => {
|
|
13655
13969
|
const config = loadConfig();
|
|
13656
13970
|
const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
13657
13971
|
const projects = Object.entries(config.projects);
|
|
13658
13972
|
if (projects.length === 0) {
|
|
13659
|
-
console.log(
|
|
13973
|
+
console.log(chalk17.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
|
|
13660
13974
|
return;
|
|
13661
13975
|
}
|
|
13662
13976
|
const dateStr = getDateStr(!!opts.yesterday);
|
|
@@ -13665,7 +13979,7 @@ var standupCommand = new Command15("standup").description("Generate daily standu
|
|
|
13665
13979
|
if (opts.project) {
|
|
13666
13980
|
const match = projects.find(([name]) => name === opts.project);
|
|
13667
13981
|
if (!match) {
|
|
13668
|
-
console.error(
|
|
13982
|
+
console.error(chalk17.red(`Project "${opts.project}" not found.`));
|
|
13669
13983
|
process.exit(1);
|
|
13670
13984
|
}
|
|
13671
13985
|
targets = [match];
|
|
@@ -13683,10 +13997,10 @@ var standupCommand = new Command15("standup").description("Generate daily standu
|
|
|
13683
13997
|
init_dist();
|
|
13684
13998
|
init_dist();
|
|
13685
13999
|
init_dist3();
|
|
13686
|
-
import { Command as
|
|
14000
|
+
import { Command as Command17 } from "commander";
|
|
13687
14001
|
import fs25 from "fs";
|
|
13688
14002
|
import path27 from "path";
|
|
13689
|
-
import
|
|
14003
|
+
import chalk18 from "chalk";
|
|
13690
14004
|
import matter4 from "gray-matter";
|
|
13691
14005
|
function readStatus(spokeVault) {
|
|
13692
14006
|
const statusFile = path27.join(spokeVault, "status.md");
|
|
@@ -13756,7 +14070,7 @@ function formatRetro(retros, fromStr, toStr, raw) {
|
|
|
13756
14070
|
const lines = [];
|
|
13757
14071
|
const header = `Retro \u2014 ${fromStr} \u2192 ${toStr}`;
|
|
13758
14072
|
lines.push(raw ? `# ${header}
|
|
13759
|
-
` :
|
|
14073
|
+
` : chalk18.bold(`
|
|
13760
14074
|
${header}
|
|
13761
14075
|
`));
|
|
13762
14076
|
let totalCommits = 0;
|
|
@@ -13766,39 +14080,39 @@ ${header}
|
|
|
13766
14080
|
totalCommits += r.commits.length;
|
|
13767
14081
|
totalPRs += r.mergedPRs.length;
|
|
13768
14082
|
totalShipped += r.shipped.length;
|
|
13769
|
-
lines.push(raw ? `## ${r.name}` :
|
|
13770
|
-
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);
|
|
13771
14085
|
if (r.mergedPRs.length > 0) {
|
|
13772
|
-
lines.push(raw ? `**PRs merged:**` :
|
|
14086
|
+
lines.push(raw ? `**PRs merged:**` : chalk18.green("PRs merged:"));
|
|
13773
14087
|
for (const pr of r.mergedPRs) lines.push(` - ${pr}`);
|
|
13774
14088
|
}
|
|
13775
|
-
renderList(lines, r.inProgress, raw, "In Progress",
|
|
13776
|
-
renderList(lines, r.blocked, raw, "Blocked",
|
|
13777
|
-
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);
|
|
13778
14092
|
const metricBits = [
|
|
13779
14093
|
`${r.commits.length} commits`,
|
|
13780
14094
|
`${r.mergedPRs.length} PRs merged`,
|
|
13781
14095
|
`${r.shipped.length} shipped`
|
|
13782
14096
|
];
|
|
13783
|
-
lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` :
|
|
14097
|
+
lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` : chalk18.dim(` ${metricBits.join(" \xB7 ")}`));
|
|
13784
14098
|
if (r.shipped.length === 0 && r.commits.length === 0 && r.mergedPRs.length === 0 && r.inProgress.length === 0 && r.blocked.length === 0) {
|
|
13785
|
-
lines.push(raw ? "_(no activity in this window)_" :
|
|
14099
|
+
lines.push(raw ? "_(no activity in this window)_" : chalk18.dim(" (no activity in this window)"));
|
|
13786
14100
|
}
|
|
13787
14101
|
lines.push("");
|
|
13788
14102
|
}
|
|
13789
14103
|
const summary = `${totalShipped} items shipped \xB7 ${totalCommits} commits \xB7 ${totalPRs} PRs merged`;
|
|
13790
14104
|
lines.push(raw ? `---
|
|
13791
14105
|
*${summary}*
|
|
13792
|
-
` :
|
|
14106
|
+
` : chalk18.dim(`--- ${summary} ---
|
|
13793
14107
|
`));
|
|
13794
14108
|
return lines.join("\n");
|
|
13795
14109
|
}
|
|
13796
|
-
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) => {
|
|
13797
14111
|
const config = loadConfig();
|
|
13798
14112
|
const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
13799
14113
|
const projects = Object.entries(config.projects);
|
|
13800
14114
|
if (projects.length === 0) {
|
|
13801
|
-
console.log(
|
|
14115
|
+
console.log(chalk18.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
|
|
13802
14116
|
return;
|
|
13803
14117
|
}
|
|
13804
14118
|
let windowDays = 7;
|
|
@@ -13815,7 +14129,7 @@ var retroCommand = new Command16("retro").description("Generate a retro (weekly/
|
|
|
13815
14129
|
if (opts.project) {
|
|
13816
14130
|
const match = projects.find(([name]) => name === opts.project);
|
|
13817
14131
|
if (!match) {
|
|
13818
|
-
console.error(
|
|
14132
|
+
console.error(chalk18.red(`Project "${opts.project}" not found.`));
|
|
13819
14133
|
process.exit(1);
|
|
13820
14134
|
}
|
|
13821
14135
|
targets = [match];
|
|
@@ -13831,8 +14145,8 @@ var retroCommand = new Command16("retro").description("Generate a retro (weekly/
|
|
|
13831
14145
|
// packages/cli/src/commands/runtime.ts
|
|
13832
14146
|
init_dist();
|
|
13833
14147
|
init_dist3();
|
|
13834
|
-
import { Command as
|
|
13835
|
-
import
|
|
14148
|
+
import { Command as Command18 } from "commander";
|
|
14149
|
+
import chalk19 from "chalk";
|
|
13836
14150
|
function buildRegistry() {
|
|
13837
14151
|
return new RuntimeRegistry({
|
|
13838
14152
|
cmux: createCmuxDriver()
|
|
@@ -13864,7 +14178,7 @@ async function needRef(resolved) {
|
|
|
13864
14178
|
}
|
|
13865
14179
|
return ref.id;
|
|
13866
14180
|
}
|
|
13867
|
-
var runtimeCommand = new
|
|
14181
|
+
var runtimeCommand = new Command18("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
|
|
13868
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) => {
|
|
13869
14183
|
const config = loadConfig();
|
|
13870
14184
|
const registry = buildRegistry();
|
|
@@ -13879,7 +14193,7 @@ runtimeCommand.command("status").description("Print 'running' or 'stopped' for a
|
|
|
13879
14193
|
process.exit(1);
|
|
13880
14194
|
}
|
|
13881
14195
|
} catch (err) {
|
|
13882
|
-
console.error(
|
|
14196
|
+
console.error(chalk19.red(err.message));
|
|
13883
14197
|
process.exit(2);
|
|
13884
14198
|
}
|
|
13885
14199
|
});
|
|
@@ -13926,9 +14240,9 @@ async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
|
|
|
13926
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) => {
|
|
13927
14241
|
try {
|
|
13928
14242
|
await runRuntimeSend(arg1, arg2, opts);
|
|
13929
|
-
console.log(
|
|
14243
|
+
console.log(chalk19.green("\u2714 Delivered (confirmed)"));
|
|
13930
14244
|
} catch (err) {
|
|
13931
|
-
console.error(
|
|
14245
|
+
console.error(chalk19.red(err.message));
|
|
13932
14246
|
process.exit(1);
|
|
13933
14247
|
}
|
|
13934
14248
|
});
|
|
@@ -13954,7 +14268,7 @@ runtimeCommand.command("read-screen").description("Print a terminal snapshot of
|
|
|
13954
14268
|
const screen = await resolved.driver.readScreen(ref);
|
|
13955
14269
|
process.stdout.write(screen);
|
|
13956
14270
|
} catch (err) {
|
|
13957
|
-
console.error(
|
|
14271
|
+
console.error(chalk19.red(err.message));
|
|
13958
14272
|
process.exit(1);
|
|
13959
14273
|
}
|
|
13960
14274
|
});
|
|
@@ -13965,13 +14279,13 @@ runtimeCommand.command("stop").description("Stop a target workspace").argument("
|
|
|
13965
14279
|
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
13966
14280
|
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
13967
14281
|
if (!ref) {
|
|
13968
|
-
console.log(
|
|
14282
|
+
console.log(chalk19.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
|
|
13969
14283
|
return;
|
|
13970
14284
|
}
|
|
13971
14285
|
await resolved.driver.stop(ref.id);
|
|
13972
|
-
console.log(
|
|
14286
|
+
console.log(chalk19.green(`\u2714 Stopped ${resolved.workspaceName}`));
|
|
13973
14287
|
} catch (err) {
|
|
13974
|
-
console.error(
|
|
14288
|
+
console.error(chalk19.red(err.message));
|
|
13975
14289
|
process.exit(1);
|
|
13976
14290
|
}
|
|
13977
14291
|
});
|
|
@@ -13979,8 +14293,8 @@ runtimeCommand.command("stop").description("Stop a target workspace").argument("
|
|
|
13979
14293
|
// packages/cli/src/commands/workspace.ts
|
|
13980
14294
|
init_dist();
|
|
13981
14295
|
init_dist3();
|
|
13982
|
-
import { Command as
|
|
13983
|
-
import
|
|
14296
|
+
import { Command as Command19 } from "commander";
|
|
14297
|
+
import chalk20 from "chalk";
|
|
13984
14298
|
function buildRegistry2() {
|
|
13985
14299
|
return new WorkspaceRegistry({
|
|
13986
14300
|
obsidian: createObsidianDriver
|
|
@@ -13998,7 +14312,7 @@ async function readStdin() {
|
|
|
13998
14312
|
}
|
|
13999
14313
|
return Buffer.concat(chunks).toString("utf-8");
|
|
14000
14314
|
}
|
|
14001
|
-
var workspaceCommand = new
|
|
14315
|
+
var workspaceCommand = new Command19("workspace").description("Interact with the workspace layer (vault storage). Bridges bash scripts to the WorkspaceDriver.");
|
|
14002
14316
|
function resolveTargetAndPath(arg1, arg2, useHub) {
|
|
14003
14317
|
if (useHub) {
|
|
14004
14318
|
if (arg2 !== void 0) {
|
|
@@ -14020,7 +14334,7 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
|
|
|
14020
14334
|
const content = await driver.read(path30);
|
|
14021
14335
|
process.stdout.write(content);
|
|
14022
14336
|
} catch (err) {
|
|
14023
|
-
console.error(
|
|
14337
|
+
console.error(chalk20.red(err.message));
|
|
14024
14338
|
process.exit(1);
|
|
14025
14339
|
}
|
|
14026
14340
|
});
|
|
@@ -14050,7 +14364,7 @@ workspaceCommand.command("write").description("Write content to a scope-relative
|
|
|
14050
14364
|
const payload = rawContent === "-" ? await readStdin() : rawContent;
|
|
14051
14365
|
await driver.write(path30, payload);
|
|
14052
14366
|
} catch (err) {
|
|
14053
|
-
console.error(
|
|
14367
|
+
console.error(chalk20.red(err.message));
|
|
14054
14368
|
process.exit(1);
|
|
14055
14369
|
}
|
|
14056
14370
|
});
|
|
@@ -14063,7 +14377,7 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
|
|
|
14063
14377
|
const entries = await driver.list(path30);
|
|
14064
14378
|
for (const entry of entries) console.log(entry);
|
|
14065
14379
|
} catch (err) {
|
|
14066
|
-
console.error(
|
|
14380
|
+
console.error(chalk20.red(err.message));
|
|
14067
14381
|
process.exit(1);
|
|
14068
14382
|
}
|
|
14069
14383
|
});
|
|
@@ -14076,7 +14390,7 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
|
|
|
14076
14390
|
const ok2 = await driver.exists(path30);
|
|
14077
14391
|
process.exit(ok2 ? 0 : 1);
|
|
14078
14392
|
} catch (err) {
|
|
14079
|
-
console.error(
|
|
14393
|
+
console.error(chalk20.red(err.message));
|
|
14080
14394
|
process.exit(2);
|
|
14081
14395
|
}
|
|
14082
14396
|
});
|
|
@@ -14088,7 +14402,7 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
|
|
|
14088
14402
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
14089
14403
|
await driver.mkdir(path30);
|
|
14090
14404
|
} catch (err) {
|
|
14091
|
-
console.error(
|
|
14405
|
+
console.error(chalk20.red(err.message));
|
|
14092
14406
|
process.exit(1);
|
|
14093
14407
|
}
|
|
14094
14408
|
});
|
|
@@ -14096,8 +14410,8 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
|
|
|
14096
14410
|
// packages/cli/src/commands/notify.ts
|
|
14097
14411
|
init_dist();
|
|
14098
14412
|
init_dist3();
|
|
14099
|
-
import { Command as
|
|
14100
|
-
import
|
|
14413
|
+
import { Command as Command20 } from "commander";
|
|
14414
|
+
import chalk21 from "chalk";
|
|
14101
14415
|
async function readStdin2() {
|
|
14102
14416
|
const chunks = [];
|
|
14103
14417
|
for await (const chunk of process.stdin) {
|
|
@@ -14105,7 +14419,7 @@ async function readStdin2() {
|
|
|
14105
14419
|
}
|
|
14106
14420
|
return Buffer.concat(chunks).toString("utf-8");
|
|
14107
14421
|
}
|
|
14108
|
-
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) => {
|
|
14109
14423
|
const config = loadConfig();
|
|
14110
14424
|
const registry = new NotifierRegistry({ cmux: createCmuxNotifier });
|
|
14111
14425
|
try {
|
|
@@ -14113,7 +14427,7 @@ var notifyCommand = new Command19("notify").description("Send a message to the u
|
|
|
14113
14427
|
if (!payload) throw new Error("Empty message");
|
|
14114
14428
|
await registry.get(config).notify(payload);
|
|
14115
14429
|
} catch (err) {
|
|
14116
|
-
console.error(
|
|
14430
|
+
console.error(chalk21.red(err.message));
|
|
14117
14431
|
process.exit(1);
|
|
14118
14432
|
}
|
|
14119
14433
|
});
|
|
@@ -14123,8 +14437,8 @@ init_dist();
|
|
|
14123
14437
|
init_dist4();
|
|
14124
14438
|
init_dist3();
|
|
14125
14439
|
init_dist();
|
|
14126
|
-
import { Command as
|
|
14127
|
-
import
|
|
14440
|
+
import { Command as Command21 } from "commander";
|
|
14441
|
+
import chalk22 from "chalk";
|
|
14128
14442
|
import fs26 from "fs";
|
|
14129
14443
|
import path28 from "path";
|
|
14130
14444
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -14171,17 +14485,17 @@ async function runEmit(opts) {
|
|
|
14171
14485
|
for (const dest of emitter.destinations(scope, projectRoot)) {
|
|
14172
14486
|
const result = await emitter.emit(source, dest, { dryRun: opts.dryRun });
|
|
14173
14487
|
if (opts.dryRun) {
|
|
14174
|
-
console.log(
|
|
14488
|
+
console.log(chalk22.cyan(`[${emitter.name}] ${dest.path}`));
|
|
14175
14489
|
console.log(result.diff ?? "(no diff)");
|
|
14176
14490
|
} else if (result.written) {
|
|
14177
14491
|
console.log(
|
|
14178
|
-
|
|
14492
|
+
chalk22.green(
|
|
14179
14493
|
`\u2714 ${emitter.name} \u2192 ${dest.path} (${result.bytesWritten} bytes)`
|
|
14180
14494
|
)
|
|
14181
14495
|
);
|
|
14182
14496
|
emittedCount.written++;
|
|
14183
14497
|
} else {
|
|
14184
|
-
console.log(
|
|
14498
|
+
console.log(chalk22.gray(`- ${emitter.name} \u2192 ${dest.path} (skipped)`));
|
|
14185
14499
|
emittedCount.skipped++;
|
|
14186
14500
|
}
|
|
14187
14501
|
}
|
|
@@ -14204,14 +14518,14 @@ async function runEmit(opts) {
|
|
|
14204
14518
|
`Unknown project '${projectName}'. Available: ${Object.keys(cfg.projects).join(", ") || "(none)"}`
|
|
14205
14519
|
);
|
|
14206
14520
|
}
|
|
14207
|
-
console.error(
|
|
14521
|
+
console.error(chalk22.yellow(`\u26A0 unknown project: ${projectName}`));
|
|
14208
14522
|
continue;
|
|
14209
14523
|
}
|
|
14210
14524
|
const source = await readProjectLevelSource(
|
|
14211
14525
|
createObsidianDriver({ root: proj.path })
|
|
14212
14526
|
);
|
|
14213
14527
|
if (!source) {
|
|
14214
|
-
console.log(
|
|
14528
|
+
console.log(chalk22.gray(`- ${projectName}: no AGENTS.md, skipping`));
|
|
14215
14529
|
continue;
|
|
14216
14530
|
}
|
|
14217
14531
|
for (const name of targets) {
|
|
@@ -14221,21 +14535,21 @@ async function runEmit(opts) {
|
|
|
14221
14535
|
}
|
|
14222
14536
|
if (!opts.dryRun) {
|
|
14223
14537
|
console.log(
|
|
14224
|
-
|
|
14538
|
+
chalk22.bold(
|
|
14225
14539
|
`
|
|
14226
14540
|
Projection complete \u2014 ${emittedCount.written} written, ${emittedCount.skipped} skipped.`
|
|
14227
14541
|
)
|
|
14228
14542
|
);
|
|
14229
14543
|
}
|
|
14230
14544
|
}
|
|
14231
|
-
var projectionCommand = new
|
|
14545
|
+
var projectionCommand = new Command21("projection").description(
|
|
14232
14546
|
"Project squadrant instructions and skills to supported agent formats"
|
|
14233
14547
|
);
|
|
14234
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) => {
|
|
14235
14549
|
try {
|
|
14236
14550
|
await runEmit({ ...opts, dryRun: false });
|
|
14237
14551
|
} catch (err) {
|
|
14238
|
-
console.error(
|
|
14552
|
+
console.error(chalk22.red(err.message));
|
|
14239
14553
|
process.exit(1);
|
|
14240
14554
|
}
|
|
14241
14555
|
});
|
|
@@ -14243,7 +14557,7 @@ projectionCommand.command("diff").description("Preview changes without writing")
|
|
|
14243
14557
|
try {
|
|
14244
14558
|
await runEmit({ ...opts, dryRun: true });
|
|
14245
14559
|
} catch (err) {
|
|
14246
|
-
console.error(
|
|
14560
|
+
console.error(chalk22.red(err.message));
|
|
14247
14561
|
process.exit(1);
|
|
14248
14562
|
}
|
|
14249
14563
|
});
|
|
@@ -14253,7 +14567,7 @@ projectionCommand.command("list").description("List registered projection target
|
|
|
14253
14567
|
const emitter = registry.get(name);
|
|
14254
14568
|
const userDests = emitter.destinations("user").map((d) => d.path);
|
|
14255
14569
|
const projectDests = emitter.destinations("project", "<project>").map((d) => d.path);
|
|
14256
|
-
console.log(
|
|
14570
|
+
console.log(chalk22.bold(name));
|
|
14257
14571
|
console.log(` user: ${userDests.join(", ") || "(none)"}`);
|
|
14258
14572
|
console.log(` project: ${projectDests.join(", ") || "(none)"}`);
|
|
14259
14573
|
}
|
|
@@ -14261,9 +14575,9 @@ projectionCommand.command("list").description("List registered projection target
|
|
|
14261
14575
|
|
|
14262
14576
|
// packages/cli/src/commands/codex-chat-smoke.ts
|
|
14263
14577
|
init_dist4();
|
|
14264
|
-
import { Command as
|
|
14578
|
+
import { Command as Command22 } from "commander";
|
|
14265
14579
|
import { resolve as resolve2 } from "path";
|
|
14266
|
-
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(
|
|
14267
14581
|
"--approval",
|
|
14268
14582
|
"include the approval round-trip (Phase 1 PASS requires this)",
|
|
14269
14583
|
false
|
|
@@ -14332,11 +14646,11 @@ init_dist();
|
|
|
14332
14646
|
init_dist();
|
|
14333
14647
|
init_dist();
|
|
14334
14648
|
init_dist2();
|
|
14335
|
-
import { Command as
|
|
14649
|
+
import { Command as Command23 } from "commander";
|
|
14336
14650
|
import fs27 from "fs";
|
|
14337
14651
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
14338
14652
|
import { dirname as dirname6, join as join25 } from "path";
|
|
14339
|
-
import
|
|
14653
|
+
import chalk23 from "chalk";
|
|
14340
14654
|
function runConfigCheck(opts) {
|
|
14341
14655
|
const raw = JSON.parse(fs27.readFileSync(opts.configPath, "utf-8"));
|
|
14342
14656
|
const def = getDefaultConfig();
|
|
@@ -14391,14 +14705,14 @@ function runConfigSet(key, value, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
14391
14705
|
}
|
|
14392
14706
|
function printRestartOutcome(outcome) {
|
|
14393
14707
|
if (outcome === "skipped-not-running") {
|
|
14394
|
-
console.log(
|
|
14708
|
+
console.log(chalk23.dim("(daemon not running \u2014 change applies on next start)"));
|
|
14395
14709
|
} else if (outcome === "skipped-opt-out") {
|
|
14396
|
-
console.log(
|
|
14710
|
+
console.log(chalk23.dim("(run 'squadrant heal daemon' to apply)"));
|
|
14397
14711
|
}
|
|
14398
14712
|
}
|
|
14399
14713
|
function runConfigSetAction(opts) {
|
|
14400
14714
|
runConfigSet(opts.key, opts.value, opts.configPath);
|
|
14401
|
-
console.log(
|
|
14715
|
+
console.log(chalk23.green(`\u2714 set ${opts.key} = ${opts.value}`));
|
|
14402
14716
|
if (isDaemonCachedKey(opts.key)) {
|
|
14403
14717
|
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
14404
14718
|
const outcome = doRestart({ reason: `config ${opts.key}`, noRestart: opts.noRestart });
|
|
@@ -14406,9 +14720,9 @@ function runConfigSetAction(opts) {
|
|
|
14406
14720
|
}
|
|
14407
14721
|
}
|
|
14408
14722
|
var SEV_COLOR = {
|
|
14409
|
-
info:
|
|
14410
|
-
advisory:
|
|
14411
|
-
warn:
|
|
14723
|
+
info: chalk23.green,
|
|
14724
|
+
advisory: chalk23.yellow,
|
|
14725
|
+
warn: chalk23.red
|
|
14412
14726
|
};
|
|
14413
14727
|
var KIND_GLYPH = {
|
|
14414
14728
|
missing: "+",
|
|
@@ -14420,14 +14734,14 @@ function printItems(items) {
|
|
|
14420
14734
|
for (const i of items) {
|
|
14421
14735
|
const color = SEV_COLOR[i.severity] ?? ((s) => s);
|
|
14422
14736
|
const detail = i.note ? ` (${i.note})` : i.suggested !== void 0 ? ` \u2192 ${JSON.stringify(i.suggested)}` : "";
|
|
14423
|
-
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));
|
|
14424
14738
|
}
|
|
14425
14739
|
}
|
|
14426
|
-
var configCommand = new
|
|
14740
|
+
var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
|
|
14427
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) => {
|
|
14428
14742
|
const pkgVersion = readPkgVersion2();
|
|
14429
14743
|
if (!fs27.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
14430
|
-
console.log(
|
|
14744
|
+
console.log(chalk23.yellow("No config found \u2014 run `squadrant init` first."));
|
|
14431
14745
|
return;
|
|
14432
14746
|
}
|
|
14433
14747
|
const res = runConfigCheck({ configPath: DEFAULT_CONFIG_PATH, pkgVersion, fix: opts.fix, accept: opts.accept });
|
|
@@ -14436,21 +14750,21 @@ configCommand.command("check").description("Detect config drift vs the current d
|
|
|
14436
14750
|
return;
|
|
14437
14751
|
}
|
|
14438
14752
|
if (res.items.length === 0) {
|
|
14439
|
-
console.log(
|
|
14753
|
+
console.log(chalk23.green("\u2714 Config is in sync with the current schema."));
|
|
14440
14754
|
return;
|
|
14441
14755
|
}
|
|
14442
|
-
console.log(
|
|
14756
|
+
console.log(chalk23.bold("\nConfig drift:\n"));
|
|
14443
14757
|
printItems(res.items);
|
|
14444
14758
|
if (opts.fix && res.applied.length) {
|
|
14445
|
-
console.log(
|
|
14759
|
+
console.log(chalk23.green(`
|
|
14446
14760
|
\u2714 Applied ${res.applied.length} safe item(s): ${res.applied.join(", ")}`));
|
|
14447
14761
|
}
|
|
14448
14762
|
const judgment = res.remaining.filter((i) => i.kind === "changed-default" || i.kind === "invalid");
|
|
14449
14763
|
if (judgment.length) {
|
|
14450
|
-
console.log(
|
|
14764
|
+
console.log(chalk23.yellow(`
|
|
14451
14765
|
${judgment.length} item(s) need review \u2014 run the config-doctor skill, or \`squadrant config check --accept\` to keep your values.`));
|
|
14452
14766
|
} else if (res.stamped) {
|
|
14453
|
-
console.log(
|
|
14767
|
+
console.log(chalk23.green("\n\u2714 Config reconciled and stamped."));
|
|
14454
14768
|
}
|
|
14455
14769
|
});
|
|
14456
14770
|
configCommand.command("get").description("Read a config value by dotted key (e.g. defaults.effort)").argument("<key>", "dotted config key").action((key) => {
|
|
@@ -14458,7 +14772,7 @@ configCommand.command("get").description("Read a config value by dotted key (e.g
|
|
|
14458
14772
|
const value = runConfigGet(key);
|
|
14459
14773
|
console.log(typeof value === "string" ? value : JSON.stringify(value));
|
|
14460
14774
|
} catch (e) {
|
|
14461
|
-
console.error(
|
|
14775
|
+
console.error(chalk23.red(e.message));
|
|
14462
14776
|
process.exit(1);
|
|
14463
14777
|
}
|
|
14464
14778
|
});
|
|
@@ -14466,7 +14780,7 @@ configCommand.command("set").description("Write a config value by dotted key (e.
|
|
|
14466
14780
|
try {
|
|
14467
14781
|
runConfigSetAction({ key, value, noRestart: opts.restart === false });
|
|
14468
14782
|
} catch (e) {
|
|
14469
|
-
console.error(
|
|
14783
|
+
console.error(chalk23.red(e.message));
|
|
14470
14784
|
process.exit(1);
|
|
14471
14785
|
}
|
|
14472
14786
|
});
|
|
@@ -14476,8 +14790,8 @@ function readPkgVersion2() {
|
|
|
14476
14790
|
}
|
|
14477
14791
|
|
|
14478
14792
|
// packages/cli/src/commands/heal.ts
|
|
14479
|
-
import { Command as
|
|
14480
|
-
import
|
|
14793
|
+
import { Command as Command24 } from "commander";
|
|
14794
|
+
import chalk24 from "chalk";
|
|
14481
14795
|
init_dist2();
|
|
14482
14796
|
init_dist2();
|
|
14483
14797
|
function buildHealStatus(components) {
|
|
@@ -14518,15 +14832,15 @@ async function runHealStatus(opts) {
|
|
|
14518
14832
|
return result.healthy ? 0 : 2;
|
|
14519
14833
|
}
|
|
14520
14834
|
if (result.healthy) {
|
|
14521
|
-
stdout.write(
|
|
14835
|
+
stdout.write(chalk24.green("\u2714 all components healthy\n"));
|
|
14522
14836
|
return 0;
|
|
14523
14837
|
}
|
|
14524
|
-
stdout.write(
|
|
14838
|
+
stdout.write(chalk24.bold("Unhealthy components:\n\n"));
|
|
14525
14839
|
for (const c of result.components) {
|
|
14526
14840
|
if (c.healCmd) {
|
|
14527
|
-
stdout.write(` ${
|
|
14841
|
+
stdout.write(` ${chalk24.red("\u2718")} ${c.kind.padEnd(8)} ${c.ref.padEnd(16)} ${chalk24.red(c.state.padEnd(8))} ${c.project}
|
|
14528
14842
|
`);
|
|
14529
|
-
stdout.write(` heal: ${
|
|
14843
|
+
stdout.write(` heal: ${chalk24.cyan(c.healCmd)}
|
|
14530
14844
|
`);
|
|
14531
14845
|
}
|
|
14532
14846
|
}
|
|
@@ -14537,7 +14851,7 @@ async function runHealDaemon(opts) {
|
|
|
14537
14851
|
stdout.write("restarting squadrantd via launchd kickstart...\n");
|
|
14538
14852
|
try {
|
|
14539
14853
|
opts.ensureDaemon();
|
|
14540
|
-
stdout.write(
|
|
14854
|
+
stdout.write(chalk24.green("\u2714 daemon kickstart complete\n"));
|
|
14541
14855
|
return 0;
|
|
14542
14856
|
} catch (e) {
|
|
14543
14857
|
stderr.write(`heal daemon failed: ${e.message}
|
|
@@ -14545,8 +14859,8 @@ async function runHealDaemon(opts) {
|
|
|
14545
14859
|
return 1;
|
|
14546
14860
|
}
|
|
14547
14861
|
}
|
|
14548
|
-
var healCommand = new
|
|
14549
|
-
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) => {
|
|
14550
14864
|
const code = await runHealStatus({
|
|
14551
14865
|
project: opts.project,
|
|
14552
14866
|
json: opts.json ?? false,
|
|
@@ -14557,7 +14871,7 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
|
|
|
14557
14871
|
process.exit(code);
|
|
14558
14872
|
})
|
|
14559
14873
|
).addCommand(
|
|
14560
|
-
new
|
|
14874
|
+
new Command24("daemon").description("Restart squadrantd via the idempotent launchd kickstart path").action(async () => {
|
|
14561
14875
|
const code = await runHealDaemon({
|
|
14562
14876
|
ensureDaemon: () => restartDaemonIfRunning({ reason: "heal", isRunning: () => true }),
|
|
14563
14877
|
stdout: process.stdout,
|
|
@@ -14568,15 +14882,15 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
|
|
|
14568
14882
|
);
|
|
14569
14883
|
|
|
14570
14884
|
// packages/cli/src/commands/group.ts
|
|
14571
|
-
import { Command as
|
|
14572
|
-
import
|
|
14885
|
+
import { Command as Command26 } from "commander";
|
|
14886
|
+
import chalk26 from "chalk";
|
|
14573
14887
|
|
|
14574
14888
|
// packages/cli/src/commands/dispatch.ts
|
|
14575
14889
|
init_dist();
|
|
14576
14890
|
init_dist2();
|
|
14577
|
-
import { Command as
|
|
14891
|
+
import { Command as Command25 } from "commander";
|
|
14578
14892
|
import { execSync as execSync13 } from "child_process";
|
|
14579
|
-
import
|
|
14893
|
+
import chalk25 from "chalk";
|
|
14580
14894
|
async function runDispatch(toProject, task, opts) {
|
|
14581
14895
|
const fromProject = resolveCurrentProject(loadConfig());
|
|
14582
14896
|
if (!fromProject) {
|
|
@@ -14601,21 +14915,21 @@ async function runDispatch(toProject, task, opts) {
|
|
|
14601
14915
|
async function dispatchAction(toProject, task, opts) {
|
|
14602
14916
|
try {
|
|
14603
14917
|
const result = await runDispatch(toProject, task, opts);
|
|
14604
|
-
console.log(
|
|
14605
|
-
console.log(
|
|
14606
|
-
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)."));
|
|
14607
14921
|
} catch (e) {
|
|
14608
|
-
console.error(
|
|
14922
|
+
console.error(chalk25.red(`\u2718 ${e.message}`));
|
|
14609
14923
|
process.exit(1);
|
|
14610
14924
|
}
|
|
14611
14925
|
}
|
|
14612
|
-
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);
|
|
14613
14927
|
|
|
14614
14928
|
// packages/cli/src/commands/group.ts
|
|
14615
14929
|
init_dist2();
|
|
14616
|
-
var groupCommand = new
|
|
14617
|
-
new
|
|
14618
|
-
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(
|
|
14619
14933
|
`\u26A0 'squadrant group dispatch' is deprecated \u2014 use 'squadrant dispatch <project> "<task>"' instead.`
|
|
14620
14934
|
));
|
|
14621
14935
|
await dispatchAction(toProject, task, opts);
|
|
@@ -14626,8 +14940,8 @@ var groupCommand = new Command25("group").description("Cross-project intra-group
|
|
|
14626
14940
|
init_dist();
|
|
14627
14941
|
init_dist2();
|
|
14628
14942
|
import { join as join26, dirname as dirname7 } from "path";
|
|
14629
|
-
import { Command as
|
|
14630
|
-
import
|
|
14943
|
+
import { Command as Command27 } from "commander";
|
|
14944
|
+
import chalk27 from "chalk";
|
|
14631
14945
|
init_require_daemon();
|
|
14632
14946
|
async function runPing(project, message) {
|
|
14633
14947
|
const config = loadConfig();
|
|
@@ -14643,20 +14957,20 @@ async function runPing(project, message) {
|
|
|
14643
14957
|
source: "cli"
|
|
14644
14958
|
});
|
|
14645
14959
|
}
|
|
14646
|
-
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) => {
|
|
14647
14961
|
try {
|
|
14648
14962
|
await runPing(project, message);
|
|
14649
|
-
console.log(
|
|
14963
|
+
console.log(chalk27.green(`\u2714 Pinged '${project}'`));
|
|
14650
14964
|
} catch (err) {
|
|
14651
|
-
console.error(
|
|
14965
|
+
console.error(chalk27.red(err.message));
|
|
14652
14966
|
process.exit(1);
|
|
14653
14967
|
}
|
|
14654
14968
|
});
|
|
14655
14969
|
|
|
14656
14970
|
// packages/cli/src/commands/cmux.ts
|
|
14657
14971
|
init_dist();
|
|
14658
|
-
import { Command as
|
|
14659
|
-
import
|
|
14972
|
+
import { Command as Command28 } from "commander";
|
|
14973
|
+
import chalk28 from "chalk";
|
|
14660
14974
|
async function runCmuxAutoconfig(opts) {
|
|
14661
14975
|
const { json, stdout, stderr } = opts;
|
|
14662
14976
|
let r;
|
|
@@ -14675,19 +14989,19 @@ async function runCmuxAutoconfig(opts) {
|
|
|
14675
14989
|
stdout.write(`wrote cmux automation config \u2192 ${r.configPath}
|
|
14676
14990
|
`);
|
|
14677
14991
|
} else {
|
|
14678
|
-
stdout.write(
|
|
14992
|
+
stdout.write(chalk28.dim(`cmux automation config already in place (${r.configPath})
|
|
14679
14993
|
`));
|
|
14680
14994
|
}
|
|
14681
14995
|
if (r.verdict === "reachable") {
|
|
14682
|
-
stdout.write(
|
|
14996
|
+
stdout.write(chalk28.green("\u2714 daemon-direct delivery is reachable \u2014 cmux control socket accepts the daemon\n"));
|
|
14683
14997
|
return 0;
|
|
14684
14998
|
}
|
|
14685
14999
|
if (r.needsRestart) {
|
|
14686
15000
|
stdout.write(
|
|
14687
|
-
|
|
15001
|
+
chalk28.yellow("\u26A0 cmux is still on the old socket mode \u2014 restart cmux to enable daemon-direct delivery.\n")
|
|
14688
15002
|
);
|
|
14689
15003
|
if (r.promptedThisRun) {
|
|
14690
|
-
stdout.write(
|
|
15004
|
+
stdout.write(chalk28.dim(" (one-time prompt \u2014 you won't be nagged again)\n"));
|
|
14691
15005
|
}
|
|
14692
15006
|
return 2;
|
|
14693
15007
|
}
|
|
@@ -14696,8 +15010,8 @@ async function runCmuxAutoconfig(opts) {
|
|
|
14696
15010
|
);
|
|
14697
15011
|
return 1;
|
|
14698
15012
|
}
|
|
14699
|
-
var cmuxCommand = new
|
|
14700
|
-
new
|
|
15013
|
+
var cmuxCommand = new Command28("cmux").description("cmux integration helpers").addCommand(
|
|
15014
|
+
new Command28("autoconfig").description(
|
|
14701
15015
|
"Write the cmux automation socket config and probe whether daemon-direct\ndelivery is reachable. Idempotent; prompts once if a cmux restart is needed."
|
|
14702
15016
|
).option("--json", "output machine-readable JSON (exit 0=reachable, 1=unknown, 2=restart-needed)").action(async (opts) => {
|
|
14703
15017
|
const code = await runCmuxAutoconfig({
|
|
@@ -14714,8 +15028,8 @@ init_dist();
|
|
|
14714
15028
|
init_dist2();
|
|
14715
15029
|
import fs28 from "fs";
|
|
14716
15030
|
import path29 from "path";
|
|
14717
|
-
import { Command as
|
|
14718
|
-
import
|
|
15031
|
+
import { Command as Command29 } from "commander";
|
|
15032
|
+
import chalk29 from "chalk";
|
|
14719
15033
|
var VALID_EFFORTS = ["max", "balance", "low"];
|
|
14720
15034
|
var EFFORT_MEANING = {
|
|
14721
15035
|
max: "tokens are plentiful \u2014 bias crew spawns toward claude/opus",
|
|
@@ -14780,29 +15094,29 @@ async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(
|
|
|
14780
15094
|
}
|
|
14781
15095
|
}
|
|
14782
15096
|
}
|
|
14783
|
-
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) => {
|
|
14784
15098
|
if (value === void 0) {
|
|
14785
15099
|
let result;
|
|
14786
15100
|
try {
|
|
14787
15101
|
result = runEffortGet(void 0, options.project);
|
|
14788
15102
|
} catch (err) {
|
|
14789
|
-
console.error(
|
|
15103
|
+
console.error(chalk29.red(err.message));
|
|
14790
15104
|
process.exit(1);
|
|
14791
15105
|
}
|
|
14792
15106
|
const label = options.project ? `${options.project} project` : "global";
|
|
14793
|
-
console.log(
|
|
14794
|
-
console.log(
|
|
15107
|
+
console.log(chalk29.bold(`Current effort (${label}):`), chalk29.cyan(result.effort));
|
|
15108
|
+
console.log(chalk29.dim(EFFORT_MEANING[result.effort]));
|
|
14795
15109
|
return;
|
|
14796
15110
|
}
|
|
14797
15111
|
try {
|
|
14798
15112
|
runEffortSet(value, void 0, options.project);
|
|
14799
15113
|
} catch (err) {
|
|
14800
|
-
console.error(
|
|
15114
|
+
console.error(chalk29.red(err.message));
|
|
14801
15115
|
process.exit(1);
|
|
14802
15116
|
}
|
|
14803
15117
|
const effort = value;
|
|
14804
|
-
console.log(
|
|
14805
|
-
console.log(
|
|
15118
|
+
console.log(chalk29.green(`\u2714 effort \u2192 ${effort} (${effortScopeLabel(options.project)})`));
|
|
15119
|
+
console.log(chalk29.dim(EFFORT_MEANING[effort]));
|
|
14806
15120
|
try {
|
|
14807
15121
|
const { createCmuxDriver: createCmuxDriver2, RuntimeRegistry: RuntimeRegistry2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports3));
|
|
14808
15122
|
const config = loadConfig();
|
|
@@ -14812,7 +15126,7 @@ var effortCommand = new Command28("effort").description("Get or set the crew tok
|
|
|
14812
15126
|
const append = (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
|
|
14813
15127
|
await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append, options.project);
|
|
14814
15128
|
} catch {
|
|
14815
|
-
console.log(
|
|
15129
|
+
console.log(chalk29.dim("(no running captain detected \u2014 change applies on next launch)"));
|
|
14816
15130
|
}
|
|
14817
15131
|
});
|
|
14818
15132
|
|
|
@@ -14821,8 +15135,8 @@ init_dist();
|
|
|
14821
15135
|
init_dist2();
|
|
14822
15136
|
import { join as join27, dirname as dirname8 } from "path";
|
|
14823
15137
|
import { emitKeypressEvents } from "readline";
|
|
14824
|
-
import { Command as
|
|
14825
|
-
import
|
|
15138
|
+
import { Command as Command30 } from "commander";
|
|
15139
|
+
import chalk30 from "chalk";
|
|
14826
15140
|
function defaultStateRoot() {
|
|
14827
15141
|
return join27(dirname8(DEFAULT_CONFIG_PATH), "state");
|
|
14828
15142
|
}
|
|
@@ -14869,11 +15183,11 @@ async function questionYesNo(prompt) {
|
|
|
14869
15183
|
});
|
|
14870
15184
|
});
|
|
14871
15185
|
}
|
|
14872
|
-
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");
|
|
14873
15187
|
telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
|
|
14874
15188
|
const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
|
|
14875
|
-
console.log(`token: ${tokenSet ?
|
|
14876
|
-
console.log(`supergroup: ${supergroupId ??
|
|
15189
|
+
console.log(`token: ${tokenSet ? chalk30.green("set") : chalk30.yellow("unset")}`);
|
|
15190
|
+
console.log(`supergroup: ${supergroupId ?? chalk30.yellow("unset")}`);
|
|
14877
15191
|
if (links.length === 0) {
|
|
14878
15192
|
console.log("no projects linked");
|
|
14879
15193
|
return;
|
|
@@ -14883,32 +15197,32 @@ telegramCommand.command("status").description("Show Telegram config and linked p
|
|
|
14883
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) => {
|
|
14884
15198
|
const cfg = loadConfig().telegram;
|
|
14885
15199
|
if (!cfg) {
|
|
14886
|
-
console.error(
|
|
15200
|
+
console.error(chalk30.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
|
|
14887
15201
|
process.exit(1);
|
|
14888
15202
|
}
|
|
14889
15203
|
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
14890
15204
|
if (!token) {
|
|
14891
|
-
console.error(
|
|
15205
|
+
console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
14892
15206
|
process.exit(1);
|
|
14893
15207
|
}
|
|
14894
15208
|
const client = createTelegramClient({ token });
|
|
14895
15209
|
const { topicId, created } = await runTelegramLink({ project, cfg, client, stateRoot: defaultStateRoot() });
|
|
14896
|
-
console.log(
|
|
15210
|
+
console.log(chalk30.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
|
|
14897
15211
|
});
|
|
14898
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) => {
|
|
14899
15213
|
if (!process.stdin.isTTY) {
|
|
14900
|
-
console.error(
|
|
15214
|
+
console.error(chalk30.red("setup requires a TTY \u2014 pipe input is not supported"));
|
|
14901
15215
|
process.exit(1);
|
|
14902
15216
|
}
|
|
14903
15217
|
console.log();
|
|
14904
|
-
console.log(
|
|
15218
|
+
console.log(chalk30.bold("Telegram setup") + " \u2014 connect squadrant to a Telegram bot for notifications + remote control");
|
|
14905
15219
|
console.log();
|
|
14906
15220
|
console.log("Before you start you need:");
|
|
14907
15221
|
console.log(" 1. A bot token from @BotFather (send /newbot)");
|
|
14908
15222
|
console.log(" 2. A forum supergroup with the bot added as an admin (Topics enabled)");
|
|
14909
15223
|
console.log(" 3. Bot privacy mode set to OFF (@BotFather \u2192 /setprivacy \u2192 Disable)");
|
|
14910
15224
|
console.log();
|
|
14911
|
-
console.log(
|
|
15225
|
+
console.log(chalk30.bold("Step 1/3 \u2014 Bot token"));
|
|
14912
15226
|
const existingCfg = loadConfig().telegram;
|
|
14913
15227
|
const existingToken = existingCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
14914
15228
|
const decision = resolveSetupToken(existingToken, { resetToken: opts.resetToken ?? false });
|
|
@@ -14920,67 +15234,67 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
|
|
|
14920
15234
|
try {
|
|
14921
15235
|
botUser = await client.getMe();
|
|
14922
15236
|
token = existingToken;
|
|
14923
|
-
console.log(
|
|
15237
|
+
console.log(chalk30.green(`Using existing bot token (@${botUser.username})`));
|
|
14924
15238
|
console.log();
|
|
14925
15239
|
} catch {
|
|
14926
|
-
console.log(
|
|
15240
|
+
console.log(chalk30.yellow("Existing token is invalid \u2014 please enter a new one."));
|
|
14927
15241
|
console.log("Paste your bot token then press Enter (input is hidden):");
|
|
14928
15242
|
token = await questionMasked();
|
|
14929
15243
|
if (!token) {
|
|
14930
|
-
console.error(
|
|
15244
|
+
console.error(chalk30.red("token required"));
|
|
14931
15245
|
process.exit(1);
|
|
14932
15246
|
}
|
|
14933
15247
|
client = createTelegramClient({ token });
|
|
14934
15248
|
try {
|
|
14935
15249
|
botUser = await client.getMe();
|
|
14936
15250
|
} catch (e) {
|
|
14937
|
-
console.error(
|
|
15251
|
+
console.error(chalk30.red(`token rejected: ${e.message}`));
|
|
14938
15252
|
process.exit(1);
|
|
14939
15253
|
}
|
|
14940
|
-
console.log(
|
|
15254
|
+
console.log(chalk30.green(`Connected as @${botUser.username}`));
|
|
14941
15255
|
console.log();
|
|
14942
15256
|
}
|
|
14943
15257
|
} else {
|
|
14944
15258
|
console.log("Paste your bot token then press Enter (input is hidden):");
|
|
14945
15259
|
token = await questionMasked();
|
|
14946
15260
|
if (!token) {
|
|
14947
|
-
console.error(
|
|
15261
|
+
console.error(chalk30.red("token required"));
|
|
14948
15262
|
process.exit(1);
|
|
14949
15263
|
}
|
|
14950
15264
|
client = createTelegramClient({ token });
|
|
14951
15265
|
try {
|
|
14952
15266
|
botUser = await client.getMe();
|
|
14953
15267
|
} catch (e) {
|
|
14954
|
-
console.error(
|
|
15268
|
+
console.error(chalk30.red(`token rejected: ${e.message}`));
|
|
14955
15269
|
process.exit(1);
|
|
14956
15270
|
}
|
|
14957
|
-
console.log(
|
|
15271
|
+
console.log(chalk30.green(`Connected as @${botUser.username}`));
|
|
14958
15272
|
console.log();
|
|
14959
15273
|
}
|
|
14960
|
-
console.log(
|
|
15274
|
+
console.log(chalk30.bold("Step 2/3 \u2014 Supergroup"));
|
|
14961
15275
|
const groupDecision = resolveSetupGroup(existingCfg?.supergroupId, { redetect: opts.redetect ?? false });
|
|
14962
15276
|
let supergroupId;
|
|
14963
15277
|
let detectedUserId;
|
|
14964
15278
|
if (groupDecision === "reuse") {
|
|
14965
15279
|
supergroupId = existingCfg.supergroupId;
|
|
14966
|
-
console.log(
|
|
15280
|
+
console.log(chalk30.green(`Using existing group: ${supergroupId}`));
|
|
14967
15281
|
console.log();
|
|
14968
15282
|
} else {
|
|
14969
15283
|
console.log("Add the bot to your forum supergroup, then send any message in it.");
|
|
14970
|
-
console.log(
|
|
15284
|
+
console.log(chalk30.dim("Waiting for a message (up to 60s)\u2026"));
|
|
14971
15285
|
try {
|
|
14972
15286
|
({ supergroupId, userId: detectedUserId } = await detectGroupAndUser(client, { timeoutMs: 6e4 }));
|
|
14973
15287
|
} catch {
|
|
14974
|
-
console.error(
|
|
14975
|
-
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"));
|
|
14976
15290
|
process.exit(1);
|
|
14977
15291
|
}
|
|
14978
|
-
console.log(
|
|
15292
|
+
console.log(chalk30.green(`Found group: ${supergroupId}`));
|
|
14979
15293
|
console.log();
|
|
14980
15294
|
}
|
|
14981
|
-
console.log(
|
|
14982
|
-
console.log(
|
|
14983
|
-
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)."));
|
|
14984
15298
|
const finalUserId = resolveSetupUserId(opts.userId, detectedUserId, defaultStateRoot());
|
|
14985
15299
|
let users;
|
|
14986
15300
|
let remoteControl;
|
|
@@ -14994,32 +15308,32 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
|
|
|
14994
15308
|
remoteControl = true;
|
|
14995
15309
|
}
|
|
14996
15310
|
} else if (groupDecision === "detect") {
|
|
14997
|
-
console.log(
|
|
14998
|
-
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."));
|
|
14999
15313
|
printedRemoteControlState = true;
|
|
15000
15314
|
} else {
|
|
15001
15315
|
const existingUsers = existingCfg?.users;
|
|
15002
15316
|
if (existingUsers && existingUsers.length > 0) {
|
|
15003
|
-
console.log(
|
|
15317
|
+
console.log(chalk30.dim(`Remote control: already configured (user-id ${existingUsers[0]}). Use --user-id to update.`));
|
|
15004
15318
|
} else {
|
|
15005
|
-
console.log(
|
|
15319
|
+
console.log(chalk30.dim("Remote control: off. Re-run with --user-id <id> to enable."));
|
|
15006
15320
|
}
|
|
15007
15321
|
printedRemoteControlState = true;
|
|
15008
15322
|
}
|
|
15009
15323
|
writeTelegramConfig(DEFAULT_CONFIG_PATH, { token, supergroupId, users, remoteControl });
|
|
15010
|
-
console.log(
|
|
15324
|
+
console.log(chalk30.green(`Wrote telegram config \u2014 token: ${maskToken(token)} group: ${supergroupId}`));
|
|
15011
15325
|
if (!printedRemoteControlState) {
|
|
15012
15326
|
if (remoteControl) {
|
|
15013
|
-
console.log(
|
|
15327
|
+
console.log(chalk30.green(`Remote control: ON (allowlisted user-id ${users[0]})`));
|
|
15014
15328
|
} else {
|
|
15015
|
-
console.log(
|
|
15329
|
+
console.log(chalk30.dim("Remote control: off (default). Re-run with --user-id <id> to enable."));
|
|
15016
15330
|
}
|
|
15017
15331
|
}
|
|
15018
15332
|
try {
|
|
15019
15333
|
await runRegisterCommands({ client });
|
|
15020
|
-
console.log(
|
|
15334
|
+
console.log(chalk30.dim("Registered the /command menu."));
|
|
15021
15335
|
} catch (e) {
|
|
15022
|
-
console.log(
|
|
15336
|
+
console.log(chalk30.yellow(`command-menu registration skipped: ${e.message}`));
|
|
15023
15337
|
}
|
|
15024
15338
|
const topics = loadState(defaultStateRoot()).topics;
|
|
15025
15339
|
const topicEntries = Object.entries(topics);
|
|
@@ -15028,28 +15342,28 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
|
|
|
15028
15342
|
const project = key.slice(0, key.indexOf("::"));
|
|
15029
15343
|
return `${project}\u2192${id}`;
|
|
15030
15344
|
}).join(", ");
|
|
15031
|
-
console.log(
|
|
15345
|
+
console.log(chalk30.dim(`Existing topics: ${summary} (already created \u2014 not recreated)`));
|
|
15032
15346
|
} else {
|
|
15033
|
-
console.log(
|
|
15347
|
+
console.log(chalk30.dim("No project topics yet \u2014 they're created on first delivery or via: squadrant telegram link <project>"));
|
|
15034
15348
|
}
|
|
15035
15349
|
runTelegramPostSetup({});
|
|
15036
15350
|
console.log();
|
|
15037
|
-
console.log(`Next: ${
|
|
15351
|
+
console.log(`Next: ${chalk30.cyan("squadrant telegram link <project>")}`);
|
|
15038
15352
|
});
|
|
15039
15353
|
telegramCommand.command("register-commands").description("Register (or re-register) the bot's / command menu with Telegram").action(async () => {
|
|
15040
15354
|
const cfg = loadConfig().telegram;
|
|
15041
15355
|
if (!cfg) {
|
|
15042
|
-
console.error(
|
|
15356
|
+
console.error(chalk30.red("telegram config absent \u2014 run: squadrant telegram setup"));
|
|
15043
15357
|
process.exit(1);
|
|
15044
15358
|
}
|
|
15045
15359
|
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
15046
15360
|
if (!token) {
|
|
15047
|
-
console.error(
|
|
15361
|
+
console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
15048
15362
|
process.exit(1);
|
|
15049
15363
|
}
|
|
15050
15364
|
const client = createTelegramClient({ token });
|
|
15051
15365
|
await runRegisterCommands({ client });
|
|
15052
|
-
console.log(
|
|
15366
|
+
console.log(chalk30.green(`registered ${BOT_COMMANDS.length} bot commands`));
|
|
15053
15367
|
});
|
|
15054
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) => {
|
|
15055
15369
|
const stateRoot = defaultStateRoot();
|
|
@@ -15060,7 +15374,7 @@ telegramCommand.command("notify").argument("[project]", "project to toggle").arg
|
|
|
15060
15374
|
return;
|
|
15061
15375
|
}
|
|
15062
15376
|
for (const r of rows) {
|
|
15063
|
-
console.log(` ${r.project}: ${r.active ?
|
|
15377
|
+
console.log(` ${r.project}: ${r.active ? chalk30.green("on") : chalk30.dim("off (muted)")}`);
|
|
15064
15378
|
}
|
|
15065
15379
|
return;
|
|
15066
15380
|
}
|
|
@@ -15069,53 +15383,53 @@ telegramCommand.command("notify").argument("[project]", "project to toggle").arg
|
|
|
15069
15383
|
const token = tgCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
15070
15384
|
if (state === "crew" || state === "cap") {
|
|
15071
15385
|
if (value === void 0) {
|
|
15072
|
-
console.error(
|
|
15386
|
+
console.error(chalk30.red(`usage: squadrant telegram notify <project> ${state} <value>`));
|
|
15073
15387
|
process.exit(1);
|
|
15074
15388
|
}
|
|
15075
15389
|
const resolved2 = resolveNotify(globalNotify, loadProjectOverride(project));
|
|
15076
15390
|
const before2 = { ...resolved2, active: isNotifyActive(stateRoot, project) };
|
|
15077
15391
|
const res = runTelegramNotifyPref({ project, dimension: state, value });
|
|
15078
15392
|
if (!res.ok) {
|
|
15079
|
-
console.error(
|
|
15393
|
+
console.error(chalk30.red(res.message));
|
|
15080
15394
|
process.exit(1);
|
|
15081
15395
|
}
|
|
15082
|
-
console.log(
|
|
15396
|
+
console.log(chalk30.green(`${project} ${state} = ${value}`));
|
|
15083
15397
|
const after2 = state === "crew" ? { ...before2, crew: value } : { ...before2, cap: value === "on" };
|
|
15084
15398
|
if (tgCfg && token) {
|
|
15085
15399
|
const client = createTelegramClient({ token });
|
|
15086
15400
|
const sent = await runNotifyConfirmation({ project, before: before2, after: after2, cfg: tgCfg, client, stateRoot });
|
|
15087
|
-
if (sent) console.log(
|
|
15401
|
+
if (sent) console.log(chalk30.dim(`\u2192 notified ${project} topic`));
|
|
15088
15402
|
}
|
|
15089
15403
|
return;
|
|
15090
15404
|
}
|
|
15091
15405
|
if (state !== "on" && state !== "off") {
|
|
15092
|
-
console.error(
|
|
15406
|
+
console.error(chalk30.red("usage: squadrant telegram notify <project> <on|off|crew <tier>|cap <on|off>>"));
|
|
15093
15407
|
process.exit(1);
|
|
15094
15408
|
}
|
|
15095
15409
|
const resolved = resolveNotify(globalNotify, loadProjectOverride(project));
|
|
15096
15410
|
const before = { ...resolved, active: isNotifyActive(stateRoot, project) };
|
|
15097
15411
|
const after = { ...before, active: state === "on" };
|
|
15098
15412
|
runTelegramNotifySet({ project, active: state === "on", stateRoot });
|
|
15099
|
-
console.log(
|
|
15413
|
+
console.log(chalk30.green(`${project} notifications ${state === "on" ? "ON" : "OFF"}`));
|
|
15100
15414
|
if (tgCfg && token) {
|
|
15101
15415
|
const client = createTelegramClient({ token });
|
|
15102
15416
|
const sent = await runNotifyConfirmation({ project, before, after, cfg: tgCfg, client, stateRoot });
|
|
15103
|
-
if (sent) console.log(
|
|
15417
|
+
if (sent) console.log(chalk30.dim(`\u2192 notified ${project} topic`));
|
|
15104
15418
|
}
|
|
15105
15419
|
});
|
|
15106
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) => {
|
|
15107
15421
|
const cfg = loadConfig().telegram;
|
|
15108
15422
|
if (!cfg) {
|
|
15109
|
-
console.error(
|
|
15423
|
+
console.error(chalk30.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
|
|
15110
15424
|
process.exit(1);
|
|
15111
15425
|
}
|
|
15112
15426
|
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
15113
15427
|
if (!token) {
|
|
15114
|
-
console.error(
|
|
15428
|
+
console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
15115
15429
|
process.exit(1);
|
|
15116
15430
|
}
|
|
15117
15431
|
if (!capAllowed(project, cfg.notify)) {
|
|
15118
|
-
console.log(
|
|
15432
|
+
console.log(chalk30.dim(`${project}: captain messages muted (cap=off) \u2014 not sent`));
|
|
15119
15433
|
return;
|
|
15120
15434
|
}
|
|
15121
15435
|
let message;
|
|
@@ -15128,19 +15442,19 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
|
|
|
15128
15442
|
for await (const line of rl) lines.push(line);
|
|
15129
15443
|
message = lines.join("\n").trimEnd();
|
|
15130
15444
|
if (!message) {
|
|
15131
|
-
console.error(
|
|
15445
|
+
console.error(chalk30.red("no message provided (stdin was empty)"));
|
|
15132
15446
|
process.exit(1);
|
|
15133
15447
|
}
|
|
15134
15448
|
} else {
|
|
15135
|
-
console.error(
|
|
15449
|
+
console.error(chalk30.red("message required \u2014 pass as argument or pipe via stdin"));
|
|
15136
15450
|
process.exit(1);
|
|
15137
15451
|
}
|
|
15138
15452
|
const client = createTelegramClient({ token });
|
|
15139
15453
|
try {
|
|
15140
15454
|
const { chatId, topicId } = await runTelegramSend({ project, message, cfg, client, stateRoot: defaultStateRoot() });
|
|
15141
|
-
console.log(
|
|
15455
|
+
console.log(chalk30.green(`sent to group ${chatId} topic ${topicId}`));
|
|
15142
15456
|
} catch (e) {
|
|
15143
|
-
console.error(
|
|
15457
|
+
console.error(chalk30.red(e.message));
|
|
15144
15458
|
process.exit(1);
|
|
15145
15459
|
}
|
|
15146
15460
|
});
|
|
@@ -15148,7 +15462,7 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
|
|
|
15148
15462
|
// packages/cli/src/commands/hooks.ts
|
|
15149
15463
|
init_dist2();
|
|
15150
15464
|
init_dist4();
|
|
15151
|
-
import { Command as
|
|
15465
|
+
import { Command as Command31 } from "commander";
|
|
15152
15466
|
import { join as join28 } from "path";
|
|
15153
15467
|
import { homedir as homedir20 } from "os";
|
|
15154
15468
|
var SOCK4 = join28(homedir20(), ".config", "squadrant", "squadrant.sock");
|
|
@@ -15175,7 +15489,7 @@ function mapHookSub(sub, payload, taskId) {
|
|
|
15175
15489
|
}
|
|
15176
15490
|
}
|
|
15177
15491
|
function hooksCommand() {
|
|
15178
|
-
const hooks = new
|
|
15492
|
+
const hooks = new Command31("hooks").description("(internal) receive lifecycle hook events from agent processes");
|
|
15179
15493
|
hooks.command("claude <sub>", { hidden: true }).description("internal: bridge a NativeHookSource claude hook to squadrantd").action(async (sub) => {
|
|
15180
15494
|
const taskId = process.env.SQUADRANT_CREW_TASK_ID;
|
|
15181
15495
|
const project = process.env.SQUADRANT_CREW_PROJECT;
|
|
@@ -15246,7 +15560,7 @@ if (process.argv[2] !== "config") {
|
|
|
15246
15560
|
if (!process.env.SQUADRANT_DAEMON_SKIP) {
|
|
15247
15561
|
ensureDaemon();
|
|
15248
15562
|
}
|
|
15249
|
-
var program = new
|
|
15563
|
+
var program = new Command32();
|
|
15250
15564
|
program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
|
|
15251
15565
|
program.addCommand(doctorCommand);
|
|
15252
15566
|
program.addCommand(initCommand);
|
|
@@ -15255,6 +15569,7 @@ program.addCommand(statusCommand);
|
|
|
15255
15569
|
addControlPlaneCrewCommands(crewCommand);
|
|
15256
15570
|
program.addCommand(crewCommand);
|
|
15257
15571
|
program.addCommand(sideCommand);
|
|
15572
|
+
program.addCommand(diffCommand);
|
|
15258
15573
|
program.addCommand(commandCommand);
|
|
15259
15574
|
program.addCommand(dashboardCommand);
|
|
15260
15575
|
program.addCommand(launchCommand);
|