gsd-pi 2.36.0 → 2.37.0-dev.68605cd
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/resources/extensions/cmux/index.js +321 -0
- package/dist/resources/extensions/cmux/package.json +7 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +334 -104
- package/dist/resources/extensions/gsd/auto-loop.js +29 -4
- package/dist/resources/extensions/gsd/auto.js +58 -5
- package/dist/resources/extensions/gsd/commands-cmux.js +120 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +131 -34
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/dist/resources/extensions/gsd/git-service.js +9 -1
- package/dist/resources/extensions/gsd/history.js +2 -1
- package/dist/resources/extensions/gsd/index.js +5 -0
- package/dist/resources/extensions/gsd/metrics.js +4 -2
- package/dist/resources/extensions/gsd/notifications.js +10 -1
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +29 -0
- package/dist/resources/extensions/gsd/preferences.js +3 -0
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/dist/resources/extensions/gsd/session-lock.js +26 -6
- package/dist/resources/extensions/gsd/templates/preferences.md +6 -0
- package/dist/resources/extensions/search-the-web/native-search.js +45 -4
- package/dist/resources/extensions/shared/format-utils.js +5 -41
- package/dist/resources/extensions/shared/layout-utils.js +46 -0
- package/dist/resources/extensions/shared/mod.js +2 -1
- package/dist/resources/extensions/shared/terminal.js +5 -0
- package/dist/resources/extensions/subagent/index.js +180 -60
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -4
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +8 -4
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +4 -0
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/src/terminal-image.ts +5 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +384 -0
- package/src/resources/extensions/cmux/package.json +7 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +363 -116
- package/src/resources/extensions/gsd/auto-loop.ts +66 -6
- package/src/resources/extensions/gsd/auto.ts +77 -5
- package/src/resources/extensions/gsd/commands-cmux.ts +143 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +139 -32
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/src/resources/extensions/gsd/git-service.ts +12 -1
- package/src/resources/extensions/gsd/history.ts +2 -1
- package/src/resources/extensions/gsd/index.ts +8 -0
- package/src/resources/extensions/gsd/metrics.ts +4 -2
- package/src/resources/extensions/gsd/notifications.ts +10 -1
- package/src/resources/extensions/gsd/preferences-types.ts +13 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +26 -0
- package/src/resources/extensions/gsd/preferences.ts +4 -0
- package/src/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/src/resources/extensions/gsd/session-lock.ts +41 -6
- package/src/resources/extensions/gsd/templates/preferences.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +39 -1
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/cmux.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +45 -0
- package/src/resources/extensions/search-the-web/native-search.ts +50 -4
- package/src/resources/extensions/shared/format-utils.ts +5 -44
- package/src/resources/extensions/shared/layout-utils.ts +49 -0
- package/src/resources/extensions/shared/mod.ts +7 -4
- package/src/resources/extensions/shared/terminal.ts +5 -0
- package/src/resources/extensions/shared/tests/format-utils.test.ts +5 -3
- package/src/resources/extensions/subagent/index.ts +236 -79
|
@@ -218,10 +218,24 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
218
218
|
}
|
|
219
219
|
try {
|
|
220
220
|
// ── Blanket try/catch: one bad iteration must not kill the session
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
221
|
+
const sessionLockBase = deps.lockBase();
|
|
222
|
+
if (sessionLockBase) {
|
|
223
|
+
const lockStatus = deps.validateSessionLock(sessionLockBase);
|
|
224
|
+
if (!lockStatus.valid) {
|
|
225
|
+
debugLog("autoLoop", {
|
|
226
|
+
phase: "session-lock-invalid",
|
|
227
|
+
reason: lockStatus.failureReason ?? "unknown",
|
|
228
|
+
existingPid: lockStatus.existingPid,
|
|
229
|
+
expectedPid: lockStatus.expectedPid,
|
|
230
|
+
});
|
|
231
|
+
deps.handleLostSessionLock(ctx, lockStatus);
|
|
232
|
+
debugLog("autoLoop", {
|
|
233
|
+
phase: "exit",
|
|
234
|
+
reason: "session-lock-lost",
|
|
235
|
+
detail: lockStatus.failureReason ?? "unknown",
|
|
236
|
+
});
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
225
239
|
}
|
|
226
240
|
// ── Phase 1: Pre-dispatch ───────────────────────────────────────────
|
|
227
241
|
// Resource version guard
|
|
@@ -258,6 +272,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
258
272
|
}
|
|
259
273
|
// Derive state
|
|
260
274
|
let state = await deps.deriveState(s.basePath);
|
|
275
|
+
deps.syncCmuxSidebar(deps.loadEffectiveGSDPreferences()?.preferences, state);
|
|
261
276
|
let mid = state.activeMilestone?.id;
|
|
262
277
|
let midTitle = state.activeMilestone?.title;
|
|
263
278
|
debugLog("autoLoop", {
|
|
@@ -270,6 +285,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
270
285
|
if (mid && s.currentMilestoneId && mid !== s.currentMilestoneId) {
|
|
271
286
|
ctx.ui.notify(`Milestone ${s.currentMilestoneId} complete. Advancing to ${mid}: ${midTitle}.`, "info");
|
|
272
287
|
deps.sendDesktopNotification("GSD", `Milestone ${s.currentMilestoneId} complete!`, "success", "milestone");
|
|
288
|
+
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, `Milestone ${s.currentMilestoneId} complete. Advancing to ${mid}.`, "success");
|
|
273
289
|
const vizPrefs = deps.loadEffectiveGSDPreferences()?.preferences;
|
|
274
290
|
if (vizPrefs?.auto_visualize) {
|
|
275
291
|
ctx.ui.notify("Run /gsd visualize to see progress overview.", "info");
|
|
@@ -363,6 +379,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
363
379
|
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
364
380
|
}
|
|
365
381
|
deps.sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone");
|
|
382
|
+
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, "All milestones complete.", "success");
|
|
366
383
|
await deps.stopAuto(ctx, pi, "All milestones complete");
|
|
367
384
|
}
|
|
368
385
|
else if (state.phase === "blocked") {
|
|
@@ -370,6 +387,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
370
387
|
await deps.stopAuto(ctx, pi, blockerMsg);
|
|
371
388
|
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
|
|
372
389
|
deps.sendDesktopNotification("GSD", blockerMsg, "error", "attention");
|
|
390
|
+
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, blockerMsg, "error");
|
|
373
391
|
}
|
|
374
392
|
else {
|
|
375
393
|
const ids = incomplete.map((m) => m.id).join(", ");
|
|
@@ -415,6 +433,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
415
433
|
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
416
434
|
}
|
|
417
435
|
deps.sendDesktopNotification("GSD", `Milestone ${mid} complete!`, "success", "milestone");
|
|
436
|
+
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, `Milestone ${mid} complete.`, "success");
|
|
418
437
|
await deps.stopAuto(ctx, pi, `Milestone ${mid} complete`);
|
|
419
438
|
debugLog("autoLoop", { phase: "exit", reason: "milestone-complete" });
|
|
420
439
|
break;
|
|
@@ -428,6 +447,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
428
447
|
await deps.stopAuto(ctx, pi, blockerMsg);
|
|
429
448
|
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
|
|
430
449
|
deps.sendDesktopNotification("GSD", blockerMsg, "error", "attention");
|
|
450
|
+
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, blockerMsg, "error");
|
|
431
451
|
debugLog("autoLoop", { phase: "exit", reason: "blocked" });
|
|
432
452
|
break;
|
|
433
453
|
}
|
|
@@ -458,30 +478,35 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
458
478
|
if (budgetEnforcementAction === "pause") {
|
|
459
479
|
ctx.ui.notify(`${msg} Pausing auto-mode — /gsd auto to override and continue.`, "warning");
|
|
460
480
|
deps.sendDesktopNotification("GSD", msg, "warning", "budget");
|
|
481
|
+
deps.logCmuxEvent(prefs, msg, "warning");
|
|
461
482
|
await deps.pauseAuto(ctx, pi);
|
|
462
483
|
debugLog("autoLoop", { phase: "exit", reason: "budget-pause" });
|
|
463
484
|
break;
|
|
464
485
|
}
|
|
465
486
|
ctx.ui.notify(`${msg} Continuing (enforcement: warn).`, "warning");
|
|
466
487
|
deps.sendDesktopNotification("GSD", msg, "warning", "budget");
|
|
488
|
+
deps.logCmuxEvent(prefs, msg, "warning");
|
|
467
489
|
}
|
|
468
490
|
else if (newBudgetAlertLevel === 90) {
|
|
469
491
|
s.lastBudgetAlertLevel =
|
|
470
492
|
newBudgetAlertLevel;
|
|
471
493
|
ctx.ui.notify(`Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
472
494
|
deps.sendDesktopNotification("GSD", `Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning", "budget");
|
|
495
|
+
deps.logCmuxEvent(prefs, `Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
473
496
|
}
|
|
474
497
|
else if (newBudgetAlertLevel === 80) {
|
|
475
498
|
s.lastBudgetAlertLevel =
|
|
476
499
|
newBudgetAlertLevel;
|
|
477
500
|
ctx.ui.notify(`Approaching budget ceiling — 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
478
501
|
deps.sendDesktopNotification("GSD", `Approaching budget ceiling — 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning", "budget");
|
|
502
|
+
deps.logCmuxEvent(prefs, `Budget 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
479
503
|
}
|
|
480
504
|
else if (newBudgetAlertLevel === 75) {
|
|
481
505
|
s.lastBudgetAlertLevel =
|
|
482
506
|
newBudgetAlertLevel;
|
|
483
507
|
ctx.ui.notify(`Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "info");
|
|
484
508
|
deps.sendDesktopNotification("GSD", `Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "info", "budget");
|
|
509
|
+
deps.logCmuxEvent(prefs, `Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "progress");
|
|
485
510
|
}
|
|
486
511
|
else if (budgetAlertLevel === 0) {
|
|
487
512
|
s.lastBudgetAlertLevel = 0;
|
|
@@ -18,7 +18,7 @@ import { invalidateAllCaches } from "./cache.js";
|
|
|
18
18
|
import { clearActivityLogState } from "./activity-log.js";
|
|
19
19
|
import { synthesizeCrashRecovery, getDeepDiagnostic, } from "./session-forensics.js";
|
|
20
20
|
import { writeLock, clearLock, readCrashLock, isLockProcessAlive, } from "./crash-recovery.js";
|
|
21
|
-
import { acquireSessionLock,
|
|
21
|
+
import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
|
|
22
22
|
import { clearUnitRuntimeRecord, readUnitRuntimeRecord, writeUnitRuntimeRecord, } from "./unit-runtime.js";
|
|
23
23
|
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, getIsolationMode, } from "./preferences.js";
|
|
24
24
|
import { sendDesktopNotification } from "./notifications.js";
|
|
@@ -50,6 +50,7 @@ import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache
|
|
|
50
50
|
import { registerSigtermHandler as _registerSigtermHandler, deregisterSigtermHandler as _deregisterSigtermHandler, } from "./auto-supervisor.js";
|
|
51
51
|
import { isDbAvailable } from "./gsd-db.js";
|
|
52
52
|
import { countPendingCaptures } from "./captures.js";
|
|
53
|
+
import { clearCmuxSidebar, logCmuxEvent, syncCmuxSidebar } from "../cmux/index.js";
|
|
53
54
|
// ── Extracted modules ──────────────────────────────────────────────────────
|
|
54
55
|
import { startUnitSupervision } from "./auto-timers.js";
|
|
55
56
|
import { runPostUnitVerification } from "./auto-verification.js";
|
|
@@ -208,6 +209,29 @@ export function stopAutoRemote(projectRoot) {
|
|
|
208
209
|
return { found: false, error: err.message };
|
|
209
210
|
}
|
|
210
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Check if a remote auto-mode session is running (from a different process).
|
|
214
|
+
* Reads the crash lock, checks PID liveness, and returns session details.
|
|
215
|
+
* Used by the guard in commands.ts to prevent bare /gsd, /gsd next, and
|
|
216
|
+
* /gsd auto from stealing the session lock.
|
|
217
|
+
*/
|
|
218
|
+
export function checkRemoteAutoSession(projectRoot) {
|
|
219
|
+
const lock = readCrashLock(projectRoot);
|
|
220
|
+
if (!lock)
|
|
221
|
+
return { running: false };
|
|
222
|
+
if (!isLockProcessAlive(lock)) {
|
|
223
|
+
// Stale lock from a dead process — not a live remote session
|
|
224
|
+
return { running: false };
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
running: true,
|
|
228
|
+
pid: lock.pid,
|
|
229
|
+
unitType: lock.unitType,
|
|
230
|
+
unitId: lock.unitId,
|
|
231
|
+
startedAt: lock.startedAt,
|
|
232
|
+
completedUnits: lock.completedUnits,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
211
235
|
export function isStepMode() {
|
|
212
236
|
return s.stepMode;
|
|
213
237
|
}
|
|
@@ -242,13 +266,28 @@ function buildSnapshotOpts(unitType, unitId) {
|
|
|
242
266
|
...(runtime?.continueHereFired ? { continueHereFired: true } : {}),
|
|
243
267
|
};
|
|
244
268
|
}
|
|
245
|
-
function handleLostSessionLock(ctx) {
|
|
246
|
-
debugLog("session-lock-lost", {
|
|
269
|
+
function handleLostSessionLock(ctx, lockStatus) {
|
|
270
|
+
debugLog("session-lock-lost", {
|
|
271
|
+
lockBase: lockBase(),
|
|
272
|
+
reason: lockStatus?.failureReason,
|
|
273
|
+
existingPid: lockStatus?.existingPid,
|
|
274
|
+
expectedPid: lockStatus?.expectedPid,
|
|
275
|
+
});
|
|
247
276
|
s.active = false;
|
|
248
277
|
s.paused = false;
|
|
249
278
|
clearUnitTimeout();
|
|
250
279
|
deregisterSigtermHandler();
|
|
251
|
-
|
|
280
|
+
clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
|
|
281
|
+
const message = lockStatus?.failureReason === "pid-mismatch"
|
|
282
|
+
? lockStatus.existingPid
|
|
283
|
+
? `Session lock moved to PID ${lockStatus.existingPid} — another GSD process appears to have taken over. Stopping gracefully.`
|
|
284
|
+
: "Session lock moved to a different process — another GSD process appears to have taken over. Stopping gracefully."
|
|
285
|
+
: lockStatus?.failureReason === "missing-metadata"
|
|
286
|
+
? "Session lock metadata disappeared, so ownership could not be confirmed. Stopping gracefully."
|
|
287
|
+
: lockStatus?.failureReason === "compromised"
|
|
288
|
+
? "Session lock was compromised or invalidated during heartbeat checks; takeover was not confirmed. Stopping gracefully."
|
|
289
|
+
: "Session lock lost. Stopping gracefully.";
|
|
290
|
+
ctx?.ui.notify(message, "error");
|
|
252
291
|
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
253
292
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
254
293
|
ctx?.ui.setFooter(undefined);
|
|
@@ -256,6 +295,7 @@ function handleLostSessionLock(ctx) {
|
|
|
256
295
|
export async function stopAuto(ctx, pi, reason) {
|
|
257
296
|
if (!s.active && !s.paused)
|
|
258
297
|
return;
|
|
298
|
+
const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
|
|
259
299
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
260
300
|
clearUnitTimeout();
|
|
261
301
|
if (lockBase())
|
|
@@ -314,6 +354,8 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
314
354
|
});
|
|
315
355
|
}
|
|
316
356
|
}
|
|
357
|
+
clearCmuxSidebar(loadedPreferences);
|
|
358
|
+
logCmuxEvent(loadedPreferences, `Auto-mode stopped${reasonSuffix || ""}.`, reason?.startsWith("Blocked:") ? "warning" : "info");
|
|
317
359
|
if (isDebugEnabled()) {
|
|
318
360
|
const logPath = writeDebugSummary();
|
|
319
361
|
if (logPath) {
|
|
@@ -455,6 +497,8 @@ function buildLoopDeps() {
|
|
|
455
497
|
pauseAuto,
|
|
456
498
|
clearUnitTimeout,
|
|
457
499
|
updateProgressWidget,
|
|
500
|
+
syncCmuxSidebar,
|
|
501
|
+
logCmuxEvent,
|
|
458
502
|
// State and cache
|
|
459
503
|
invalidateAllCaches,
|
|
460
504
|
deriveState,
|
|
@@ -466,7 +510,7 @@ function buildLoopDeps() {
|
|
|
466
510
|
// Resource version guard
|
|
467
511
|
checkResourcesStale,
|
|
468
512
|
// Session lock
|
|
469
|
-
validateSessionLock,
|
|
513
|
+
validateSessionLock: getSessionLockStatus,
|
|
470
514
|
updateSessionLock,
|
|
471
515
|
handleLostSessionLock,
|
|
472
516
|
// Milestone transition
|
|
@@ -605,6 +649,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
605
649
|
restoreHookState(s.basePath);
|
|
606
650
|
try {
|
|
607
651
|
await rebuildState(s.basePath);
|
|
652
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
608
653
|
}
|
|
609
654
|
catch (e) {
|
|
610
655
|
debugLog("resume-rebuild-state-failed", {
|
|
@@ -634,6 +679,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
634
679
|
}
|
|
635
680
|
updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
|
|
636
681
|
writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
|
|
682
|
+
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
|
|
637
683
|
await autoLoop(ctx, pi, s, buildLoopDeps());
|
|
638
684
|
return;
|
|
639
685
|
}
|
|
@@ -647,6 +693,13 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
647
693
|
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps);
|
|
648
694
|
if (!ready)
|
|
649
695
|
return;
|
|
696
|
+
try {
|
|
697
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
698
|
+
}
|
|
699
|
+
catch {
|
|
700
|
+
// Best-effort only — sidebar sync must never block auto-mode startup
|
|
701
|
+
}
|
|
702
|
+
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
|
|
650
703
|
// Dispatch the first unit
|
|
651
704
|
await autoLoop(ctx, pi, s, buildLoopDeps());
|
|
652
705
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { clearCmuxSidebar, CmuxClient, detectCmuxEnvironment, resolveCmuxConfig } from "../cmux/index.js";
|
|
3
|
+
import { saveFile } from "./files.js";
|
|
4
|
+
import { getProjectGSDPreferencesPath, loadEffectiveGSDPreferences, loadProjectGSDPreferences, } from "./preferences.js";
|
|
5
|
+
import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
|
|
6
|
+
function extractBodyAfterFrontmatter(content) {
|
|
7
|
+
const start = content.startsWith("---\n") ? 4 : content.startsWith("---\r\n") ? 5 : -1;
|
|
8
|
+
if (start === -1)
|
|
9
|
+
return null;
|
|
10
|
+
const closingIdx = content.indexOf("\n---", start);
|
|
11
|
+
if (closingIdx === -1)
|
|
12
|
+
return null;
|
|
13
|
+
const after = content.slice(closingIdx + 4);
|
|
14
|
+
return after.trim() ? after : null;
|
|
15
|
+
}
|
|
16
|
+
async function writeProjectCmuxPreferences(ctx, updater) {
|
|
17
|
+
const path = getProjectGSDPreferencesPath();
|
|
18
|
+
await ensurePreferencesFile(path, ctx, "project");
|
|
19
|
+
const existing = loadProjectGSDPreferences();
|
|
20
|
+
const prefs = existing?.preferences ? { ...existing.preferences } : { version: 1 };
|
|
21
|
+
updater(prefs);
|
|
22
|
+
prefs.version = prefs.version || 1;
|
|
23
|
+
const frontmatter = serializePreferencesToFrontmatter(prefs);
|
|
24
|
+
let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
|
|
25
|
+
if (existsSync(path)) {
|
|
26
|
+
const preserved = extractBodyAfterFrontmatter(readFileSync(path, "utf-8"));
|
|
27
|
+
if (preserved)
|
|
28
|
+
body = preserved;
|
|
29
|
+
}
|
|
30
|
+
await saveFile(path, `---\n${frontmatter}---${body}`);
|
|
31
|
+
await ctx.waitForIdle();
|
|
32
|
+
await ctx.reload();
|
|
33
|
+
}
|
|
34
|
+
function formatCmuxStatus() {
|
|
35
|
+
const loaded = loadEffectiveGSDPreferences();
|
|
36
|
+
const detected = detectCmuxEnvironment();
|
|
37
|
+
const resolved = resolveCmuxConfig(loaded?.preferences);
|
|
38
|
+
const capabilities = new CmuxClient(resolved).getCapabilities();
|
|
39
|
+
const accessMode = typeof capabilities?.mode === "string"
|
|
40
|
+
? capabilities.mode
|
|
41
|
+
: typeof capabilities?.access_mode === "string"
|
|
42
|
+
? capabilities.access_mode
|
|
43
|
+
: "unknown";
|
|
44
|
+
const methods = Array.isArray(capabilities?.methods) ? capabilities.methods.length : 0;
|
|
45
|
+
return [
|
|
46
|
+
"cmux status",
|
|
47
|
+
"",
|
|
48
|
+
`Detected: ${detected.available ? "yes" : "no"}`,
|
|
49
|
+
`Enabled: ${resolved.enabled ? "yes" : "no"}`,
|
|
50
|
+
`CLI available: ${detected.cliAvailable ? "yes" : "no"}`,
|
|
51
|
+
`Socket: ${detected.socketPath}`,
|
|
52
|
+
`Workspace: ${detected.workspaceId ?? "(none)"}`,
|
|
53
|
+
`Surface: ${detected.surfaceId ?? "(none)"}`,
|
|
54
|
+
`Features: notifications=${resolved.notifications ? "on" : "off"}, sidebar=${resolved.sidebar ? "on" : "off"}, splits=${resolved.splits ? "on" : "off"}, browser=${resolved.browser ? "on" : "off"}`,
|
|
55
|
+
`Capabilities: access=${accessMode}, methods=${methods}`,
|
|
56
|
+
].join("\n");
|
|
57
|
+
}
|
|
58
|
+
function ensureCmuxAvailableForEnable(ctx) {
|
|
59
|
+
const detected = detectCmuxEnvironment();
|
|
60
|
+
if (detected.available)
|
|
61
|
+
return true;
|
|
62
|
+
ctx.ui.notify("cmux not detected. Install it from https://cmux.com and run gsd inside a cmux terminal.", "warning");
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
export async function handleCmux(args, ctx) {
|
|
66
|
+
const trimmed = args.trim();
|
|
67
|
+
if (!trimmed || trimmed === "status") {
|
|
68
|
+
ctx.ui.notify(formatCmuxStatus(), "info");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (trimmed === "on") {
|
|
72
|
+
if (!ensureCmuxAvailableForEnable(ctx))
|
|
73
|
+
return;
|
|
74
|
+
await writeProjectCmuxPreferences(ctx, (prefs) => {
|
|
75
|
+
prefs.cmux = {
|
|
76
|
+
enabled: true,
|
|
77
|
+
notifications: true,
|
|
78
|
+
sidebar: true,
|
|
79
|
+
splits: false,
|
|
80
|
+
browser: false,
|
|
81
|
+
...(prefs.cmux ?? {}),
|
|
82
|
+
};
|
|
83
|
+
prefs.cmux.enabled = true;
|
|
84
|
+
});
|
|
85
|
+
ctx.ui.notify("cmux integration enabled in project preferences.", "info");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (trimmed === "off") {
|
|
89
|
+
const effective = loadEffectiveGSDPreferences()?.preferences;
|
|
90
|
+
await writeProjectCmuxPreferences(ctx, (prefs) => {
|
|
91
|
+
prefs.cmux = { ...(prefs.cmux ?? {}), enabled: false };
|
|
92
|
+
});
|
|
93
|
+
clearCmuxSidebar(effective);
|
|
94
|
+
ctx.ui.notify("cmux integration disabled in project preferences.", "info");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const parts = trimmed.split(/\s+/);
|
|
98
|
+
if (parts.length === 2 && ["notifications", "sidebar", "splits", "browser"].includes(parts[0]) && ["on", "off"].includes(parts[1])) {
|
|
99
|
+
const feature = parts[0];
|
|
100
|
+
const enabled = parts[1] === "on";
|
|
101
|
+
if (enabled && !ensureCmuxAvailableForEnable(ctx))
|
|
102
|
+
return;
|
|
103
|
+
await writeProjectCmuxPreferences(ctx, (prefs) => {
|
|
104
|
+
const next = { ...(prefs.cmux ?? {}) };
|
|
105
|
+
next[feature] = enabled;
|
|
106
|
+
if (enabled)
|
|
107
|
+
next.enabled = true;
|
|
108
|
+
prefs.cmux = next;
|
|
109
|
+
});
|
|
110
|
+
if (!enabled && feature === "sidebar") {
|
|
111
|
+
clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
|
|
112
|
+
}
|
|
113
|
+
const note = feature === "browser" && enabled
|
|
114
|
+
? " Browser surfaces are still a follow-up path."
|
|
115
|
+
: "";
|
|
116
|
+
ctx.ui.notify(`cmux ${feature} ${enabled ? "enabled" : "disabled"}.${note}`, "info");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
ctx.ui.notify("Usage: /gsd cmux <status|on|off|notifications on|notifications off|sidebar on|sidebar off|splits on|splits off|browser on|browser off>", "info");
|
|
120
|
+
}
|
|
@@ -626,7 +626,7 @@ export function serializePreferencesToFrontmatter(prefs) {
|
|
|
626
626
|
"skill_rules", "custom_instructions", "models", "skill_discovery",
|
|
627
627
|
"skill_staleness_days", "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
|
|
628
628
|
"budget_ceiling", "budget_enforcement", "context_pause_threshold",
|
|
629
|
-
"notifications", "remote_questions", "git",
|
|
629
|
+
"notifications", "cmux", "remote_questions", "git",
|
|
630
630
|
"post_unit_hooks", "pre_dispatch_hooks",
|
|
631
631
|
"dynamic_routing", "token_profile", "phases", "parallel",
|
|
632
632
|
"auto_visualize", "auto_report",
|
|
@@ -12,7 +12,7 @@ import { deriveState } from "./state.js";
|
|
|
12
12
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
13
13
|
import { GSDVisualizerOverlay } from "./visualizer-overlay.js";
|
|
14
14
|
import { showQueue, showDiscuss, showHeadlessMilestoneCreation } from "./guided-flow.js";
|
|
15
|
-
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, stopAutoRemote } from "./auto.js";
|
|
15
|
+
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, stopAutoRemote, checkRemoteAutoSession } from "./auto.js";
|
|
16
16
|
import { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
17
17
|
import { resolveProjectRoot } from "./worktree.js";
|
|
18
18
|
import { assertSafeDirectory } from "./validate-directory.js";
|
|
@@ -36,7 +36,8 @@ import { computeProgressScore, formatProgressLine } from "./progress-score.js";
|
|
|
36
36
|
import { runEnvironmentChecks } from "./doctor-environment.js";
|
|
37
37
|
import { handleLogs } from "./commands-logs.js";
|
|
38
38
|
import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
|
|
39
|
-
import {
|
|
39
|
+
import { handleCmux } from "./commands-cmux.js";
|
|
40
|
+
import { showNextAction } from "../shared/mod.js";
|
|
40
41
|
/** Resolve the effective project root, accounting for worktree paths. */
|
|
41
42
|
export function projectRoot() {
|
|
42
43
|
const cwd = process.cwd();
|
|
@@ -56,40 +57,85 @@ export function projectRoot() {
|
|
|
56
57
|
return root;
|
|
57
58
|
}
|
|
58
59
|
/**
|
|
59
|
-
*
|
|
60
|
-
* Returns
|
|
60
|
+
* Guard against starting auto-mode when a remote session is already running.
|
|
61
|
+
* Returns true if the caller should proceed with startAuto, false if handled.
|
|
61
62
|
*/
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
63
|
+
async function guardRemoteSession(ctx, pi) {
|
|
64
|
+
// Local session already active — proceed (startAuto handles re-entrant calls)
|
|
65
|
+
if (isAutoActive() || isAutoPaused())
|
|
66
|
+
return true;
|
|
67
|
+
const remote = checkRemoteAutoSession(projectRoot());
|
|
68
|
+
if (!remote.running || !remote.pid)
|
|
69
|
+
return true;
|
|
70
|
+
const unitLabel = remote.unitType && remote.unitId
|
|
71
|
+
? `${remote.unitType} (${remote.unitId})`
|
|
72
|
+
: "unknown unit";
|
|
73
|
+
const unitsMsg = remote.completedUnits != null
|
|
74
|
+
? `${remote.completedUnits} units completed`
|
|
75
|
+
: "";
|
|
76
|
+
const choice = await showNextAction(ctx, {
|
|
77
|
+
title: `Auto-mode is running in another terminal (PID ${remote.pid})`,
|
|
78
|
+
summary: [
|
|
79
|
+
`Currently executing: ${unitLabel}`,
|
|
80
|
+
...(unitsMsg ? [unitsMsg] : []),
|
|
81
|
+
...(remote.startedAt ? [`Started: ${remote.startedAt}`] : []),
|
|
82
|
+
],
|
|
83
|
+
actions: [
|
|
84
|
+
{
|
|
85
|
+
id: "status",
|
|
86
|
+
label: "View status",
|
|
87
|
+
description: "Show the current GSD progress dashboard.",
|
|
88
|
+
recommended: true,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "steer",
|
|
92
|
+
label: "Steer the session",
|
|
93
|
+
description: "Use /gsd steer <instruction> to redirect the running session.",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "stop",
|
|
97
|
+
label: "Stop remote session",
|
|
98
|
+
description: `Send SIGTERM to PID ${remote.pid} to stop it gracefully.`,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "force",
|
|
102
|
+
label: "Force start (steal lock)",
|
|
103
|
+
description: "Start a new session, terminating the existing one.",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
notYetMessage: "Run /gsd when ready.",
|
|
107
|
+
});
|
|
108
|
+
if (choice === "status") {
|
|
109
|
+
await handleStatus(ctx);
|
|
79
110
|
return false;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
111
|
+
}
|
|
112
|
+
if (choice === "steer") {
|
|
113
|
+
ctx.ui.notify("Use /gsd steer <instruction> to redirect the running auto-mode session.\n" +
|
|
114
|
+
"Example: /gsd steer Use Postgres instead of SQLite", "info");
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (choice === "stop") {
|
|
118
|
+
const result = stopAutoRemote(projectRoot());
|
|
119
|
+
if (result.found) {
|
|
120
|
+
ctx.ui.notify(`Sent stop signal to auto-mode session (PID ${result.pid}). It will shut down gracefully.`, "info");
|
|
121
|
+
}
|
|
122
|
+
else if (result.error) {
|
|
123
|
+
ctx.ui.notify(`Failed to stop remote auto-mode: ${result.error}`, "error");
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
ctx.ui.notify("Remote session is no longer running.", "info");
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
if (choice === "force") {
|
|
131
|
+
return true; // Proceed — startAuto will steal the lock
|
|
132
|
+
}
|
|
133
|
+
// "not_yet" or escape
|
|
134
|
+
return false;
|
|
89
135
|
}
|
|
90
136
|
export function registerGSDCommand(pi) {
|
|
91
137
|
pi.registerCommand("gsd", {
|
|
92
|
-
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|update",
|
|
138
|
+
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|update",
|
|
93
139
|
getArgumentCompletions: (prefix) => {
|
|
94
140
|
const subcommands = [
|
|
95
141
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
@@ -98,6 +144,7 @@ export function registerGSDCommand(pi) {
|
|
|
98
144
|
{ cmd: "stop", desc: "Stop auto mode gracefully" },
|
|
99
145
|
{ cmd: "pause", desc: "Pause auto-mode (preserves state, /gsd auto to resume)" },
|
|
100
146
|
{ cmd: "status", desc: "Progress dashboard" },
|
|
147
|
+
{ cmd: "widget", desc: "Cycle widget: full → small → min → off" },
|
|
101
148
|
{ cmd: "visualize", desc: "Open 10-tab workflow visualizer (progress, timeline, deps, metrics, health, agent, changes, knowledge, captures, export)" },
|
|
102
149
|
{ cmd: "queue", desc: "Queue and reorder future milestones" },
|
|
103
150
|
{ cmd: "quick", desc: "Execute a quick task without full planning overhead" },
|
|
@@ -131,6 +178,7 @@ export function registerGSDCommand(pi) {
|
|
|
131
178
|
{ cmd: "knowledge", desc: "Add persistent project knowledge (rule, pattern, or lesson)" },
|
|
132
179
|
{ cmd: "new-milestone", desc: "Create a milestone from a specification document (headless)" },
|
|
133
180
|
{ cmd: "parallel", desc: "Parallel milestone orchestration (start, status, stop, merge)" },
|
|
181
|
+
{ cmd: "cmux", desc: "Manage cmux integration (status, sidebar, notifications, splits)" },
|
|
134
182
|
{ cmd: "park", desc: "Park a milestone — skip without deleting" },
|
|
135
183
|
{ cmd: "unpark", desc: "Reactivate a parked milestone" },
|
|
136
184
|
{ cmd: "update", desc: "Update GSD to the latest version" },
|
|
@@ -182,6 +230,36 @@ export function registerGSDCommand(pi) {
|
|
|
182
230
|
.filter((s) => s.cmd.startsWith(subPrefix))
|
|
183
231
|
.map((s) => ({ value: `parallel ${s.cmd}`, label: s.cmd, description: s.desc }));
|
|
184
232
|
}
|
|
233
|
+
if (parts[0] === "cmux") {
|
|
234
|
+
if (parts.length <= 2) {
|
|
235
|
+
const subPrefix = parts[1] ?? "";
|
|
236
|
+
const subs = [
|
|
237
|
+
{ cmd: "status", desc: "Show cmux detection, prefs, and capabilities" },
|
|
238
|
+
{ cmd: "on", desc: "Enable cmux integration" },
|
|
239
|
+
{ cmd: "off", desc: "Disable cmux integration" },
|
|
240
|
+
{ cmd: "notifications", desc: "Toggle cmux desktop notifications" },
|
|
241
|
+
{ cmd: "sidebar", desc: "Toggle cmux sidebar metadata" },
|
|
242
|
+
{ cmd: "splits", desc: "Toggle cmux visual subagent splits" },
|
|
243
|
+
{ cmd: "browser", desc: "Toggle future browser integration flag" },
|
|
244
|
+
];
|
|
245
|
+
return subs
|
|
246
|
+
.filter((s) => s.cmd.startsWith(subPrefix))
|
|
247
|
+
.map((s) => ({ value: `cmux ${s.cmd}`, label: s.cmd, description: s.desc }));
|
|
248
|
+
}
|
|
249
|
+
if (parts.length <= 3 && ["notifications", "sidebar", "splits", "browser"].includes(parts[1])) {
|
|
250
|
+
const togglePrefix = parts[2] ?? "";
|
|
251
|
+
return [
|
|
252
|
+
{ cmd: "on", desc: "Enable this cmux area" },
|
|
253
|
+
{ cmd: "off", desc: "Disable this cmux area" },
|
|
254
|
+
]
|
|
255
|
+
.filter((item) => item.cmd.startsWith(togglePrefix))
|
|
256
|
+
.map((item) => ({
|
|
257
|
+
value: `cmux ${parts[1]} ${item.cmd}`,
|
|
258
|
+
label: item.cmd,
|
|
259
|
+
description: item.desc,
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
185
263
|
if (parts[0] === "setup" && parts.length <= 2) {
|
|
186
264
|
const subPrefix = parts[1] ?? "";
|
|
187
265
|
const subs = [
|
|
@@ -430,6 +508,18 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
430
508
|
await handleStatus(ctx);
|
|
431
509
|
return;
|
|
432
510
|
}
|
|
511
|
+
if (trimmed === "widget" || trimmed.startsWith("widget ")) {
|
|
512
|
+
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await import("./auto-dashboard.js");
|
|
513
|
+
const arg = trimmed.replace(/^widget\s*/, "").trim();
|
|
514
|
+
if (arg === "full" || arg === "small" || arg === "min" || arg === "off") {
|
|
515
|
+
setWidgetMode(arg);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
cycleWidgetMode();
|
|
519
|
+
}
|
|
520
|
+
ctx.ui.notify(`Widget: ${getWidgetMode()}`, "info");
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
433
523
|
if (trimmed === "visualize") {
|
|
434
524
|
await handleVisualize(ctx);
|
|
435
525
|
return;
|
|
@@ -446,6 +536,10 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
446
536
|
await handlePrefs(trimmed.replace(/^prefs\s*/, "").trim(), ctx);
|
|
447
537
|
return;
|
|
448
538
|
}
|
|
539
|
+
if (trimmed === "cmux" || trimmed.startsWith("cmux ")) {
|
|
540
|
+
await handleCmux(trimmed.replace(/^cmux\s*/, "").trim(), ctx);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
449
543
|
if (trimmed === "init") {
|
|
450
544
|
const { detectProjectState } = await import("./detection.js");
|
|
451
545
|
const { showProjectInit, handleReinit } = await import("./init-wizard.js");
|
|
@@ -493,12 +587,12 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
493
587
|
await handleDryRun(ctx, projectRoot());
|
|
494
588
|
return;
|
|
495
589
|
}
|
|
496
|
-
if (notifyRemoteAutoActive(ctx, projectRoot()))
|
|
497
|
-
return;
|
|
498
590
|
const verboseMode = trimmed.includes("--verbose");
|
|
499
591
|
const debugMode = trimmed.includes("--debug");
|
|
500
592
|
if (debugMode)
|
|
501
593
|
enableDebug(projectRoot());
|
|
594
|
+
if (!(await guardRemoteSession(ctx, pi)))
|
|
595
|
+
return;
|
|
502
596
|
await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
|
|
503
597
|
return;
|
|
504
598
|
}
|
|
@@ -507,6 +601,8 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
507
601
|
const debugMode = trimmed.includes("--debug");
|
|
508
602
|
if (debugMode)
|
|
509
603
|
enableDebug(projectRoot());
|
|
604
|
+
if (!(await guardRemoteSession(ctx, pi)))
|
|
605
|
+
return;
|
|
510
606
|
await startAuto(ctx, pi, projectRoot(), verboseMode);
|
|
511
607
|
return;
|
|
512
608
|
}
|
|
@@ -850,7 +946,7 @@ Examples:
|
|
|
850
946
|
return;
|
|
851
947
|
}
|
|
852
948
|
if (trimmed === "") {
|
|
853
|
-
if (
|
|
949
|
+
if (!(await guardRemoteSession(ctx, pi)))
|
|
854
950
|
return;
|
|
855
951
|
await startAuto(ctx, pi, projectRoot(), false, { step: true });
|
|
856
952
|
return;
|
|
@@ -900,6 +996,7 @@ function showHelp(ctx) {
|
|
|
900
996
|
" /gsd setup Global setup status [llm|search|remote|keys|prefs]",
|
|
901
997
|
" /gsd mode Set workflow mode (solo/team) [global|project]",
|
|
902
998
|
" /gsd prefs Manage preferences [global|project|status|wizard|setup|import-claude]",
|
|
999
|
+
" /gsd cmux Manage cmux integration [status|on|off|notifications|sidebar|splits|browser]",
|
|
903
1000
|
" /gsd config Set API keys for external tools",
|
|
904
1001
|
" /gsd keys API key manager [list|add|remove|test|rotate|doctor]",
|
|
905
1002
|
" /gsd hooks Show post-unit hook configuration",
|