squadrant 0.17.0 → 0.18.0
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 +588 -326
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +52 -5
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/captain-ops/SKILL.md +51 -65
- package/plugin/skills/command-ops/SKILL.md +12 -20
- package/plugin/skills/handback/SKILL.md +8 -0
- package/plugin/skills/karpathy-principles/SKILL.md +1 -1
- package/plugin/skills/takeover/SKILL.md +11 -0
- package/templates/captain.claude.md +10 -5
- package/templates/captain.generic.md +9 -3
- package/templates/command.claude.md +2 -2
- package/templates/crew.generic.md +1 -1
- package/templates/crew.opencode.md +1 -1
- package/templates/learnings.claude.md +1 -1
package/dist/index.js
CHANGED
|
@@ -773,6 +773,12 @@ function ensureSpotlightExcluded(repoRoot, worktreeDir) {
|
|
|
773
773
|
}
|
|
774
774
|
}
|
|
775
775
|
function resolveWorktreeBase(repoRoot, fallback = "develop") {
|
|
776
|
+
try {
|
|
777
|
+
const head = execFileSync2("git", ["-C", repoRoot, "rev-parse", "--abbrev-ref", "HEAD"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
778
|
+
if (head && head !== "HEAD")
|
|
779
|
+
return head;
|
|
780
|
+
} catch {
|
|
781
|
+
}
|
|
776
782
|
try {
|
|
777
783
|
const ref = execFileSync2("git", ["-C", repoRoot, "symbolic-ref", "refs/remotes/origin/HEAD"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
778
784
|
const m = ref.match(/^refs\/remotes\/origin\/(.+)$/);
|
|
@@ -839,13 +845,17 @@ function installWorktreeDependencies(wt) {
|
|
|
839
845
|
`);
|
|
840
846
|
}
|
|
841
847
|
}
|
|
842
|
-
function
|
|
848
|
+
function worktreeDirtyFiles(wtPath) {
|
|
843
849
|
try {
|
|
844
|
-
execFileSync2("git", ["-C",
|
|
850
|
+
return execFileSync2("git", ["-C", wtPath, "status", "--porcelain", "--untracked-files=all"], { stdio: ["ignore", "pipe", "ignore"] }).toString().split("\n").map((l) => l.slice(3).trim()).filter(Boolean);
|
|
845
851
|
} catch {
|
|
846
|
-
|
|
852
|
+
return [];
|
|
847
853
|
}
|
|
848
854
|
}
|
|
855
|
+
function removeWorktree(repoRoot, wtPath, opts) {
|
|
856
|
+
const args = ["-C", repoRoot, "worktree", "remove", ...opts?.force ? ["--force"] : [], wtPath];
|
|
857
|
+
execFileSync2("git", args, { stdio: "pipe" });
|
|
858
|
+
}
|
|
849
859
|
var init_git_worktree = __esm({
|
|
850
860
|
"packages/shared/dist/lib/git-worktree.js"() {
|
|
851
861
|
}
|
|
@@ -977,7 +987,7 @@ function ensureRuntimeSynced(opts) {
|
|
|
977
987
|
var CREW_SKILLS, MANAGED_TARGETS;
|
|
978
988
|
var init_runtime_sync = __esm({
|
|
979
989
|
"packages/shared/dist/lib/runtime-sync.js"() {
|
|
980
|
-
CREW_SKILLS = ["karpathy-principles"];
|
|
990
|
+
CREW_SKILLS = ["karpathy-principles", "takeover", "handback"];
|
|
981
991
|
MANAGED_TARGETS = [
|
|
982
992
|
{ name: "plugin", srcRel: "plugin", mode: "tree" },
|
|
983
993
|
{ name: "plugin-crew", srcRel: "plugin", mode: "subset", skills: CREW_SKILLS },
|
|
@@ -1302,6 +1312,7 @@ __export(dist_exports, {
|
|
|
1302
1312
|
saveConfig: () => saveConfig,
|
|
1303
1313
|
saveProjectOverride: () => saveProjectOverride,
|
|
1304
1314
|
withStamp: () => withStamp,
|
|
1315
|
+
worktreeDirtyFiles: () => worktreeDirtyFiles,
|
|
1305
1316
|
worktreePath: () => worktreePath,
|
|
1306
1317
|
writeUpdateCheckState: () => writeUpdateCheckState
|
|
1307
1318
|
});
|
|
@@ -1361,7 +1372,21 @@ function nextPendingMonitor(current, ev, now) {
|
|
|
1361
1372
|
}
|
|
1362
1373
|
function reduce(rec, ev, now) {
|
|
1363
1374
|
if (ev.type === "task.reopened") {
|
|
1364
|
-
return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type };
|
|
1375
|
+
return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type, workingStretchStartedAt: now };
|
|
1376
|
+
}
|
|
1377
|
+
if (ev.type === "crew.takeover.started") {
|
|
1378
|
+
if (rec.operatorHold)
|
|
1379
|
+
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1380
|
+
return {
|
|
1381
|
+
...rec,
|
|
1382
|
+
operatorHold: { since: now, ...ev.note !== void 0 ? { note: ev.note } : {} },
|
|
1383
|
+
lastHeartbeat: now,
|
|
1384
|
+
lastEvent: ev.type
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
if (ev.type === "crew.takeover.ended") {
|
|
1388
|
+
const { operatorHold: _dropped, ...rest } = rec;
|
|
1389
|
+
return { ...rest, lastHeartbeat: now, lastEvent: ev.type };
|
|
1365
1390
|
}
|
|
1366
1391
|
if (TERMINAL_STATES.has(rec.state))
|
|
1367
1392
|
return rec;
|
|
@@ -1377,8 +1402,9 @@ function reduce(rec, ev, now) {
|
|
|
1377
1402
|
// resuming after a blocked→reply clears the question
|
|
1378
1403
|
pendingTool: void 0,
|
|
1379
1404
|
// #354: a new turn closes any prior tool window
|
|
1380
|
-
pendingMonitor: void 0
|
|
1405
|
+
pendingMonitor: void 0,
|
|
1381
1406
|
// #594a: same reset — a new turn moots any prior watch
|
|
1407
|
+
workingStretchStartedAt: now
|
|
1382
1408
|
};
|
|
1383
1409
|
case "task.progress": {
|
|
1384
1410
|
const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
|
|
@@ -1549,6 +1575,8 @@ function firePush(deps, project, prev, next, event, lastCaptainTurnAt) {
|
|
|
1549
1575
|
return;
|
|
1550
1576
|
if (!ATTENTION_STATES.has(next.state))
|
|
1551
1577
|
return;
|
|
1578
|
+
if (next.operatorHold)
|
|
1579
|
+
return;
|
|
1552
1580
|
if (next.state === "awaiting-input" && lastCaptainTurnAt != null && deps.now() - lastCaptainTurnAt <= IDLE_DEBOUNCE_MS) {
|
|
1553
1581
|
return;
|
|
1554
1582
|
}
|
|
@@ -1757,9 +1785,32 @@ function createDaemon(deps) {
|
|
|
1757
1785
|
store.delete(r.project, r.id);
|
|
1758
1786
|
continue;
|
|
1759
1787
|
}
|
|
1788
|
+
if (r.operatorHold) {
|
|
1789
|
+
const threshold = (deps.takeoverNudgeHours ?? 6) * 36e5;
|
|
1790
|
+
const holdAge = t - r.operatorHold.since;
|
|
1791
|
+
if (holdAge > threshold) {
|
|
1792
|
+
const timeSinceLastNudge = t - (r.operatorHold.lastNudgeAt ?? 0);
|
|
1793
|
+
if (timeSinceLastNudge > threshold) {
|
|
1794
|
+
const hrs = Math.round(holdAge / 36e5);
|
|
1795
|
+
const tag = crewTag(r);
|
|
1796
|
+
const message = `CREW HELD-LONG ${tag} \u2014 held ${hrs}h. Ask the operator whether it is still in use. Do not release it yourself.`;
|
|
1797
|
+
store.put({ ...r, operatorHold: { ...r.operatorHold, lastNudgeAt: t } });
|
|
1798
|
+
if (deps.notify) {
|
|
1799
|
+
try {
|
|
1800
|
+
const p = deps.notify({ project: r.project, message, record: r, event: { type: "task.progress", id: r.id } });
|
|
1801
|
+
if (p && typeof p.catch === "function")
|
|
1802
|
+
p.catch(() => {
|
|
1803
|
+
});
|
|
1804
|
+
} catch {
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1760
1810
|
if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {
|
|
1761
1811
|
const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
|
|
1762
|
-
|
|
1812
|
+
const refTime = r.workingStretchStartedAt ?? r.createdAt;
|
|
1813
|
+
if (t - refTime > ceiling) {
|
|
1763
1814
|
const prevState = r.state;
|
|
1764
1815
|
const tag = crewTag(r);
|
|
1765
1816
|
const hrs = Math.round(ceiling / 36e5);
|
|
@@ -1913,8 +1964,11 @@ var init_reduce = __esm({
|
|
|
1913
1964
|
"task.reconcile-failed",
|
|
1914
1965
|
"task.cancelled",
|
|
1915
1966
|
"task.session.ended",
|
|
1916
|
-
"task.first-turn.confirmed"
|
|
1967
|
+
"task.first-turn.confirmed",
|
|
1917
1968
|
// #466: delivery confirmation
|
|
1969
|
+
"crew.takeover.started",
|
|
1970
|
+
"crew.takeover.ended"
|
|
1971
|
+
// #649: operator takeover
|
|
1918
1972
|
]);
|
|
1919
1973
|
}
|
|
1920
1974
|
});
|
|
@@ -3198,7 +3252,9 @@ function buildContext(opts) {
|
|
|
3198
3252
|
const sockPath = opts.sockPath ?? join9(homedir6(), ".config", "squadrant", "squadrant.sock");
|
|
3199
3253
|
const store = createStore(stateRoot);
|
|
3200
3254
|
const bootedAt = Date.now();
|
|
3201
|
-
const
|
|
3255
|
+
const config = loadConfig();
|
|
3256
|
+
const taskTimeoutMs = config.defaults.taskTimeoutMs;
|
|
3257
|
+
const takeoverNudgeHours = config.defaults.takeoverNudgeHours;
|
|
3202
3258
|
const isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
|
|
3203
3259
|
const spawn2 = opts.spawn ?? realSpawn;
|
|
3204
3260
|
const resultsDir = join9(stateRoot, "_results");
|
|
@@ -3218,6 +3274,7 @@ function buildContext(opts) {
|
|
|
3218
3274
|
bootedAt,
|
|
3219
3275
|
lastSweepAt: { value: null },
|
|
3220
3276
|
taskTimeoutMs,
|
|
3277
|
+
takeoverNudgeHours,
|
|
3221
3278
|
isPidAlive,
|
|
3222
3279
|
spawn: spawn2,
|
|
3223
3280
|
resultsDir,
|
|
@@ -4068,6 +4125,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4068
4125
|
isPidAlive,
|
|
4069
4126
|
notify,
|
|
4070
4127
|
taskTimeoutMs,
|
|
4128
|
+
takeoverNudgeHours: ctx.takeoverNudgeHours,
|
|
4071
4129
|
isSurfaceAlive: surfaceProbe,
|
|
4072
4130
|
resendFirstTurn: ctx.resendFirstTurn,
|
|
4073
4131
|
launchHeadless: opts.launchHeadless,
|
|
@@ -6008,12 +6066,17 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
6008
6066
|
}
|
|
6009
6067
|
}
|
|
6010
6068
|
const name = input.name ?? nextAutoName(existingTitles, input.project);
|
|
6069
|
+
let base = "";
|
|
6070
|
+
if (!input.shared) {
|
|
6071
|
+
base = resolveWorktreeBase(proj.path);
|
|
6072
|
+
deps.onBaseResolved?.(base);
|
|
6073
|
+
}
|
|
6011
6074
|
const spawnCwd = !input.shared ? addWorktree({
|
|
6012
6075
|
repoRoot: proj.path,
|
|
6013
6076
|
worktreeDir: config.defaults.worktreeDir ?? ".worktrees",
|
|
6014
6077
|
project: input.project,
|
|
6015
6078
|
name,
|
|
6016
|
-
base
|
|
6079
|
+
base
|
|
6017
6080
|
}) : proj.path;
|
|
6018
6081
|
let firstTurnTask = input.task;
|
|
6019
6082
|
if (input.taskFile && input.taskFile !== "-" && !input.shared) {
|
|
@@ -6181,7 +6244,7 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
|
|
|
6181
6244
|
function pickMostRecentTask(tasks) {
|
|
6182
6245
|
return tasks.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
|
|
6183
6246
|
}
|
|
6184
|
-
async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
6247
|
+
async function runCrewSend(project, name, message, runtime, workspaceId, deps, opts) {
|
|
6185
6248
|
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
6186
6249
|
if (!crew) {
|
|
6187
6250
|
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
@@ -6190,9 +6253,17 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
|
6190
6253
|
if (deps.isBlockedByModal && await deps.isBlockedByModal(crew)) {
|
|
6191
6254
|
throw new Error(blockedByModalMessage());
|
|
6192
6255
|
}
|
|
6256
|
+
let task;
|
|
6193
6257
|
try {
|
|
6194
6258
|
const matches = (await deps.listTasks(project)).filter((t) => t.name === name);
|
|
6195
|
-
|
|
6259
|
+
task = matches.length > 0 ? pickMostRecentTask(matches) : void 0;
|
|
6260
|
+
} catch {
|
|
6261
|
+
}
|
|
6262
|
+
if (task && task.operatorHold && !opts?.force) {
|
|
6263
|
+
const heldForMin = Math.round((Date.now() - task.operatorHold.since) / 6e4);
|
|
6264
|
+
throw new Error(`Crew '${name}' is under operator takeover (held ${heldForMin}m${task.operatorHold.note ? `: ${task.operatorHold.note}` : ""}). The operator is working in that tab \u2014 sending a message disrupts their conversation. Ask them to run 'squadrant crew handback ${project} ${name}', or pass --force if they told you to.`);
|
|
6265
|
+
}
|
|
6266
|
+
try {
|
|
6196
6267
|
if (task) {
|
|
6197
6268
|
if (TERMINAL_STATES.has(task.state)) {
|
|
6198
6269
|
await deps.emitEvent(project, { type: "task.reopened", id: task.id });
|
|
@@ -6218,23 +6289,60 @@ async function runCrewRead(project, name, runtime, workspaceId) {
|
|
|
6218
6289
|
}
|
|
6219
6290
|
return runtime.readPaneScreen(crew);
|
|
6220
6291
|
}
|
|
6221
|
-
|
|
6292
|
+
function buildRecoveryHint(sessId, provider, worktreeCwd) {
|
|
6293
|
+
if (!sessId || !worktreeCwd)
|
|
6294
|
+
return "";
|
|
6295
|
+
if (provider === "claude") {
|
|
6296
|
+
const escaped = worktreeCwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
6297
|
+
const transcriptPath = path11.join(os5.homedir(), ".claude", "projects", escaped, `${sessId}.jsonl`);
|
|
6298
|
+
return `
|
|
6299
|
+
transcript: ${transcriptPath}
|
|
6300
|
+
resume: claude --resume ${sessId} (run from the worktree path above)
|
|
6301
|
+
`;
|
|
6302
|
+
}
|
|
6303
|
+
return "";
|
|
6304
|
+
}
|
|
6305
|
+
async function runCrewClose(project, name, runtime, workspaceId, deps, opts) {
|
|
6222
6306
|
const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
6223
6307
|
const projRoot = loadConfig().projects[project]?.path;
|
|
6224
|
-
let
|
|
6225
|
-
let worktreeCwd;
|
|
6308
|
+
let matches = [];
|
|
6226
6309
|
try {
|
|
6227
|
-
|
|
6310
|
+
matches = (await deps.listTasks(project)).filter((t) => t.name === name);
|
|
6228
6311
|
for (let attempt = 0; attempt < CLOSE_LOOKUP_RETRIES && matches.length === 0; attempt++) {
|
|
6229
6312
|
await sleep3(CLOSE_LOOKUP_RETRY_DELAY_MS);
|
|
6230
6313
|
matches = (await deps.listTasks(project)).filter((t) => t.name === name);
|
|
6231
6314
|
}
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6315
|
+
} catch {
|
|
6316
|
+
}
|
|
6317
|
+
let taskId;
|
|
6318
|
+
let worktreeCwd;
|
|
6319
|
+
let sessId;
|
|
6320
|
+
let provider;
|
|
6321
|
+
if (matches.length > 0) {
|
|
6322
|
+
const primary = pickMostRecentTask(matches);
|
|
6323
|
+
taskId = primary.id;
|
|
6324
|
+
sessId = primary.sessionId;
|
|
6325
|
+
provider = primary.provider;
|
|
6326
|
+
if (primary.cwd && projRoot && primary.cwd !== projRoot) {
|
|
6327
|
+
worktreeCwd = primary.cwd;
|
|
6328
|
+
}
|
|
6329
|
+
if (primary.operatorHold && !opts?.force) {
|
|
6330
|
+
const heldForMin = Math.round((Date.now() - primary.operatorHold.since) / 6e4);
|
|
6331
|
+
throw new Error(`Crew '${name}' is under operator takeover (held ${heldForMin}m${primary.operatorHold.note ? `: ${primary.operatorHold.note}` : ""}). The operator is working in that tab \u2014 closing it kills their session and prunes the worktree. Ask them to run 'squadrant crew handback ${project} ${name}', or pass --force if they told you to.`);
|
|
6332
|
+
}
|
|
6333
|
+
}
|
|
6334
|
+
if (worktreeCwd && projRoot) {
|
|
6335
|
+
const dirty = worktreeDirtyFiles(worktreeCwd);
|
|
6336
|
+
if (dirty.length > 0 && !opts?.force) {
|
|
6337
|
+
const transcriptStr = buildRecoveryHint(sessId, provider, worktreeCwd);
|
|
6338
|
+
throw new Error(`Worktree '${worktreeCwd}' has uncommitted files:
|
|
6339
|
+
${dirty.map((f) => ` ${f}`).join("\n")}
|
|
6340
|
+
Why are they uncommitted? Commit them, or pass --force to destroy them.
|
|
6341
|
+
${transcriptStr}`);
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
if (matches.length > 0) {
|
|
6345
|
+
try {
|
|
6238
6346
|
for (const task of matches) {
|
|
6239
6347
|
if (!TERMINAL_STATES.has(task.state)) {
|
|
6240
6348
|
await deps.emitEvent(project, { type: "task.cancelled", id: task.id, reason: "closed by captain" });
|
|
@@ -6243,8 +6351,8 @@ async function runCrewClose(project, name, runtime, workspaceId, deps) {
|
|
|
6243
6351
|
await deps.closeCodexThread(task.id);
|
|
6244
6352
|
}
|
|
6245
6353
|
}
|
|
6354
|
+
} catch {
|
|
6246
6355
|
}
|
|
6247
|
-
} catch {
|
|
6248
6356
|
}
|
|
6249
6357
|
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
6250
6358
|
if (crew) {
|
|
@@ -6257,12 +6365,16 @@ async function runCrewClose(project, name, runtime, workspaceId, deps) {
|
|
|
6257
6365
|
}
|
|
6258
6366
|
if (worktreeCwd && projRoot) {
|
|
6259
6367
|
try {
|
|
6260
|
-
removeWorktree(projRoot, worktreeCwd);
|
|
6368
|
+
removeWorktree(projRoot, worktreeCwd, opts);
|
|
6261
6369
|
} catch (e) {
|
|
6262
6370
|
process.stderr.write(`(worktree remove failed: ${e.message})
|
|
6263
6371
|
`);
|
|
6264
6372
|
}
|
|
6265
6373
|
}
|
|
6374
|
+
const hint = buildRecoveryHint(sessId, provider, worktreeCwd);
|
|
6375
|
+
if (hint) {
|
|
6376
|
+
process.stdout.write(hint);
|
|
6377
|
+
}
|
|
6266
6378
|
}
|
|
6267
6379
|
async function runCrewList(project, runtime, workspaceId) {
|
|
6268
6380
|
const crews = await listCrewPanes(runtime, workspaceId, project);
|
|
@@ -10269,8 +10381,8 @@ var require_daemon_exports = {};
|
|
|
10269
10381
|
__export(require_daemon_exports, {
|
|
10270
10382
|
requireDaemon: () => requireDaemon
|
|
10271
10383
|
});
|
|
10272
|
-
import { join as
|
|
10273
|
-
import { homedir as
|
|
10384
|
+
import { join as join22 } from "path";
|
|
10385
|
+
import { homedir as homedir17 } from "os";
|
|
10274
10386
|
async function requireDaemon(sockPath = DEFAULT_SOCK_PATH3) {
|
|
10275
10387
|
const isLive = await isDaemonSocketLive(sockPath);
|
|
10276
10388
|
if (!isLive) {
|
|
@@ -10281,7 +10393,168 @@ var DEFAULT_SOCK_PATH3;
|
|
|
10281
10393
|
var init_require_daemon = __esm({
|
|
10282
10394
|
"packages/cli/src/lib/require-daemon.ts"() {
|
|
10283
10395
|
init_dist2();
|
|
10284
|
-
DEFAULT_SOCK_PATH3 =
|
|
10396
|
+
DEFAULT_SOCK_PATH3 = join22(homedir17(), ".config", "squadrant", "squadrant.sock");
|
|
10397
|
+
}
|
|
10398
|
+
});
|
|
10399
|
+
|
|
10400
|
+
// packages/cli/src/commands/runtime.ts
|
|
10401
|
+
var runtime_exports = {};
|
|
10402
|
+
__export(runtime_exports, {
|
|
10403
|
+
buildRegistry: () => buildRegistry,
|
|
10404
|
+
needRef: () => needRef,
|
|
10405
|
+
resolveTarget: () => resolveTarget,
|
|
10406
|
+
runRuntimeSend: () => runRuntimeSend,
|
|
10407
|
+
runtimeCommand: () => runtimeCommand
|
|
10408
|
+
});
|
|
10409
|
+
import { Command as Command8 } from "commander";
|
|
10410
|
+
import chalk9 from "chalk";
|
|
10411
|
+
function buildRegistry() {
|
|
10412
|
+
return new RuntimeRegistry({
|
|
10413
|
+
cmux: createCmuxDriver()
|
|
10414
|
+
});
|
|
10415
|
+
}
|
|
10416
|
+
function resolveTarget(registry, config, target, useCommand) {
|
|
10417
|
+
if (useCommand) {
|
|
10418
|
+
return {
|
|
10419
|
+
driver: registry.global(config),
|
|
10420
|
+
workspaceName: config.commandName
|
|
10421
|
+
};
|
|
10422
|
+
}
|
|
10423
|
+
if (!target) {
|
|
10424
|
+
throw new Error("Missing target: pass a project name or use --command");
|
|
10425
|
+
}
|
|
10426
|
+
const proj = config.projects[target];
|
|
10427
|
+
if (!proj) {
|
|
10428
|
+
throw new Error(`Project '${target}' not found. Run 'squadrant projects list'.`);
|
|
10429
|
+
}
|
|
10430
|
+
return {
|
|
10431
|
+
driver: registry.forProject(target, config),
|
|
10432
|
+
workspaceName: proj.captainName
|
|
10433
|
+
};
|
|
10434
|
+
}
|
|
10435
|
+
async function needRef(resolved) {
|
|
10436
|
+
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
10437
|
+
if (!ref) {
|
|
10438
|
+
throw new Error(`Workspace '${resolved.workspaceName}' is not running`);
|
|
10439
|
+
}
|
|
10440
|
+
return ref.id;
|
|
10441
|
+
}
|
|
10442
|
+
async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
|
|
10443
|
+
const config = loadConfig();
|
|
10444
|
+
const registry = buildRegistry();
|
|
10445
|
+
if (opts.command && arg2 !== void 0) {
|
|
10446
|
+
throw new Error("With --command, pass only the message (not a project name)");
|
|
10447
|
+
}
|
|
10448
|
+
const target = opts.command ? void 0 : arg1;
|
|
10449
|
+
const message = opts.command ? arg1 : arg2;
|
|
10450
|
+
if (!message) throw new Error("Message is required");
|
|
10451
|
+
const { requireDaemon: requireDaemon2 } = await Promise.resolve().then(() => (init_require_daemon(), require_daemon_exports));
|
|
10452
|
+
const { appendCaptainMessage: appendCaptainMessage2, waitForCaptainDelivery: waitForCaptainDelivery2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
|
|
10453
|
+
await requireDaemon2();
|
|
10454
|
+
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
10455
|
+
await needRef(resolved);
|
|
10456
|
+
const finalProject = opts.command ? config.commandName : target;
|
|
10457
|
+
const { join: join31, dirname: dirname10 } = await import("path");
|
|
10458
|
+
const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
10459
|
+
const stateRoot = join31(dirname10(DEFAULT_CONFIG_PATH2), "state");
|
|
10460
|
+
const seq = await appendCaptainMessage2({
|
|
10461
|
+
stateRoot,
|
|
10462
|
+
project: finalProject,
|
|
10463
|
+
text: message,
|
|
10464
|
+
source: "cli"
|
|
10465
|
+
});
|
|
10466
|
+
const timeoutMs = confirmOpts?.timeoutMs ?? SEND_CONFIRM_TIMEOUT_MS;
|
|
10467
|
+
const delivered = await waitForCaptainDelivery2({
|
|
10468
|
+
stateRoot,
|
|
10469
|
+
project: finalProject,
|
|
10470
|
+
seq,
|
|
10471
|
+
timeoutMs,
|
|
10472
|
+
pollMs: confirmOpts?.pollMs ?? SEND_CONFIRM_POLL_MS
|
|
10473
|
+
});
|
|
10474
|
+
if (!delivered) {
|
|
10475
|
+
throw new Error(
|
|
10476
|
+
`Message queued for '${finalProject}' (seq=${seq}) but delivery was not confirmed within ${Math.round(timeoutMs / 1e3)}s. It may still be pending \u2014 check with 'squadrant runtime read-screen ${finalProject}${opts.command ? " --command" : ""}'.`
|
|
10477
|
+
);
|
|
10478
|
+
}
|
|
10479
|
+
}
|
|
10480
|
+
var runtimeCommand, SEND_CONFIRM_TIMEOUT_MS, SEND_CONFIRM_POLL_MS;
|
|
10481
|
+
var init_runtime2 = __esm({
|
|
10482
|
+
"packages/cli/src/commands/runtime.ts"() {
|
|
10483
|
+
init_dist();
|
|
10484
|
+
init_dist3();
|
|
10485
|
+
runtimeCommand = new Command8("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
|
|
10486
|
+
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) => {
|
|
10487
|
+
const config = loadConfig();
|
|
10488
|
+
const registry = buildRegistry();
|
|
10489
|
+
try {
|
|
10490
|
+
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
10491
|
+
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
10492
|
+
if (ref) {
|
|
10493
|
+
console.log("running");
|
|
10494
|
+
process.exit(0);
|
|
10495
|
+
} else {
|
|
10496
|
+
console.log("stopped");
|
|
10497
|
+
process.exit(1);
|
|
10498
|
+
}
|
|
10499
|
+
} catch (err) {
|
|
10500
|
+
console.error(chalk9.red(err.message));
|
|
10501
|
+
process.exit(2);
|
|
10502
|
+
}
|
|
10503
|
+
});
|
|
10504
|
+
SEND_CONFIRM_TIMEOUT_MS = 15e3;
|
|
10505
|
+
SEND_CONFIRM_POLL_MS = 500;
|
|
10506
|
+
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) => {
|
|
10507
|
+
try {
|
|
10508
|
+
await runRuntimeSend(arg1, arg2, opts);
|
|
10509
|
+
console.log(chalk9.green("\u2714 Delivered (confirmed)"));
|
|
10510
|
+
} catch (err) {
|
|
10511
|
+
console.error(chalk9.red(err.message));
|
|
10512
|
+
process.exit(1);
|
|
10513
|
+
}
|
|
10514
|
+
});
|
|
10515
|
+
runtimeCommand.command("list").description("List all workspaces from the global runtime").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
10516
|
+
const config = loadConfig();
|
|
10517
|
+
const registry = buildRegistry();
|
|
10518
|
+
const driver = registry.global(config);
|
|
10519
|
+
const refs = await driver.list();
|
|
10520
|
+
if (opts.json) {
|
|
10521
|
+
console.log(JSON.stringify(refs, null, 2));
|
|
10522
|
+
} else {
|
|
10523
|
+
for (const r of refs) {
|
|
10524
|
+
console.log(`${r.id} ${r.name} ${r.status}`);
|
|
10525
|
+
}
|
|
10526
|
+
}
|
|
10527
|
+
});
|
|
10528
|
+
runtimeCommand.command("read-screen").description("Print a terminal snapshot of a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
|
|
10529
|
+
const config = loadConfig();
|
|
10530
|
+
const registry = buildRegistry();
|
|
10531
|
+
try {
|
|
10532
|
+
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
10533
|
+
const ref = await needRef(resolved);
|
|
10534
|
+
const screen = await resolved.driver.readScreen(ref);
|
|
10535
|
+
process.stdout.write(screen);
|
|
10536
|
+
} catch (err) {
|
|
10537
|
+
console.error(chalk9.red(err.message));
|
|
10538
|
+
process.exit(1);
|
|
10539
|
+
}
|
|
10540
|
+
});
|
|
10541
|
+
runtimeCommand.command("stop").description("Stop a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
|
|
10542
|
+
const config = loadConfig();
|
|
10543
|
+
const registry = buildRegistry();
|
|
10544
|
+
try {
|
|
10545
|
+
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
10546
|
+
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
10547
|
+
if (!ref) {
|
|
10548
|
+
console.log(chalk9.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
|
|
10549
|
+
return;
|
|
10550
|
+
}
|
|
10551
|
+
await resolved.driver.stop(ref.id);
|
|
10552
|
+
console.log(chalk9.green(`\u2714 Stopped ${resolved.workspaceName}`));
|
|
10553
|
+
} catch (err) {
|
|
10554
|
+
console.error(chalk9.red(err.message));
|
|
10555
|
+
process.exit(1);
|
|
10556
|
+
}
|
|
10557
|
+
});
|
|
10285
10558
|
}
|
|
10286
10559
|
});
|
|
10287
10560
|
|
|
@@ -11264,20 +11537,20 @@ init_dist();
|
|
|
11264
11537
|
init_dist3();
|
|
11265
11538
|
init_dist4();
|
|
11266
11539
|
init_dist2();
|
|
11267
|
-
import { Command as
|
|
11268
|
-
import
|
|
11540
|
+
import { Command as Command10 } from "commander";
|
|
11541
|
+
import chalk10 from "chalk";
|
|
11269
11542
|
|
|
11270
11543
|
// packages/cli/src/commands/crew-control.ts
|
|
11271
11544
|
init_dist2();
|
|
11272
11545
|
init_dist2();
|
|
11273
11546
|
init_dist();
|
|
11274
11547
|
init_dist4();
|
|
11275
|
-
import { Command as
|
|
11548
|
+
import { Command as Command9 } from "commander";
|
|
11276
11549
|
import { createConnection as createConnection3 } from "net";
|
|
11277
11550
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
11278
11551
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
11279
|
-
import { homedir as
|
|
11280
|
-
import { join as
|
|
11552
|
+
import { homedir as homedir18 } from "os";
|
|
11553
|
+
import { join as join23 } from "path";
|
|
11281
11554
|
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync11 } from "fs";
|
|
11282
11555
|
|
|
11283
11556
|
// packages/cli/src/commands/crew-output.ts
|
|
@@ -11328,7 +11601,26 @@ function formatCompactTasks(records, opts) {
|
|
|
11328
11601
|
if (opts.compact === false) {
|
|
11329
11602
|
return JSON.stringify(records, null, 2);
|
|
11330
11603
|
}
|
|
11331
|
-
|
|
11604
|
+
const active = records.filter((r) => !r.operatorHold);
|
|
11605
|
+
const held = records.filter((r) => r.operatorHold);
|
|
11606
|
+
let out = "";
|
|
11607
|
+
if (active.length > 0) {
|
|
11608
|
+
if (held.length > 0) out += `active (${active.length}):
|
|
11609
|
+
`;
|
|
11610
|
+
out += active.map((r) => (held.length > 0 ? " " : "") + formatTaskLine(r)).join("\n");
|
|
11611
|
+
}
|
|
11612
|
+
if (held.length > 0) {
|
|
11613
|
+
if (out) out += "\n";
|
|
11614
|
+
out += `HELD BY OPERATOR (${held.length}) \u2014 not counted toward maxCrew:
|
|
11615
|
+
`;
|
|
11616
|
+
out += held.map((r) => {
|
|
11617
|
+
const m = Math.round((Date.now() - r.operatorHold.since) / 6e4);
|
|
11618
|
+
const hm = m >= 60 ? `${Math.floor(m / 60)}h${m % 60}m` : `${m}m`;
|
|
11619
|
+
const note = r.operatorHold.note ? ` \xB7 "${r.operatorHold.note}"` : "";
|
|
11620
|
+
return ` ${formatTaskLine(r)} \xB7 held ${hm}${note}`;
|
|
11621
|
+
}).join("\n");
|
|
11622
|
+
}
|
|
11623
|
+
return out;
|
|
11332
11624
|
}
|
|
11333
11625
|
|
|
11334
11626
|
// packages/cli/src/commands/crew-attach.ts
|
|
@@ -11576,7 +11868,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
|
|
|
11576
11868
|
});
|
|
11577
11869
|
|
|
11578
11870
|
// packages/cli/src/commands/crew-control.ts
|
|
11579
|
-
var SOCK2 =
|
|
11871
|
+
var SOCK2 = join23(homedir18(), ".config", "squadrant", "squadrant.sock");
|
|
11580
11872
|
var CODEX_FIRST_TURN_DELAY_MS = 1500;
|
|
11581
11873
|
async function sendCodexFirstTurn(taskId, text) {
|
|
11582
11874
|
await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
|
|
@@ -11640,6 +11932,40 @@ function buildGateResolveRequest(o) {
|
|
|
11640
11932
|
payload: { text: o.message, ...decision ? { decision } : {} }
|
|
11641
11933
|
};
|
|
11642
11934
|
}
|
|
11935
|
+
async function runCrewTakeover(mode, opts, deps) {
|
|
11936
|
+
const taskId = opts.taskId ?? process.env.SQUADRANT_CREW_TASK_ID;
|
|
11937
|
+
let project = opts.project ?? process.env.SQUADRANT_CREW_PROJECT;
|
|
11938
|
+
let target;
|
|
11939
|
+
if (taskId) {
|
|
11940
|
+
if (!project) {
|
|
11941
|
+
throw new Error("not running under a crew (SQUADRANT_CREW_PROJECT unset)");
|
|
11942
|
+
}
|
|
11943
|
+
const tasks = await deps.listTasks(project);
|
|
11944
|
+
target = tasks.find((t) => t.id === taskId);
|
|
11945
|
+
if (!target) throw new Error(`Task '${taskId}' not found for project '${project}'`);
|
|
11946
|
+
} else if (project && opts.crew) {
|
|
11947
|
+
const tasks = await deps.listTasks(project);
|
|
11948
|
+
target = resolveApproveTarget(tasks, opts.crew) || void 0;
|
|
11949
|
+
if (!target) throw new Error(`Crew '${opts.crew}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
11950
|
+
} else {
|
|
11951
|
+
throw new Error("must provide either <project> <crew> or --task-id");
|
|
11952
|
+
}
|
|
11953
|
+
const nameDisp = project && opts.crew ? `${project}/${opts.crew}` : target.name ? `${target.project}/${target.name}` : `${target.project}/${target.id}`;
|
|
11954
|
+
if (mode === "start") {
|
|
11955
|
+
const event = { type: "crew.takeover.started", id: target.id, ...opts.note !== void 0 ? { note: opts.note } : {} };
|
|
11956
|
+
await deps.emitEvent(target.project, event);
|
|
11957
|
+
const msg = `CREW TAKEOVER [${nameDisp}] \u2014 operator is driving this tab. Do not send, do not close, do not act on its signals until handback.`;
|
|
11958
|
+
await deps.runtimeSend(target.project, msg);
|
|
11959
|
+
deps.printSuccess(msg);
|
|
11960
|
+
} else {
|
|
11961
|
+
const event = { type: "crew.takeover.ended", id: target.id };
|
|
11962
|
+
await deps.emitEvent(target.project, event);
|
|
11963
|
+
const msg = `CREW HANDBACK [${nameDisp}] \u2014 operator returned control. State: ${target.state}. Run 'squadrant crew read ${target.project} ${target.name || target.id}' before acting; the tab has history you did not see.`;
|
|
11964
|
+
await deps.runtimeSend(target.project, msg);
|
|
11965
|
+
deps.printSuccess(msg);
|
|
11966
|
+
}
|
|
11967
|
+
return target;
|
|
11968
|
+
}
|
|
11643
11969
|
async function squadrantdCall(req) {
|
|
11644
11970
|
try {
|
|
11645
11971
|
return await sendRequest(SOCK2, req);
|
|
@@ -11683,9 +12009,9 @@ function buildSignalRequest(signal, o) {
|
|
|
11683
12009
|
return { kind: "event", project, event };
|
|
11684
12010
|
}
|
|
11685
12011
|
function defaultWriteResult(id, payload) {
|
|
11686
|
-
const dir =
|
|
12012
|
+
const dir = join23(homedir18(), ".config", "squadrant", "state", "_results");
|
|
11687
12013
|
mkdirSync9(dir, { recursive: true });
|
|
11688
|
-
const file =
|
|
12014
|
+
const file = join23(dir, `${id}.txt`);
|
|
11689
12015
|
writeFileSync11(file, payload);
|
|
11690
12016
|
return file;
|
|
11691
12017
|
}
|
|
@@ -11865,6 +12191,60 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11865
12191
|
`);
|
|
11866
12192
|
} catch (e) {
|
|
11867
12193
|
process.stderr.write(`${e.message}
|
|
12194
|
+
`);
|
|
12195
|
+
process.exit(1);
|
|
12196
|
+
}
|
|
12197
|
+
});
|
|
12198
|
+
crew.command("takeover [project] [crewName]").description("Take over a crew's tab explicitly, suppressing captain action until handback (#649)").option("--task-id <id>", "Explicit task id (overrides SQUADRANT_CREW_TASK_ID env)").option("--note <note>", "Optional note explaining the takeover").action(async (project, crewName, opts) => {
|
|
12199
|
+
try {
|
|
12200
|
+
await runCrewTakeover("start", { project, crew: crewName, taskId: opts.taskId, note: opts.note }, {
|
|
12201
|
+
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
12202
|
+
emitEvent: async (p, event) => {
|
|
12203
|
+
await squadrantdCall({ kind: "event", project: p, event });
|
|
12204
|
+
},
|
|
12205
|
+
runtimeSend: async (p, msg) => {
|
|
12206
|
+
const { runRuntimeSend: runRuntimeSend2 } = await Promise.resolve().then(() => (init_runtime2(), runtime_exports));
|
|
12207
|
+
await runRuntimeSend2(p, msg, {});
|
|
12208
|
+
},
|
|
12209
|
+
printError: (msg) => {
|
|
12210
|
+
process.stderr.write(`${msg}
|
|
12211
|
+
`);
|
|
12212
|
+
},
|
|
12213
|
+
printSuccess: (msg) => {
|
|
12214
|
+
process.stdout.write(`\u2714 ${msg}
|
|
12215
|
+
`);
|
|
12216
|
+
}
|
|
12217
|
+
});
|
|
12218
|
+
process.exit(0);
|
|
12219
|
+
} catch (e) {
|
|
12220
|
+
process.stderr.write(`${e.message}
|
|
12221
|
+
`);
|
|
12222
|
+
process.exit(1);
|
|
12223
|
+
}
|
|
12224
|
+
});
|
|
12225
|
+
crew.command("handback [project] [crewName]").description("Return control of a held crew tab to the captain (#649)").option("--task-id <id>", "Explicit task id (overrides SQUADRANT_CREW_TASK_ID env)").action(async (project, crewName, opts) => {
|
|
12226
|
+
try {
|
|
12227
|
+
await runCrewTakeover("end", { project, crew: crewName, taskId: opts.taskId }, {
|
|
12228
|
+
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
12229
|
+
emitEvent: async (p, event) => {
|
|
12230
|
+
await squadrantdCall({ kind: "event", project: p, event });
|
|
12231
|
+
},
|
|
12232
|
+
runtimeSend: async (p, msg) => {
|
|
12233
|
+
const { runRuntimeSend: runRuntimeSend2 } = await Promise.resolve().then(() => (init_runtime2(), runtime_exports));
|
|
12234
|
+
await runRuntimeSend2(p, msg, {});
|
|
12235
|
+
},
|
|
12236
|
+
printError: (msg) => {
|
|
12237
|
+
process.stderr.write(`${msg}
|
|
12238
|
+
`);
|
|
12239
|
+
},
|
|
12240
|
+
printSuccess: (msg) => {
|
|
12241
|
+
process.stdout.write(`\u2714 ${msg}
|
|
12242
|
+
`);
|
|
12243
|
+
}
|
|
12244
|
+
});
|
|
12245
|
+
process.exit(0);
|
|
12246
|
+
} catch (e) {
|
|
12247
|
+
process.stderr.write(`${e.message}
|
|
11868
12248
|
`);
|
|
11869
12249
|
process.exit(1);
|
|
11870
12250
|
}
|
|
@@ -11872,7 +12252,7 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11872
12252
|
crew.addCommand(crewAttachCommand);
|
|
11873
12253
|
crew.addCommand(crewChatCommand);
|
|
11874
12254
|
}
|
|
11875
|
-
var crewControlCommand = new
|
|
12255
|
+
var crewControlCommand = new Command9("crew").description("Dispatch and track crew via the squadrant control plane");
|
|
11876
12256
|
addControlPlaneCrewCommands(crewControlCommand);
|
|
11877
12257
|
|
|
11878
12258
|
// packages/cli/src/commands/crew.ts
|
|
@@ -11904,13 +12284,14 @@ async function runCrewSpawn2(input) {
|
|
|
11904
12284
|
await squadrantdCall({ kind: "event", project: p, event });
|
|
11905
12285
|
},
|
|
11906
12286
|
onRouted: (route) => console.log(
|
|
11907
|
-
|
|
12287
|
+
chalk10.dim(
|
|
11908
12288
|
`routed: tier=${route.tier} \u2192 ${route.agent}${route.model ? `/${route.model}` : ""} (rule: "${route.matchedRule}")`
|
|
11909
12289
|
)
|
|
11910
|
-
)
|
|
12290
|
+
),
|
|
12291
|
+
onBaseResolved: (base) => console.log(chalk10.dim(`base: ${base}`))
|
|
11911
12292
|
});
|
|
11912
12293
|
}
|
|
11913
|
-
async function runCrewSend2(project, name, message) {
|
|
12294
|
+
async function runCrewSend2(project, name, message, opts) {
|
|
11914
12295
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
11915
12296
|
return runCrewSend(project, name, message, runtime, workspaceId, {
|
|
11916
12297
|
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
@@ -11922,13 +12303,13 @@ async function runCrewSend2(project, name, message) {
|
|
|
11922
12303
|
sendToPane: (pane, msg) => confirmedSendToPane(runtime, pane, msg),
|
|
11923
12304
|
// #516: side-effect-free modal precheck, run before any daemon-state emit.
|
|
11924
12305
|
isBlockedByModal: (pane) => paneHasOpenModal(runtime, pane)
|
|
11925
|
-
});
|
|
12306
|
+
}, opts);
|
|
11926
12307
|
}
|
|
11927
12308
|
async function runCrewRead2(project, name) {
|
|
11928
12309
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
11929
12310
|
return runCrewRead(project, name, runtime, workspaceId);
|
|
11930
12311
|
}
|
|
11931
|
-
async function runCrewClose2(project, name) {
|
|
12312
|
+
async function runCrewClose2(project, name, opts) {
|
|
11932
12313
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
11933
12314
|
return runCrewClose(project, name, runtime, workspaceId, {
|
|
11934
12315
|
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
@@ -11938,13 +12319,13 @@ async function runCrewClose2(project, name) {
|
|
|
11938
12319
|
closeCodexThread: async (taskId) => {
|
|
11939
12320
|
await squadrantdCall({ kind: "codex-close", taskId });
|
|
11940
12321
|
}
|
|
11941
|
-
});
|
|
12322
|
+
}, opts);
|
|
11942
12323
|
}
|
|
11943
12324
|
async function runCrewList2(project) {
|
|
11944
12325
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
11945
12326
|
return runCrewList(project, runtime, workspaceId);
|
|
11946
12327
|
}
|
|
11947
|
-
var crewCommand = new
|
|
12328
|
+
var crewCommand = new Command10("crew").description(
|
|
11948
12329
|
"Spawn and manage interactive crew sessions next to the project's captain"
|
|
11949
12330
|
);
|
|
11950
12331
|
crewCommand.command("spawn").description(
|
|
@@ -11970,9 +12351,9 @@ crewCommand.command("spawn").description(
|
|
|
11970
12351
|
// into the isolated worktree root for relative-path access.
|
|
11971
12352
|
...opts.taskFile && opts.taskFile !== "-" ? { taskFile: opts.taskFile } : {}
|
|
11972
12353
|
});
|
|
11973
|
-
console.log(
|
|
12354
|
+
console.log(chalk10.green(`\u2714 Crew '${pane.title}' spawned (${pane.surfaceId})`));
|
|
11974
12355
|
} catch (err) {
|
|
11975
|
-
console.error(
|
|
12356
|
+
console.error(chalk10.red(err.message));
|
|
11976
12357
|
process.exit(1);
|
|
11977
12358
|
}
|
|
11978
12359
|
}
|
|
@@ -11981,24 +12362,48 @@ crewCommand.command("list").description("List live crew sessions for a project")
|
|
|
11981
12362
|
try {
|
|
11982
12363
|
const crews = await runCrewList2(project);
|
|
11983
12364
|
if (crews.length === 0) {
|
|
11984
|
-
console.log(
|
|
12365
|
+
console.log(chalk10.yellow(`No live crew sessions for ${project}.`));
|
|
11985
12366
|
return;
|
|
11986
12367
|
}
|
|
12368
|
+
const tasks = await squadrantdCall({ kind: "list", project }).catch(() => []);
|
|
12369
|
+
const active = [];
|
|
12370
|
+
const held = [];
|
|
11987
12371
|
for (const c of crews) {
|
|
11988
|
-
|
|
12372
|
+
const task = tasks.find((t2) => t2.name === c.name);
|
|
12373
|
+
const t = task ? resolveApproveTarget(tasks, c.name) : void 0;
|
|
12374
|
+
if (t?.operatorHold) {
|
|
12375
|
+
held.push({ c, t });
|
|
12376
|
+
} else {
|
|
12377
|
+
active.push({ c, t });
|
|
12378
|
+
}
|
|
12379
|
+
}
|
|
12380
|
+
if (active.length > 0) {
|
|
12381
|
+
console.log(`active (${active.length}):`);
|
|
12382
|
+
for (const { c, t } of active) {
|
|
12383
|
+
console.log(` ${c.name.padEnd(10)} ${t ? t.state : "unknown"} (${c.surfaceId})`);
|
|
12384
|
+
}
|
|
12385
|
+
}
|
|
12386
|
+
if (held.length > 0) {
|
|
12387
|
+
console.log(`HELD BY OPERATOR (${held.length}) \u2014 not counted toward maxCrew:`);
|
|
12388
|
+
for (const { c, t } of held) {
|
|
12389
|
+
const m = Math.round((Date.now() - t.operatorHold.since) / 6e4);
|
|
12390
|
+
const hm = m >= 60 ? `${Math.floor(m / 60)}h${m % 60}m` : `${m}m`;
|
|
12391
|
+
const note = t.operatorHold.note ? ` \xB7 "${t.operatorHold.note}"` : "";
|
|
12392
|
+
console.log(` ${c.name.padEnd(10)} ${t.state} \xB7 held ${hm}${note} (${c.surfaceId})`);
|
|
12393
|
+
}
|
|
11989
12394
|
}
|
|
11990
12395
|
} catch (err) {
|
|
11991
|
-
console.error(
|
|
12396
|
+
console.error(chalk10.red(err.message));
|
|
11992
12397
|
process.exit(1);
|
|
11993
12398
|
}
|
|
11994
12399
|
});
|
|
11995
|
-
crewCommand.command("send").description("Send a follow-up message to an existing crew session").argument("<project>", "Project name").argument("<name>", "Crew name (e.g. crew-1)").argument("[message]", "Message to send (omit with --message-file)").option("--message-file <path>", "Read message from file instead of positional arg ('-' for stdin)").action(async (project, name, message, opts) => {
|
|
12400
|
+
crewCommand.command("send").description("Send a follow-up message to an existing crew session").argument("<project>", "Project name").argument("<name>", "Crew name (e.g. crew-1)").argument("[message]", "Message to send (omit with --message-file)").option("--message-file <path>", "Read message from file instead of positional arg ('-' for stdin)").option("--force", "override an operator takeover (only when the operator told you to)", false).action(async (project, name, message, opts) => {
|
|
11996
12401
|
try {
|
|
11997
12402
|
const resolvedMessage = await resolveTextInput({ positional: message, filePath: opts.messageFile, label: "message" });
|
|
11998
|
-
await runCrewSend2(project, name, resolvedMessage);
|
|
11999
|
-
console.log(
|
|
12000
|
-
} catch (
|
|
12001
|
-
console.error(
|
|
12403
|
+
await runCrewSend2(project, name, resolvedMessage, opts);
|
|
12404
|
+
console.log(chalk10.green(`\u2714 Sent to ${project}:${name}`));
|
|
12405
|
+
} catch (e) {
|
|
12406
|
+
console.error(chalk10.red(e.message));
|
|
12002
12407
|
process.exit(1);
|
|
12003
12408
|
}
|
|
12004
12409
|
});
|
|
@@ -12008,16 +12413,16 @@ crewCommand.command("read").description("Read the current screen of a crew sessi
|
|
|
12008
12413
|
const out = opts.full ? screen : tailLines(screen, Number(opts.lines ?? 40));
|
|
12009
12414
|
console.log(out);
|
|
12010
12415
|
} catch (err) {
|
|
12011
|
-
console.error(
|
|
12416
|
+
console.error(chalk10.red(err.message));
|
|
12012
12417
|
process.exit(1);
|
|
12013
12418
|
}
|
|
12014
12419
|
});
|
|
12015
|
-
crewCommand.command("close").description("Shutdown a crew session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Crew name").action(async (project, name) => {
|
|
12420
|
+
crewCommand.command("close").description("Shutdown a crew session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Crew name").option("--force", "override an operator takeover (only when the operator told you to)", false).action(async (project, name, opts) => {
|
|
12016
12421
|
try {
|
|
12017
|
-
await runCrewClose2(project, name);
|
|
12018
|
-
console.log(
|
|
12422
|
+
await runCrewClose2(project, name, opts);
|
|
12423
|
+
console.log(chalk10.green(`\u2714 Closed ${project}:${name}`));
|
|
12019
12424
|
} catch (err) {
|
|
12020
|
-
console.error(
|
|
12425
|
+
console.error(chalk10.red(err.message));
|
|
12021
12426
|
process.exit(1);
|
|
12022
12427
|
}
|
|
12023
12428
|
});
|
|
@@ -12025,8 +12430,8 @@ crewCommand.command("close").description("Shutdown a crew session (closes its ta
|
|
|
12025
12430
|
// packages/cli/src/commands/diff.ts
|
|
12026
12431
|
init_dist();
|
|
12027
12432
|
init_dist3();
|
|
12028
|
-
import { Command as
|
|
12029
|
-
import
|
|
12433
|
+
import { Command as Command11 } from "commander";
|
|
12434
|
+
import chalk11 from "chalk";
|
|
12030
12435
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
12031
12436
|
import readline2 from "readline";
|
|
12032
12437
|
function resolveDiffTarget(tasks, crew, projectPath) {
|
|
@@ -12143,7 +12548,7 @@ async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
|
|
|
12143
12548
|
const label = sources.length > 1 ? "staged or unstaged" : sources[0];
|
|
12144
12549
|
console.log(`No ${label} changes on ${branchLabel}.`);
|
|
12145
12550
|
} else {
|
|
12146
|
-
console.log(
|
|
12551
|
+
console.log(chalk11.dim(`Opened ${opened} working-tree diff(s) (${sources.join(", ")}) for ${branchLabel}.`));
|
|
12147
12552
|
}
|
|
12148
12553
|
return;
|
|
12149
12554
|
}
|
|
@@ -12166,7 +12571,7 @@ async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
|
|
|
12166
12571
|
lastTurn: opts.lastTurn,
|
|
12167
12572
|
source: "branch"
|
|
12168
12573
|
});
|
|
12169
|
-
console.log(
|
|
12574
|
+
console.log(chalk11.dim(`Opened ${branchLabel} vs ${base} in cmux diff.`));
|
|
12170
12575
|
}
|
|
12171
12576
|
async function runDiff(project, crewArg, opts) {
|
|
12172
12577
|
const config = loadConfig();
|
|
@@ -12187,7 +12592,7 @@ async function runDiff(project, crewArg, opts) {
|
|
|
12187
12592
|
return;
|
|
12188
12593
|
}
|
|
12189
12594
|
await runtime.showPatch({ workspaceId, patch, title, layout: opts.layout, focus: opts.focus });
|
|
12190
|
-
console.log(
|
|
12595
|
+
console.log(chalk11.dim(`Opened ${title} in cmux diff.`));
|
|
12191
12596
|
return;
|
|
12192
12597
|
}
|
|
12193
12598
|
let crew;
|
|
@@ -12213,7 +12618,7 @@ async function runDiff(project, crewArg, opts) {
|
|
|
12213
12618
|
}
|
|
12214
12619
|
await openCrewDiff(project, proj, crew, opts, runtime, workspaceId);
|
|
12215
12620
|
}
|
|
12216
|
-
var diffCommand = new
|
|
12621
|
+
var diffCommand = new Command11("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);
|
|
12217
12622
|
|
|
12218
12623
|
// packages/cli/src/commands/side.ts
|
|
12219
12624
|
init_dist();
|
|
@@ -12222,11 +12627,11 @@ init_dist4();
|
|
|
12222
12627
|
init_dist3();
|
|
12223
12628
|
init_dist();
|
|
12224
12629
|
init_dist2();
|
|
12225
|
-
import { Command as
|
|
12630
|
+
import { Command as Command12 } from "commander";
|
|
12226
12631
|
import fs20 from "fs";
|
|
12227
12632
|
import path22 from "path";
|
|
12228
12633
|
import os12 from "os";
|
|
12229
|
-
import
|
|
12634
|
+
import chalk12 from "chalk";
|
|
12230
12635
|
var TEMPLATES_DIR3 = path22.join(os12.homedir(), ".config", "squadrant", "templates");
|
|
12231
12636
|
async function runSideSpawn2(input) {
|
|
12232
12637
|
const config = loadConfig();
|
|
@@ -12288,7 +12693,7 @@ async function runSideClose2(project, name) {
|
|
|
12288
12693
|
config.defaults.worktreeDir ?? ".worktrees"
|
|
12289
12694
|
);
|
|
12290
12695
|
}
|
|
12291
|
-
var sideCommand = new
|
|
12696
|
+
var sideCommand = new Command12("side").description(
|
|
12292
12697
|
"Spawn and manage side-sessions (research/debug) \u2014 fresh-context tabs off the daemon lifecycle"
|
|
12293
12698
|
);
|
|
12294
12699
|
sideCommand.command("spawn").description(
|
|
@@ -12313,9 +12718,9 @@ sideCommand.command("spawn").description(
|
|
|
12313
12718
|
direction: opts.direction,
|
|
12314
12719
|
agent: opts.agent
|
|
12315
12720
|
});
|
|
12316
|
-
console.log(
|
|
12721
|
+
console.log(chalk12.green(`\u2714 Side session '${pane.title}' spawned (${pane.surfaceId})`));
|
|
12317
12722
|
} catch (err) {
|
|
12318
|
-
console.error(
|
|
12723
|
+
console.error(chalk12.red(err.message));
|
|
12319
12724
|
process.exit(1);
|
|
12320
12725
|
}
|
|
12321
12726
|
}
|
|
@@ -12324,14 +12729,14 @@ sideCommand.command("list").description("List live side-sessions for a project")
|
|
|
12324
12729
|
try {
|
|
12325
12730
|
const sessions = await runSideList2(project);
|
|
12326
12731
|
if (sessions.length === 0) {
|
|
12327
|
-
console.log(
|
|
12732
|
+
console.log(chalk12.yellow(`No live side-sessions for ${project}.`));
|
|
12328
12733
|
return;
|
|
12329
12734
|
}
|
|
12330
12735
|
for (const s of sessions) {
|
|
12331
12736
|
console.log(` ${s.name} (${s.surfaceId})`);
|
|
12332
12737
|
}
|
|
12333
12738
|
} catch (err) {
|
|
12334
|
-
console.error(
|
|
12739
|
+
console.error(chalk12.red(err.message));
|
|
12335
12740
|
process.exit(1);
|
|
12336
12741
|
}
|
|
12337
12742
|
});
|
|
@@ -12344,9 +12749,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
12344
12749
|
label: "message"
|
|
12345
12750
|
});
|
|
12346
12751
|
await runSideSend2(project, name, resolvedMessage);
|
|
12347
|
-
console.log(
|
|
12752
|
+
console.log(chalk12.green(`\u2714 Sent to ${project}:${name}`));
|
|
12348
12753
|
} catch (err) {
|
|
12349
|
-
console.error(
|
|
12754
|
+
console.error(chalk12.red(err.message));
|
|
12350
12755
|
process.exit(1);
|
|
12351
12756
|
}
|
|
12352
12757
|
}
|
|
@@ -12354,9 +12759,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
12354
12759
|
sideCommand.command("close").description("Close a side-session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Session name").action(async (project, name) => {
|
|
12355
12760
|
try {
|
|
12356
12761
|
await runSideClose2(project, name);
|
|
12357
|
-
console.log(
|
|
12762
|
+
console.log(chalk12.green(`\u2714 Closed ${project}:${name}`));
|
|
12358
12763
|
} catch (err) {
|
|
12359
|
-
console.error(
|
|
12764
|
+
console.error(chalk12.red(err.message));
|
|
12360
12765
|
process.exit(1);
|
|
12361
12766
|
}
|
|
12362
12767
|
});
|
|
@@ -12364,11 +12769,11 @@ sideCommand.command("close").description("Close a side-session (closes its tab)"
|
|
|
12364
12769
|
// packages/cli/src/commands/dashboard.ts
|
|
12365
12770
|
init_dist();
|
|
12366
12771
|
init_dist3();
|
|
12367
|
-
import { Command as
|
|
12772
|
+
import { Command as Command13 } from "commander";
|
|
12368
12773
|
import { execSync as execSync10 } from "child_process";
|
|
12369
|
-
import { homedir as
|
|
12370
|
-
import { join as
|
|
12371
|
-
import
|
|
12774
|
+
import { homedir as homedir20 } from "os";
|
|
12775
|
+
import { join as join25 } from "path";
|
|
12776
|
+
import chalk14 from "chalk";
|
|
12372
12777
|
|
|
12373
12778
|
// packages/web/dist/read-status.js
|
|
12374
12779
|
function deriveState(tasks) {
|
|
@@ -12445,14 +12850,14 @@ async function readAllStatuses(deps) {
|
|
|
12445
12850
|
}
|
|
12446
12851
|
|
|
12447
12852
|
// packages/web/dist/render.js
|
|
12448
|
-
import
|
|
12853
|
+
import chalk13 from "chalk";
|
|
12449
12854
|
var ICON = {
|
|
12450
|
-
idle:
|
|
12451
|
-
busy:
|
|
12452
|
-
blocked:
|
|
12453
|
-
errored:
|
|
12454
|
-
offline:
|
|
12455
|
-
unknown:
|
|
12855
|
+
idle: chalk13.green,
|
|
12856
|
+
busy: chalk13.cyan,
|
|
12857
|
+
blocked: chalk13.yellow,
|
|
12858
|
+
errored: chalk13.red,
|
|
12859
|
+
offline: chalk13.dim,
|
|
12860
|
+
unknown: chalk13.gray
|
|
12456
12861
|
};
|
|
12457
12862
|
var ICON_CHAR = {
|
|
12458
12863
|
idle: "\u25CF",
|
|
@@ -12495,10 +12900,10 @@ function renderDashboard(rows, opts) {
|
|
|
12495
12900
|
const width = opts.width ?? 100;
|
|
12496
12901
|
const lines = [];
|
|
12497
12902
|
lines.push("");
|
|
12498
|
-
lines.push(" " +
|
|
12903
|
+
lines.push(" " + chalk13.bold("\u{1F4CA} Squadrant Dashboard") + " " + chalk13.dim(opts.now));
|
|
12499
12904
|
lines.push("");
|
|
12500
12905
|
if (rows.length === 0) {
|
|
12501
|
-
lines.push(" " +
|
|
12906
|
+
lines.push(" " + chalk13.yellow("No projects registered. Add one with: squadrant projects add <name> <path>"));
|
|
12502
12907
|
lines.push("");
|
|
12503
12908
|
return lines.join("\n");
|
|
12504
12909
|
}
|
|
@@ -12509,14 +12914,14 @@ function renderDashboard(rows, opts) {
|
|
|
12509
12914
|
const excerptW = Math.max(20, width - FIXED);
|
|
12510
12915
|
for (const r of rows) {
|
|
12511
12916
|
const icon = ICON[r.state](ICON_CHAR[r.state]);
|
|
12512
|
-
const name =
|
|
12917
|
+
const name = chalk13.cyan(pad(r.project, NAME_W));
|
|
12513
12918
|
const state = ICON[r.state](pad(r.state, STATE_W));
|
|
12514
12919
|
const age = pad(formatAge(r.lastChecked, opts.now), AGE_W);
|
|
12515
|
-
const excerpt =
|
|
12920
|
+
const excerpt = chalk13.dim(truncate(firstLine(r.excerpt), excerptW));
|
|
12516
12921
|
lines.push(` ${icon} ${name} ${state} ${age} \u2502 ${excerpt}`);
|
|
12517
12922
|
}
|
|
12518
12923
|
lines.push("");
|
|
12519
|
-
lines.push(
|
|
12924
|
+
lines.push(chalk13.dim(" Refreshes every 10s \xB7 Ctrl+C to exit"));
|
|
12520
12925
|
lines.push("");
|
|
12521
12926
|
return lines.join("\n");
|
|
12522
12927
|
}
|
|
@@ -12580,8 +12985,8 @@ function mergeSnapshot(daemon, external, now) {
|
|
|
12580
12985
|
// packages/web/dist/probes.js
|
|
12581
12986
|
init_dist();
|
|
12582
12987
|
init_dist();
|
|
12583
|
-
import { join as
|
|
12584
|
-
import { homedir as
|
|
12988
|
+
import { join as join24 } from "path";
|
|
12989
|
+
import { homedir as homedir19 } from "os";
|
|
12585
12990
|
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
12586
12991
|
import { execFile as execFile4 } from "child_process";
|
|
12587
12992
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
@@ -12618,7 +13023,7 @@ function vaultProbe(run, dir) {
|
|
|
12618
13023
|
return { state: "unknown", detail: "no vault configured" };
|
|
12619
13024
|
if (!run.pathExists(dir))
|
|
12620
13025
|
return { state: "gone", detail: "vault directory missing" };
|
|
12621
|
-
if (!run.pathExists(
|
|
13026
|
+
if (!run.pathExists(join24(dir, ".obsidian")))
|
|
12622
13027
|
return { state: "gone", detail: "no .obsidian/ (not a vault)" };
|
|
12623
13028
|
return { state: "alive" };
|
|
12624
13029
|
} catch {
|
|
@@ -12686,10 +13091,10 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
12686
13091
|
const sessions = probeSessions(run);
|
|
12687
13092
|
return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
|
|
12688
13093
|
}
|
|
12689
|
-
var SESSIONS_PATH =
|
|
13094
|
+
var SESSIONS_PATH = join24(homedir19(), ".config", "squadrant", "sessions.json");
|
|
12690
13095
|
function onPath(cli) {
|
|
12691
13096
|
const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
12692
|
-
return dirs.some((d) => existsSync12(
|
|
13097
|
+
return dirs.some((d) => existsSync12(join24(d, cli)));
|
|
12693
13098
|
}
|
|
12694
13099
|
function readSessionsHashes() {
|
|
12695
13100
|
const raw = JSON.parse(readFileSync15(SESSIONS_PATH, "utf-8"));
|
|
@@ -13597,7 +14002,7 @@ async function startWebServer(opts) {
|
|
|
13597
14002
|
|
|
13598
14003
|
// packages/cli/src/commands/dashboard.ts
|
|
13599
14004
|
init_dist();
|
|
13600
|
-
var SOCK3 =
|
|
14005
|
+
var SOCK3 = join25(homedir20(), ".config", "squadrant", "squadrant.sock");
|
|
13601
14006
|
function detectCurrentWorkspace2() {
|
|
13602
14007
|
const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
|
|
13603
14008
|
const match = out.match(/workspace:\d+/);
|
|
@@ -13639,10 +14044,10 @@ async function runDashboardWeb(input) {
|
|
|
13639
14044
|
sockPath: SOCK3,
|
|
13640
14045
|
runners: defaultProbeRunners()
|
|
13641
14046
|
});
|
|
13642
|
-
console.log(
|
|
13643
|
-
console.log(
|
|
14047
|
+
console.log(chalk14.green(`\u2714 Squadrant system dashboard \u2192 http://127.0.0.1:${handle.port}`));
|
|
14048
|
+
console.log(chalk14.dim(` polling the daemon every ${input.interval}s \xB7 localhost only \xB7 read-only \xB7 Ctrl-C to stop`));
|
|
13644
14049
|
}
|
|
13645
|
-
var dashboardCommand = new
|
|
14050
|
+
var dashboardCommand = new Command13("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) => {
|
|
13646
14051
|
try {
|
|
13647
14052
|
if (opts.web) {
|
|
13648
14053
|
await runDashboardWeb({ port: opts.port, interval: opts.interval ?? 5 });
|
|
@@ -13650,12 +14055,12 @@ var dashboardCommand = new Command12("dashboard").description("Live status grid
|
|
|
13650
14055
|
}
|
|
13651
14056
|
if (opts.pane) {
|
|
13652
14057
|
const pane = await runDashboardPane({ direction: opts.direction, interval: opts.interval ?? 10 });
|
|
13653
|
-
console.log(
|
|
14058
|
+
console.log(chalk14.green(`\u2714 Dashboard pane opened in ${pane.workspaceId} ${pane.surfaceId}`));
|
|
13654
14059
|
return;
|
|
13655
14060
|
}
|
|
13656
14061
|
await runDashboardOnce();
|
|
13657
14062
|
} catch (err) {
|
|
13658
|
-
console.error(
|
|
14063
|
+
console.error(chalk14.red(err.message));
|
|
13659
14064
|
process.exit(1);
|
|
13660
14065
|
}
|
|
13661
14066
|
});
|
|
@@ -13666,12 +14071,12 @@ dashboardCommand.command("sync-hub").description("Mirror each spoke status.md in
|
|
|
13666
14071
|
return;
|
|
13667
14072
|
}
|
|
13668
14073
|
if (results.length === 0) {
|
|
13669
|
-
console.log(
|
|
14074
|
+
console.log(chalk14.dim("\n No mirrors written (no projects with usable status.md, or hubVault unset).\n"));
|
|
13670
14075
|
return;
|
|
13671
14076
|
}
|
|
13672
|
-
console.log(
|
|
14077
|
+
console.log(chalk14.bold("\n \u{1F4CA} Hub mirror sync\n"));
|
|
13673
14078
|
for (const r of results) {
|
|
13674
|
-
console.log(` ${
|
|
14079
|
+
console.log(` ${chalk14.green("\u2714")} ${chalk14.cyan(r.project.padEnd(16))} \u2192 ${chalk14.dim(r.hubPath)}`);
|
|
13675
14080
|
}
|
|
13676
14081
|
console.log("");
|
|
13677
14082
|
});
|
|
@@ -13681,12 +14086,12 @@ init_dist();
|
|
|
13681
14086
|
init_dist4();
|
|
13682
14087
|
init_dist3();
|
|
13683
14088
|
init_dist2();
|
|
13684
|
-
import { Command as
|
|
14089
|
+
import { Command as Command14 } from "commander";
|
|
13685
14090
|
import { execSync as execSync11 } from "child_process";
|
|
13686
14091
|
import fs22 from "fs";
|
|
13687
14092
|
import path24 from "path";
|
|
13688
14093
|
import os13 from "os";
|
|
13689
|
-
import
|
|
14094
|
+
import chalk15 from "chalk";
|
|
13690
14095
|
|
|
13691
14096
|
// packages/cli/src/commands/launch-interactive.ts
|
|
13692
14097
|
import checkbox, { Separator } from "@inquirer/checkbox";
|
|
@@ -13767,16 +14172,16 @@ var TEMPLATES_DIR4 = path24.join(os13.homedir(), ".config", "squadrant", "templa
|
|
|
13767
14172
|
var SESSIONS_PATH2 = path24.join(os13.homedir(), ".config", "squadrant", "sessions.json");
|
|
13768
14173
|
function ensureCmuxReady(headless) {
|
|
13769
14174
|
if (headless || isInsideCmux()) return;
|
|
13770
|
-
console.log(
|
|
14175
|
+
console.log(chalk15.yellow("\n Not running inside cmux. Opening cmux app...\n"));
|
|
13771
14176
|
execSync11(`open "${CMUX_APP}"`, { stdio: "inherit" });
|
|
13772
|
-
console.log(
|
|
14177
|
+
console.log(chalk15.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
|
|
13773
14178
|
process.exit(0);
|
|
13774
14179
|
}
|
|
13775
|
-
var launchCommand = new
|
|
14180
|
+
var launchCommand = new Command14("launch").description(
|
|
13776
14181
|
"Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
|
|
13777
14182
|
).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) => {
|
|
13778
14183
|
if (opts.fresh && opts.keep) {
|
|
13779
|
-
console.error(
|
|
14184
|
+
console.error(chalk15.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
|
|
13780
14185
|
process.exit(1);
|
|
13781
14186
|
}
|
|
13782
14187
|
const config = loadConfig();
|
|
@@ -13824,29 +14229,29 @@ var launchCommand = new Command13("launch").description(
|
|
|
13824
14229
|
return null;
|
|
13825
14230
|
}
|
|
13826
14231
|
},
|
|
13827
|
-
onFreshReason: (reason) => console.log(
|
|
13828
|
-
onStoppingStale: (name) => console.log(
|
|
13829
|
-
onAlreadyExists: (name) => console.log(
|
|
13830
|
-
onCreated: (name) => console.log(
|
|
14232
|
+
onFreshReason: (reason) => console.log(chalk15.cyan(` \u21BB ${reason}`)),
|
|
14233
|
+
onStoppingStale: (name) => console.log(chalk15.yellow(` Closing stale workspace '${name}' for fresh start`)),
|
|
14234
|
+
onAlreadyExists: (name) => console.log(chalk15.yellow(` Workspace '${name}' already exists \u2014 switching to it`)),
|
|
14235
|
+
onCreated: (name) => console.log(chalk15.green(` \u2714 Workspace '${name}' created`))
|
|
13831
14236
|
});
|
|
13832
14237
|
} catch (err) {
|
|
13833
|
-
console.error(
|
|
14238
|
+
console.error(chalk15.red(` \u2718 Failed: ${err.message}`));
|
|
13834
14239
|
hadFailure = true;
|
|
13835
14240
|
}
|
|
13836
14241
|
}
|
|
13837
14242
|
if (opts.all) {
|
|
13838
14243
|
const hubPath = resolveHome(config.hubVault);
|
|
13839
14244
|
fs22.mkdirSync(hubPath, { recursive: true });
|
|
13840
|
-
console.log(
|
|
14245
|
+
console.log(chalk15.bold("\nLaunching all captain workspaces\n"));
|
|
13841
14246
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
13842
14247
|
const projPath = resolveHome(proj.path);
|
|
13843
14248
|
const spokePath = resolveHome(proj.spokeVault);
|
|
13844
14249
|
if (!fs22.existsSync(spokePath)) {
|
|
13845
14250
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
13846
14251
|
await ensureSpokeLayout(spokeDriver);
|
|
13847
|
-
console.log(
|
|
14252
|
+
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13848
14253
|
}
|
|
13849
|
-
console.log(
|
|
14254
|
+
console.log(chalk15.bold(`
|
|
13850
14255
|
Captain: ${proj.captainName} (${name})`));
|
|
13851
14256
|
await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
|
|
13852
14257
|
}
|
|
@@ -13854,7 +14259,7 @@ var launchCommand = new Command13("launch").description(
|
|
|
13854
14259
|
} else if (!project) {
|
|
13855
14260
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
13856
14261
|
console.error(
|
|
13857
|
-
|
|
14262
|
+
chalk15.red(
|
|
13858
14263
|
"\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"
|
|
13859
14264
|
)
|
|
13860
14265
|
);
|
|
@@ -13869,10 +14274,10 @@ var launchCommand = new Command13("launch").description(
|
|
|
13869
14274
|
}));
|
|
13870
14275
|
const selected = await selectCaptainsInteractive(entries);
|
|
13871
14276
|
if (selected.length === 0) {
|
|
13872
|
-
console.log(
|
|
14277
|
+
console.log(chalk15.yellow("\n No captains selected.\n"));
|
|
13873
14278
|
return;
|
|
13874
14279
|
}
|
|
13875
|
-
console.log(
|
|
14280
|
+
console.log(chalk15.bold(`
|
|
13876
14281
|
Launching ${selected.length} captain workspace(s) in parallel
|
|
13877
14282
|
`));
|
|
13878
14283
|
await Promise.all(selected.map(async (name) => {
|
|
@@ -13882,9 +14287,9 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13882
14287
|
if (!fs22.existsSync(spokePath)) {
|
|
13883
14288
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
13884
14289
|
await ensureSpokeLayout(spokeDriver);
|
|
13885
|
-
console.log(
|
|
14290
|
+
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13886
14291
|
}
|
|
13887
|
-
console.log(
|
|
14292
|
+
console.log(chalk15.bold(`
|
|
13888
14293
|
Captain: ${proj.captainName} (${name})`));
|
|
13889
14294
|
await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
|
|
13890
14295
|
}));
|
|
@@ -13892,7 +14297,7 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13892
14297
|
} else {
|
|
13893
14298
|
if (!config.projects[project]) {
|
|
13894
14299
|
console.error(
|
|
13895
|
-
|
|
14300
|
+
chalk15.red(
|
|
13896
14301
|
`
|
|
13897
14302
|
\u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
|
|
13898
14303
|
`
|
|
@@ -13906,10 +14311,10 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13906
14311
|
if (!fs22.existsSync(spokePath)) {
|
|
13907
14312
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
|
|
13908
14313
|
await ensureSpokeLayout(spokeDriver);
|
|
13909
|
-
console.log(
|
|
14314
|
+
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13910
14315
|
}
|
|
13911
14316
|
console.log(
|
|
13912
|
-
|
|
14317
|
+
chalk15.bold(
|
|
13913
14318
|
`
|
|
13914
14319
|
Launching captain workspace for '${project}' (${proj.captainName})
|
|
13915
14320
|
`
|
|
@@ -13923,8 +14328,8 @@ Launching captain workspace for '${project}' (${proj.captainName})
|
|
|
13923
14328
|
// packages/cli/src/commands/shutdown.ts
|
|
13924
14329
|
init_dist();
|
|
13925
14330
|
init_dist3();
|
|
13926
|
-
import { Command as
|
|
13927
|
-
import
|
|
14331
|
+
import { Command as Command15 } from "commander";
|
|
14332
|
+
import chalk16 from "chalk";
|
|
13928
14333
|
init_dist();
|
|
13929
14334
|
function nameVariants(name) {
|
|
13930
14335
|
const stripped = name.replace(/^⚓\s+/, "").trim();
|
|
@@ -13937,23 +14342,23 @@ async function closeMatching(runtime, variants, label) {
|
|
|
13937
14342
|
const failed = [];
|
|
13938
14343
|
if (matches.length === 0) {
|
|
13939
14344
|
console.log(
|
|
13940
|
-
|
|
14345
|
+
chalk16.yellow(` \u26A0 Workspace '${label}' not found \u2014 already closed?`)
|
|
13941
14346
|
);
|
|
13942
14347
|
return { closed, failed };
|
|
13943
14348
|
}
|
|
13944
14349
|
for (const ws of matches) {
|
|
13945
14350
|
try {
|
|
13946
14351
|
await runtime.stop(ws.id);
|
|
13947
|
-
console.log(
|
|
14352
|
+
console.log(chalk16.green(` \u2714 Closed: ${ws.name}`));
|
|
13948
14353
|
closed.push(ws.name);
|
|
13949
14354
|
} catch {
|
|
13950
|
-
console.log(
|
|
14355
|
+
console.log(chalk16.red(` \u2718 Failed to close: ${ws.name}`));
|
|
13951
14356
|
failed.push(ws.name);
|
|
13952
14357
|
}
|
|
13953
14358
|
}
|
|
13954
14359
|
return { closed, failed };
|
|
13955
14360
|
}
|
|
13956
|
-
var shutdownCommand = new
|
|
14361
|
+
var shutdownCommand = new Command15("shutdown").description(
|
|
13957
14362
|
"Shutdown command + all captain workspaces (no args) or one captain workspace"
|
|
13958
14363
|
).argument("[project]", "Project name to shut down captain for").action(async (project) => {
|
|
13959
14364
|
const config = loadConfig();
|
|
@@ -13969,11 +14374,11 @@ var shutdownCommand = new Command14("shutdown").description(
|
|
|
13969
14374
|
const allVariants = /* @__PURE__ */ new Set([...captainVariants, ...commandVariants]);
|
|
13970
14375
|
const squadrantWorkspaces = workspaces.filter((w) => allVariants.has(w.name));
|
|
13971
14376
|
if (squadrantWorkspaces.length === 0) {
|
|
13972
|
-
console.log(
|
|
14377
|
+
console.log(chalk16.yellow("\nNo squadrant workspaces found to close.\n"));
|
|
13973
14378
|
return;
|
|
13974
14379
|
}
|
|
13975
14380
|
console.log(
|
|
13976
|
-
|
|
14381
|
+
chalk16.bold(
|
|
13977
14382
|
`
|
|
13978
14383
|
Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
13979
14384
|
`
|
|
@@ -13993,9 +14398,9 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
13993
14398
|
for (const ws of squadrantWorkspaces) {
|
|
13994
14399
|
try {
|
|
13995
14400
|
await globalRuntime.stop(ws.id);
|
|
13996
|
-
console.log(
|
|
14401
|
+
console.log(chalk16.green(` \u2714 Closed: ${ws.name}`));
|
|
13997
14402
|
} catch {
|
|
13998
|
-
console.log(
|
|
14403
|
+
console.log(chalk16.red(` \u2718 Failed to close: ${ws.name}`));
|
|
13999
14404
|
}
|
|
14000
14405
|
}
|
|
14001
14406
|
console.log("");
|
|
@@ -14003,7 +14408,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
14003
14408
|
}
|
|
14004
14409
|
if (!config.projects[project]) {
|
|
14005
14410
|
console.error(
|
|
14006
|
-
|
|
14411
|
+
chalk16.red(
|
|
14007
14412
|
`
|
|
14008
14413
|
\u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
|
|
14009
14414
|
`
|
|
@@ -14014,7 +14419,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
14014
14419
|
const captainName = config.projects[project].captainName;
|
|
14015
14420
|
const runtime = runtimes.forProject(project, config);
|
|
14016
14421
|
console.log(
|
|
14017
|
-
|
|
14422
|
+
chalk16.bold(`
|
|
14018
14423
|
Shutting down captain workspace for '${project}'...
|
|
14019
14424
|
`)
|
|
14020
14425
|
);
|
|
@@ -14038,13 +14443,13 @@ Shutting down captain workspace for '${project}'...
|
|
|
14038
14443
|
|
|
14039
14444
|
// packages/cli/src/commands/feedback.ts
|
|
14040
14445
|
init_dist();
|
|
14041
|
-
import { Command as
|
|
14446
|
+
import { Command as Command16 } from "commander";
|
|
14042
14447
|
import fs23 from "fs";
|
|
14043
14448
|
import os14 from "os";
|
|
14044
14449
|
import path25 from "path";
|
|
14045
14450
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14046
14451
|
import { execSync as execSync12 } from "child_process";
|
|
14047
|
-
import
|
|
14452
|
+
import chalk17 from "chalk";
|
|
14048
14453
|
var REPO_URL = "https://github.com/tu11aa/squadrant";
|
|
14049
14454
|
function readPkgVersion() {
|
|
14050
14455
|
try {
|
|
@@ -14092,21 +14497,21 @@ function buildIssueUrl(metrics, squadrantVersion) {
|
|
|
14092
14497
|
});
|
|
14093
14498
|
return `${REPO_URL}/issues/new?${params.toString()}`;
|
|
14094
14499
|
}
|
|
14095
|
-
var feedbackCommand = new
|
|
14500
|
+
var feedbackCommand = new Command16("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
|
|
14096
14501
|
const config = loadConfig();
|
|
14097
14502
|
const metricsPath = config.metrics?.path || path25.join(os14.homedir(), ".config", "squadrant", "metrics.json");
|
|
14098
14503
|
const metrics = readMetrics(metricsPath);
|
|
14099
14504
|
const version = readStamp(config) ?? readPkgVersion();
|
|
14100
14505
|
const issueUrl = buildIssueUrl(metrics, version);
|
|
14101
|
-
console.log(
|
|
14102
|
-
console.log(
|
|
14506
|
+
console.log(chalk17.bold("\nOpening feedback issue in browser...\n"));
|
|
14507
|
+
console.log(chalk17.dim(` URL: ${issueUrl.substring(0, 80)}...
|
|
14103
14508
|
`));
|
|
14104
14509
|
try {
|
|
14105
14510
|
execSync12(`open "${issueUrl}"`, { stdio: "ignore" });
|
|
14106
|
-
console.log(
|
|
14511
|
+
console.log(chalk17.green(" \u2714 Browser opened\n"));
|
|
14107
14512
|
} catch {
|
|
14108
|
-
console.log(
|
|
14109
|
-
console.log(` Open manually: ${
|
|
14513
|
+
console.log(chalk17.yellow(" \u26A0 Could not open browser automatically."));
|
|
14514
|
+
console.log(` Open manually: ${chalk17.cyan(issueUrl)}
|
|
14110
14515
|
`);
|
|
14111
14516
|
}
|
|
14112
14517
|
});
|
|
@@ -14115,8 +14520,8 @@ var feedbackCommand = new Command15("feedback").description("Open a pre-filled G
|
|
|
14115
14520
|
init_dist();
|
|
14116
14521
|
init_dist();
|
|
14117
14522
|
init_dist3();
|
|
14118
|
-
import { Command as
|
|
14119
|
-
import
|
|
14523
|
+
import { Command as Command17 } from "commander";
|
|
14524
|
+
import chalk18 from "chalk";
|
|
14120
14525
|
function getDateStr(yesterday) {
|
|
14121
14526
|
return iso(daysAgo(yesterday ? 1 : 0));
|
|
14122
14527
|
}
|
|
@@ -14135,7 +14540,7 @@ function formatStandup(standups, dateStr, raw) {
|
|
|
14135
14540
|
const lines = [];
|
|
14136
14541
|
const header = `Standup \u2014 ${dateStr}`;
|
|
14137
14542
|
if (!raw) {
|
|
14138
|
-
lines.push(
|
|
14543
|
+
lines.push(chalk18.bold(`
|
|
14139
14544
|
${header}
|
|
14140
14545
|
`));
|
|
14141
14546
|
} else {
|
|
@@ -14145,12 +14550,12 @@ ${header}
|
|
|
14145
14550
|
let hasBlockers = false;
|
|
14146
14551
|
for (const s of standups) {
|
|
14147
14552
|
if (!raw) {
|
|
14148
|
-
lines.push(
|
|
14553
|
+
lines.push(chalk18.cyan.bold(`## ${s.name}`));
|
|
14149
14554
|
} else {
|
|
14150
14555
|
lines.push(`## ${s.name}`);
|
|
14151
14556
|
}
|
|
14152
14557
|
if (s.gitCommits.length > 0) {
|
|
14153
|
-
lines.push(!raw ?
|
|
14558
|
+
lines.push(!raw ? chalk18.green("Done:") : "**Done:**");
|
|
14154
14559
|
for (const commit of s.gitCommits) {
|
|
14155
14560
|
lines.push(` - ${commit}`);
|
|
14156
14561
|
}
|
|
@@ -14162,7 +14567,7 @@ ${header}
|
|
|
14162
14567
|
if (match) {
|
|
14163
14568
|
const items = match[1].trim().split("\n").filter((l) => l.trim().startsWith("-"));
|
|
14164
14569
|
if (items.length > 0 && section === "Tomorrow") {
|
|
14165
|
-
lines.push(!raw ?
|
|
14570
|
+
lines.push(!raw ? chalk18.blue("Next:") : "**Next:**");
|
|
14166
14571
|
for (const item of items) lines.push(` ${item.trim()}`);
|
|
14167
14572
|
}
|
|
14168
14573
|
}
|
|
@@ -14170,19 +14575,19 @@ ${header}
|
|
|
14170
14575
|
}
|
|
14171
14576
|
if (s.blockers.length > 0) {
|
|
14172
14577
|
hasBlockers = true;
|
|
14173
|
-
lines.push(!raw ?
|
|
14578
|
+
lines.push(!raw ? chalk18.red("Blocked:") : "**Blocked:**");
|
|
14174
14579
|
for (const b of s.blockers) {
|
|
14175
14580
|
lines.push(` - ${b}`);
|
|
14176
14581
|
}
|
|
14177
14582
|
}
|
|
14178
14583
|
if (s.gitCommits.length === 0 && !s.dailyLog) {
|
|
14179
|
-
lines.push(!raw ?
|
|
14584
|
+
lines.push(!raw ? chalk18.dim(" (no activity)") : " (no activity)");
|
|
14180
14585
|
}
|
|
14181
14586
|
lines.push("");
|
|
14182
14587
|
}
|
|
14183
14588
|
const totalCommits = standups.reduce((sum, s) => sum + s.gitCommits.length, 0);
|
|
14184
14589
|
if (!raw) {
|
|
14185
|
-
lines.push(
|
|
14590
|
+
lines.push(chalk18.dim(`--- ${totalCommits} commits${hasBlockers ? ", HAS BLOCKERS" : ""} (task tracking: no data source \u2014 #630) ---
|
|
14186
14591
|
`));
|
|
14187
14592
|
} else {
|
|
14188
14593
|
lines.push(`---
|
|
@@ -14191,12 +14596,12 @@ ${header}
|
|
|
14191
14596
|
}
|
|
14192
14597
|
return lines.join("\n");
|
|
14193
14598
|
}
|
|
14194
|
-
var standupCommand = new
|
|
14599
|
+
var standupCommand = new Command17("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) => {
|
|
14195
14600
|
const config = loadConfig();
|
|
14196
14601
|
const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
14197
14602
|
const projects = Object.entries(config.projects);
|
|
14198
14603
|
if (projects.length === 0) {
|
|
14199
|
-
console.log(
|
|
14604
|
+
console.log(chalk18.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
|
|
14200
14605
|
return;
|
|
14201
14606
|
}
|
|
14202
14607
|
const dateStr = getDateStr(!!opts.yesterday);
|
|
@@ -14205,7 +14610,7 @@ var standupCommand = new Command16("standup").description("Generate daily standu
|
|
|
14205
14610
|
if (opts.project) {
|
|
14206
14611
|
const match = projects.find(([name]) => name === opts.project);
|
|
14207
14612
|
if (!match) {
|
|
14208
|
-
console.error(
|
|
14613
|
+
console.error(chalk18.red(`Project "${opts.project}" not found.`));
|
|
14209
14614
|
process.exit(1);
|
|
14210
14615
|
}
|
|
14211
14616
|
targets = [match];
|
|
@@ -14223,8 +14628,8 @@ var standupCommand = new Command16("standup").description("Generate daily standu
|
|
|
14223
14628
|
init_dist();
|
|
14224
14629
|
init_dist();
|
|
14225
14630
|
init_dist3();
|
|
14226
|
-
import { Command as
|
|
14227
|
-
import
|
|
14631
|
+
import { Command as Command18 } from "commander";
|
|
14632
|
+
import chalk19 from "chalk";
|
|
14228
14633
|
function dedupe(items) {
|
|
14229
14634
|
const seen = /* @__PURE__ */ new Set();
|
|
14230
14635
|
const out = [];
|
|
@@ -14280,7 +14685,7 @@ function formatRetro(retros, fromStr, toStr, raw) {
|
|
|
14280
14685
|
const lines = [];
|
|
14281
14686
|
const header = `Retro \u2014 ${fromStr} \u2192 ${toStr}`;
|
|
14282
14687
|
lines.push(raw ? `# ${header}
|
|
14283
|
-
` :
|
|
14688
|
+
` : chalk19.bold(`
|
|
14284
14689
|
${header}
|
|
14285
14690
|
`));
|
|
14286
14691
|
let totalCommits = 0;
|
|
@@ -14290,39 +14695,39 @@ ${header}
|
|
|
14290
14695
|
totalCommits += r.commits.length;
|
|
14291
14696
|
totalPRs += r.mergedPRs.length;
|
|
14292
14697
|
totalShipped += r.shipped.length;
|
|
14293
|
-
lines.push(raw ? `## ${r.name}` :
|
|
14294
|
-
renderList(lines, r.shipped, raw, "Shipped",
|
|
14698
|
+
lines.push(raw ? `## ${r.name}` : chalk19.cyan.bold(`## ${r.name}`));
|
|
14699
|
+
renderList(lines, r.shipped, raw, "Shipped", chalk19.green);
|
|
14295
14700
|
if (r.mergedPRs.length > 0) {
|
|
14296
|
-
lines.push(raw ? `**PRs merged:**` :
|
|
14701
|
+
lines.push(raw ? `**PRs merged:**` : chalk19.green("PRs merged:"));
|
|
14297
14702
|
for (const pr of r.mergedPRs) lines.push(` - ${pr}`);
|
|
14298
14703
|
}
|
|
14299
|
-
renderList(lines, r.inProgress, raw, "In Progress",
|
|
14300
|
-
renderList(lines, r.blocked, raw, "Blocked",
|
|
14301
|
-
renderList(lines, r.decisions, raw, "Key Decisions",
|
|
14704
|
+
renderList(lines, r.inProgress, raw, "In Progress", chalk19.yellow);
|
|
14705
|
+
renderList(lines, r.blocked, raw, "Blocked", chalk19.red);
|
|
14706
|
+
renderList(lines, r.decisions, raw, "Key Decisions", chalk19.magenta);
|
|
14302
14707
|
const metricBits = [
|
|
14303
14708
|
`${r.commits.length} commits`,
|
|
14304
14709
|
`${r.mergedPRs.length} PRs merged`,
|
|
14305
14710
|
`${r.shipped.length} shipped`
|
|
14306
14711
|
];
|
|
14307
|
-
lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` :
|
|
14712
|
+
lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` : chalk19.dim(` ${metricBits.join(" \xB7 ")}`));
|
|
14308
14713
|
if (r.shipped.length === 0 && r.commits.length === 0 && r.mergedPRs.length === 0 && r.inProgress.length === 0 && r.blocked.length === 0) {
|
|
14309
|
-
lines.push(raw ? "_(no activity in this window)_" :
|
|
14714
|
+
lines.push(raw ? "_(no activity in this window)_" : chalk19.dim(" (no activity in this window)"));
|
|
14310
14715
|
}
|
|
14311
14716
|
lines.push("");
|
|
14312
14717
|
}
|
|
14313
14718
|
const summary = `${totalShipped} items shipped \xB7 ${totalCommits} commits \xB7 ${totalPRs} PRs merged`;
|
|
14314
14719
|
lines.push(raw ? `---
|
|
14315
14720
|
*${summary}*
|
|
14316
|
-
` :
|
|
14721
|
+
` : chalk19.dim(`--- ${summary} ---
|
|
14317
14722
|
`));
|
|
14318
14723
|
return lines.join("\n");
|
|
14319
14724
|
}
|
|
14320
|
-
var retroCommand = new
|
|
14725
|
+
var retroCommand = new Command18("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) => {
|
|
14321
14726
|
const config = loadConfig();
|
|
14322
14727
|
const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
14323
14728
|
const projects = Object.entries(config.projects);
|
|
14324
14729
|
if (projects.length === 0) {
|
|
14325
|
-
console.log(
|
|
14730
|
+
console.log(chalk19.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
|
|
14326
14731
|
return;
|
|
14327
14732
|
}
|
|
14328
14733
|
let windowDays = 7;
|
|
@@ -14339,7 +14744,7 @@ var retroCommand = new Command17("retro").description("Generate a retro (weekly/
|
|
|
14339
14744
|
if (opts.project) {
|
|
14340
14745
|
const match = projects.find(([name]) => name === opts.project);
|
|
14341
14746
|
if (!match) {
|
|
14342
|
-
console.error(
|
|
14747
|
+
console.error(chalk19.red(`Project "${opts.project}" not found.`));
|
|
14343
14748
|
process.exit(1);
|
|
14344
14749
|
}
|
|
14345
14750
|
targets = [match];
|
|
@@ -14352,153 +14757,8 @@ var retroCommand = new Command17("retro").description("Generate a retro (weekly/
|
|
|
14352
14757
|
console.log(formatRetro(retros, fromStr, toStr, raw));
|
|
14353
14758
|
});
|
|
14354
14759
|
|
|
14355
|
-
// packages/cli/src/
|
|
14356
|
-
|
|
14357
|
-
init_dist3();
|
|
14358
|
-
import { Command as Command18 } from "commander";
|
|
14359
|
-
import chalk19 from "chalk";
|
|
14360
|
-
function buildRegistry() {
|
|
14361
|
-
return new RuntimeRegistry({
|
|
14362
|
-
cmux: createCmuxDriver()
|
|
14363
|
-
});
|
|
14364
|
-
}
|
|
14365
|
-
function resolveTarget(registry, config, target, useCommand) {
|
|
14366
|
-
if (useCommand) {
|
|
14367
|
-
return {
|
|
14368
|
-
driver: registry.global(config),
|
|
14369
|
-
workspaceName: config.commandName
|
|
14370
|
-
};
|
|
14371
|
-
}
|
|
14372
|
-
if (!target) {
|
|
14373
|
-
throw new Error("Missing target: pass a project name or use --command");
|
|
14374
|
-
}
|
|
14375
|
-
const proj = config.projects[target];
|
|
14376
|
-
if (!proj) {
|
|
14377
|
-
throw new Error(`Project '${target}' not found. Run 'squadrant projects list'.`);
|
|
14378
|
-
}
|
|
14379
|
-
return {
|
|
14380
|
-
driver: registry.forProject(target, config),
|
|
14381
|
-
workspaceName: proj.captainName
|
|
14382
|
-
};
|
|
14383
|
-
}
|
|
14384
|
-
async function needRef(resolved) {
|
|
14385
|
-
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
14386
|
-
if (!ref) {
|
|
14387
|
-
throw new Error(`Workspace '${resolved.workspaceName}' is not running`);
|
|
14388
|
-
}
|
|
14389
|
-
return ref.id;
|
|
14390
|
-
}
|
|
14391
|
-
var runtimeCommand = new Command18("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
|
|
14392
|
-
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) => {
|
|
14393
|
-
const config = loadConfig();
|
|
14394
|
-
const registry = buildRegistry();
|
|
14395
|
-
try {
|
|
14396
|
-
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
14397
|
-
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
14398
|
-
if (ref) {
|
|
14399
|
-
console.log("running");
|
|
14400
|
-
process.exit(0);
|
|
14401
|
-
} else {
|
|
14402
|
-
console.log("stopped");
|
|
14403
|
-
process.exit(1);
|
|
14404
|
-
}
|
|
14405
|
-
} catch (err) {
|
|
14406
|
-
console.error(chalk19.red(err.message));
|
|
14407
|
-
process.exit(2);
|
|
14408
|
-
}
|
|
14409
|
-
});
|
|
14410
|
-
var SEND_CONFIRM_TIMEOUT_MS = 15e3;
|
|
14411
|
-
var SEND_CONFIRM_POLL_MS = 500;
|
|
14412
|
-
async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
|
|
14413
|
-
const config = loadConfig();
|
|
14414
|
-
const registry = buildRegistry();
|
|
14415
|
-
if (opts.command && arg2 !== void 0) {
|
|
14416
|
-
throw new Error("With --command, pass only the message (not a project name)");
|
|
14417
|
-
}
|
|
14418
|
-
const target = opts.command ? void 0 : arg1;
|
|
14419
|
-
const message = opts.command ? arg1 : arg2;
|
|
14420
|
-
if (!message) throw new Error("Message is required");
|
|
14421
|
-
const { requireDaemon: requireDaemon2 } = await Promise.resolve().then(() => (init_require_daemon(), require_daemon_exports));
|
|
14422
|
-
const { appendCaptainMessage: appendCaptainMessage2, waitForCaptainDelivery: waitForCaptainDelivery2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
|
|
14423
|
-
await requireDaemon2();
|
|
14424
|
-
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
14425
|
-
await needRef(resolved);
|
|
14426
|
-
const finalProject = opts.command ? config.commandName : target;
|
|
14427
|
-
const { join: join31, dirname: dirname10 } = await import("path");
|
|
14428
|
-
const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
14429
|
-
const stateRoot = join31(dirname10(DEFAULT_CONFIG_PATH2), "state");
|
|
14430
|
-
const seq = await appendCaptainMessage2({
|
|
14431
|
-
stateRoot,
|
|
14432
|
-
project: finalProject,
|
|
14433
|
-
text: message,
|
|
14434
|
-
source: "cli"
|
|
14435
|
-
});
|
|
14436
|
-
const timeoutMs = confirmOpts?.timeoutMs ?? SEND_CONFIRM_TIMEOUT_MS;
|
|
14437
|
-
const delivered = await waitForCaptainDelivery2({
|
|
14438
|
-
stateRoot,
|
|
14439
|
-
project: finalProject,
|
|
14440
|
-
seq,
|
|
14441
|
-
timeoutMs,
|
|
14442
|
-
pollMs: confirmOpts?.pollMs ?? SEND_CONFIRM_POLL_MS
|
|
14443
|
-
});
|
|
14444
|
-
if (!delivered) {
|
|
14445
|
-
throw new Error(
|
|
14446
|
-
`Message queued for '${finalProject}' (seq=${seq}) but delivery was not confirmed within ${Math.round(timeoutMs / 1e3)}s. It may still be pending \u2014 check with 'squadrant runtime read-screen ${finalProject}${opts.command ? " --command" : ""}'.`
|
|
14447
|
-
);
|
|
14448
|
-
}
|
|
14449
|
-
}
|
|
14450
|
-
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) => {
|
|
14451
|
-
try {
|
|
14452
|
-
await runRuntimeSend(arg1, arg2, opts);
|
|
14453
|
-
console.log(chalk19.green("\u2714 Delivered (confirmed)"));
|
|
14454
|
-
} catch (err) {
|
|
14455
|
-
console.error(chalk19.red(err.message));
|
|
14456
|
-
process.exit(1);
|
|
14457
|
-
}
|
|
14458
|
-
});
|
|
14459
|
-
runtimeCommand.command("list").description("List all workspaces from the global runtime").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
14460
|
-
const config = loadConfig();
|
|
14461
|
-
const registry = buildRegistry();
|
|
14462
|
-
const driver = registry.global(config);
|
|
14463
|
-
const refs = await driver.list();
|
|
14464
|
-
if (opts.json) {
|
|
14465
|
-
console.log(JSON.stringify(refs, null, 2));
|
|
14466
|
-
} else {
|
|
14467
|
-
for (const r of refs) {
|
|
14468
|
-
console.log(`${r.id} ${r.name} ${r.status}`);
|
|
14469
|
-
}
|
|
14470
|
-
}
|
|
14471
|
-
});
|
|
14472
|
-
runtimeCommand.command("read-screen").description("Print a terminal snapshot of a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
|
|
14473
|
-
const config = loadConfig();
|
|
14474
|
-
const registry = buildRegistry();
|
|
14475
|
-
try {
|
|
14476
|
-
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
14477
|
-
const ref = await needRef(resolved);
|
|
14478
|
-
const screen = await resolved.driver.readScreen(ref);
|
|
14479
|
-
process.stdout.write(screen);
|
|
14480
|
-
} catch (err) {
|
|
14481
|
-
console.error(chalk19.red(err.message));
|
|
14482
|
-
process.exit(1);
|
|
14483
|
-
}
|
|
14484
|
-
});
|
|
14485
|
-
runtimeCommand.command("stop").description("Stop a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
|
|
14486
|
-
const config = loadConfig();
|
|
14487
|
-
const registry = buildRegistry();
|
|
14488
|
-
try {
|
|
14489
|
-
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
14490
|
-
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
14491
|
-
if (!ref) {
|
|
14492
|
-
console.log(chalk19.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
|
|
14493
|
-
return;
|
|
14494
|
-
}
|
|
14495
|
-
await resolved.driver.stop(ref.id);
|
|
14496
|
-
console.log(chalk19.green(`\u2714 Stopped ${resolved.workspaceName}`));
|
|
14497
|
-
} catch (err) {
|
|
14498
|
-
console.error(chalk19.red(err.message));
|
|
14499
|
-
process.exit(1);
|
|
14500
|
-
}
|
|
14501
|
-
});
|
|
14760
|
+
// packages/cli/src/index.ts
|
|
14761
|
+
init_runtime2();
|
|
14502
14762
|
|
|
14503
14763
|
// packages/cli/src/commands/workspace.ts
|
|
14504
14764
|
init_dist();
|
|
@@ -15149,10 +15409,11 @@ var groupCommand = new Command26("group").description("Cross-project intra-group
|
|
|
15149
15409
|
// packages/cli/src/commands/ping.ts
|
|
15150
15410
|
init_dist();
|
|
15151
15411
|
init_dist2();
|
|
15412
|
+
init_runtime2();
|
|
15413
|
+
init_require_daemon();
|
|
15152
15414
|
import { join as join27, dirname as dirname7 } from "path";
|
|
15153
15415
|
import { Command as Command27 } from "commander";
|
|
15154
15416
|
import chalk27 from "chalk";
|
|
15155
|
-
init_require_daemon();
|
|
15156
15417
|
async function runPing(project, message) {
|
|
15157
15418
|
const config = loadConfig();
|
|
15158
15419
|
const registry = buildRegistry();
|
|
@@ -16395,7 +16656,7 @@ function gatherOpenPRs(runner, projectPath) {
|
|
|
16395
16656
|
}
|
|
16396
16657
|
}
|
|
16397
16658
|
function gatherLiveCrews(tasks) {
|
|
16398
|
-
return tasks.filter((t) => !TERMINAL_STATES.has(t.state)).map((t) => ({ name: t.name ?? t.id, state: t.state, task: t.task, question: t.question }));
|
|
16659
|
+
return tasks.filter((t) => !TERMINAL_STATES.has(t.state)).map((t) => ({ name: t.name ?? t.id, state: t.state, task: t.task, question: t.question, operatorHold: t.operatorHold }));
|
|
16399
16660
|
}
|
|
16400
16661
|
function ghAheadOfBase(runner, projectPath, nameWithOwner, base, branch) {
|
|
16401
16662
|
return tryInt(
|
|
@@ -16416,7 +16677,7 @@ function localAheadOfBase(runner, projectPath, base) {
|
|
|
16416
16677
|
function readFetchAgeMs(projectPath, now) {
|
|
16417
16678
|
try {
|
|
16418
16679
|
const stat2 = fs29.statSync(path31.join(projectPath, ".git", "FETCH_HEAD"));
|
|
16419
|
-
return now - stat2.mtime.getTime();
|
|
16680
|
+
return Math.max(0, now - stat2.mtime.getTime());
|
|
16420
16681
|
} catch {
|
|
16421
16682
|
return null;
|
|
16422
16683
|
}
|
|
@@ -16429,6 +16690,7 @@ function gatherLiveRepoState(projectPath, fallbackBaseBranch, tasks, runner = de
|
|
|
16429
16690
|
const ghInfo = gatherGhRepoInfo(runner, projectPath);
|
|
16430
16691
|
const baseBranch = ghInfo?.defaultBranch ?? fallbackBaseBranch;
|
|
16431
16692
|
const baseBranchSource = ghInfo ? "gh-api" : "local-fallback";
|
|
16693
|
+
const branchState = gatherBranchState(runner, projectPath, branch, baseBranch, detached, fetch2);
|
|
16432
16694
|
const fetchAgeMs = readFetchAgeMs(projectPath, now);
|
|
16433
16695
|
let aheadOfBase = 0;
|
|
16434
16696
|
let aheadOfBaseSource = "unknown";
|
|
@@ -16478,7 +16740,7 @@ function gatherLiveRepoState(projectPath, fallbackBaseBranch, tasks, runner = de
|
|
|
16478
16740
|
openPRs: gatherOpenPRs(runner, projectPath),
|
|
16479
16741
|
liveCrews: gatherLiveCrews(tasks),
|
|
16480
16742
|
conflicts,
|
|
16481
|
-
branchState
|
|
16743
|
+
branchState,
|
|
16482
16744
|
unreleasedAheadOfReleaseBranch
|
|
16483
16745
|
};
|
|
16484
16746
|
}
|