squadrant 0.16.4 → 0.16.6
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 +698 -309
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +160 -38
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/captain-ops/SKILL.md +23 -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 };
|
|
@@ -1320,6 +1323,11 @@ function nextPendingTool(current, ev, now) {
|
|
|
1320
1323
|
return void 0;
|
|
1321
1324
|
return current;
|
|
1322
1325
|
}
|
|
1326
|
+
function nextPendingMonitor(current, ev, now) {
|
|
1327
|
+
if (ev.note === "agent.hook.PreToolUse" && ev.tool === "Monitor")
|
|
1328
|
+
return { since: now };
|
|
1329
|
+
return current;
|
|
1330
|
+
}
|
|
1323
1331
|
function reduce(rec, ev, now) {
|
|
1324
1332
|
if (ev.type === "task.reopened") {
|
|
1325
1333
|
return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type };
|
|
@@ -1336,20 +1344,23 @@ function reduce(rec, ev, now) {
|
|
|
1336
1344
|
sessionId: ev.sessionId ?? rec.sessionId,
|
|
1337
1345
|
question: void 0,
|
|
1338
1346
|
// resuming after a blocked→reply clears the question
|
|
1339
|
-
pendingTool: void 0
|
|
1347
|
+
pendingTool: void 0,
|
|
1340
1348
|
// #354: a new turn closes any prior tool window
|
|
1349
|
+
pendingMonitor: void 0
|
|
1350
|
+
// #594a: same reset — a new turn moots any prior watch
|
|
1341
1351
|
};
|
|
1342
1352
|
case "task.progress": {
|
|
1343
1353
|
const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1354
|
+
const pendingMonitor = nextPendingMonitor(rec.pendingMonitor, ev, now);
|
|
1355
|
+
if (isStickyAttention(rec.state))
|
|
1356
|
+
return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool, pendingMonitor };
|
|
1357
|
+
const b = { ...base, pendingTool, pendingMonitor };
|
|
1347
1358
|
if (rec.state === "awaiting-input" || rec.state === "stalled")
|
|
1348
1359
|
return { ...stampAttempt(b, {}, now), state: "working" };
|
|
1349
1360
|
return stampAttempt(b, {}, now);
|
|
1350
1361
|
}
|
|
1351
1362
|
case "heartbeat":
|
|
1352
|
-
if (rec.state
|
|
1363
|
+
if (isStickyAttention(rec.state))
|
|
1353
1364
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1354
1365
|
if (rec.state === "awaiting-input")
|
|
1355
1366
|
return { ...base, state: "working" };
|
|
@@ -1357,8 +1368,13 @@ function reduce(rec, ev, now) {
|
|
|
1357
1368
|
case "task.blocked":
|
|
1358
1369
|
if (rec.state === "blocked")
|
|
1359
1370
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1360
|
-
return { ...base, state: "blocked", question: ev.question, pendingTool: void 0 };
|
|
1371
|
+
return { ...base, state: "blocked", question: ev.question, pendingTool: void 0, pendingMonitor: void 0 };
|
|
1372
|
+
case "task.review":
|
|
1373
|
+
return { ...base, state: "review", reviewNote: ev.message, pendingTool: void 0, pendingMonitor: void 0 };
|
|
1361
1374
|
case "task.done":
|
|
1375
|
+
if (rec.state === "review" && ev.source !== "approve") {
|
|
1376
|
+
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1377
|
+
}
|
|
1362
1378
|
return { ...base, state: "done", resultRef: ev.resultRef, parseWarning: ev.parseWarning };
|
|
1363
1379
|
case "task.failed":
|
|
1364
1380
|
return { ...base, state: "failed", error: ev.error, exitCode: ev.exitCode };
|
|
@@ -1369,19 +1385,19 @@ function reduce(rec, ev, now) {
|
|
|
1369
1385
|
case "task.session":
|
|
1370
1386
|
return stampAttempt(base, { resumeRef: ev.resumeRef }, now);
|
|
1371
1387
|
case "task.turn.started":
|
|
1372
|
-
return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0 };
|
|
1388
|
+
return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0, pendingMonitor: void 0 };
|
|
1373
1389
|
case "task.turn.completed":
|
|
1374
|
-
if (rec.state
|
|
1390
|
+
if (isStickyAttention(rec.state))
|
|
1375
1391
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1376
|
-
if (rec.pendingTool)
|
|
1392
|
+
if (rec.pendingTool || rec.pendingMonitor)
|
|
1377
1393
|
return stampAttempt(base, {}, now);
|
|
1378
|
-
return { ...stampAttempt(base, {}, now), state: "awaiting-input", pendingTool: void 0 };
|
|
1394
|
+
return { ...stampAttempt(base, {}, now), state: "awaiting-input", pendingTool: void 0, pendingMonitor: void 0 };
|
|
1379
1395
|
case "task.delta":
|
|
1380
1396
|
return stampAttempt(base, {}, now);
|
|
1381
1397
|
// heartbeat-only
|
|
1382
1398
|
case "task.input.requested":
|
|
1383
1399
|
case "task.approval.requested":
|
|
1384
|
-
return { ...stampAttempt(base, {}, now), state: "blocked", question: ev.question, pendingTool: void 0 };
|
|
1400
|
+
return { ...stampAttempt(base, {}, now), state: "blocked", question: ev.question, pendingTool: void 0, pendingMonitor: void 0 };
|
|
1385
1401
|
case "task.reattached":
|
|
1386
1402
|
return stampAttempt(base, {}, now);
|
|
1387
1403
|
case "task.first-turn.confirmed":
|
|
@@ -1406,15 +1422,21 @@ var init_state_machine = __esm({
|
|
|
1406
1422
|
});
|
|
1407
1423
|
|
|
1408
1424
|
// packages/core/dist/watchdog.js
|
|
1409
|
-
function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS) {
|
|
1425
|
+
function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS, monitorStallMs = MONITOR_STALL_BUDGET_MS) {
|
|
1410
1426
|
if (rec.state !== "working")
|
|
1411
1427
|
return null;
|
|
1412
1428
|
if (rec.mode === "interactive") {
|
|
1413
|
-
if (
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
return
|
|
1417
|
-
|
|
1429
|
+
if (rec.pendingTool) {
|
|
1430
|
+
if (now - rec.pendingTool.since <= toolStallMs)
|
|
1431
|
+
return null;
|
|
1432
|
+
return { ...rec, state: "stalled", lastEvent: "watchdog.tool-stall" };
|
|
1433
|
+
}
|
|
1434
|
+
if (rec.pendingMonitor) {
|
|
1435
|
+
if (now - rec.pendingMonitor.since <= monitorStallMs)
|
|
1436
|
+
return null;
|
|
1437
|
+
return { ...rec, state: "stalled", lastEvent: "watchdog.monitor-stall" };
|
|
1438
|
+
}
|
|
1439
|
+
return null;
|
|
1418
1440
|
}
|
|
1419
1441
|
const liveness = rec.attempts.at(-1)?.lastHeartbeatAt ?? rec.lastHeartbeat;
|
|
1420
1442
|
if (now - liveness <= rec.heartbeatBudgetMs)
|
|
@@ -1424,12 +1446,13 @@ function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS) {
|
|
|
1424
1446
|
function recoverStall(rec, now) {
|
|
1425
1447
|
if (rec.state !== "stalled")
|
|
1426
1448
|
return null;
|
|
1427
|
-
return { ...rec, state: "working", lastHeartbeat: now, lastEvent: "watchdog.recover", pendingTool: void 0 };
|
|
1449
|
+
return { ...rec, state: "working", lastHeartbeat: now, lastEvent: "watchdog.recover", pendingTool: void 0, pendingMonitor: void 0 };
|
|
1428
1450
|
}
|
|
1429
|
-
var TOOL_STALL_BUDGET_MS;
|
|
1451
|
+
var TOOL_STALL_BUDGET_MS, MONITOR_STALL_BUDGET_MS;
|
|
1430
1452
|
var init_watchdog = __esm({
|
|
1431
1453
|
"packages/core/dist/watchdog.js"() {
|
|
1432
1454
|
TOOL_STALL_BUDGET_MS = 10 * 60 * 1e3;
|
|
1455
|
+
MONITOR_STALL_BUDGET_MS = 60 * 60 * 1e3;
|
|
1433
1456
|
}
|
|
1434
1457
|
});
|
|
1435
1458
|
|
|
@@ -1454,6 +1477,10 @@ function formatMessage(rec, event) {
|
|
|
1454
1477
|
}
|
|
1455
1478
|
case "blocked":
|
|
1456
1479
|
return `CREW BLOCKED ${tag}: ${(rec.question ?? "(no question)").trim()}`;
|
|
1480
|
+
case "review": {
|
|
1481
|
+
const note = (rec.reviewNote ?? "").trim();
|
|
1482
|
+
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.`;
|
|
1483
|
+
}
|
|
1457
1484
|
case "failed":
|
|
1458
1485
|
return `CREW FAILED ${tag}: ${(rec.error ?? "(no error)").trim()}`;
|
|
1459
1486
|
case "stalled": {
|
|
@@ -1699,7 +1726,7 @@ function createDaemon(deps) {
|
|
|
1699
1726
|
store.delete(r.project, r.id);
|
|
1700
1727
|
continue;
|
|
1701
1728
|
}
|
|
1702
|
-
if (!TERMINAL_STATES.has(r.state)) {
|
|
1729
|
+
if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {
|
|
1703
1730
|
const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
|
|
1704
1731
|
if (t - r.createdAt > ceiling) {
|
|
1705
1732
|
const prevState = r.state;
|
|
@@ -1744,7 +1771,7 @@ function createDaemon(deps) {
|
|
|
1744
1771
|
const idle = evaluateStall(r, t);
|
|
1745
1772
|
if (idle) {
|
|
1746
1773
|
store.put(idle);
|
|
1747
|
-
const synthEvent = idle.pendingTool ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: idle.pendingTool.name, elapsedMs: t - idle.pendingTool.since } : { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs };
|
|
1774
|
+
const synthEvent = idle.pendingTool ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: idle.pendingTool.name, elapsedMs: t - idle.pendingTool.since } : idle.pendingMonitor ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: "Monitor", elapsedMs: t - idle.pendingMonitor.since } : { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs };
|
|
1748
1775
|
firePush(deps, r.project, r.state, idle, synthEvent, lastCaptainTurnAt.get(r.id));
|
|
1749
1776
|
continue;
|
|
1750
1777
|
}
|
|
@@ -1829,14 +1856,15 @@ var init_reduce = __esm({
|
|
|
1829
1856
|
DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1e3;
|
|
1830
1857
|
TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1831
1858
|
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"]);
|
|
1859
|
+
ATTENTION_STATES = /* @__PURE__ */ new Set(["done", "blocked", "review", "failed", "stalled", "awaiting-input"]);
|
|
1860
|
+
REAPABLE_SURFACE_STATES = /* @__PURE__ */ new Set(["working", "stalled", "awaiting-input", "blocked", "review"]);
|
|
1834
1861
|
IDLE_DEBOUNCE_MS = 12e3;
|
|
1835
1862
|
KNOWN_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1836
1863
|
"task.started",
|
|
1837
1864
|
"task.progress",
|
|
1838
1865
|
"heartbeat",
|
|
1839
1866
|
"task.blocked",
|
|
1867
|
+
"task.review",
|
|
1840
1868
|
"task.done",
|
|
1841
1869
|
"task.failed",
|
|
1842
1870
|
"task.session",
|
|
@@ -3306,9 +3334,11 @@ var init_defer_delivery = __esm({
|
|
|
3306
3334
|
"packages/core/dist/delivery/defer-delivery.js"() {
|
|
3307
3335
|
DeferDelivery = class extends Error {
|
|
3308
3336
|
draft;
|
|
3309
|
-
|
|
3337
|
+
reason;
|
|
3338
|
+
constructor(draft = null, reason = "draft") {
|
|
3310
3339
|
super("deferred: captain composing");
|
|
3311
3340
|
this.draft = draft;
|
|
3341
|
+
this.reason = reason;
|
|
3312
3342
|
this.name = "DeferDelivery";
|
|
3313
3343
|
}
|
|
3314
3344
|
};
|
|
@@ -3332,6 +3362,7 @@ var init_captain_delivery = __esm({
|
|
|
3332
3362
|
deferCounts = /* @__PURE__ */ new Map();
|
|
3333
3363
|
lastContent = /* @__PURE__ */ new Map();
|
|
3334
3364
|
stableCounts = /* @__PURE__ */ new Map();
|
|
3365
|
+
lastReason = /* @__PURE__ */ new Map();
|
|
3335
3366
|
constructor(opts) {
|
|
3336
3367
|
this.opts = opts;
|
|
3337
3368
|
}
|
|
@@ -3354,29 +3385,40 @@ var init_captain_delivery = __esm({
|
|
|
3354
3385
|
this.deferCounts.delete(seq);
|
|
3355
3386
|
this.stableCounts.delete(seq);
|
|
3356
3387
|
this.lastContent.delete(seq);
|
|
3388
|
+
this.lastReason.delete(seq);
|
|
3357
3389
|
return { delivered: true };
|
|
3358
3390
|
} catch (e) {
|
|
3359
3391
|
if (e instanceof DeferDelivery) {
|
|
3360
3392
|
this.deferCounts.set(seq, deferCount + 1);
|
|
3361
3393
|
const content = e.draft;
|
|
3394
|
+
let stableCount;
|
|
3362
3395
|
if (content && content === this.lastContent.get(seq)) {
|
|
3363
|
-
|
|
3396
|
+
stableCount = (this.stableCounts.get(seq) ?? 0) + 1;
|
|
3397
|
+
this.stableCounts.set(seq, stableCount);
|
|
3364
3398
|
} else {
|
|
3399
|
+
stableCount = 0;
|
|
3365
3400
|
this.stableCounts.set(seq, 0);
|
|
3366
3401
|
}
|
|
3367
3402
|
this.lastContent.set(seq, content);
|
|
3368
|
-
|
|
3403
|
+
const reason = e.reason !== "draft" ? e.reason : stableCount >= this.opts.stableProbePolls ? "stable" : "draft";
|
|
3404
|
+
this.lastReason.set(seq, reason);
|
|
3405
|
+
return { deferred: true, reason };
|
|
3369
3406
|
}
|
|
3370
|
-
|
|
3407
|
+
this.lastReason.set(seq, "unknown");
|
|
3408
|
+
return { deferred: true, reason: "unknown" };
|
|
3371
3409
|
}
|
|
3372
3410
|
}
|
|
3373
3411
|
/** Read-only. Never mutates — safe to poll from the snapshot assembler every tick. */
|
|
3374
3412
|
stats() {
|
|
3375
3413
|
let maxDeferCount = 0;
|
|
3376
|
-
|
|
3377
|
-
|
|
3414
|
+
let reason;
|
|
3415
|
+
for (const [seq, c] of this.deferCounts) {
|
|
3416
|
+
if (c > maxDeferCount) {
|
|
3378
3417
|
maxDeferCount = c;
|
|
3379
|
-
|
|
3418
|
+
reason = this.lastReason.get(seq);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
return { maxDeferCount, stuck: maxDeferCount >= this.opts.maxDefers, reason };
|
|
3380
3422
|
}
|
|
3381
3423
|
};
|
|
3382
3424
|
}
|
|
@@ -3476,6 +3518,10 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3476
3518
|
const notifyFault = ctx.notifyFault ?? (() => {
|
|
3477
3519
|
});
|
|
3478
3520
|
const defaultNotify = async (args) => {
|
|
3521
|
+
const fresh = store.get(args.project, args.record.id);
|
|
3522
|
+
if (fresh && TERMINAL_STATES.has(fresh.state) && fresh.state !== args.record.state) {
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3479
3525
|
try {
|
|
3480
3526
|
await appendToMailbox({
|
|
3481
3527
|
stateRoot,
|
|
@@ -3565,16 +3611,19 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3565
3611
|
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=delivered`);
|
|
3566
3612
|
await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
|
|
3567
3613
|
} else {
|
|
3568
|
-
|
|
3614
|
+
const { maxDeferCount } = d.stats();
|
|
3615
|
+
if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {
|
|
3616
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);
|
|
3617
|
+
}
|
|
3569
3618
|
break;
|
|
3570
3619
|
}
|
|
3571
3620
|
}
|
|
3572
3621
|
const stuck = d.stats().stuck;
|
|
3573
3622
|
if (stuck && !stuckNotified.has(project)) {
|
|
3574
3623
|
stuckNotified.add(project);
|
|
3575
|
-
const { maxDeferCount } = d.stats();
|
|
3576
|
-
log(`delivery stuck project=${project} deferCount=${maxDeferCount}`);
|
|
3577
|
-
const text = `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
|
|
3624
|
+
const { maxDeferCount, reason } = d.stats();
|
|
3625
|
+
log(`delivery stuck project=${project} deferCount=${maxDeferCount} reason=${reason ?? "unknown"}`);
|
|
3626
|
+
const text = reason === "modal" ? `\u26A0\uFE0F DELIVERY STUCK: a modal question is open in your captain pane and has blocked pending notification(s) for ${maxDeferCount}+ retries. This keeps retrying safely and will deliver automatically once you answer or dismiss it.` : `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
|
|
3578
3627
|
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`delivery stuck alert failed project=${project}: ${e.message}`));
|
|
3579
3628
|
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
3580
3629
|
telegramBridge?.pushRaw(project, text);
|
|
@@ -4309,8 +4358,12 @@ var init_commands = __esm({
|
|
|
4309
4358
|
build: (a) => a[0] ? ok("crews", ["crew", "list", a[0]]) : usage("crews", "usage: /crews <project>")
|
|
4310
4359
|
},
|
|
4311
4360
|
launch: {
|
|
4361
|
+
// --headless (#586, same reason as #520 on the boot-if-down path): runCommand
|
|
4362
|
+
// execs this argv from the daemon, which has no CMUX_WORKSPACE_ID and no
|
|
4363
|
+
// terminal — a plain `launch` would open the cmux GUI app and exit 0 before
|
|
4364
|
+
// the workspace is ever launched.
|
|
4312
4365
|
usage: "/launch <project>",
|
|
4313
|
-
build: (a) => a[0] ? ok("launch", ["launch", a[0]]) : usage("launch", "usage: /launch <project>")
|
|
4366
|
+
build: (a) => a[0] ? ok("launch", ["launch", a[0], "--headless"]) : usage("launch", "usage: /launch <project>")
|
|
4314
4367
|
},
|
|
4315
4368
|
effort: {
|
|
4316
4369
|
usage: "/effort [max|balance|low]",
|
|
@@ -4484,6 +4537,9 @@ ${ev.message}` : "");
|
|
|
4484
4537
|
case "task.blocked":
|
|
4485
4538
|
return `\u{1F6A7} [${project}] CREW BLOCKED \xB7 ${ev.id}
|
|
4486
4539
|
${ev.question}`;
|
|
4540
|
+
case "task.review":
|
|
4541
|
+
return `\u{1F440} [${project}] CREW REVIEW \xB7 ${ev.id}` + (ev.message ? `
|
|
4542
|
+
${ev.message}` : "");
|
|
4487
4543
|
case "task.idle":
|
|
4488
4544
|
return `\u{1F4A4} [${project}] CREW IDLE \xB7 ${ev.id}`;
|
|
4489
4545
|
case "task.failed":
|
|
@@ -4716,6 +4772,7 @@ var init_tiers = __esm({
|
|
|
4716
4772
|
ALERTS = /* @__PURE__ */ new Set([
|
|
4717
4773
|
...DONE_ONLY,
|
|
4718
4774
|
"task.blocked",
|
|
4775
|
+
"task.review",
|
|
4719
4776
|
"task.approval.requested",
|
|
4720
4777
|
"task.input.requested",
|
|
4721
4778
|
"task.timeout"
|
|
@@ -5932,7 +5989,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
|
5932
5989
|
if (task) {
|
|
5933
5990
|
if (TERMINAL_STATES.has(task.state)) {
|
|
5934
5991
|
await deps.emitEvent(project, { type: "task.reopened", id: task.id });
|
|
5935
|
-
} else if (task.state === "blocked" || task.state === "awaiting-input") {
|
|
5992
|
+
} else if (task.state === "blocked" || task.state === "awaiting-input" || task.state === "review") {
|
|
5936
5993
|
await deps.emitEvent(project, { type: "task.started", id: task.id });
|
|
5937
5994
|
}
|
|
5938
5995
|
}
|
|
@@ -6058,6 +6115,7 @@ __export(dist_exports2, {
|
|
|
6058
6115
|
GROUP_DISPATCH_WARMUP_TIMEOUT_MS: () => GROUP_DISPATCH_WARMUP_TIMEOUT_MS,
|
|
6059
6116
|
IDLE_DEBOUNCE_MS: () => IDLE_DEBOUNCE_MS,
|
|
6060
6117
|
LABEL: () => LABEL,
|
|
6118
|
+
MONITOR_STALL_BUDGET_MS: () => MONITOR_STALL_BUDGET_MS,
|
|
6061
6119
|
PROBE_QUIET_MS: () => PROBE_QUIET_MS,
|
|
6062
6120
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
6063
6121
|
STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
|
|
@@ -6127,6 +6185,7 @@ __export(dist_exports2, {
|
|
|
6127
6185
|
isDaemonSocketLive: () => isDaemonSocketLive,
|
|
6128
6186
|
isNotifyActive: () => isNotifyActive,
|
|
6129
6187
|
isSideTitle: () => isSideTitle,
|
|
6188
|
+
isStickyAttention: () => isStickyAttention,
|
|
6130
6189
|
isTurnAccepted: () => isTurnAccepted,
|
|
6131
6190
|
kickstartArgv: () => kickstartArgv,
|
|
6132
6191
|
launchOneWorkspace: () => launchOneWorkspace,
|
|
@@ -6277,6 +6336,18 @@ function cmux(args) {
|
|
|
6277
6336
|
);
|
|
6278
6337
|
});
|
|
6279
6338
|
}
|
|
6339
|
+
function cmuxStdin(args, input) {
|
|
6340
|
+
return new Promise((resolve3, reject) => {
|
|
6341
|
+
const child = execFile2(resolveCmuxBin(), args, { encoding: "utf-8", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: "1" } }, (err, stdout) => {
|
|
6342
|
+
if (err) {
|
|
6343
|
+
reject(err.code === "ETIMEDOUT" ? new CmuxTimeoutError(args.join(" ")) : err);
|
|
6344
|
+
return;
|
|
6345
|
+
}
|
|
6346
|
+
resolve3(stdout.trim());
|
|
6347
|
+
});
|
|
6348
|
+
child.stdin.end(input);
|
|
6349
|
+
});
|
|
6350
|
+
}
|
|
6280
6351
|
function parseList(output) {
|
|
6281
6352
|
let parsed;
|
|
6282
6353
|
try {
|
|
@@ -6633,9 +6704,9 @@ function createCmuxDriver() {
|
|
|
6633
6704
|
}
|
|
6634
6705
|
const draft = parseDraftFromScreen(screen);
|
|
6635
6706
|
if (draft === null)
|
|
6636
|
-
throw new DeferDelivery(null);
|
|
6707
|
+
throw new DeferDelivery(null, "no-box");
|
|
6637
6708
|
if (hasModalOptionList(screen))
|
|
6638
|
-
throw new DeferDelivery(null);
|
|
6709
|
+
throw new DeferDelivery(null, "modal");
|
|
6639
6710
|
if (draft === "") {
|
|
6640
6711
|
await deliver();
|
|
6641
6712
|
return;
|
|
@@ -6676,6 +6747,37 @@ function createCmuxDriver() {
|
|
|
6676
6747
|
}
|
|
6677
6748
|
throw new DeferDelivery(draft);
|
|
6678
6749
|
},
|
|
6750
|
+
async showDiff(opts) {
|
|
6751
|
+
const source = opts.source ?? "branch";
|
|
6752
|
+
const args = ["diff"];
|
|
6753
|
+
if (source === "staged") {
|
|
6754
|
+
args.push("--staged");
|
|
6755
|
+
} else if (source === "unstaged") {
|
|
6756
|
+
args.push("--unstaged");
|
|
6757
|
+
} else {
|
|
6758
|
+
args.push("--branch", "--base", opts.base);
|
|
6759
|
+
if (opts.lastTurn)
|
|
6760
|
+
args.push("--last-turn");
|
|
6761
|
+
}
|
|
6762
|
+
args.push("--cwd", opts.cwd, "--workspace", opts.workspaceId, "--layout", opts.layout ?? "split");
|
|
6763
|
+
if (opts.title)
|
|
6764
|
+
args.push("--title", opts.title);
|
|
6765
|
+
if (opts.focus === false)
|
|
6766
|
+
args.push("--no-focus");
|
|
6767
|
+
else
|
|
6768
|
+
args.push("--focus", "true");
|
|
6769
|
+
await cmux(args);
|
|
6770
|
+
},
|
|
6771
|
+
async showPatch(opts) {
|
|
6772
|
+
const args = ["diff", "-", "--workspace", opts.workspaceId, "--layout", opts.layout ?? "split"];
|
|
6773
|
+
if (opts.title)
|
|
6774
|
+
args.push("--title", opts.title);
|
|
6775
|
+
if (opts.focus === false)
|
|
6776
|
+
args.push("--no-focus");
|
|
6777
|
+
else
|
|
6778
|
+
args.push("--focus", "true");
|
|
6779
|
+
await cmuxStdin(args, opts.patch);
|
|
6780
|
+
},
|
|
6679
6781
|
async listSurfaces(workspaceId) {
|
|
6680
6782
|
let output;
|
|
6681
6783
|
try {
|
|
@@ -7429,6 +7531,7 @@ function installClaudeHooks(opts = {}) {
|
|
|
7429
7531
|
});
|
|
7430
7532
|
let settings = {};
|
|
7431
7533
|
const raw = readFile6(settingsPath);
|
|
7534
|
+
const hadExistingSettings = raw !== void 0;
|
|
7432
7535
|
if (raw) {
|
|
7433
7536
|
try {
|
|
7434
7537
|
settings = JSON.parse(raw);
|
|
@@ -7441,6 +7544,7 @@ function installClaudeHooks(opts = {}) {
|
|
|
7441
7544
|
}
|
|
7442
7545
|
const hooks = settings.hooks;
|
|
7443
7546
|
let changed = false;
|
|
7547
|
+
const repaired = [];
|
|
7444
7548
|
for (const [eventName, sub, matcher] of CLAUDE_HOOK_EVENTS) {
|
|
7445
7549
|
if (!Array.isArray(hooks[eventName])) {
|
|
7446
7550
|
hooks[eventName] = [];
|
|
@@ -7452,6 +7556,26 @@ function installClaudeHooks(opts = {}) {
|
|
|
7452
7556
|
if (!alreadyPresent) {
|
|
7453
7557
|
entries.push({ matcher: hookMatcher, hooks: [{ type: "command", command, timeout: 10 }] });
|
|
7454
7558
|
changed = true;
|
|
7559
|
+
repaired.push(`${eventName}/${sub}`);
|
|
7560
|
+
}
|
|
7561
|
+
}
|
|
7562
|
+
if (repaired.length > 0 && hadExistingSettings) {
|
|
7563
|
+
log(`native-hook: repaired ${repaired.length} missing squadrant hook(s) in ${settingsPath} [${repaired.join(", ")}] \u2014 WARNING: blocked-signalling or lifecycle tracking may have been broken until this run`);
|
|
7564
|
+
}
|
|
7565
|
+
if (opts.claudeEnv && Object.keys(opts.claudeEnv).length > 0) {
|
|
7566
|
+
if (typeof settings.env !== "object" || settings.env === null || Array.isArray(settings.env)) {
|
|
7567
|
+
settings.env = {};
|
|
7568
|
+
}
|
|
7569
|
+
const env = settings.env;
|
|
7570
|
+
for (const [key, value] of Object.entries(opts.claudeEnv)) {
|
|
7571
|
+
if (key in env) {
|
|
7572
|
+
if (env[key] !== value) {
|
|
7573
|
+
log(`native-hook: claudeEnv key '${key}' already set to '${String(env[key])}' in ${settingsPath} \u2014 not overwriting with '${value}'`);
|
|
7574
|
+
}
|
|
7575
|
+
continue;
|
|
7576
|
+
}
|
|
7577
|
+
env[key] = value;
|
|
7578
|
+
changed = true;
|
|
7455
7579
|
}
|
|
7456
7580
|
}
|
|
7457
7581
|
if (changed) {
|
|
@@ -7526,9 +7650,9 @@ var init_native_hook_source = __esm({
|
|
|
7526
7650
|
cache = /* @__PURE__ */ new Map();
|
|
7527
7651
|
active = false;
|
|
7528
7652
|
constructor(opts = {}) {
|
|
7529
|
-
this.hookInstall = opts.hookInstall ?? {};
|
|
7530
7653
|
this.log = opts.log ?? (() => {
|
|
7531
7654
|
});
|
|
7655
|
+
this.hookInstall = { log: this.log, ...opts.hookInstall };
|
|
7532
7656
|
}
|
|
7533
7657
|
start(deps) {
|
|
7534
7658
|
this.deps = deps;
|
|
@@ -9944,7 +10068,7 @@ var init_require_daemon = __esm({
|
|
|
9944
10068
|
// packages/cli/src/index.ts
|
|
9945
10069
|
init_dist();
|
|
9946
10070
|
init_dist2();
|
|
9947
|
-
import { Command as
|
|
10071
|
+
import { Command as Command32 } from "commander";
|
|
9948
10072
|
import { readFileSync as readFileSync15, existsSync as existsSync12, writeFileSync as writeFileSync11 } from "fs";
|
|
9949
10073
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
9950
10074
|
import { dirname as dirname9, join as join29 } from "path";
|
|
@@ -10975,6 +11099,7 @@ init_dist4();
|
|
|
10975
11099
|
import { Command as Command8 } from "commander";
|
|
10976
11100
|
import { createConnection as createConnection3 } from "net";
|
|
10977
11101
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
11102
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
10978
11103
|
import { homedir as homedir16 } from "os";
|
|
10979
11104
|
import { join as join21 } from "path";
|
|
10980
11105
|
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "fs";
|
|
@@ -11374,6 +11499,8 @@ function buildSignalRequest(signal, o) {
|
|
|
11374
11499
|
};
|
|
11375
11500
|
} else if (signal === "blocked") {
|
|
11376
11501
|
event = { type: "task.blocked", id: taskId, reason: "crew signaled blocked", question: o.question ?? "" };
|
|
11502
|
+
} else if (signal === "review") {
|
|
11503
|
+
event = { type: "task.review", id: taskId, ...o.message !== void 0 ? { message: o.message } : {} };
|
|
11377
11504
|
} else {
|
|
11378
11505
|
event = { type: "task.failed", id: taskId, error: o.error ?? "crew signaled failed" };
|
|
11379
11506
|
}
|
|
@@ -11402,6 +11529,63 @@ async function runCrewSignal(signal, o, deps) {
|
|
|
11402
11529
|
const req = buildSignalRequest(signal, { ...o, writeResult: o.writeResult ?? defaultWriteResult });
|
|
11403
11530
|
await deps.call(req);
|
|
11404
11531
|
}
|
|
11532
|
+
function resolveApproveTarget(tasks, crew) {
|
|
11533
|
+
const matches = tasks.filter((t) => t.name === crew);
|
|
11534
|
+
if (matches.length === 0) return null;
|
|
11535
|
+
return matches.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
|
|
11536
|
+
}
|
|
11537
|
+
function defaultPushBranch(cwd, branch) {
|
|
11538
|
+
execFileSync6("git", ["-C", cwd, "push", "-u", "origin", branch], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
11539
|
+
}
|
|
11540
|
+
function defaultCreatePr(cwd, o) {
|
|
11541
|
+
return execFileSync6(
|
|
11542
|
+
"gh",
|
|
11543
|
+
["pr", "create", "--base", o.base, "--head", o.branch, "--title", o.title, "--body", o.body],
|
|
11544
|
+
{ cwd, encoding: "utf-8" }
|
|
11545
|
+
).trim();
|
|
11546
|
+
}
|
|
11547
|
+
function defaultGetCommitSubject(cwd) {
|
|
11548
|
+
try {
|
|
11549
|
+
return execFileSync6("git", ["-C", cwd, "log", "-1", "--format=%s"], {
|
|
11550
|
+
encoding: "utf-8",
|
|
11551
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
11552
|
+
}).trim();
|
|
11553
|
+
} catch {
|
|
11554
|
+
return "";
|
|
11555
|
+
}
|
|
11556
|
+
}
|
|
11557
|
+
async function runCrewApprove(project, crew, deps) {
|
|
11558
|
+
const config = loadConfig();
|
|
11559
|
+
const proj = config.projects[project];
|
|
11560
|
+
if (!proj) throw new Error(`Project '${project}' not found. Run 'squadrant projects list'.`);
|
|
11561
|
+
const tasks = await deps.call({ kind: "list", project });
|
|
11562
|
+
const task = resolveApproveTarget(tasks, crew);
|
|
11563
|
+
if (!task) throw new Error(`Crew '${crew}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
11564
|
+
if (task.state !== "review") {
|
|
11565
|
+
throw new Error(
|
|
11566
|
+
`Crew '${crew}' is not awaiting review (state=${task.state}). Only a crew that signaled 'review' can be approved.`
|
|
11567
|
+
);
|
|
11568
|
+
}
|
|
11569
|
+
const cwd = task.cwd ?? proj.path;
|
|
11570
|
+
const base = resolveWorktreeBase(proj.path);
|
|
11571
|
+
const branch = crewBranch(crew);
|
|
11572
|
+
const body = (task.reviewNote ?? task.task ?? "").trim();
|
|
11573
|
+
const getCommitSubject = deps.getCommitSubject ?? defaultGetCommitSubject;
|
|
11574
|
+
const commitSubject = getCommitSubject(cwd).trim();
|
|
11575
|
+
const reviewNoteFirstLine = (task.reviewNote ?? "").split(/\r?\n/)[0].trim();
|
|
11576
|
+
const taskFirstLine = (task.task ?? branch).split(/\r?\n/)[0].trim();
|
|
11577
|
+
const title = (commitSubject || reviewNoteFirstLine || taskFirstLine).slice(0, 100);
|
|
11578
|
+
const pushBranch = deps.pushBranch ?? defaultPushBranch;
|
|
11579
|
+
const createPr = deps.createPr ?? defaultCreatePr;
|
|
11580
|
+
pushBranch(cwd, branch);
|
|
11581
|
+
const prUrl = createPr(cwd, { base, branch, title, body });
|
|
11582
|
+
await deps.call({
|
|
11583
|
+
kind: "event",
|
|
11584
|
+
project,
|
|
11585
|
+
event: { type: "task.done", id: task.id, resultRef: "", message: `Approved \u2014 PR opened: ${prUrl}`, source: "approve" }
|
|
11586
|
+
});
|
|
11587
|
+
return prUrl;
|
|
11588
|
+
}
|
|
11405
11589
|
function addControlPlaneCrewCommands(crew) {
|
|
11406
11590
|
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
11591
|
const req = buildDispatchRequest({ project, task, provider: opts.provider, mode: opts.mode, cwd: opts.cwd });
|
|
@@ -11476,9 +11660,9 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11476
11660
|
}
|
|
11477
11661
|
process.exit(0);
|
|
11478
11662
|
});
|
|
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)
|
|
11663
|
+
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) => {
|
|
11664
|
+
if (state !== "done" && state !== "blocked" && state !== "failed" && state !== "review") {
|
|
11665
|
+
process.stderr.write(`unknown signal '${state}' (expected: done|blocked|failed|review)
|
|
11482
11666
|
`);
|
|
11483
11667
|
process.exit(2);
|
|
11484
11668
|
}
|
|
@@ -11494,6 +11678,17 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11494
11678
|
process.exit(0);
|
|
11495
11679
|
} catch (e) {
|
|
11496
11680
|
process.stderr.write(`${e.message}
|
|
11681
|
+
`);
|
|
11682
|
+
process.exit(1);
|
|
11683
|
+
}
|
|
11684
|
+
});
|
|
11685
|
+
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) => {
|
|
11686
|
+
try {
|
|
11687
|
+
const prUrl = await runCrewApprove(project, crewName, { call: squadrantdCall });
|
|
11688
|
+
process.stdout.write(`\u2714 Approved ${crewBranch(crewName)} \u2014 pushed + PR opened: ${prUrl}
|
|
11689
|
+
`);
|
|
11690
|
+
} catch (e) {
|
|
11691
|
+
process.stderr.write(`${e.message}
|
|
11497
11692
|
`);
|
|
11498
11693
|
process.exit(1);
|
|
11499
11694
|
}
|
|
@@ -11651,6 +11846,199 @@ crewCommand.command("close").description("Shutdown a crew session (closes its ta
|
|
|
11651
11846
|
}
|
|
11652
11847
|
});
|
|
11653
11848
|
|
|
11849
|
+
// packages/cli/src/commands/diff.ts
|
|
11850
|
+
init_dist();
|
|
11851
|
+
init_dist3();
|
|
11852
|
+
import { Command as Command10 } from "commander";
|
|
11853
|
+
import chalk10 from "chalk";
|
|
11854
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
11855
|
+
import readline2 from "readline";
|
|
11856
|
+
function resolveDiffTarget(tasks, crew, projectPath) {
|
|
11857
|
+
const matches = tasks.filter((t) => t.name === crew);
|
|
11858
|
+
if (matches.length === 0) return null;
|
|
11859
|
+
const task = matches.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
|
|
11860
|
+
const cwd = task.cwd ?? projectPath;
|
|
11861
|
+
return { cwd, isShared: cwd === projectPath };
|
|
11862
|
+
}
|
|
11863
|
+
function resolveDiffSources(opts) {
|
|
11864
|
+
if (opts.working) return ["unstaged", "staged"];
|
|
11865
|
+
if (opts.staged) return ["staged"];
|
|
11866
|
+
if (opts.unstaged) return ["unstaged"];
|
|
11867
|
+
return [];
|
|
11868
|
+
}
|
|
11869
|
+
function resolveDiffMode(crew, opts) {
|
|
11870
|
+
const requestedModes = [crew !== void 0, opts.pr !== void 0, opts.base !== void 0 || opts.head !== void 0 || opts.against !== void 0];
|
|
11871
|
+
if (requestedModes.filter(Boolean).length > 1) {
|
|
11872
|
+
throw new Error(
|
|
11873
|
+
"squadrant diff: a crew argument, --pr, and --base/--head/--against are mutually exclusive."
|
|
11874
|
+
);
|
|
11875
|
+
}
|
|
11876
|
+
if (opts.against !== void 0 && (opts.base !== void 0 || opts.head !== void 0)) {
|
|
11877
|
+
throw new Error("squadrant diff: --against cannot be combined with --base/--head.");
|
|
11878
|
+
}
|
|
11879
|
+
if (crew !== void 0) return { mode: "crew", crew };
|
|
11880
|
+
if (opts.pr !== void 0) return { mode: "pr", pr: opts.pr };
|
|
11881
|
+
if (opts.against !== void 0) return { mode: "refs", base: opts.against, head: "HEAD" };
|
|
11882
|
+
if (opts.base !== void 0 || opts.head !== void 0) {
|
|
11883
|
+
if (opts.base === void 0 || opts.head === void 0) {
|
|
11884
|
+
throw new Error(
|
|
11885
|
+
"squadrant diff: --base and --head must be used together (or use --against <ref> to diff against HEAD)."
|
|
11886
|
+
);
|
|
11887
|
+
}
|
|
11888
|
+
return { mode: "refs", base: opts.base, head: opts.head };
|
|
11889
|
+
}
|
|
11890
|
+
return { mode: "pick" };
|
|
11891
|
+
}
|
|
11892
|
+
function buildCrewDiffStats(tasks, projectPath, base, getStat) {
|
|
11893
|
+
const live = /* @__PURE__ */ new Map();
|
|
11894
|
+
for (const t of tasks) {
|
|
11895
|
+
if (!t.name || TERMINAL_STATES.has(t.state)) continue;
|
|
11896
|
+
const prev = live.get(t.name);
|
|
11897
|
+
if (!prev || (t.createdAt ?? 0) > (prev.createdAt ?? 0)) live.set(t.name, t);
|
|
11898
|
+
}
|
|
11899
|
+
return [...live.values()].map((t) => {
|
|
11900
|
+
const cwd = t.cwd ?? projectPath;
|
|
11901
|
+
return { name: t.name, cwd, stat: getStat(cwd, base).trim() };
|
|
11902
|
+
});
|
|
11903
|
+
}
|
|
11904
|
+
function parseCrewPick(raw, stats) {
|
|
11905
|
+
const trimmed = raw.trim();
|
|
11906
|
+
const idx = Number(trimmed);
|
|
11907
|
+
if (Number.isInteger(idx) && idx >= 1 && idx <= stats.length) {
|
|
11908
|
+
return stats[idx - 1].name;
|
|
11909
|
+
}
|
|
11910
|
+
const byName = stats.find((s) => s.name === trimmed);
|
|
11911
|
+
if (byName) return byName.name;
|
|
11912
|
+
throw new Error(`Invalid selection '${raw}'. Enter a number 1-${stats.length} or a crew name.`);
|
|
11913
|
+
}
|
|
11914
|
+
function promptLine2(question) {
|
|
11915
|
+
return new Promise((resolve3) => {
|
|
11916
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
11917
|
+
rl.question(question, (answer) => {
|
|
11918
|
+
rl.close();
|
|
11919
|
+
resolve3(answer);
|
|
11920
|
+
});
|
|
11921
|
+
});
|
|
11922
|
+
}
|
|
11923
|
+
function getPrDiff(projectPath, pr) {
|
|
11924
|
+
try {
|
|
11925
|
+
return execFileSync7("gh", ["pr", "diff", pr], { cwd: projectPath, encoding: "utf-8" });
|
|
11926
|
+
} catch (e) {
|
|
11927
|
+
throw new Error(`Could not fetch PR #${pr} diff (gh pr diff failed): ${e.message}`);
|
|
11928
|
+
}
|
|
11929
|
+
}
|
|
11930
|
+
function getRefsDiff(projectPath, base, head) {
|
|
11931
|
+
try {
|
|
11932
|
+
return execFileSync7("git", ["-C", projectPath, "diff", `${base}...${head}`], { encoding: "utf-8" });
|
|
11933
|
+
} catch (e) {
|
|
11934
|
+
throw new Error(`Could not diff ${base}...${head}: ${e.message}`);
|
|
11935
|
+
}
|
|
11936
|
+
}
|
|
11937
|
+
async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
|
|
11938
|
+
const tasks = await squadrantdCall({ kind: "list", project });
|
|
11939
|
+
const target = resolveDiffTarget(tasks, crew, proj.path);
|
|
11940
|
+
if (!target) {
|
|
11941
|
+
throw new Error(`Crew '${crew}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
11942
|
+
}
|
|
11943
|
+
const base = resolveWorktreeBase(proj.path);
|
|
11944
|
+
const branchLabel = crewBranch(crew);
|
|
11945
|
+
if (!runtime.showDiff) {
|
|
11946
|
+
throw new Error(`Runtime '${runtime.name}' has no native diff viewer yet \u2014 Phase 1 supports cmux only.`);
|
|
11947
|
+
}
|
|
11948
|
+
const sources = resolveDiffSources(opts);
|
|
11949
|
+
if (sources.length > 0) {
|
|
11950
|
+
let opened = 0;
|
|
11951
|
+
for (const source of sources) {
|
|
11952
|
+
const statArgs = source === "staged" ? ["diff", "--stat", "--cached"] : ["diff", "--stat"];
|
|
11953
|
+
const stat2 = execFileSync7("git", ["-C", target.cwd, ...statArgs], { encoding: "utf-8" }).trim();
|
|
11954
|
+
if (!stat2) continue;
|
|
11955
|
+
await runtime.showDiff({
|
|
11956
|
+
workspaceId,
|
|
11957
|
+
cwd: target.cwd,
|
|
11958
|
+
base,
|
|
11959
|
+
title: `${branchLabel} \u2014 ${source}`,
|
|
11960
|
+
layout: opts.layout,
|
|
11961
|
+
focus: opts.focus,
|
|
11962
|
+
source
|
|
11963
|
+
});
|
|
11964
|
+
opened++;
|
|
11965
|
+
}
|
|
11966
|
+
if (opened === 0) {
|
|
11967
|
+
const label = sources.length > 1 ? "staged or unstaged" : sources[0];
|
|
11968
|
+
console.log(`No ${label} changes on ${branchLabel}.`);
|
|
11969
|
+
} else {
|
|
11970
|
+
console.log(chalk10.dim(`Opened ${opened} working-tree diff(s) (${sources.join(", ")}) for ${branchLabel}.`));
|
|
11971
|
+
}
|
|
11972
|
+
return;
|
|
11973
|
+
}
|
|
11974
|
+
const diffStat = execFileSync7(
|
|
11975
|
+
"git",
|
|
11976
|
+
["-C", target.cwd, "diff", "--stat", `${base}...HEAD`],
|
|
11977
|
+
{ encoding: "utf-8" }
|
|
11978
|
+
).trim();
|
|
11979
|
+
if (!diffStat) {
|
|
11980
|
+
console.log(`No changes on ${branchLabel} vs ${base}.`);
|
|
11981
|
+
return;
|
|
11982
|
+
}
|
|
11983
|
+
await runtime.showDiff({
|
|
11984
|
+
workspaceId,
|
|
11985
|
+
cwd: target.cwd,
|
|
11986
|
+
base,
|
|
11987
|
+
title: `${branchLabel} vs ${base}`,
|
|
11988
|
+
layout: opts.layout,
|
|
11989
|
+
focus: opts.focus,
|
|
11990
|
+
lastTurn: opts.lastTurn,
|
|
11991
|
+
source: "branch"
|
|
11992
|
+
});
|
|
11993
|
+
console.log(chalk10.dim(`Opened ${branchLabel} vs ${base} in cmux diff.`));
|
|
11994
|
+
}
|
|
11995
|
+
async function runDiff(project, crewArg, opts) {
|
|
11996
|
+
const config = loadConfig();
|
|
11997
|
+
const proj = config.projects[project];
|
|
11998
|
+
if (!proj) {
|
|
11999
|
+
throw new Error(`Project '${project}' not found. Run 'squadrant projects list'.`);
|
|
12000
|
+
}
|
|
12001
|
+
const mode = resolveDiffMode(crewArg, opts);
|
|
12002
|
+
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
12003
|
+
if (mode.mode === "pr" || mode.mode === "refs") {
|
|
12004
|
+
if (!runtime.showPatch) {
|
|
12005
|
+
throw new Error(`Runtime '${runtime.name}' has no native patch viewer yet \u2014 Phase 1 supports cmux only.`);
|
|
12006
|
+
}
|
|
12007
|
+
const title = mode.mode === "pr" ? `PR #${mode.pr}` : `${mode.base}...${mode.head}`;
|
|
12008
|
+
const patch = mode.mode === "pr" ? getPrDiff(proj.path, mode.pr) : getRefsDiff(proj.path, mode.base, mode.head);
|
|
12009
|
+
if (!patch.trim()) {
|
|
12010
|
+
console.log(`No changes in ${title}.`);
|
|
12011
|
+
return;
|
|
12012
|
+
}
|
|
12013
|
+
await runtime.showPatch({ workspaceId, patch, title, layout: opts.layout, focus: opts.focus });
|
|
12014
|
+
console.log(chalk10.dim(`Opened ${title} in cmux diff.`));
|
|
12015
|
+
return;
|
|
12016
|
+
}
|
|
12017
|
+
let crew;
|
|
12018
|
+
if (mode.mode === "pick") {
|
|
12019
|
+
const tasks = await squadrantdCall({ kind: "list", project });
|
|
12020
|
+
const base = resolveWorktreeBase(proj.path);
|
|
12021
|
+
const stats = buildCrewDiffStats(tasks, proj.path, base, (cwd, b) => {
|
|
12022
|
+
try {
|
|
12023
|
+
return execFileSync7("git", ["-C", cwd, "diff", "--stat", `${b}...HEAD`], { encoding: "utf-8" });
|
|
12024
|
+
} catch {
|
|
12025
|
+
return "";
|
|
12026
|
+
}
|
|
12027
|
+
});
|
|
12028
|
+
if (stats.length === 0) {
|
|
12029
|
+
throw new Error(`No live crews for ${project}. Run 'squadrant crew list ${project}' to check, or 'squadrant crew spawn' one.`);
|
|
12030
|
+
}
|
|
12031
|
+
console.log(`Live crews for ${project} (vs ${base}):`);
|
|
12032
|
+
stats.forEach((s, i) => console.log(` ${i + 1}. ${s.name} \u2014 ${s.stat || "no changes"}`));
|
|
12033
|
+
const raw = await promptLine2("Pick a crew to diff (number or name): ");
|
|
12034
|
+
crew = parseCrewPick(raw, stats);
|
|
12035
|
+
} else {
|
|
12036
|
+
crew = mode.crew;
|
|
12037
|
+
}
|
|
12038
|
+
await openCrewDiff(project, proj, crew, opts, runtime, workspaceId);
|
|
12039
|
+
}
|
|
12040
|
+
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);
|
|
12041
|
+
|
|
11654
12042
|
// packages/cli/src/commands/side.ts
|
|
11655
12043
|
init_dist();
|
|
11656
12044
|
init_dist3();
|
|
@@ -11658,11 +12046,11 @@ init_dist4();
|
|
|
11658
12046
|
init_dist3();
|
|
11659
12047
|
init_dist();
|
|
11660
12048
|
init_dist2();
|
|
11661
|
-
import { Command as
|
|
12049
|
+
import { Command as Command11 } from "commander";
|
|
11662
12050
|
import fs20 from "fs";
|
|
11663
12051
|
import path22 from "path";
|
|
11664
12052
|
import os12 from "os";
|
|
11665
|
-
import
|
|
12053
|
+
import chalk11 from "chalk";
|
|
11666
12054
|
var TEMPLATES_DIR3 = path22.join(os12.homedir(), ".config", "squadrant", "templates");
|
|
11667
12055
|
async function runSideSpawn2(input) {
|
|
11668
12056
|
const config = loadConfig();
|
|
@@ -11724,7 +12112,7 @@ async function runSideClose2(project, name) {
|
|
|
11724
12112
|
config.defaults.worktreeDir ?? ".worktrees"
|
|
11725
12113
|
);
|
|
11726
12114
|
}
|
|
11727
|
-
var sideCommand = new
|
|
12115
|
+
var sideCommand = new Command11("side").description(
|
|
11728
12116
|
"Spawn and manage side-sessions (research/debug) \u2014 fresh-context tabs off the daemon lifecycle"
|
|
11729
12117
|
);
|
|
11730
12118
|
sideCommand.command("spawn").description(
|
|
@@ -11749,9 +12137,9 @@ sideCommand.command("spawn").description(
|
|
|
11749
12137
|
direction: opts.direction,
|
|
11750
12138
|
agent: opts.agent
|
|
11751
12139
|
});
|
|
11752
|
-
console.log(
|
|
12140
|
+
console.log(chalk11.green(`\u2714 Side session '${pane.title}' spawned (${pane.surfaceId})`));
|
|
11753
12141
|
} catch (err) {
|
|
11754
|
-
console.error(
|
|
12142
|
+
console.error(chalk11.red(err.message));
|
|
11755
12143
|
process.exit(1);
|
|
11756
12144
|
}
|
|
11757
12145
|
}
|
|
@@ -11760,14 +12148,14 @@ sideCommand.command("list").description("List live side-sessions for a project")
|
|
|
11760
12148
|
try {
|
|
11761
12149
|
const sessions = await runSideList2(project);
|
|
11762
12150
|
if (sessions.length === 0) {
|
|
11763
|
-
console.log(
|
|
12151
|
+
console.log(chalk11.yellow(`No live side-sessions for ${project}.`));
|
|
11764
12152
|
return;
|
|
11765
12153
|
}
|
|
11766
12154
|
for (const s of sessions) {
|
|
11767
12155
|
console.log(` ${s.name} (${s.surfaceId})`);
|
|
11768
12156
|
}
|
|
11769
12157
|
} catch (err) {
|
|
11770
|
-
console.error(
|
|
12158
|
+
console.error(chalk11.red(err.message));
|
|
11771
12159
|
process.exit(1);
|
|
11772
12160
|
}
|
|
11773
12161
|
});
|
|
@@ -11780,9 +12168,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
11780
12168
|
label: "message"
|
|
11781
12169
|
});
|
|
11782
12170
|
await runSideSend2(project, name, resolvedMessage);
|
|
11783
|
-
console.log(
|
|
12171
|
+
console.log(chalk11.green(`\u2714 Sent to ${project}:${name}`));
|
|
11784
12172
|
} catch (err) {
|
|
11785
|
-
console.error(
|
|
12173
|
+
console.error(chalk11.red(err.message));
|
|
11786
12174
|
process.exit(1);
|
|
11787
12175
|
}
|
|
11788
12176
|
}
|
|
@@ -11790,9 +12178,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
11790
12178
|
sideCommand.command("close").description("Close a side-session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Session name").action(async (project, name) => {
|
|
11791
12179
|
try {
|
|
11792
12180
|
await runSideClose2(project, name);
|
|
11793
|
-
console.log(
|
|
12181
|
+
console.log(chalk11.green(`\u2714 Closed ${project}:${name}`));
|
|
11794
12182
|
} catch (err) {
|
|
11795
|
-
console.error(
|
|
12183
|
+
console.error(chalk11.red(err.message));
|
|
11796
12184
|
process.exit(1);
|
|
11797
12185
|
}
|
|
11798
12186
|
});
|
|
@@ -11800,15 +12188,15 @@ sideCommand.command("close").description("Close a side-session (closes its tab)"
|
|
|
11800
12188
|
// packages/cli/src/commands/dashboard.ts
|
|
11801
12189
|
init_dist();
|
|
11802
12190
|
init_dist3();
|
|
11803
|
-
import { Command as
|
|
12191
|
+
import { Command as Command12 } from "commander";
|
|
11804
12192
|
import { execSync as execSync10 } from "child_process";
|
|
11805
12193
|
import { homedir as homedir18 } from "os";
|
|
11806
12194
|
import { join as join23 } from "path";
|
|
11807
|
-
import
|
|
12195
|
+
import chalk13 from "chalk";
|
|
11808
12196
|
|
|
11809
12197
|
// packages/web/dist/read-status.js
|
|
11810
12198
|
function deriveState(tasks) {
|
|
11811
|
-
if (tasks.some((t) => t.state === "blocked" || t.state === "awaiting-input"))
|
|
12199
|
+
if (tasks.some((t) => t.state === "blocked" || t.state === "awaiting-input" || t.state === "review"))
|
|
11812
12200
|
return "blocked";
|
|
11813
12201
|
if (tasks.some((t) => t.state === "failed" || t.state === "stalled"))
|
|
11814
12202
|
return "errored";
|
|
@@ -11823,14 +12211,14 @@ function deriveRowState(tasks, captainState) {
|
|
|
11823
12211
|
}
|
|
11824
12212
|
function buildExcerpt(tasks) {
|
|
11825
12213
|
const working = tasks.filter((t) => t.state === "working").length;
|
|
11826
|
-
const blocked = tasks.filter((t) => t.state === "blocked" || t.state === "awaiting-input").length;
|
|
12214
|
+
const blocked = tasks.filter((t) => t.state === "blocked" || t.state === "awaiting-input" || t.state === "review").length;
|
|
11827
12215
|
const parts = [];
|
|
11828
12216
|
if (working > 0)
|
|
11829
12217
|
parts.push(`${working} working`);
|
|
11830
12218
|
if (blocked > 0)
|
|
11831
12219
|
parts.push(`${blocked} blocked`);
|
|
11832
12220
|
const summary = parts.length > 0 ? parts.join(", ") : "idle";
|
|
11833
|
-
const active = tasks.filter((t) => ["working", "blocked", "awaiting-input", "submitted"].includes(t.state));
|
|
12221
|
+
const active = tasks.filter((t) => ["working", "blocked", "awaiting-input", "review", "submitted"].includes(t.state));
|
|
11834
12222
|
const titles = active.slice(0, 3).map((t) => {
|
|
11835
12223
|
const firstLine2 = t.task ? t.task.split("\n")[0] : "";
|
|
11836
12224
|
return t.name ?? (firstLine2 || t.id.slice(0, 8));
|
|
@@ -11881,14 +12269,14 @@ async function readAllStatuses(deps) {
|
|
|
11881
12269
|
}
|
|
11882
12270
|
|
|
11883
12271
|
// packages/web/dist/render.js
|
|
11884
|
-
import
|
|
12272
|
+
import chalk12 from "chalk";
|
|
11885
12273
|
var ICON = {
|
|
11886
|
-
idle:
|
|
11887
|
-
busy:
|
|
11888
|
-
blocked:
|
|
11889
|
-
errored:
|
|
11890
|
-
offline:
|
|
11891
|
-
unknown:
|
|
12274
|
+
idle: chalk12.green,
|
|
12275
|
+
busy: chalk12.cyan,
|
|
12276
|
+
blocked: chalk12.yellow,
|
|
12277
|
+
errored: chalk12.red,
|
|
12278
|
+
offline: chalk12.dim,
|
|
12279
|
+
unknown: chalk12.gray
|
|
11892
12280
|
};
|
|
11893
12281
|
var ICON_CHAR = {
|
|
11894
12282
|
idle: "\u25CF",
|
|
@@ -11931,10 +12319,10 @@ function renderDashboard(rows, opts) {
|
|
|
11931
12319
|
const width = opts.width ?? 100;
|
|
11932
12320
|
const lines = [];
|
|
11933
12321
|
lines.push("");
|
|
11934
|
-
lines.push(" " +
|
|
12322
|
+
lines.push(" " + chalk12.bold("\u{1F4CA} Squadrant Dashboard") + " " + chalk12.dim(opts.now));
|
|
11935
12323
|
lines.push("");
|
|
11936
12324
|
if (rows.length === 0) {
|
|
11937
|
-
lines.push(" " +
|
|
12325
|
+
lines.push(" " + chalk12.yellow("No projects registered. Add one with: squadrant projects add <name> <path>"));
|
|
11938
12326
|
lines.push("");
|
|
11939
12327
|
return lines.join("\n");
|
|
11940
12328
|
}
|
|
@@ -11945,14 +12333,14 @@ function renderDashboard(rows, opts) {
|
|
|
11945
12333
|
const excerptW = Math.max(20, width - FIXED);
|
|
11946
12334
|
for (const r of rows) {
|
|
11947
12335
|
const icon = ICON[r.state](ICON_CHAR[r.state]);
|
|
11948
|
-
const name =
|
|
12336
|
+
const name = chalk12.cyan(pad(r.project, NAME_W));
|
|
11949
12337
|
const state = ICON[r.state](pad(r.state, STATE_W));
|
|
11950
12338
|
const age = pad(formatAge(r.lastChecked, opts.now), AGE_W);
|
|
11951
|
-
const excerpt =
|
|
12339
|
+
const excerpt = chalk12.dim(truncate(firstLine(r.excerpt), excerptW));
|
|
11952
12340
|
lines.push(` ${icon} ${name} ${state} ${age} \u2502 ${excerpt}`);
|
|
11953
12341
|
}
|
|
11954
12342
|
lines.push("");
|
|
11955
|
-
lines.push(
|
|
12343
|
+
lines.push(chalk12.dim(" Refreshes every 10s \xB7 Ctrl+C to exit"));
|
|
11956
12344
|
lines.push("");
|
|
11957
12345
|
return lines.join("\n");
|
|
11958
12346
|
}
|
|
@@ -13075,10 +13463,10 @@ async function runDashboardWeb(input) {
|
|
|
13075
13463
|
sockPath: SOCK3,
|
|
13076
13464
|
runners: defaultProbeRunners()
|
|
13077
13465
|
});
|
|
13078
|
-
console.log(
|
|
13079
|
-
console.log(
|
|
13466
|
+
console.log(chalk13.green(`\u2714 Squadrant system dashboard \u2192 http://127.0.0.1:${handle.port}`));
|
|
13467
|
+
console.log(chalk13.dim(` polling the daemon every ${input.interval}s \xB7 localhost only \xB7 read-only \xB7 Ctrl-C to stop`));
|
|
13080
13468
|
}
|
|
13081
|
-
var dashboardCommand = new
|
|
13469
|
+
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
13470
|
try {
|
|
13083
13471
|
if (opts.web) {
|
|
13084
13472
|
await runDashboardWeb({ port: opts.port, interval: opts.interval ?? 5 });
|
|
@@ -13086,12 +13474,12 @@ var dashboardCommand = new Command11("dashboard").description("Live status grid
|
|
|
13086
13474
|
}
|
|
13087
13475
|
if (opts.pane) {
|
|
13088
13476
|
const pane = await runDashboardPane({ direction: opts.direction, interval: opts.interval ?? 10 });
|
|
13089
|
-
console.log(
|
|
13477
|
+
console.log(chalk13.green(`\u2714 Dashboard pane opened in ${pane.workspaceId} ${pane.surfaceId}`));
|
|
13090
13478
|
return;
|
|
13091
13479
|
}
|
|
13092
13480
|
await runDashboardOnce();
|
|
13093
13481
|
} catch (err) {
|
|
13094
|
-
console.error(
|
|
13482
|
+
console.error(chalk13.red(err.message));
|
|
13095
13483
|
process.exit(1);
|
|
13096
13484
|
}
|
|
13097
13485
|
});
|
|
@@ -13102,12 +13490,12 @@ dashboardCommand.command("sync-hub").description("Mirror each spoke status.md in
|
|
|
13102
13490
|
return;
|
|
13103
13491
|
}
|
|
13104
13492
|
if (results.length === 0) {
|
|
13105
|
-
console.log(
|
|
13493
|
+
console.log(chalk13.dim("\n No mirrors written (no projects with usable status.md, or hubVault unset).\n"));
|
|
13106
13494
|
return;
|
|
13107
13495
|
}
|
|
13108
|
-
console.log(
|
|
13496
|
+
console.log(chalk13.bold("\n \u{1F4CA} Hub mirror sync\n"));
|
|
13109
13497
|
for (const r of results) {
|
|
13110
|
-
console.log(` ${
|
|
13498
|
+
console.log(` ${chalk13.green("\u2714")} ${chalk13.cyan(r.project.padEnd(16))} \u2192 ${chalk13.dim(r.hubPath)}`);
|
|
13111
13499
|
}
|
|
13112
13500
|
console.log("");
|
|
13113
13501
|
});
|
|
@@ -13117,12 +13505,12 @@ init_dist();
|
|
|
13117
13505
|
init_dist4();
|
|
13118
13506
|
init_dist3();
|
|
13119
13507
|
init_dist2();
|
|
13120
|
-
import { Command as
|
|
13508
|
+
import { Command as Command13 } from "commander";
|
|
13121
13509
|
import { execSync as execSync11 } from "child_process";
|
|
13122
13510
|
import fs22 from "fs";
|
|
13123
13511
|
import path24 from "path";
|
|
13124
13512
|
import os13 from "os";
|
|
13125
|
-
import
|
|
13513
|
+
import chalk14 from "chalk";
|
|
13126
13514
|
|
|
13127
13515
|
// packages/cli/src/commands/launch-interactive.ts
|
|
13128
13516
|
import checkbox, { Separator } from "@inquirer/checkbox";
|
|
@@ -13203,16 +13591,16 @@ var TEMPLATES_DIR4 = path24.join(os13.homedir(), ".config", "squadrant", "templa
|
|
|
13203
13591
|
var SESSIONS_PATH2 = path24.join(os13.homedir(), ".config", "squadrant", "sessions.json");
|
|
13204
13592
|
function ensureCmuxReady(headless) {
|
|
13205
13593
|
if (headless || isInsideCmux()) return;
|
|
13206
|
-
console.log(
|
|
13594
|
+
console.log(chalk14.yellow("\n Not running inside cmux. Opening cmux app...\n"));
|
|
13207
13595
|
execSync11(`open "${CMUX_APP}"`, { stdio: "inherit" });
|
|
13208
|
-
console.log(
|
|
13596
|
+
console.log(chalk14.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
|
|
13209
13597
|
process.exit(0);
|
|
13210
13598
|
}
|
|
13211
|
-
var launchCommand = new
|
|
13599
|
+
var launchCommand = new Command13("launch").description(
|
|
13212
13600
|
"Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
|
|
13213
13601
|
).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
13602
|
if (opts.fresh && opts.keep) {
|
|
13215
|
-
console.error(
|
|
13603
|
+
console.error(chalk14.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
|
|
13216
13604
|
process.exit(1);
|
|
13217
13605
|
}
|
|
13218
13606
|
const config = loadConfig();
|
|
@@ -13260,29 +13648,29 @@ var launchCommand = new Command12("launch").description(
|
|
|
13260
13648
|
return null;
|
|
13261
13649
|
}
|
|
13262
13650
|
},
|
|
13263
|
-
onFreshReason: (reason) => console.log(
|
|
13264
|
-
onStoppingStale: (name) => console.log(
|
|
13265
|
-
onAlreadyExists: (name) => console.log(
|
|
13266
|
-
onCreated: (name) => console.log(
|
|
13651
|
+
onFreshReason: (reason) => console.log(chalk14.cyan(` \u21BB ${reason}`)),
|
|
13652
|
+
onStoppingStale: (name) => console.log(chalk14.yellow(` Closing stale workspace '${name}' for fresh start`)),
|
|
13653
|
+
onAlreadyExists: (name) => console.log(chalk14.yellow(` Workspace '${name}' already exists \u2014 switching to it`)),
|
|
13654
|
+
onCreated: (name) => console.log(chalk14.green(` \u2714 Workspace '${name}' created`))
|
|
13267
13655
|
});
|
|
13268
13656
|
} catch (err) {
|
|
13269
|
-
console.error(
|
|
13657
|
+
console.error(chalk14.red(` \u2718 Failed: ${err.message}`));
|
|
13270
13658
|
hadFailure = true;
|
|
13271
13659
|
}
|
|
13272
13660
|
}
|
|
13273
13661
|
if (opts.all) {
|
|
13274
13662
|
const hubPath = resolveHome(config.hubVault);
|
|
13275
13663
|
fs22.mkdirSync(hubPath, { recursive: true });
|
|
13276
|
-
console.log(
|
|
13664
|
+
console.log(chalk14.bold("\nLaunching all captain workspaces\n"));
|
|
13277
13665
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
13278
13666
|
const projPath = resolveHome(proj.path);
|
|
13279
13667
|
const spokePath = resolveHome(proj.spokeVault);
|
|
13280
13668
|
if (!fs22.existsSync(spokePath)) {
|
|
13281
13669
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
13282
13670
|
await ensureSpokeLayout(spokeDriver);
|
|
13283
|
-
console.log(
|
|
13671
|
+
console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13284
13672
|
}
|
|
13285
|
-
console.log(
|
|
13673
|
+
console.log(chalk14.bold(`
|
|
13286
13674
|
Captain: ${proj.captainName} (${name})`));
|
|
13287
13675
|
await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
|
|
13288
13676
|
}
|
|
@@ -13290,7 +13678,7 @@ var launchCommand = new Command12("launch").description(
|
|
|
13290
13678
|
} else if (!project) {
|
|
13291
13679
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
13292
13680
|
console.error(
|
|
13293
|
-
|
|
13681
|
+
chalk14.red(
|
|
13294
13682
|
"\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
13683
|
)
|
|
13296
13684
|
);
|
|
@@ -13305,10 +13693,10 @@ var launchCommand = new Command12("launch").description(
|
|
|
13305
13693
|
}));
|
|
13306
13694
|
const selected = await selectCaptainsInteractive(entries);
|
|
13307
13695
|
if (selected.length === 0) {
|
|
13308
|
-
console.log(
|
|
13696
|
+
console.log(chalk14.yellow("\n No captains selected.\n"));
|
|
13309
13697
|
return;
|
|
13310
13698
|
}
|
|
13311
|
-
console.log(
|
|
13699
|
+
console.log(chalk14.bold(`
|
|
13312
13700
|
Launching ${selected.length} captain workspace(s) in parallel
|
|
13313
13701
|
`));
|
|
13314
13702
|
await Promise.all(selected.map(async (name) => {
|
|
@@ -13318,9 +13706,9 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13318
13706
|
if (!fs22.existsSync(spokePath)) {
|
|
13319
13707
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
13320
13708
|
await ensureSpokeLayout(spokeDriver);
|
|
13321
|
-
console.log(
|
|
13709
|
+
console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13322
13710
|
}
|
|
13323
|
-
console.log(
|
|
13711
|
+
console.log(chalk14.bold(`
|
|
13324
13712
|
Captain: ${proj.captainName} (${name})`));
|
|
13325
13713
|
await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
|
|
13326
13714
|
}));
|
|
@@ -13328,7 +13716,7 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13328
13716
|
} else {
|
|
13329
13717
|
if (!config.projects[project]) {
|
|
13330
13718
|
console.error(
|
|
13331
|
-
|
|
13719
|
+
chalk14.red(
|
|
13332
13720
|
`
|
|
13333
13721
|
\u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
|
|
13334
13722
|
`
|
|
@@ -13342,10 +13730,10 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13342
13730
|
if (!fs22.existsSync(spokePath)) {
|
|
13343
13731
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
|
|
13344
13732
|
await ensureSpokeLayout(spokeDriver);
|
|
13345
|
-
console.log(
|
|
13733
|
+
console.log(chalk14.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13346
13734
|
}
|
|
13347
13735
|
console.log(
|
|
13348
|
-
|
|
13736
|
+
chalk14.bold(
|
|
13349
13737
|
`
|
|
13350
13738
|
Launching captain workspace for '${project}' (${proj.captainName})
|
|
13351
13739
|
`
|
|
@@ -13359,8 +13747,8 @@ Launching captain workspace for '${project}' (${proj.captainName})
|
|
|
13359
13747
|
// packages/cli/src/commands/shutdown.ts
|
|
13360
13748
|
init_dist();
|
|
13361
13749
|
init_dist3();
|
|
13362
|
-
import { Command as
|
|
13363
|
-
import
|
|
13750
|
+
import { Command as Command14 } from "commander";
|
|
13751
|
+
import chalk15 from "chalk";
|
|
13364
13752
|
init_dist();
|
|
13365
13753
|
function nameVariants(name) {
|
|
13366
13754
|
const stripped = name.replace(/^⚓\s+/, "").trim();
|
|
@@ -13373,23 +13761,23 @@ async function closeMatching(runtime, variants, label) {
|
|
|
13373
13761
|
const failed = [];
|
|
13374
13762
|
if (matches.length === 0) {
|
|
13375
13763
|
console.log(
|
|
13376
|
-
|
|
13764
|
+
chalk15.yellow(` \u26A0 Workspace '${label}' not found \u2014 already closed?`)
|
|
13377
13765
|
);
|
|
13378
13766
|
return { closed, failed };
|
|
13379
13767
|
}
|
|
13380
13768
|
for (const ws of matches) {
|
|
13381
13769
|
try {
|
|
13382
13770
|
await runtime.stop(ws.id);
|
|
13383
|
-
console.log(
|
|
13771
|
+
console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
|
|
13384
13772
|
closed.push(ws.name);
|
|
13385
13773
|
} catch {
|
|
13386
|
-
console.log(
|
|
13774
|
+
console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
|
|
13387
13775
|
failed.push(ws.name);
|
|
13388
13776
|
}
|
|
13389
13777
|
}
|
|
13390
13778
|
return { closed, failed };
|
|
13391
13779
|
}
|
|
13392
|
-
var shutdownCommand = new
|
|
13780
|
+
var shutdownCommand = new Command14("shutdown").description(
|
|
13393
13781
|
"Shutdown command + all captain workspaces (no args) or one captain workspace"
|
|
13394
13782
|
).argument("[project]", "Project name to shut down captain for").action(async (project) => {
|
|
13395
13783
|
const config = loadConfig();
|
|
@@ -13405,11 +13793,11 @@ var shutdownCommand = new Command13("shutdown").description(
|
|
|
13405
13793
|
const allVariants = /* @__PURE__ */ new Set([...captainVariants, ...commandVariants]);
|
|
13406
13794
|
const squadrantWorkspaces = workspaces.filter((w) => allVariants.has(w.name));
|
|
13407
13795
|
if (squadrantWorkspaces.length === 0) {
|
|
13408
|
-
console.log(
|
|
13796
|
+
console.log(chalk15.yellow("\nNo squadrant workspaces found to close.\n"));
|
|
13409
13797
|
return;
|
|
13410
13798
|
}
|
|
13411
13799
|
console.log(
|
|
13412
|
-
|
|
13800
|
+
chalk15.bold(
|
|
13413
13801
|
`
|
|
13414
13802
|
Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
13415
13803
|
`
|
|
@@ -13429,9 +13817,9 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
13429
13817
|
for (const ws of squadrantWorkspaces) {
|
|
13430
13818
|
try {
|
|
13431
13819
|
await globalRuntime.stop(ws.id);
|
|
13432
|
-
console.log(
|
|
13820
|
+
console.log(chalk15.green(` \u2714 Closed: ${ws.name}`));
|
|
13433
13821
|
} catch {
|
|
13434
|
-
console.log(
|
|
13822
|
+
console.log(chalk15.red(` \u2718 Failed to close: ${ws.name}`));
|
|
13435
13823
|
}
|
|
13436
13824
|
}
|
|
13437
13825
|
console.log("");
|
|
@@ -13439,7 +13827,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
13439
13827
|
}
|
|
13440
13828
|
if (!config.projects[project]) {
|
|
13441
13829
|
console.error(
|
|
13442
|
-
|
|
13830
|
+
chalk15.red(
|
|
13443
13831
|
`
|
|
13444
13832
|
\u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
|
|
13445
13833
|
`
|
|
@@ -13450,7 +13838,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
13450
13838
|
const captainName = config.projects[project].captainName;
|
|
13451
13839
|
const runtime = runtimes.forProject(project, config);
|
|
13452
13840
|
console.log(
|
|
13453
|
-
|
|
13841
|
+
chalk15.bold(`
|
|
13454
13842
|
Shutting down captain workspace for '${project}'...
|
|
13455
13843
|
`)
|
|
13456
13844
|
);
|
|
@@ -13474,13 +13862,13 @@ Shutting down captain workspace for '${project}'...
|
|
|
13474
13862
|
|
|
13475
13863
|
// packages/cli/src/commands/feedback.ts
|
|
13476
13864
|
init_dist();
|
|
13477
|
-
import { Command as
|
|
13865
|
+
import { Command as Command15 } from "commander";
|
|
13478
13866
|
import fs23 from "fs";
|
|
13479
13867
|
import os14 from "os";
|
|
13480
13868
|
import path25 from "path";
|
|
13481
13869
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13482
13870
|
import { execSync as execSync12 } from "child_process";
|
|
13483
|
-
import
|
|
13871
|
+
import chalk16 from "chalk";
|
|
13484
13872
|
var REPO_URL = "https://github.com/tu11aa/squadrant";
|
|
13485
13873
|
function readPkgVersion() {
|
|
13486
13874
|
try {
|
|
@@ -13528,21 +13916,21 @@ function buildIssueUrl(metrics, squadrantVersion) {
|
|
|
13528
13916
|
});
|
|
13529
13917
|
return `${REPO_URL}/issues/new?${params.toString()}`;
|
|
13530
13918
|
}
|
|
13531
|
-
var feedbackCommand = new
|
|
13919
|
+
var feedbackCommand = new Command15("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
|
|
13532
13920
|
const config = loadConfig();
|
|
13533
13921
|
const metricsPath = config.metrics?.path || path25.join(os14.homedir(), ".config", "squadrant", "metrics.json");
|
|
13534
13922
|
const metrics = readMetrics(metricsPath);
|
|
13535
13923
|
const version = readStamp(config) ?? readPkgVersion();
|
|
13536
13924
|
const issueUrl = buildIssueUrl(metrics, version);
|
|
13537
|
-
console.log(
|
|
13538
|
-
console.log(
|
|
13925
|
+
console.log(chalk16.bold("\nOpening feedback issue in browser...\n"));
|
|
13926
|
+
console.log(chalk16.dim(` URL: ${issueUrl.substring(0, 80)}...
|
|
13539
13927
|
`));
|
|
13540
13928
|
try {
|
|
13541
13929
|
execSync12(`open "${issueUrl}"`, { stdio: "ignore" });
|
|
13542
|
-
console.log(
|
|
13930
|
+
console.log(chalk16.green(" \u2714 Browser opened\n"));
|
|
13543
13931
|
} catch {
|
|
13544
|
-
console.log(
|
|
13545
|
-
console.log(` Open manually: ${
|
|
13932
|
+
console.log(chalk16.yellow(" \u26A0 Could not open browser automatically."));
|
|
13933
|
+
console.log(` Open manually: ${chalk16.cyan(issueUrl)}
|
|
13546
13934
|
`);
|
|
13547
13935
|
}
|
|
13548
13936
|
});
|
|
@@ -13551,10 +13939,10 @@ var feedbackCommand = new Command14("feedback").description("Open a pre-filled G
|
|
|
13551
13939
|
init_dist();
|
|
13552
13940
|
init_dist();
|
|
13553
13941
|
init_dist3();
|
|
13554
|
-
import { Command as
|
|
13942
|
+
import { Command as Command16 } from "commander";
|
|
13555
13943
|
import fs24 from "fs";
|
|
13556
13944
|
import path26 from "path";
|
|
13557
|
-
import
|
|
13945
|
+
import chalk17 from "chalk";
|
|
13558
13946
|
import matter3 from "gray-matter";
|
|
13559
13947
|
function getDateStr(yesterday) {
|
|
13560
13948
|
return iso(daysAgo(yesterday ? 1 : 0));
|
|
@@ -13584,7 +13972,7 @@ function formatStandup(standups, dateStr, raw) {
|
|
|
13584
13972
|
const lines = [];
|
|
13585
13973
|
const header = `Standup \u2014 ${dateStr}`;
|
|
13586
13974
|
if (!raw) {
|
|
13587
|
-
lines.push(
|
|
13975
|
+
lines.push(chalk17.bold(`
|
|
13588
13976
|
${header}
|
|
13589
13977
|
`));
|
|
13590
13978
|
} else {
|
|
@@ -13597,12 +13985,12 @@ ${header}
|
|
|
13597
13985
|
const tasksTotal = s.status.tasks_total ?? 0;
|
|
13598
13986
|
const tasksInProgress = s.status.tasks_in_progress ?? 0;
|
|
13599
13987
|
if (!raw) {
|
|
13600
|
-
lines.push(
|
|
13988
|
+
lines.push(chalk17.cyan.bold(`## ${s.name}`));
|
|
13601
13989
|
} else {
|
|
13602
13990
|
lines.push(`## ${s.name}`);
|
|
13603
13991
|
}
|
|
13604
13992
|
if (s.gitCommits.length > 0 || tasksDone > 0) {
|
|
13605
|
-
lines.push(!raw ?
|
|
13993
|
+
lines.push(!raw ? chalk17.green("Done:") : "**Done:**");
|
|
13606
13994
|
for (const commit of s.gitCommits) {
|
|
13607
13995
|
lines.push(` - ${commit}`);
|
|
13608
13996
|
}
|
|
@@ -13611,7 +13999,7 @@ ${header}
|
|
|
13611
13999
|
}
|
|
13612
14000
|
}
|
|
13613
14001
|
if (tasksInProgress > 0) {
|
|
13614
|
-
lines.push(!raw ?
|
|
14002
|
+
lines.push(!raw ? chalk17.yellow("In Progress:") : "**In Progress:**");
|
|
13615
14003
|
lines.push(` - ${tasksInProgress} task(s) active`);
|
|
13616
14004
|
}
|
|
13617
14005
|
if (s.dailyLog) {
|
|
@@ -13621,7 +14009,7 @@ ${header}
|
|
|
13621
14009
|
if (match) {
|
|
13622
14010
|
const items = match[1].trim().split("\n").filter((l) => l.trim().startsWith("-"));
|
|
13623
14011
|
if (items.length > 0 && section === "Tomorrow") {
|
|
13624
|
-
lines.push(!raw ?
|
|
14012
|
+
lines.push(!raw ? chalk17.blue("Next:") : "**Next:**");
|
|
13625
14013
|
for (const item of items) lines.push(` ${item.trim()}`);
|
|
13626
14014
|
}
|
|
13627
14015
|
}
|
|
@@ -13629,20 +14017,20 @@ ${header}
|
|
|
13629
14017
|
}
|
|
13630
14018
|
if (s.blockers.length > 0) {
|
|
13631
14019
|
hasBlockers = true;
|
|
13632
|
-
lines.push(!raw ?
|
|
14020
|
+
lines.push(!raw ? chalk17.red("Blocked:") : "**Blocked:**");
|
|
13633
14021
|
for (const b of s.blockers) {
|
|
13634
14022
|
lines.push(` - ${b}`);
|
|
13635
14023
|
}
|
|
13636
14024
|
}
|
|
13637
14025
|
if (s.gitCommits.length === 0 && tasksDone === 0 && !s.dailyLog) {
|
|
13638
|
-
lines.push(!raw ?
|
|
14026
|
+
lines.push(!raw ? chalk17.dim(" (no activity)") : " (no activity)");
|
|
13639
14027
|
}
|
|
13640
14028
|
lines.push("");
|
|
13641
14029
|
}
|
|
13642
14030
|
const totalCommits = standups.reduce((sum, s) => sum + s.gitCommits.length, 0);
|
|
13643
14031
|
const totalDone = standups.reduce((sum, s) => sum + (s.status.tasks_completed ?? 0), 0);
|
|
13644
14032
|
if (!raw) {
|
|
13645
|
-
lines.push(
|
|
14033
|
+
lines.push(chalk17.dim(`--- ${totalCommits} commits, ${totalDone} tasks done${hasBlockers ? ", HAS BLOCKERS" : ""} ---
|
|
13646
14034
|
`));
|
|
13647
14035
|
} else {
|
|
13648
14036
|
lines.push(`---
|
|
@@ -13651,12 +14039,12 @@ ${header}
|
|
|
13651
14039
|
}
|
|
13652
14040
|
return lines.join("\n");
|
|
13653
14041
|
}
|
|
13654
|
-
var standupCommand = new
|
|
14042
|
+
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
14043
|
const config = loadConfig();
|
|
13656
14044
|
const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
13657
14045
|
const projects = Object.entries(config.projects);
|
|
13658
14046
|
if (projects.length === 0) {
|
|
13659
|
-
console.log(
|
|
14047
|
+
console.log(chalk17.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
|
|
13660
14048
|
return;
|
|
13661
14049
|
}
|
|
13662
14050
|
const dateStr = getDateStr(!!opts.yesterday);
|
|
@@ -13665,7 +14053,7 @@ var standupCommand = new Command15("standup").description("Generate daily standu
|
|
|
13665
14053
|
if (opts.project) {
|
|
13666
14054
|
const match = projects.find(([name]) => name === opts.project);
|
|
13667
14055
|
if (!match) {
|
|
13668
|
-
console.error(
|
|
14056
|
+
console.error(chalk17.red(`Project "${opts.project}" not found.`));
|
|
13669
14057
|
process.exit(1);
|
|
13670
14058
|
}
|
|
13671
14059
|
targets = [match];
|
|
@@ -13683,10 +14071,10 @@ var standupCommand = new Command15("standup").description("Generate daily standu
|
|
|
13683
14071
|
init_dist();
|
|
13684
14072
|
init_dist();
|
|
13685
14073
|
init_dist3();
|
|
13686
|
-
import { Command as
|
|
14074
|
+
import { Command as Command17 } from "commander";
|
|
13687
14075
|
import fs25 from "fs";
|
|
13688
14076
|
import path27 from "path";
|
|
13689
|
-
import
|
|
14077
|
+
import chalk18 from "chalk";
|
|
13690
14078
|
import matter4 from "gray-matter";
|
|
13691
14079
|
function readStatus(spokeVault) {
|
|
13692
14080
|
const statusFile = path27.join(spokeVault, "status.md");
|
|
@@ -13756,7 +14144,7 @@ function formatRetro(retros, fromStr, toStr, raw) {
|
|
|
13756
14144
|
const lines = [];
|
|
13757
14145
|
const header = `Retro \u2014 ${fromStr} \u2192 ${toStr}`;
|
|
13758
14146
|
lines.push(raw ? `# ${header}
|
|
13759
|
-
` :
|
|
14147
|
+
` : chalk18.bold(`
|
|
13760
14148
|
${header}
|
|
13761
14149
|
`));
|
|
13762
14150
|
let totalCommits = 0;
|
|
@@ -13766,39 +14154,39 @@ ${header}
|
|
|
13766
14154
|
totalCommits += r.commits.length;
|
|
13767
14155
|
totalPRs += r.mergedPRs.length;
|
|
13768
14156
|
totalShipped += r.shipped.length;
|
|
13769
|
-
lines.push(raw ? `## ${r.name}` :
|
|
13770
|
-
renderList(lines, r.shipped, raw, "Shipped",
|
|
14157
|
+
lines.push(raw ? `## ${r.name}` : chalk18.cyan.bold(`## ${r.name}`));
|
|
14158
|
+
renderList(lines, r.shipped, raw, "Shipped", chalk18.green);
|
|
13771
14159
|
if (r.mergedPRs.length > 0) {
|
|
13772
|
-
lines.push(raw ? `**PRs merged:**` :
|
|
14160
|
+
lines.push(raw ? `**PRs merged:**` : chalk18.green("PRs merged:"));
|
|
13773
14161
|
for (const pr of r.mergedPRs) lines.push(` - ${pr}`);
|
|
13774
14162
|
}
|
|
13775
|
-
renderList(lines, r.inProgress, raw, "In Progress",
|
|
13776
|
-
renderList(lines, r.blocked, raw, "Blocked",
|
|
13777
|
-
renderList(lines, r.decisions, raw, "Key Decisions",
|
|
14163
|
+
renderList(lines, r.inProgress, raw, "In Progress", chalk18.yellow);
|
|
14164
|
+
renderList(lines, r.blocked, raw, "Blocked", chalk18.red);
|
|
14165
|
+
renderList(lines, r.decisions, raw, "Key Decisions", chalk18.magenta);
|
|
13778
14166
|
const metricBits = [
|
|
13779
14167
|
`${r.commits.length} commits`,
|
|
13780
14168
|
`${r.mergedPRs.length} PRs merged`,
|
|
13781
14169
|
`${r.shipped.length} shipped`
|
|
13782
14170
|
];
|
|
13783
|
-
lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` :
|
|
14171
|
+
lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` : chalk18.dim(` ${metricBits.join(" \xB7 ")}`));
|
|
13784
14172
|
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)_" :
|
|
14173
|
+
lines.push(raw ? "_(no activity in this window)_" : chalk18.dim(" (no activity in this window)"));
|
|
13786
14174
|
}
|
|
13787
14175
|
lines.push("");
|
|
13788
14176
|
}
|
|
13789
14177
|
const summary = `${totalShipped} items shipped \xB7 ${totalCommits} commits \xB7 ${totalPRs} PRs merged`;
|
|
13790
14178
|
lines.push(raw ? `---
|
|
13791
14179
|
*${summary}*
|
|
13792
|
-
` :
|
|
14180
|
+
` : chalk18.dim(`--- ${summary} ---
|
|
13793
14181
|
`));
|
|
13794
14182
|
return lines.join("\n");
|
|
13795
14183
|
}
|
|
13796
|
-
var retroCommand = new
|
|
14184
|
+
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
14185
|
const config = loadConfig();
|
|
13798
14186
|
const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
13799
14187
|
const projects = Object.entries(config.projects);
|
|
13800
14188
|
if (projects.length === 0) {
|
|
13801
|
-
console.log(
|
|
14189
|
+
console.log(chalk18.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
|
|
13802
14190
|
return;
|
|
13803
14191
|
}
|
|
13804
14192
|
let windowDays = 7;
|
|
@@ -13815,7 +14203,7 @@ var retroCommand = new Command16("retro").description("Generate a retro (weekly/
|
|
|
13815
14203
|
if (opts.project) {
|
|
13816
14204
|
const match = projects.find(([name]) => name === opts.project);
|
|
13817
14205
|
if (!match) {
|
|
13818
|
-
console.error(
|
|
14206
|
+
console.error(chalk18.red(`Project "${opts.project}" not found.`));
|
|
13819
14207
|
process.exit(1);
|
|
13820
14208
|
}
|
|
13821
14209
|
targets = [match];
|
|
@@ -13831,8 +14219,8 @@ var retroCommand = new Command16("retro").description("Generate a retro (weekly/
|
|
|
13831
14219
|
// packages/cli/src/commands/runtime.ts
|
|
13832
14220
|
init_dist();
|
|
13833
14221
|
init_dist3();
|
|
13834
|
-
import { Command as
|
|
13835
|
-
import
|
|
14222
|
+
import { Command as Command18 } from "commander";
|
|
14223
|
+
import chalk19 from "chalk";
|
|
13836
14224
|
function buildRegistry() {
|
|
13837
14225
|
return new RuntimeRegistry({
|
|
13838
14226
|
cmux: createCmuxDriver()
|
|
@@ -13864,7 +14252,7 @@ async function needRef(resolved) {
|
|
|
13864
14252
|
}
|
|
13865
14253
|
return ref.id;
|
|
13866
14254
|
}
|
|
13867
|
-
var runtimeCommand = new
|
|
14255
|
+
var runtimeCommand = new Command18("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
|
|
13868
14256
|
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
14257
|
const config = loadConfig();
|
|
13870
14258
|
const registry = buildRegistry();
|
|
@@ -13879,7 +14267,7 @@ runtimeCommand.command("status").description("Print 'running' or 'stopped' for a
|
|
|
13879
14267
|
process.exit(1);
|
|
13880
14268
|
}
|
|
13881
14269
|
} catch (err) {
|
|
13882
|
-
console.error(
|
|
14270
|
+
console.error(chalk19.red(err.message));
|
|
13883
14271
|
process.exit(2);
|
|
13884
14272
|
}
|
|
13885
14273
|
});
|
|
@@ -13926,9 +14314,9 @@ async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
|
|
|
13926
14314
|
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
14315
|
try {
|
|
13928
14316
|
await runRuntimeSend(arg1, arg2, opts);
|
|
13929
|
-
console.log(
|
|
14317
|
+
console.log(chalk19.green("\u2714 Delivered (confirmed)"));
|
|
13930
14318
|
} catch (err) {
|
|
13931
|
-
console.error(
|
|
14319
|
+
console.error(chalk19.red(err.message));
|
|
13932
14320
|
process.exit(1);
|
|
13933
14321
|
}
|
|
13934
14322
|
});
|
|
@@ -13954,7 +14342,7 @@ runtimeCommand.command("read-screen").description("Print a terminal snapshot of
|
|
|
13954
14342
|
const screen = await resolved.driver.readScreen(ref);
|
|
13955
14343
|
process.stdout.write(screen);
|
|
13956
14344
|
} catch (err) {
|
|
13957
|
-
console.error(
|
|
14345
|
+
console.error(chalk19.red(err.message));
|
|
13958
14346
|
process.exit(1);
|
|
13959
14347
|
}
|
|
13960
14348
|
});
|
|
@@ -13965,13 +14353,13 @@ runtimeCommand.command("stop").description("Stop a target workspace").argument("
|
|
|
13965
14353
|
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
13966
14354
|
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
13967
14355
|
if (!ref) {
|
|
13968
|
-
console.log(
|
|
14356
|
+
console.log(chalk19.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
|
|
13969
14357
|
return;
|
|
13970
14358
|
}
|
|
13971
14359
|
await resolved.driver.stop(ref.id);
|
|
13972
|
-
console.log(
|
|
14360
|
+
console.log(chalk19.green(`\u2714 Stopped ${resolved.workspaceName}`));
|
|
13973
14361
|
} catch (err) {
|
|
13974
|
-
console.error(
|
|
14362
|
+
console.error(chalk19.red(err.message));
|
|
13975
14363
|
process.exit(1);
|
|
13976
14364
|
}
|
|
13977
14365
|
});
|
|
@@ -13979,8 +14367,8 @@ runtimeCommand.command("stop").description("Stop a target workspace").argument("
|
|
|
13979
14367
|
// packages/cli/src/commands/workspace.ts
|
|
13980
14368
|
init_dist();
|
|
13981
14369
|
init_dist3();
|
|
13982
|
-
import { Command as
|
|
13983
|
-
import
|
|
14370
|
+
import { Command as Command19 } from "commander";
|
|
14371
|
+
import chalk20 from "chalk";
|
|
13984
14372
|
function buildRegistry2() {
|
|
13985
14373
|
return new WorkspaceRegistry({
|
|
13986
14374
|
obsidian: createObsidianDriver
|
|
@@ -13998,7 +14386,7 @@ async function readStdin() {
|
|
|
13998
14386
|
}
|
|
13999
14387
|
return Buffer.concat(chunks).toString("utf-8");
|
|
14000
14388
|
}
|
|
14001
|
-
var workspaceCommand = new
|
|
14389
|
+
var workspaceCommand = new Command19("workspace").description("Interact with the workspace layer (vault storage). Bridges bash scripts to the WorkspaceDriver.");
|
|
14002
14390
|
function resolveTargetAndPath(arg1, arg2, useHub) {
|
|
14003
14391
|
if (useHub) {
|
|
14004
14392
|
if (arg2 !== void 0) {
|
|
@@ -14020,7 +14408,7 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
|
|
|
14020
14408
|
const content = await driver.read(path30);
|
|
14021
14409
|
process.stdout.write(content);
|
|
14022
14410
|
} catch (err) {
|
|
14023
|
-
console.error(
|
|
14411
|
+
console.error(chalk20.red(err.message));
|
|
14024
14412
|
process.exit(1);
|
|
14025
14413
|
}
|
|
14026
14414
|
});
|
|
@@ -14050,7 +14438,7 @@ workspaceCommand.command("write").description("Write content to a scope-relative
|
|
|
14050
14438
|
const payload = rawContent === "-" ? await readStdin() : rawContent;
|
|
14051
14439
|
await driver.write(path30, payload);
|
|
14052
14440
|
} catch (err) {
|
|
14053
|
-
console.error(
|
|
14441
|
+
console.error(chalk20.red(err.message));
|
|
14054
14442
|
process.exit(1);
|
|
14055
14443
|
}
|
|
14056
14444
|
});
|
|
@@ -14063,7 +14451,7 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
|
|
|
14063
14451
|
const entries = await driver.list(path30);
|
|
14064
14452
|
for (const entry of entries) console.log(entry);
|
|
14065
14453
|
} catch (err) {
|
|
14066
|
-
console.error(
|
|
14454
|
+
console.error(chalk20.red(err.message));
|
|
14067
14455
|
process.exit(1);
|
|
14068
14456
|
}
|
|
14069
14457
|
});
|
|
@@ -14076,7 +14464,7 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
|
|
|
14076
14464
|
const ok2 = await driver.exists(path30);
|
|
14077
14465
|
process.exit(ok2 ? 0 : 1);
|
|
14078
14466
|
} catch (err) {
|
|
14079
|
-
console.error(
|
|
14467
|
+
console.error(chalk20.red(err.message));
|
|
14080
14468
|
process.exit(2);
|
|
14081
14469
|
}
|
|
14082
14470
|
});
|
|
@@ -14088,7 +14476,7 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
|
|
|
14088
14476
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
14089
14477
|
await driver.mkdir(path30);
|
|
14090
14478
|
} catch (err) {
|
|
14091
|
-
console.error(
|
|
14479
|
+
console.error(chalk20.red(err.message));
|
|
14092
14480
|
process.exit(1);
|
|
14093
14481
|
}
|
|
14094
14482
|
});
|
|
@@ -14096,8 +14484,8 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
|
|
|
14096
14484
|
// packages/cli/src/commands/notify.ts
|
|
14097
14485
|
init_dist();
|
|
14098
14486
|
init_dist3();
|
|
14099
|
-
import { Command as
|
|
14100
|
-
import
|
|
14487
|
+
import { Command as Command20 } from "commander";
|
|
14488
|
+
import chalk21 from "chalk";
|
|
14101
14489
|
async function readStdin2() {
|
|
14102
14490
|
const chunks = [];
|
|
14103
14491
|
for await (const chunk of process.stdin) {
|
|
@@ -14105,7 +14493,7 @@ async function readStdin2() {
|
|
|
14105
14493
|
}
|
|
14106
14494
|
return Buffer.concat(chunks).toString("utf-8");
|
|
14107
14495
|
}
|
|
14108
|
-
var notifyCommand = new
|
|
14496
|
+
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
14497
|
const config = loadConfig();
|
|
14110
14498
|
const registry = new NotifierRegistry({ cmux: createCmuxNotifier });
|
|
14111
14499
|
try {
|
|
@@ -14113,7 +14501,7 @@ var notifyCommand = new Command19("notify").description("Send a message to the u
|
|
|
14113
14501
|
if (!payload) throw new Error("Empty message");
|
|
14114
14502
|
await registry.get(config).notify(payload);
|
|
14115
14503
|
} catch (err) {
|
|
14116
|
-
console.error(
|
|
14504
|
+
console.error(chalk21.red(err.message));
|
|
14117
14505
|
process.exit(1);
|
|
14118
14506
|
}
|
|
14119
14507
|
});
|
|
@@ -14123,8 +14511,8 @@ init_dist();
|
|
|
14123
14511
|
init_dist4();
|
|
14124
14512
|
init_dist3();
|
|
14125
14513
|
init_dist();
|
|
14126
|
-
import { Command as
|
|
14127
|
-
import
|
|
14514
|
+
import { Command as Command21 } from "commander";
|
|
14515
|
+
import chalk22 from "chalk";
|
|
14128
14516
|
import fs26 from "fs";
|
|
14129
14517
|
import path28 from "path";
|
|
14130
14518
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -14171,17 +14559,17 @@ async function runEmit(opts) {
|
|
|
14171
14559
|
for (const dest of emitter.destinations(scope, projectRoot)) {
|
|
14172
14560
|
const result = await emitter.emit(source, dest, { dryRun: opts.dryRun });
|
|
14173
14561
|
if (opts.dryRun) {
|
|
14174
|
-
console.log(
|
|
14562
|
+
console.log(chalk22.cyan(`[${emitter.name}] ${dest.path}`));
|
|
14175
14563
|
console.log(result.diff ?? "(no diff)");
|
|
14176
14564
|
} else if (result.written) {
|
|
14177
14565
|
console.log(
|
|
14178
|
-
|
|
14566
|
+
chalk22.green(
|
|
14179
14567
|
`\u2714 ${emitter.name} \u2192 ${dest.path} (${result.bytesWritten} bytes)`
|
|
14180
14568
|
)
|
|
14181
14569
|
);
|
|
14182
14570
|
emittedCount.written++;
|
|
14183
14571
|
} else {
|
|
14184
|
-
console.log(
|
|
14572
|
+
console.log(chalk22.gray(`- ${emitter.name} \u2192 ${dest.path} (skipped)`));
|
|
14185
14573
|
emittedCount.skipped++;
|
|
14186
14574
|
}
|
|
14187
14575
|
}
|
|
@@ -14204,14 +14592,14 @@ async function runEmit(opts) {
|
|
|
14204
14592
|
`Unknown project '${projectName}'. Available: ${Object.keys(cfg.projects).join(", ") || "(none)"}`
|
|
14205
14593
|
);
|
|
14206
14594
|
}
|
|
14207
|
-
console.error(
|
|
14595
|
+
console.error(chalk22.yellow(`\u26A0 unknown project: ${projectName}`));
|
|
14208
14596
|
continue;
|
|
14209
14597
|
}
|
|
14210
14598
|
const source = await readProjectLevelSource(
|
|
14211
14599
|
createObsidianDriver({ root: proj.path })
|
|
14212
14600
|
);
|
|
14213
14601
|
if (!source) {
|
|
14214
|
-
console.log(
|
|
14602
|
+
console.log(chalk22.gray(`- ${projectName}: no AGENTS.md, skipping`));
|
|
14215
14603
|
continue;
|
|
14216
14604
|
}
|
|
14217
14605
|
for (const name of targets) {
|
|
@@ -14221,21 +14609,21 @@ async function runEmit(opts) {
|
|
|
14221
14609
|
}
|
|
14222
14610
|
if (!opts.dryRun) {
|
|
14223
14611
|
console.log(
|
|
14224
|
-
|
|
14612
|
+
chalk22.bold(
|
|
14225
14613
|
`
|
|
14226
14614
|
Projection complete \u2014 ${emittedCount.written} written, ${emittedCount.skipped} skipped.`
|
|
14227
14615
|
)
|
|
14228
14616
|
);
|
|
14229
14617
|
}
|
|
14230
14618
|
}
|
|
14231
|
-
var projectionCommand = new
|
|
14619
|
+
var projectionCommand = new Command21("projection").description(
|
|
14232
14620
|
"Project squadrant instructions and skills to supported agent formats"
|
|
14233
14621
|
);
|
|
14234
14622
|
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
14623
|
try {
|
|
14236
14624
|
await runEmit({ ...opts, dryRun: false });
|
|
14237
14625
|
} catch (err) {
|
|
14238
|
-
console.error(
|
|
14626
|
+
console.error(chalk22.red(err.message));
|
|
14239
14627
|
process.exit(1);
|
|
14240
14628
|
}
|
|
14241
14629
|
});
|
|
@@ -14243,7 +14631,7 @@ projectionCommand.command("diff").description("Preview changes without writing")
|
|
|
14243
14631
|
try {
|
|
14244
14632
|
await runEmit({ ...opts, dryRun: true });
|
|
14245
14633
|
} catch (err) {
|
|
14246
|
-
console.error(
|
|
14634
|
+
console.error(chalk22.red(err.message));
|
|
14247
14635
|
process.exit(1);
|
|
14248
14636
|
}
|
|
14249
14637
|
});
|
|
@@ -14253,7 +14641,7 @@ projectionCommand.command("list").description("List registered projection target
|
|
|
14253
14641
|
const emitter = registry.get(name);
|
|
14254
14642
|
const userDests = emitter.destinations("user").map((d) => d.path);
|
|
14255
14643
|
const projectDests = emitter.destinations("project", "<project>").map((d) => d.path);
|
|
14256
|
-
console.log(
|
|
14644
|
+
console.log(chalk22.bold(name));
|
|
14257
14645
|
console.log(` user: ${userDests.join(", ") || "(none)"}`);
|
|
14258
14646
|
console.log(` project: ${projectDests.join(", ") || "(none)"}`);
|
|
14259
14647
|
}
|
|
@@ -14261,9 +14649,9 @@ projectionCommand.command("list").description("List registered projection target
|
|
|
14261
14649
|
|
|
14262
14650
|
// packages/cli/src/commands/codex-chat-smoke.ts
|
|
14263
14651
|
init_dist4();
|
|
14264
|
-
import { Command as
|
|
14652
|
+
import { Command as Command22 } from "commander";
|
|
14265
14653
|
import { resolve as resolve2 } from "path";
|
|
14266
|
-
var codexChatSmokeCommand = new
|
|
14654
|
+
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
14655
|
"--approval",
|
|
14268
14656
|
"include the approval round-trip (Phase 1 PASS requires this)",
|
|
14269
14657
|
false
|
|
@@ -14332,11 +14720,11 @@ init_dist();
|
|
|
14332
14720
|
init_dist();
|
|
14333
14721
|
init_dist();
|
|
14334
14722
|
init_dist2();
|
|
14335
|
-
import { Command as
|
|
14723
|
+
import { Command as Command23 } from "commander";
|
|
14336
14724
|
import fs27 from "fs";
|
|
14337
14725
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
14338
14726
|
import { dirname as dirname6, join as join25 } from "path";
|
|
14339
|
-
import
|
|
14727
|
+
import chalk23 from "chalk";
|
|
14340
14728
|
function runConfigCheck(opts) {
|
|
14341
14729
|
const raw = JSON.parse(fs27.readFileSync(opts.configPath, "utf-8"));
|
|
14342
14730
|
const def = getDefaultConfig();
|
|
@@ -14391,14 +14779,14 @@ function runConfigSet(key, value, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
14391
14779
|
}
|
|
14392
14780
|
function printRestartOutcome(outcome) {
|
|
14393
14781
|
if (outcome === "skipped-not-running") {
|
|
14394
|
-
console.log(
|
|
14782
|
+
console.log(chalk23.dim("(daemon not running \u2014 change applies on next start)"));
|
|
14395
14783
|
} else if (outcome === "skipped-opt-out") {
|
|
14396
|
-
console.log(
|
|
14784
|
+
console.log(chalk23.dim("(run 'squadrant heal daemon' to apply)"));
|
|
14397
14785
|
}
|
|
14398
14786
|
}
|
|
14399
14787
|
function runConfigSetAction(opts) {
|
|
14400
14788
|
runConfigSet(opts.key, opts.value, opts.configPath);
|
|
14401
|
-
console.log(
|
|
14789
|
+
console.log(chalk23.green(`\u2714 set ${opts.key} = ${opts.value}`));
|
|
14402
14790
|
if (isDaemonCachedKey(opts.key)) {
|
|
14403
14791
|
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
14404
14792
|
const outcome = doRestart({ reason: `config ${opts.key}`, noRestart: opts.noRestart });
|
|
@@ -14406,9 +14794,9 @@ function runConfigSetAction(opts) {
|
|
|
14406
14794
|
}
|
|
14407
14795
|
}
|
|
14408
14796
|
var SEV_COLOR = {
|
|
14409
|
-
info:
|
|
14410
|
-
advisory:
|
|
14411
|
-
warn:
|
|
14797
|
+
info: chalk23.green,
|
|
14798
|
+
advisory: chalk23.yellow,
|
|
14799
|
+
warn: chalk23.red
|
|
14412
14800
|
};
|
|
14413
14801
|
var KIND_GLYPH = {
|
|
14414
14802
|
missing: "+",
|
|
@@ -14420,14 +14808,14 @@ function printItems(items) {
|
|
|
14420
14808
|
for (const i of items) {
|
|
14421
14809
|
const color = SEV_COLOR[i.severity] ?? ((s) => s);
|
|
14422
14810
|
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}`) +
|
|
14811
|
+
console.log(" " + color(`${KIND_GLYPH[i.kind]} ${i.kind}: ${i.path}`) + chalk23.dim(detail));
|
|
14424
14812
|
}
|
|
14425
14813
|
}
|
|
14426
|
-
var configCommand = new
|
|
14814
|
+
var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
|
|
14427
14815
|
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
14816
|
const pkgVersion = readPkgVersion2();
|
|
14429
14817
|
if (!fs27.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
14430
|
-
console.log(
|
|
14818
|
+
console.log(chalk23.yellow("No config found \u2014 run `squadrant init` first."));
|
|
14431
14819
|
return;
|
|
14432
14820
|
}
|
|
14433
14821
|
const res = runConfigCheck({ configPath: DEFAULT_CONFIG_PATH, pkgVersion, fix: opts.fix, accept: opts.accept });
|
|
@@ -14436,21 +14824,21 @@ configCommand.command("check").description("Detect config drift vs the current d
|
|
|
14436
14824
|
return;
|
|
14437
14825
|
}
|
|
14438
14826
|
if (res.items.length === 0) {
|
|
14439
|
-
console.log(
|
|
14827
|
+
console.log(chalk23.green("\u2714 Config is in sync with the current schema."));
|
|
14440
14828
|
return;
|
|
14441
14829
|
}
|
|
14442
|
-
console.log(
|
|
14830
|
+
console.log(chalk23.bold("\nConfig drift:\n"));
|
|
14443
14831
|
printItems(res.items);
|
|
14444
14832
|
if (opts.fix && res.applied.length) {
|
|
14445
|
-
console.log(
|
|
14833
|
+
console.log(chalk23.green(`
|
|
14446
14834
|
\u2714 Applied ${res.applied.length} safe item(s): ${res.applied.join(", ")}`));
|
|
14447
14835
|
}
|
|
14448
14836
|
const judgment = res.remaining.filter((i) => i.kind === "changed-default" || i.kind === "invalid");
|
|
14449
14837
|
if (judgment.length) {
|
|
14450
|
-
console.log(
|
|
14838
|
+
console.log(chalk23.yellow(`
|
|
14451
14839
|
${judgment.length} item(s) need review \u2014 run the config-doctor skill, or \`squadrant config check --accept\` to keep your values.`));
|
|
14452
14840
|
} else if (res.stamped) {
|
|
14453
|
-
console.log(
|
|
14841
|
+
console.log(chalk23.green("\n\u2714 Config reconciled and stamped."));
|
|
14454
14842
|
}
|
|
14455
14843
|
});
|
|
14456
14844
|
configCommand.command("get").description("Read a config value by dotted key (e.g. defaults.effort)").argument("<key>", "dotted config key").action((key) => {
|
|
@@ -14458,7 +14846,7 @@ configCommand.command("get").description("Read a config value by dotted key (e.g
|
|
|
14458
14846
|
const value = runConfigGet(key);
|
|
14459
14847
|
console.log(typeof value === "string" ? value : JSON.stringify(value));
|
|
14460
14848
|
} catch (e) {
|
|
14461
|
-
console.error(
|
|
14849
|
+
console.error(chalk23.red(e.message));
|
|
14462
14850
|
process.exit(1);
|
|
14463
14851
|
}
|
|
14464
14852
|
});
|
|
@@ -14466,7 +14854,7 @@ configCommand.command("set").description("Write a config value by dotted key (e.
|
|
|
14466
14854
|
try {
|
|
14467
14855
|
runConfigSetAction({ key, value, noRestart: opts.restart === false });
|
|
14468
14856
|
} catch (e) {
|
|
14469
|
-
console.error(
|
|
14857
|
+
console.error(chalk23.red(e.message));
|
|
14470
14858
|
process.exit(1);
|
|
14471
14859
|
}
|
|
14472
14860
|
});
|
|
@@ -14476,8 +14864,8 @@ function readPkgVersion2() {
|
|
|
14476
14864
|
}
|
|
14477
14865
|
|
|
14478
14866
|
// packages/cli/src/commands/heal.ts
|
|
14479
|
-
import { Command as
|
|
14480
|
-
import
|
|
14867
|
+
import { Command as Command24 } from "commander";
|
|
14868
|
+
import chalk24 from "chalk";
|
|
14481
14869
|
init_dist2();
|
|
14482
14870
|
init_dist2();
|
|
14483
14871
|
function buildHealStatus(components) {
|
|
@@ -14518,15 +14906,15 @@ async function runHealStatus(opts) {
|
|
|
14518
14906
|
return result.healthy ? 0 : 2;
|
|
14519
14907
|
}
|
|
14520
14908
|
if (result.healthy) {
|
|
14521
|
-
stdout.write(
|
|
14909
|
+
stdout.write(chalk24.green("\u2714 all components healthy\n"));
|
|
14522
14910
|
return 0;
|
|
14523
14911
|
}
|
|
14524
|
-
stdout.write(
|
|
14912
|
+
stdout.write(chalk24.bold("Unhealthy components:\n\n"));
|
|
14525
14913
|
for (const c of result.components) {
|
|
14526
14914
|
if (c.healCmd) {
|
|
14527
|
-
stdout.write(` ${
|
|
14915
|
+
stdout.write(` ${chalk24.red("\u2718")} ${c.kind.padEnd(8)} ${c.ref.padEnd(16)} ${chalk24.red(c.state.padEnd(8))} ${c.project}
|
|
14528
14916
|
`);
|
|
14529
|
-
stdout.write(` heal: ${
|
|
14917
|
+
stdout.write(` heal: ${chalk24.cyan(c.healCmd)}
|
|
14530
14918
|
`);
|
|
14531
14919
|
}
|
|
14532
14920
|
}
|
|
@@ -14537,7 +14925,7 @@ async function runHealDaemon(opts) {
|
|
|
14537
14925
|
stdout.write("restarting squadrantd via launchd kickstart...\n");
|
|
14538
14926
|
try {
|
|
14539
14927
|
opts.ensureDaemon();
|
|
14540
|
-
stdout.write(
|
|
14928
|
+
stdout.write(chalk24.green("\u2714 daemon kickstart complete\n"));
|
|
14541
14929
|
return 0;
|
|
14542
14930
|
} catch (e) {
|
|
14543
14931
|
stderr.write(`heal daemon failed: ${e.message}
|
|
@@ -14545,8 +14933,8 @@ async function runHealDaemon(opts) {
|
|
|
14545
14933
|
return 1;
|
|
14546
14934
|
}
|
|
14547
14935
|
}
|
|
14548
|
-
var healCommand = new
|
|
14549
|
-
new
|
|
14936
|
+
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(
|
|
14937
|
+
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
14938
|
const code = await runHealStatus({
|
|
14551
14939
|
project: opts.project,
|
|
14552
14940
|
json: opts.json ?? false,
|
|
@@ -14557,7 +14945,7 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
|
|
|
14557
14945
|
process.exit(code);
|
|
14558
14946
|
})
|
|
14559
14947
|
).addCommand(
|
|
14560
|
-
new
|
|
14948
|
+
new Command24("daemon").description("Restart squadrantd via the idempotent launchd kickstart path").action(async () => {
|
|
14561
14949
|
const code = await runHealDaemon({
|
|
14562
14950
|
ensureDaemon: () => restartDaemonIfRunning({ reason: "heal", isRunning: () => true }),
|
|
14563
14951
|
stdout: process.stdout,
|
|
@@ -14568,15 +14956,15 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
|
|
|
14568
14956
|
);
|
|
14569
14957
|
|
|
14570
14958
|
// packages/cli/src/commands/group.ts
|
|
14571
|
-
import { Command as
|
|
14572
|
-
import
|
|
14959
|
+
import { Command as Command26 } from "commander";
|
|
14960
|
+
import chalk26 from "chalk";
|
|
14573
14961
|
|
|
14574
14962
|
// packages/cli/src/commands/dispatch.ts
|
|
14575
14963
|
init_dist();
|
|
14576
14964
|
init_dist2();
|
|
14577
|
-
import { Command as
|
|
14965
|
+
import { Command as Command25 } from "commander";
|
|
14578
14966
|
import { execSync as execSync13 } from "child_process";
|
|
14579
|
-
import
|
|
14967
|
+
import chalk25 from "chalk";
|
|
14580
14968
|
async function runDispatch(toProject, task, opts) {
|
|
14581
14969
|
const fromProject = resolveCurrentProject(loadConfig());
|
|
14582
14970
|
if (!fromProject) {
|
|
@@ -14601,21 +14989,21 @@ async function runDispatch(toProject, task, opts) {
|
|
|
14601
14989
|
async function dispatchAction(toProject, task, opts) {
|
|
14602
14990
|
try {
|
|
14603
14991
|
const result = await runDispatch(toProject, task, opts);
|
|
14604
|
-
console.log(
|
|
14605
|
-
console.log(
|
|
14606
|
-
console.log(
|
|
14992
|
+
console.log(chalk25.green(`\u2714 Dispatched to '${toProject}' (task ${result.id.slice(0, 8)})`));
|
|
14993
|
+
console.log(chalk25.dim(` originProject: ${result.originProject ?? "none"}`));
|
|
14994
|
+
console.log(chalk25.dim(" You will be notified when the task settles (done/blocked/failed)."));
|
|
14607
14995
|
} catch (e) {
|
|
14608
|
-
console.error(
|
|
14996
|
+
console.error(chalk25.red(`\u2718 ${e.message}`));
|
|
14609
14997
|
process.exit(1);
|
|
14610
14998
|
}
|
|
14611
14999
|
}
|
|
14612
|
-
var dispatchCommand = new
|
|
15000
|
+
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
15001
|
|
|
14614
15002
|
// packages/cli/src/commands/group.ts
|
|
14615
15003
|
init_dist2();
|
|
14616
|
-
var groupCommand = new
|
|
14617
|
-
new
|
|
14618
|
-
console.error(
|
|
15004
|
+
var groupCommand = new Command26("group").description("Cross-project intra-group operations (Phase 1: dispatch)").addCommand(
|
|
15005
|
+
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) => {
|
|
15006
|
+
console.error(chalk26.yellow(
|
|
14619
15007
|
`\u26A0 'squadrant group dispatch' is deprecated \u2014 use 'squadrant dispatch <project> "<task>"' instead.`
|
|
14620
15008
|
));
|
|
14621
15009
|
await dispatchAction(toProject, task, opts);
|
|
@@ -14626,8 +15014,8 @@ var groupCommand = new Command25("group").description("Cross-project intra-group
|
|
|
14626
15014
|
init_dist();
|
|
14627
15015
|
init_dist2();
|
|
14628
15016
|
import { join as join26, dirname as dirname7 } from "path";
|
|
14629
|
-
import { Command as
|
|
14630
|
-
import
|
|
15017
|
+
import { Command as Command27 } from "commander";
|
|
15018
|
+
import chalk27 from "chalk";
|
|
14631
15019
|
init_require_daemon();
|
|
14632
15020
|
async function runPing(project, message) {
|
|
14633
15021
|
const config = loadConfig();
|
|
@@ -14643,20 +15031,20 @@ async function runPing(project, message) {
|
|
|
14643
15031
|
source: "cli"
|
|
14644
15032
|
});
|
|
14645
15033
|
}
|
|
14646
|
-
var pingCommand = new
|
|
15034
|
+
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
15035
|
try {
|
|
14648
15036
|
await runPing(project, message);
|
|
14649
|
-
console.log(
|
|
15037
|
+
console.log(chalk27.green(`\u2714 Pinged '${project}'`));
|
|
14650
15038
|
} catch (err) {
|
|
14651
|
-
console.error(
|
|
15039
|
+
console.error(chalk27.red(err.message));
|
|
14652
15040
|
process.exit(1);
|
|
14653
15041
|
}
|
|
14654
15042
|
});
|
|
14655
15043
|
|
|
14656
15044
|
// packages/cli/src/commands/cmux.ts
|
|
14657
15045
|
init_dist();
|
|
14658
|
-
import { Command as
|
|
14659
|
-
import
|
|
15046
|
+
import { Command as Command28 } from "commander";
|
|
15047
|
+
import chalk28 from "chalk";
|
|
14660
15048
|
async function runCmuxAutoconfig(opts) {
|
|
14661
15049
|
const { json, stdout, stderr } = opts;
|
|
14662
15050
|
let r;
|
|
@@ -14675,19 +15063,19 @@ async function runCmuxAutoconfig(opts) {
|
|
|
14675
15063
|
stdout.write(`wrote cmux automation config \u2192 ${r.configPath}
|
|
14676
15064
|
`);
|
|
14677
15065
|
} else {
|
|
14678
|
-
stdout.write(
|
|
15066
|
+
stdout.write(chalk28.dim(`cmux automation config already in place (${r.configPath})
|
|
14679
15067
|
`));
|
|
14680
15068
|
}
|
|
14681
15069
|
if (r.verdict === "reachable") {
|
|
14682
|
-
stdout.write(
|
|
15070
|
+
stdout.write(chalk28.green("\u2714 daemon-direct delivery is reachable \u2014 cmux control socket accepts the daemon\n"));
|
|
14683
15071
|
return 0;
|
|
14684
15072
|
}
|
|
14685
15073
|
if (r.needsRestart) {
|
|
14686
15074
|
stdout.write(
|
|
14687
|
-
|
|
15075
|
+
chalk28.yellow("\u26A0 cmux is still on the old socket mode \u2014 restart cmux to enable daemon-direct delivery.\n")
|
|
14688
15076
|
);
|
|
14689
15077
|
if (r.promptedThisRun) {
|
|
14690
|
-
stdout.write(
|
|
15078
|
+
stdout.write(chalk28.dim(" (one-time prompt \u2014 you won't be nagged again)\n"));
|
|
14691
15079
|
}
|
|
14692
15080
|
return 2;
|
|
14693
15081
|
}
|
|
@@ -14696,8 +15084,8 @@ async function runCmuxAutoconfig(opts) {
|
|
|
14696
15084
|
);
|
|
14697
15085
|
return 1;
|
|
14698
15086
|
}
|
|
14699
|
-
var cmuxCommand = new
|
|
14700
|
-
new
|
|
15087
|
+
var cmuxCommand = new Command28("cmux").description("cmux integration helpers").addCommand(
|
|
15088
|
+
new Command28("autoconfig").description(
|
|
14701
15089
|
"Write the cmux automation socket config and probe whether daemon-direct\ndelivery is reachable. Idempotent; prompts once if a cmux restart is needed."
|
|
14702
15090
|
).option("--json", "output machine-readable JSON (exit 0=reachable, 1=unknown, 2=restart-needed)").action(async (opts) => {
|
|
14703
15091
|
const code = await runCmuxAutoconfig({
|
|
@@ -14714,8 +15102,8 @@ init_dist();
|
|
|
14714
15102
|
init_dist2();
|
|
14715
15103
|
import fs28 from "fs";
|
|
14716
15104
|
import path29 from "path";
|
|
14717
|
-
import { Command as
|
|
14718
|
-
import
|
|
15105
|
+
import { Command as Command29 } from "commander";
|
|
15106
|
+
import chalk29 from "chalk";
|
|
14719
15107
|
var VALID_EFFORTS = ["max", "balance", "low"];
|
|
14720
15108
|
var EFFORT_MEANING = {
|
|
14721
15109
|
max: "tokens are plentiful \u2014 bias crew spawns toward claude/opus",
|
|
@@ -14780,29 +15168,29 @@ async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(
|
|
|
14780
15168
|
}
|
|
14781
15169
|
}
|
|
14782
15170
|
}
|
|
14783
|
-
var effortCommand = new
|
|
15171
|
+
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
15172
|
if (value === void 0) {
|
|
14785
15173
|
let result;
|
|
14786
15174
|
try {
|
|
14787
15175
|
result = runEffortGet(void 0, options.project);
|
|
14788
15176
|
} catch (err) {
|
|
14789
|
-
console.error(
|
|
15177
|
+
console.error(chalk29.red(err.message));
|
|
14790
15178
|
process.exit(1);
|
|
14791
15179
|
}
|
|
14792
15180
|
const label = options.project ? `${options.project} project` : "global";
|
|
14793
|
-
console.log(
|
|
14794
|
-
console.log(
|
|
15181
|
+
console.log(chalk29.bold(`Current effort (${label}):`), chalk29.cyan(result.effort));
|
|
15182
|
+
console.log(chalk29.dim(EFFORT_MEANING[result.effort]));
|
|
14795
15183
|
return;
|
|
14796
15184
|
}
|
|
14797
15185
|
try {
|
|
14798
15186
|
runEffortSet(value, void 0, options.project);
|
|
14799
15187
|
} catch (err) {
|
|
14800
|
-
console.error(
|
|
15188
|
+
console.error(chalk29.red(err.message));
|
|
14801
15189
|
process.exit(1);
|
|
14802
15190
|
}
|
|
14803
15191
|
const effort = value;
|
|
14804
|
-
console.log(
|
|
14805
|
-
console.log(
|
|
15192
|
+
console.log(chalk29.green(`\u2714 effort \u2192 ${effort} (${effortScopeLabel(options.project)})`));
|
|
15193
|
+
console.log(chalk29.dim(EFFORT_MEANING[effort]));
|
|
14806
15194
|
try {
|
|
14807
15195
|
const { createCmuxDriver: createCmuxDriver2, RuntimeRegistry: RuntimeRegistry2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports3));
|
|
14808
15196
|
const config = loadConfig();
|
|
@@ -14812,7 +15200,7 @@ var effortCommand = new Command28("effort").description("Get or set the crew tok
|
|
|
14812
15200
|
const append = (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
|
|
14813
15201
|
await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append, options.project);
|
|
14814
15202
|
} catch {
|
|
14815
|
-
console.log(
|
|
15203
|
+
console.log(chalk29.dim("(no running captain detected \u2014 change applies on next launch)"));
|
|
14816
15204
|
}
|
|
14817
15205
|
});
|
|
14818
15206
|
|
|
@@ -14821,8 +15209,8 @@ init_dist();
|
|
|
14821
15209
|
init_dist2();
|
|
14822
15210
|
import { join as join27, dirname as dirname8 } from "path";
|
|
14823
15211
|
import { emitKeypressEvents } from "readline";
|
|
14824
|
-
import { Command as
|
|
14825
|
-
import
|
|
15212
|
+
import { Command as Command30 } from "commander";
|
|
15213
|
+
import chalk30 from "chalk";
|
|
14826
15214
|
function defaultStateRoot() {
|
|
14827
15215
|
return join27(dirname8(DEFAULT_CONFIG_PATH), "state");
|
|
14828
15216
|
}
|
|
@@ -14869,11 +15257,11 @@ async function questionYesNo(prompt) {
|
|
|
14869
15257
|
});
|
|
14870
15258
|
});
|
|
14871
15259
|
}
|
|
14872
|
-
var telegramCommand = new
|
|
15260
|
+
var telegramCommand = new Command30("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
|
|
14873
15261
|
telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
|
|
14874
15262
|
const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
|
|
14875
|
-
console.log(`token: ${tokenSet ?
|
|
14876
|
-
console.log(`supergroup: ${supergroupId ??
|
|
15263
|
+
console.log(`token: ${tokenSet ? chalk30.green("set") : chalk30.yellow("unset")}`);
|
|
15264
|
+
console.log(`supergroup: ${supergroupId ?? chalk30.yellow("unset")}`);
|
|
14877
15265
|
if (links.length === 0) {
|
|
14878
15266
|
console.log("no projects linked");
|
|
14879
15267
|
return;
|
|
@@ -14883,32 +15271,32 @@ telegramCommand.command("status").description("Show Telegram config and linked p
|
|
|
14883
15271
|
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
15272
|
const cfg = loadConfig().telegram;
|
|
14885
15273
|
if (!cfg) {
|
|
14886
|
-
console.error(
|
|
15274
|
+
console.error(chalk30.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
|
|
14887
15275
|
process.exit(1);
|
|
14888
15276
|
}
|
|
14889
15277
|
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
14890
15278
|
if (!token) {
|
|
14891
|
-
console.error(
|
|
15279
|
+
console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
14892
15280
|
process.exit(1);
|
|
14893
15281
|
}
|
|
14894
15282
|
const client = createTelegramClient({ token });
|
|
14895
15283
|
const { topicId, created } = await runTelegramLink({ project, cfg, client, stateRoot: defaultStateRoot() });
|
|
14896
|
-
console.log(
|
|
15284
|
+
console.log(chalk30.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
|
|
14897
15285
|
});
|
|
14898
15286
|
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
15287
|
if (!process.stdin.isTTY) {
|
|
14900
|
-
console.error(
|
|
15288
|
+
console.error(chalk30.red("setup requires a TTY \u2014 pipe input is not supported"));
|
|
14901
15289
|
process.exit(1);
|
|
14902
15290
|
}
|
|
14903
15291
|
console.log();
|
|
14904
|
-
console.log(
|
|
15292
|
+
console.log(chalk30.bold("Telegram setup") + " \u2014 connect squadrant to a Telegram bot for notifications + remote control");
|
|
14905
15293
|
console.log();
|
|
14906
15294
|
console.log("Before you start you need:");
|
|
14907
15295
|
console.log(" 1. A bot token from @BotFather (send /newbot)");
|
|
14908
15296
|
console.log(" 2. A forum supergroup with the bot added as an admin (Topics enabled)");
|
|
14909
15297
|
console.log(" 3. Bot privacy mode set to OFF (@BotFather \u2192 /setprivacy \u2192 Disable)");
|
|
14910
15298
|
console.log();
|
|
14911
|
-
console.log(
|
|
15299
|
+
console.log(chalk30.bold("Step 1/3 \u2014 Bot token"));
|
|
14912
15300
|
const existingCfg = loadConfig().telegram;
|
|
14913
15301
|
const existingToken = existingCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
14914
15302
|
const decision = resolveSetupToken(existingToken, { resetToken: opts.resetToken ?? false });
|
|
@@ -14920,67 +15308,67 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
|
|
|
14920
15308
|
try {
|
|
14921
15309
|
botUser = await client.getMe();
|
|
14922
15310
|
token = existingToken;
|
|
14923
|
-
console.log(
|
|
15311
|
+
console.log(chalk30.green(`Using existing bot token (@${botUser.username})`));
|
|
14924
15312
|
console.log();
|
|
14925
15313
|
} catch {
|
|
14926
|
-
console.log(
|
|
15314
|
+
console.log(chalk30.yellow("Existing token is invalid \u2014 please enter a new one."));
|
|
14927
15315
|
console.log("Paste your bot token then press Enter (input is hidden):");
|
|
14928
15316
|
token = await questionMasked();
|
|
14929
15317
|
if (!token) {
|
|
14930
|
-
console.error(
|
|
15318
|
+
console.error(chalk30.red("token required"));
|
|
14931
15319
|
process.exit(1);
|
|
14932
15320
|
}
|
|
14933
15321
|
client = createTelegramClient({ token });
|
|
14934
15322
|
try {
|
|
14935
15323
|
botUser = await client.getMe();
|
|
14936
15324
|
} catch (e) {
|
|
14937
|
-
console.error(
|
|
15325
|
+
console.error(chalk30.red(`token rejected: ${e.message}`));
|
|
14938
15326
|
process.exit(1);
|
|
14939
15327
|
}
|
|
14940
|
-
console.log(
|
|
15328
|
+
console.log(chalk30.green(`Connected as @${botUser.username}`));
|
|
14941
15329
|
console.log();
|
|
14942
15330
|
}
|
|
14943
15331
|
} else {
|
|
14944
15332
|
console.log("Paste your bot token then press Enter (input is hidden):");
|
|
14945
15333
|
token = await questionMasked();
|
|
14946
15334
|
if (!token) {
|
|
14947
|
-
console.error(
|
|
15335
|
+
console.error(chalk30.red("token required"));
|
|
14948
15336
|
process.exit(1);
|
|
14949
15337
|
}
|
|
14950
15338
|
client = createTelegramClient({ token });
|
|
14951
15339
|
try {
|
|
14952
15340
|
botUser = await client.getMe();
|
|
14953
15341
|
} catch (e) {
|
|
14954
|
-
console.error(
|
|
15342
|
+
console.error(chalk30.red(`token rejected: ${e.message}`));
|
|
14955
15343
|
process.exit(1);
|
|
14956
15344
|
}
|
|
14957
|
-
console.log(
|
|
15345
|
+
console.log(chalk30.green(`Connected as @${botUser.username}`));
|
|
14958
15346
|
console.log();
|
|
14959
15347
|
}
|
|
14960
|
-
console.log(
|
|
15348
|
+
console.log(chalk30.bold("Step 2/3 \u2014 Supergroup"));
|
|
14961
15349
|
const groupDecision = resolveSetupGroup(existingCfg?.supergroupId, { redetect: opts.redetect ?? false });
|
|
14962
15350
|
let supergroupId;
|
|
14963
15351
|
let detectedUserId;
|
|
14964
15352
|
if (groupDecision === "reuse") {
|
|
14965
15353
|
supergroupId = existingCfg.supergroupId;
|
|
14966
|
-
console.log(
|
|
15354
|
+
console.log(chalk30.green(`Using existing group: ${supergroupId}`));
|
|
14967
15355
|
console.log();
|
|
14968
15356
|
} else {
|
|
14969
15357
|
console.log("Add the bot to your forum supergroup, then send any message in it.");
|
|
14970
|
-
console.log(
|
|
15358
|
+
console.log(chalk30.dim("Waiting for a message (up to 60s)\u2026"));
|
|
14971
15359
|
try {
|
|
14972
15360
|
({ supergroupId, userId: detectedUserId } = await detectGroupAndUser(client, { timeoutMs: 6e4 }));
|
|
14973
15361
|
} catch {
|
|
14974
|
-
console.error(
|
|
14975
|
-
console.error(
|
|
15362
|
+
console.error(chalk30.red("Timed out \u2014 no supergroup message received within 60s."));
|
|
15363
|
+
console.error(chalk30.yellow("Check: bot is an admin in the group \xB7 privacy mode is OFF \xB7 Topics enabled"));
|
|
14976
15364
|
process.exit(1);
|
|
14977
15365
|
}
|
|
14978
|
-
console.log(
|
|
15366
|
+
console.log(chalk30.green(`Found group: ${supergroupId}`));
|
|
14979
15367
|
console.log();
|
|
14980
15368
|
}
|
|
14981
|
-
console.log(
|
|
14982
|
-
console.log(
|
|
14983
|
-
console.log(
|
|
15369
|
+
console.log(chalk30.bold("Step 3/3 \u2014 Remote control + Save"));
|
|
15370
|
+
console.log(chalk30.dim("Remote control enables auto-launching captains and the General command channel"));
|
|
15371
|
+
console.log(chalk30.dim("from your phone \u2014 gated to your Telegram user-id only (fail-closed)."));
|
|
14984
15372
|
const finalUserId = resolveSetupUserId(opts.userId, detectedUserId, defaultStateRoot());
|
|
14985
15373
|
let users;
|
|
14986
15374
|
let remoteControl;
|
|
@@ -14994,32 +15382,32 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
|
|
|
14994
15382
|
remoteControl = true;
|
|
14995
15383
|
}
|
|
14996
15384
|
} else if (groupDecision === "detect") {
|
|
14997
|
-
console.log(
|
|
14998
|
-
console.log(
|
|
15385
|
+
console.log(chalk30.yellow("Could not read your user-id from that message \u2014 skipping remote control."));
|
|
15386
|
+
console.log(chalk30.yellow("Re-run with --user-id <id> to enable, or edit telegram.users in config manually."));
|
|
14999
15387
|
printedRemoteControlState = true;
|
|
15000
15388
|
} else {
|
|
15001
15389
|
const existingUsers = existingCfg?.users;
|
|
15002
15390
|
if (existingUsers && existingUsers.length > 0) {
|
|
15003
|
-
console.log(
|
|
15391
|
+
console.log(chalk30.dim(`Remote control: already configured (user-id ${existingUsers[0]}). Use --user-id to update.`));
|
|
15004
15392
|
} else {
|
|
15005
|
-
console.log(
|
|
15393
|
+
console.log(chalk30.dim("Remote control: off. Re-run with --user-id <id> to enable."));
|
|
15006
15394
|
}
|
|
15007
15395
|
printedRemoteControlState = true;
|
|
15008
15396
|
}
|
|
15009
15397
|
writeTelegramConfig(DEFAULT_CONFIG_PATH, { token, supergroupId, users, remoteControl });
|
|
15010
|
-
console.log(
|
|
15398
|
+
console.log(chalk30.green(`Wrote telegram config \u2014 token: ${maskToken(token)} group: ${supergroupId}`));
|
|
15011
15399
|
if (!printedRemoteControlState) {
|
|
15012
15400
|
if (remoteControl) {
|
|
15013
|
-
console.log(
|
|
15401
|
+
console.log(chalk30.green(`Remote control: ON (allowlisted user-id ${users[0]})`));
|
|
15014
15402
|
} else {
|
|
15015
|
-
console.log(
|
|
15403
|
+
console.log(chalk30.dim("Remote control: off (default). Re-run with --user-id <id> to enable."));
|
|
15016
15404
|
}
|
|
15017
15405
|
}
|
|
15018
15406
|
try {
|
|
15019
15407
|
await runRegisterCommands({ client });
|
|
15020
|
-
console.log(
|
|
15408
|
+
console.log(chalk30.dim("Registered the /command menu."));
|
|
15021
15409
|
} catch (e) {
|
|
15022
|
-
console.log(
|
|
15410
|
+
console.log(chalk30.yellow(`command-menu registration skipped: ${e.message}`));
|
|
15023
15411
|
}
|
|
15024
15412
|
const topics = loadState(defaultStateRoot()).topics;
|
|
15025
15413
|
const topicEntries = Object.entries(topics);
|
|
@@ -15028,28 +15416,28 @@ telegramCommand.command("setup").description("Interactive wizard \u2014 bot toke
|
|
|
15028
15416
|
const project = key.slice(0, key.indexOf("::"));
|
|
15029
15417
|
return `${project}\u2192${id}`;
|
|
15030
15418
|
}).join(", ");
|
|
15031
|
-
console.log(
|
|
15419
|
+
console.log(chalk30.dim(`Existing topics: ${summary} (already created \u2014 not recreated)`));
|
|
15032
15420
|
} else {
|
|
15033
|
-
console.log(
|
|
15421
|
+
console.log(chalk30.dim("No project topics yet \u2014 they're created on first delivery or via: squadrant telegram link <project>"));
|
|
15034
15422
|
}
|
|
15035
15423
|
runTelegramPostSetup({});
|
|
15036
15424
|
console.log();
|
|
15037
|
-
console.log(`Next: ${
|
|
15425
|
+
console.log(`Next: ${chalk30.cyan("squadrant telegram link <project>")}`);
|
|
15038
15426
|
});
|
|
15039
15427
|
telegramCommand.command("register-commands").description("Register (or re-register) the bot's / command menu with Telegram").action(async () => {
|
|
15040
15428
|
const cfg = loadConfig().telegram;
|
|
15041
15429
|
if (!cfg) {
|
|
15042
|
-
console.error(
|
|
15430
|
+
console.error(chalk30.red("telegram config absent \u2014 run: squadrant telegram setup"));
|
|
15043
15431
|
process.exit(1);
|
|
15044
15432
|
}
|
|
15045
15433
|
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
15046
15434
|
if (!token) {
|
|
15047
|
-
console.error(
|
|
15435
|
+
console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
15048
15436
|
process.exit(1);
|
|
15049
15437
|
}
|
|
15050
15438
|
const client = createTelegramClient({ token });
|
|
15051
15439
|
await runRegisterCommands({ client });
|
|
15052
|
-
console.log(
|
|
15440
|
+
console.log(chalk30.green(`registered ${BOT_COMMANDS.length} bot commands`));
|
|
15053
15441
|
});
|
|
15054
15442
|
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
15443
|
const stateRoot = defaultStateRoot();
|
|
@@ -15060,7 +15448,7 @@ telegramCommand.command("notify").argument("[project]", "project to toggle").arg
|
|
|
15060
15448
|
return;
|
|
15061
15449
|
}
|
|
15062
15450
|
for (const r of rows) {
|
|
15063
|
-
console.log(` ${r.project}: ${r.active ?
|
|
15451
|
+
console.log(` ${r.project}: ${r.active ? chalk30.green("on") : chalk30.dim("off (muted)")}`);
|
|
15064
15452
|
}
|
|
15065
15453
|
return;
|
|
15066
15454
|
}
|
|
@@ -15069,53 +15457,53 @@ telegramCommand.command("notify").argument("[project]", "project to toggle").arg
|
|
|
15069
15457
|
const token = tgCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
15070
15458
|
if (state === "crew" || state === "cap") {
|
|
15071
15459
|
if (value === void 0) {
|
|
15072
|
-
console.error(
|
|
15460
|
+
console.error(chalk30.red(`usage: squadrant telegram notify <project> ${state} <value>`));
|
|
15073
15461
|
process.exit(1);
|
|
15074
15462
|
}
|
|
15075
15463
|
const resolved2 = resolveNotify(globalNotify, loadProjectOverride(project));
|
|
15076
15464
|
const before2 = { ...resolved2, active: isNotifyActive(stateRoot, project) };
|
|
15077
15465
|
const res = runTelegramNotifyPref({ project, dimension: state, value });
|
|
15078
15466
|
if (!res.ok) {
|
|
15079
|
-
console.error(
|
|
15467
|
+
console.error(chalk30.red(res.message));
|
|
15080
15468
|
process.exit(1);
|
|
15081
15469
|
}
|
|
15082
|
-
console.log(
|
|
15470
|
+
console.log(chalk30.green(`${project} ${state} = ${value}`));
|
|
15083
15471
|
const after2 = state === "crew" ? { ...before2, crew: value } : { ...before2, cap: value === "on" };
|
|
15084
15472
|
if (tgCfg && token) {
|
|
15085
15473
|
const client = createTelegramClient({ token });
|
|
15086
15474
|
const sent = await runNotifyConfirmation({ project, before: before2, after: after2, cfg: tgCfg, client, stateRoot });
|
|
15087
|
-
if (sent) console.log(
|
|
15475
|
+
if (sent) console.log(chalk30.dim(`\u2192 notified ${project} topic`));
|
|
15088
15476
|
}
|
|
15089
15477
|
return;
|
|
15090
15478
|
}
|
|
15091
15479
|
if (state !== "on" && state !== "off") {
|
|
15092
|
-
console.error(
|
|
15480
|
+
console.error(chalk30.red("usage: squadrant telegram notify <project> <on|off|crew <tier>|cap <on|off>>"));
|
|
15093
15481
|
process.exit(1);
|
|
15094
15482
|
}
|
|
15095
15483
|
const resolved = resolveNotify(globalNotify, loadProjectOverride(project));
|
|
15096
15484
|
const before = { ...resolved, active: isNotifyActive(stateRoot, project) };
|
|
15097
15485
|
const after = { ...before, active: state === "on" };
|
|
15098
15486
|
runTelegramNotifySet({ project, active: state === "on", stateRoot });
|
|
15099
|
-
console.log(
|
|
15487
|
+
console.log(chalk30.green(`${project} notifications ${state === "on" ? "ON" : "OFF"}`));
|
|
15100
15488
|
if (tgCfg && token) {
|
|
15101
15489
|
const client = createTelegramClient({ token });
|
|
15102
15490
|
const sent = await runNotifyConfirmation({ project, before, after, cfg: tgCfg, client, stateRoot });
|
|
15103
|
-
if (sent) console.log(
|
|
15491
|
+
if (sent) console.log(chalk30.dim(`\u2192 notified ${project} topic`));
|
|
15104
15492
|
}
|
|
15105
15493
|
});
|
|
15106
15494
|
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
15495
|
const cfg = loadConfig().telegram;
|
|
15108
15496
|
if (!cfg) {
|
|
15109
|
-
console.error(
|
|
15497
|
+
console.error(chalk30.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
|
|
15110
15498
|
process.exit(1);
|
|
15111
15499
|
}
|
|
15112
15500
|
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
15113
15501
|
if (!token) {
|
|
15114
|
-
console.error(
|
|
15502
|
+
console.error(chalk30.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
15115
15503
|
process.exit(1);
|
|
15116
15504
|
}
|
|
15117
15505
|
if (!capAllowed(project, cfg.notify)) {
|
|
15118
|
-
console.log(
|
|
15506
|
+
console.log(chalk30.dim(`${project}: captain messages muted (cap=off) \u2014 not sent`));
|
|
15119
15507
|
return;
|
|
15120
15508
|
}
|
|
15121
15509
|
let message;
|
|
@@ -15128,19 +15516,19 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
|
|
|
15128
15516
|
for await (const line of rl) lines.push(line);
|
|
15129
15517
|
message = lines.join("\n").trimEnd();
|
|
15130
15518
|
if (!message) {
|
|
15131
|
-
console.error(
|
|
15519
|
+
console.error(chalk30.red("no message provided (stdin was empty)"));
|
|
15132
15520
|
process.exit(1);
|
|
15133
15521
|
}
|
|
15134
15522
|
} else {
|
|
15135
|
-
console.error(
|
|
15523
|
+
console.error(chalk30.red("message required \u2014 pass as argument or pipe via stdin"));
|
|
15136
15524
|
process.exit(1);
|
|
15137
15525
|
}
|
|
15138
15526
|
const client = createTelegramClient({ token });
|
|
15139
15527
|
try {
|
|
15140
15528
|
const { chatId, topicId } = await runTelegramSend({ project, message, cfg, client, stateRoot: defaultStateRoot() });
|
|
15141
|
-
console.log(
|
|
15529
|
+
console.log(chalk30.green(`sent to group ${chatId} topic ${topicId}`));
|
|
15142
15530
|
} catch (e) {
|
|
15143
|
-
console.error(
|
|
15531
|
+
console.error(chalk30.red(e.message));
|
|
15144
15532
|
process.exit(1);
|
|
15145
15533
|
}
|
|
15146
15534
|
});
|
|
@@ -15148,7 +15536,7 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
|
|
|
15148
15536
|
// packages/cli/src/commands/hooks.ts
|
|
15149
15537
|
init_dist2();
|
|
15150
15538
|
init_dist4();
|
|
15151
|
-
import { Command as
|
|
15539
|
+
import { Command as Command31 } from "commander";
|
|
15152
15540
|
import { join as join28 } from "path";
|
|
15153
15541
|
import { homedir as homedir20 } from "os";
|
|
15154
15542
|
var SOCK4 = join28(homedir20(), ".config", "squadrant", "squadrant.sock");
|
|
@@ -15175,7 +15563,7 @@ function mapHookSub(sub, payload, taskId) {
|
|
|
15175
15563
|
}
|
|
15176
15564
|
}
|
|
15177
15565
|
function hooksCommand() {
|
|
15178
|
-
const hooks = new
|
|
15566
|
+
const hooks = new Command31("hooks").description("(internal) receive lifecycle hook events from agent processes");
|
|
15179
15567
|
hooks.command("claude <sub>", { hidden: true }).description("internal: bridge a NativeHookSource claude hook to squadrantd").action(async (sub) => {
|
|
15180
15568
|
const taskId = process.env.SQUADRANT_CREW_TASK_ID;
|
|
15181
15569
|
const project = process.env.SQUADRANT_CREW_PROJECT;
|
|
@@ -15246,7 +15634,7 @@ if (process.argv[2] !== "config") {
|
|
|
15246
15634
|
if (!process.env.SQUADRANT_DAEMON_SKIP) {
|
|
15247
15635
|
ensureDaemon();
|
|
15248
15636
|
}
|
|
15249
|
-
var program = new
|
|
15637
|
+
var program = new Command32();
|
|
15250
15638
|
program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
|
|
15251
15639
|
program.addCommand(doctorCommand);
|
|
15252
15640
|
program.addCommand(initCommand);
|
|
@@ -15255,6 +15643,7 @@ program.addCommand(statusCommand);
|
|
|
15255
15643
|
addControlPlaneCrewCommands(crewCommand);
|
|
15256
15644
|
program.addCommand(crewCommand);
|
|
15257
15645
|
program.addCommand(sideCommand);
|
|
15646
|
+
program.addCommand(diffCommand);
|
|
15258
15647
|
program.addCommand(commandCommand);
|
|
15259
15648
|
program.addCommand(dashboardCommand);
|
|
15260
15649
|
program.addCommand(launchCommand);
|