@opengsd/gsd-pi 1.0.2-dev.5961fbf → 1.0.2-dev.5f7864c

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.
Files changed (223) hide show
  1. package/README.md +63 -12
  2. package/dist/onboarding.js +22 -3
  3. package/dist/resource-loader.d.ts +2 -0
  4. package/dist/resource-loader.js +18 -1
  5. package/dist/resources/.managed-resources-content-hash +1 -1
  6. package/dist/resources/extensions/context7/index.js +12 -2
  7. package/dist/resources/extensions/get-secrets-from-user.js +16 -16
  8. package/dist/resources/extensions/google-cli/index.js +30 -0
  9. package/dist/resources/extensions/google-cli/models.js +55 -0
  10. package/dist/resources/extensions/google-cli/package.json +11 -0
  11. package/dist/resources/extensions/google-cli/readiness.js +12 -0
  12. package/dist/resources/extensions/google-cli/stream-adapter.js +191 -0
  13. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  14. package/dist/resources/extensions/gsd/auto-start.js +232 -49
  15. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
  16. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -15
  17. package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
  18. package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
  19. package/dist/resources/extensions/gsd/commands-handlers.js +3 -0
  20. package/dist/resources/extensions/gsd/commands-usage.js +105 -1
  21. package/dist/resources/extensions/gsd/config-overlay.js +20 -14
  22. package/dist/resources/extensions/gsd/context-overlay.js +22 -16
  23. package/dist/resources/extensions/gsd/dashboard-overlay.js +10 -23
  24. package/dist/resources/extensions/gsd/doctor-providers.js +54 -24
  25. package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
  26. package/dist/resources/extensions/gsd/guided-flow.js +1 -1
  27. package/dist/resources/extensions/gsd/key-manager.js +45 -13
  28. package/dist/resources/extensions/gsd/notification-overlay.js +8 -9
  29. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +15 -13
  30. package/dist/resources/extensions/gsd/prompt-loader.js +2 -0
  31. package/dist/resources/extensions/gsd/prompts/discuss.md +4 -2
  32. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  33. package/dist/resources/extensions/gsd/queue-reorder-ui.js +28 -18
  34. package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
  35. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -1
  36. package/dist/resources/extensions/gsd/tui/render-kit.js +51 -0
  37. package/dist/resources/extensions/gsd/vision-ask.js +22 -0
  38. package/dist/resources/extensions/gsd/visualizer-overlay.js +8 -36
  39. package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
  40. package/dist/resources/extensions/search-the-web/native-search.js +57 -8
  41. package/dist/resources/extensions/shared/confirm-ui.js +9 -6
  42. package/dist/resources/extensions/shared/dialog-frame.js +42 -0
  43. package/dist/resources/extensions/shared/interview-ui.js +42 -30
  44. package/dist/resources/extensions/shared/next-action-ui.js +6 -6
  45. package/dist/resources/shared/package-manager-detection.js +36 -0
  46. package/dist/update-check.d.ts +6 -2
  47. package/dist/update-check.js +7 -3
  48. package/dist/web/standalone/.next/BUILD_ID +1 -1
  49. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  50. package/dist/web/standalone/.next/build-manifest.json +2 -2
  51. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  52. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/index.html +1 -1
  70. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  77. package/dist/web/standalone/.next/server/chunks/1834.js +2 -2
  78. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  80. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  81. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  82. package/package.json +1 -1
  83. package/packages/cloud-mcp-gateway/package.json +2 -2
  84. package/packages/contracts/package.json +1 -1
  85. package/packages/daemon/package.json +4 -4
  86. package/packages/gsd-agent-core/package.json +5 -5
  87. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts +12 -0
  88. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts.map +1 -0
  89. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js +45 -0
  90. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js.map +1 -0
  91. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts +3 -2
  92. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  93. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js +11 -11
  94. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js.map +1 -1
  95. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts +3 -3
  96. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  97. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js +13 -11
  98. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js.map +1 -1
  99. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts +3 -3
  100. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  101. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js +12 -10
  102. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js.map +1 -1
  103. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts +1 -0
  104. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts.map +1 -1
  105. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js +1 -0
  106. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js.map +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  108. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js +2 -2
  110. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js.map +1 -1
  111. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts +6 -1
  112. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  113. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js +9 -6
  114. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  115. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  116. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
  117. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  118. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts +3 -0
  119. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
  120. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +144 -2
  121. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
  122. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.d.ts.map +1 -1
  123. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js +2 -14
  124. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js.map +1 -1
  125. package/packages/gsd-agent-modes/package.json +7 -7
  126. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  127. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  128. package/packages/mcp-server/package.json +3 -3
  129. package/packages/native/package.json +1 -1
  130. package/packages/pi-agent-core/dist/agent-loop.js +13 -13
  131. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  132. package/packages/pi-agent-core/package.json +1 -1
  133. package/packages/pi-ai/dist/models.generated.d.ts +57 -17
  134. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  135. package/packages/pi-ai/dist/models.generated.js +64 -28
  136. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  137. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  138. package/packages/pi-ai/dist/providers/anthropic.js +50 -0
  139. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  140. package/packages/pi-ai/dist/types.d.ts +2 -0
  141. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  142. package/packages/pi-ai/dist/types.js.map +1 -1
  143. package/packages/pi-ai/package.json +1 -1
  144. package/packages/pi-coding-agent/package.json +7 -7
  145. package/packages/pi-tui/package.json +1 -1
  146. package/packages/rpc-client/package.json +2 -2
  147. package/pkg/package.json +1 -1
  148. package/scripts/install/detect-existing.js +17 -3
  149. package/scripts/install/npm-global.js +103 -33
  150. package/scripts/install.js +1 -0
  151. package/src/resources/extensions/context7/index.ts +15 -2
  152. package/src/resources/extensions/get-secrets-from-user.ts +17 -16
  153. package/src/resources/extensions/google-cli/index.ts +34 -0
  154. package/src/resources/extensions/google-cli/models.ts +57 -0
  155. package/src/resources/extensions/google-cli/package.json +11 -0
  156. package/src/resources/extensions/google-cli/readiness.ts +15 -0
  157. package/src/resources/extensions/google-cli/stream-adapter.ts +245 -0
  158. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  159. package/src/resources/extensions/gsd/auto-start.ts +307 -56
  160. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
  161. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -15
  162. package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
  163. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
  164. package/src/resources/extensions/gsd/commands-handlers.ts +2 -0
  165. package/src/resources/extensions/gsd/commands-usage.ts +110 -5
  166. package/src/resources/extensions/gsd/config-overlay.ts +19 -16
  167. package/src/resources/extensions/gsd/context-overlay.ts +24 -19
  168. package/src/resources/extensions/gsd/dashboard-overlay.ts +14 -27
  169. package/src/resources/extensions/gsd/doctor-providers.ts +55 -27
  170. package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
  171. package/src/resources/extensions/gsd/guided-flow.ts +1 -1
  172. package/src/resources/extensions/gsd/key-manager.ts +57 -14
  173. package/src/resources/extensions/gsd/notification-overlay.ts +12 -11
  174. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +16 -12
  175. package/src/resources/extensions/gsd/prompt-loader.ts +2 -0
  176. package/src/resources/extensions/gsd/prompts/discuss.md +4 -2
  177. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  178. package/src/resources/extensions/gsd/queue-reorder-ui.ts +29 -20
  179. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
  180. package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
  181. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +31 -0
  182. package/src/resources/extensions/gsd/tests/commands-context.test.ts +5 -3
  183. package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
  184. package/src/resources/extensions/gsd/tests/commands-usage.test.ts +97 -0
  185. package/src/resources/extensions/gsd/tests/context-chart.test.ts +9 -0
  186. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +25 -0
  187. package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +4 -2
  188. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +105 -0
  189. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +6 -0
  190. package/src/resources/extensions/gsd/tests/key-manager.test.ts +23 -4
  191. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +6 -1
  192. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
  193. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +7 -1
  194. package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +46 -0
  195. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +4 -0
  196. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
  197. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
  198. package/src/resources/extensions/gsd/tests/tui-border-assertions.ts +28 -0
  199. package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +14 -0
  200. package/src/resources/extensions/gsd/tests/vision-ask.test.ts +23 -0
  201. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -1
  202. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +60 -0
  203. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +54 -0
  204. package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
  205. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
  206. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
  207. package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
  208. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +56 -4
  209. package/src/resources/extensions/gsd/tui/render-kit.ts +82 -0
  210. package/src/resources/extensions/gsd/vision-ask.ts +28 -0
  211. package/src/resources/extensions/gsd/visualizer-overlay.ts +12 -40
  212. package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
  213. package/src/resources/extensions/search-the-web/native-search.ts +60 -8
  214. package/src/resources/extensions/shared/confirm-ui.ts +8 -12
  215. package/src/resources/extensions/shared/dialog-frame.ts +71 -0
  216. package/src/resources/extensions/shared/interview-ui.ts +43 -42
  217. package/src/resources/extensions/shared/next-action-ui.ts +6 -6
  218. package/src/resources/extensions/shared/tests/confirm-ui.test.ts +57 -0
  219. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +163 -0
  220. package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +55 -0
  221. package/src/resources/shared/package-manager-detection.ts +39 -0
  222. /package/dist/web/standalone/.next/static/{spUYLkQXoHJyxYOMH9VQy → IjxvcC7sl_MHNKXsUZrAy}/_buildManifest.js +0 -0
  223. /package/dist/web/standalone/.next/static/{spUYLkQXoHJyxYOMH9VQy → IjxvcC7sl_MHNKXsUZrAy}/_ssgManifest.js +0 -0
@@ -678,8 +678,9 @@ export function registerDbTools(pi) {
678
678
  promptSnippet: "Complete a GSD task (DB write + summary render + checkbox toggle)",
679
679
  promptGuidelines: [
680
680
  "Use gsd_task_complete (or gsd_complete_task) when a task is finished and needs to be recorded.",
681
- "All string fields are required. verificationEvidence is an array of objects with command, exitCode, verdict, durationMs.",
682
- "The tool validates required fields and returns an error message if any are missing.",
681
+ "Include verification whenever possible. If verification is omitted, the executor derives it from verificationEvidence when possible.",
682
+ "verificationEvidence is an array of objects with command, exitCode, verdict, durationMs.",
683
+ "The tool validates required fields and returns an error message if verification cannot be derived.",
683
684
  "On success, returns the summaryPath where the SUMMARY.md was written.",
684
685
  "Idempotent — calling with the same params twice will upsert (INSERT OR REPLACE) without error.",
685
686
  ],
@@ -690,7 +691,7 @@ export function registerDbTools(pi) {
690
691
  milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
691
692
  oneLiner: Type.String({ description: "One-line summary of what was accomplished" }),
692
693
  narrative: Type.String({ description: "Detailed narrative of what happened during the task" }),
693
- verification: Type.String({ description: "What was verified and how — commands run, tests passed, behavior confirmed" }),
694
+ verification: Type.Optional(Type.String({ description: "What was verified and how — commands run, tests passed, behavior confirmed. If omitted, derived from verificationEvidence when possible." })),
694
695
  // ── Enrichment metadata (optional — defaults to empty) ────────────
695
696
  deviations: Type.Optional(Type.String({ description: "Deviations from the task plan, or 'None.'" })),
696
697
  knownIssues: Type.Optional(Type.String({ description: "Known issues discovered but not fixed, or 'None.'" })),
@@ -418,6 +418,17 @@ function initSessionNotifications(ctx) {
418
418
  installNotifyInterceptor(ctx);
419
419
  initNotificationWidget(ctx);
420
420
  }
421
+ async function prepareWorkflowMcpForHookContext(ctx, basePath) {
422
+ // Skip MCP auto-prep when running inside an auto-worktree. The worktree
423
+ // already has .mcp.json from createAutoWorktree, and re-running the writer
424
+ // post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
425
+ // CLI path resolution), dirtying the tree and breaking the milestone merge.
426
+ const { isInAutoWorktree } = await import("../auto-worktree.js");
427
+ if (isInAutoWorktree(basePath))
428
+ return;
429
+ const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
430
+ prepareWorkflowMcpForProject(ctx, basePath);
431
+ }
421
432
  export function registerHooks(pi, ecosystemHandlers) {
422
433
  // ADR-005 Phase 3b: surface pi-ai ProviderSwitchReport via audit, notification, and counter.
423
434
  // Idempotent — only the first registerHooks call installs.
@@ -438,12 +449,7 @@ export function registerHooks(pi, ecosystemHandlers) {
438
449
  await syncServiceTierStatus(ctx);
439
450
  await applyDisabledModelProviderPolicy(ctx);
440
451
  await applyCompactionThresholdOverride(ctx);
441
- // Skip MCP auto-prep when running inside an auto-worktree (see session_switch below).
442
- const { isInAutoWorktree } = await import("../auto-worktree.js");
443
- if (!isInAutoWorktree(basePath)) {
444
- const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
445
- prepareWorkflowMcpForProject(ctx, basePath);
446
- }
452
+ await prepareWorkflowMcpForHookContext(ctx, basePath);
447
453
  // Apply show_token_cost preference (#1515)
448
454
  try {
449
455
  const { loadEffectiveGSDPreferences } = await import("../preferences.js");
@@ -468,15 +474,7 @@ export function registerHooks(pi, ecosystemHandlers) {
468
474
  await syncServiceTierStatus(ctx);
469
475
  await applyDisabledModelProviderPolicy(ctx);
470
476
  await applyCompactionThresholdOverride(ctx);
471
- // Skip MCP auto-prep when running inside an auto-worktree. The worktree
472
- // already has .mcp.json from createAutoWorktree, and re-running the writer
473
- // post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
474
- // CLI path resolution), dirtying the tree and breaking the milestone merge.
475
- const { isInAutoWorktree } = await import("../auto-worktree.js");
476
- if (!isInAutoWorktree(basePath)) {
477
- const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
478
- prepareWorkflowMcpForProject(ctx, basePath);
479
- }
477
+ await prepareWorkflowMcpForHookContext(ctx, basePath);
480
478
  await loadToolApiKeysForSession();
481
479
  if (!isAutoActive()) {
482
480
  ctx.ui.setWidget("gsd-progress", undefined);
@@ -511,6 +509,10 @@ export function registerHooks(pi, ecosystemHandlers) {
511
509
  }
512
510
  }
513
511
  clearDeferredApprovalGate(beforeAgentBasePath);
512
+ // session_start can fire before the active provider has settled. By
513
+ // before_agent_start, Claude Code CLI sessions should get the same
514
+ // project MCP config that /gsd mcp init would write.
515
+ await prepareWorkflowMcpForHookContext(ctx, beforeAgentBasePath);
514
516
  // GSD's own context injection (existing behavior — unchanged).
515
517
  const { buildBeforeAgentStartResult } = await import("./system-context.js");
516
518
  const gsdResult = await buildBeforeAgentStartResult(event, ctx);
@@ -167,7 +167,13 @@ export function getCloseoutManualResolveBlocker(basePath) {
167
167
  if (conflictProbe.status === "dirty" && conflictProbe.unmerged.length > 0) {
168
168
  return `Unmerged paths remain in ${basePath}: ${conflictProbe.unmerged.slice(0, 5).join(", ")}`;
169
169
  }
170
- const status = runGit(basePath, ["status", "--porcelain"]);
170
+ let status;
171
+ try {
172
+ status = runGit(basePath, ["status", "--porcelain"]);
173
+ }
174
+ catch {
175
+ return `Could not inspect git status in ${basePath}.`;
176
+ }
171
177
  if (status) {
172
178
  return `Working tree still has uncommitted changes in ${basePath}. Commit, stash, or run /gsd closeout retry first.`;
173
179
  }
@@ -197,7 +197,15 @@ export async function handleAutoCommand(trimmed, ctx, pi) {
197
197
  if (trimmed === "") {
198
198
  if (!(await guardRemoteSession(ctx, pi)))
199
199
  return true;
200
- if (await hasUnresolvedCloseoutBlocker(ctx, projectRoot()))
200
+ const basePath = projectRoot();
201
+ const { hasGsdBootstrapArtifacts } = await import("../../detection.js");
202
+ const { gsdRoot } = await import("../../paths.js");
203
+ if (!hasGsdBootstrapArtifacts(gsdRoot(basePath))) {
204
+ const { showSmartEntry } = await import("../../guided-flow.js");
205
+ await showSmartEntry(ctx, pi, basePath, { step: true });
206
+ return true;
207
+ }
208
+ if (await hasUnresolvedCloseoutBlocker(ctx, basePath))
201
209
  return true;
202
210
  const { showGsdHome } = await import("../../gsd-command-home.js");
203
211
  await showGsdHome(ctx, pi, projectRoot());
@@ -17,6 +17,7 @@ import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
17
17
  import { getAutoWorktreePath } from "./auto-worktree.js";
18
18
  import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
19
19
  import { loadPrompt } from "./prompt-loader.js";
20
+ import { isPnpmInstall } from "../../shared/package-manager-detection.js";
20
21
  import { buildDoctorHealIssuePayload, buildDoctorHealSummary, buildWorkflowDispatchContent, } from "./workflow-protocol.js";
21
22
  import { restoreGsdWorkflowTools, scopeGsdWorkflowToolsForDispatch, } from "./bootstrap/register-hooks.js";
22
23
  const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/@opengsd%2fgsd-pi/latest";
@@ -42,6 +43,8 @@ function isBunInstall(argv1 = process.argv[1]) {
42
43
  function resolveInstallCommand(pkg) {
43
44
  if (isBunInstall())
44
45
  return `bun add -g ${pkg}`;
46
+ if (isPnpmInstall())
47
+ return `pnpm add -g ${pkg}`;
45
48
  return `npm install -g ${pkg}`;
46
49
  }
47
50
  async function fetchLatestVersionForCommand() {
@@ -3,8 +3,10 @@
3
3
  *
4
4
  * Shows current LLM context window usage and session token totals.
5
5
  */
6
+ import { Key, matchesKey } from "@gsd/pi-tui";
6
7
  import { formatCost, formatPercent, formatTokenCount } from "./metrics.js";
7
8
  import { loadEffectiveGSDPreferences } from "./preferences.js";
9
+ import { renderDialogFrame, renderKeyHints } from "./tui/render-kit.js";
8
10
  export function scanSessionTokenTotals(entries) {
9
11
  const totals = {
10
12
  input: 0,
@@ -115,6 +117,97 @@ export function formatUsageReport(options) {
115
117
  }
116
118
  return lines.join("\n");
117
119
  }
120
+ async function showUsageDialog(ctx, reportText) {
121
+ return ctx.ui.custom((tui, theme, _kb, done) => {
122
+ let cachedLines;
123
+ let cachedWidth;
124
+ let cachedRows;
125
+ let cachedScrollOffset;
126
+ let scrollOffset = 0;
127
+ let lastMaxScroll = 0;
128
+ let lastVisibleRows = 1;
129
+ function render(width) {
130
+ const terminalRows = process.stdout.rows || 0;
131
+ if (cachedLines &&
132
+ cachedWidth === width &&
133
+ cachedRows === terminalRows &&
134
+ cachedScrollOffset === scrollOffset) {
135
+ return cachedLines;
136
+ }
137
+ const contentWidth = Math.max(1, width - 4);
138
+ const body = reportText.split("\n");
139
+ if (body[0] === "Context Usage")
140
+ body.shift();
141
+ while (body[0] === "")
142
+ body.shift();
143
+ const maxOverlayRows = terminalRows > 0 ? Math.max(5, Math.floor(terminalRows * 0.8)) : 24;
144
+ const frameRows = 4;
145
+ const visibleRows = Math.max(1, maxOverlayRows - frameRows);
146
+ const maxScroll = Math.max(0, body.length - visibleRows);
147
+ scrollOffset = Math.min(Math.max(scrollOffset, 0), maxScroll);
148
+ lastMaxScroll = maxScroll;
149
+ lastVisibleRows = visibleRows;
150
+ const visible = body.slice(scrollOffset, scrollOffset + visibleRows);
151
+ const scrollable = body.length > visibleRows;
152
+ cachedLines = renderDialogFrame(theme, "Context Usage", visible, width, {
153
+ footer: renderKeyHints(theme, scrollable ? ["↑↓ scroll", "any key close"] : ["any key close"], contentWidth),
154
+ scroll: { offset: scrollOffset, visibleRows, totalRows: body.length },
155
+ });
156
+ cachedWidth = width;
157
+ cachedRows = terminalRows;
158
+ cachedScrollOffset = scrollOffset;
159
+ return cachedLines;
160
+ }
161
+ function scrollBy(delta) {
162
+ if (lastMaxScroll <= 0)
163
+ return false;
164
+ const nextOffset = Math.min(Math.max(scrollOffset + delta, 0), lastMaxScroll);
165
+ if (nextOffset !== scrollOffset) {
166
+ scrollOffset = nextOffset;
167
+ cachedLines = undefined;
168
+ cachedScrollOffset = undefined;
169
+ tui.requestRender();
170
+ }
171
+ return true;
172
+ }
173
+ return {
174
+ render,
175
+ invalidate: () => {
176
+ cachedLines = undefined;
177
+ cachedWidth = undefined;
178
+ cachedRows = undefined;
179
+ cachedScrollOffset = undefined;
180
+ },
181
+ handleInput: (data) => {
182
+ if (matchesKey(data, Key.down) || matchesKey(data, "j")) {
183
+ if (scrollBy(1))
184
+ return;
185
+ }
186
+ if (matchesKey(data, Key.up) || matchesKey(data, "k")) {
187
+ if (scrollBy(-1))
188
+ return;
189
+ }
190
+ if (matchesKey(data, Key.pageDown)) {
191
+ if (scrollBy(lastVisibleRows))
192
+ return;
193
+ }
194
+ if (matchesKey(data, Key.pageUp)) {
195
+ if (scrollBy(-lastVisibleRows))
196
+ return;
197
+ }
198
+ done(true);
199
+ },
200
+ };
201
+ }, {
202
+ overlay: true,
203
+ overlayOptions: {
204
+ width: "70%",
205
+ minWidth: 64,
206
+ maxHeight: "80%",
207
+ anchor: "center",
208
+ },
209
+ });
210
+ }
118
211
  export async function handleUsage(args, ctx) {
119
212
  const contextUsage = ctx.getContextUsage?.();
120
213
  const sessionTotals = scanSessionTokenTotals(ctx.sessionManager.getEntries());
@@ -133,5 +226,16 @@ export async function handleUsage(args, ctx) {
133
226
  }, null, 2), "info");
134
227
  return;
135
228
  }
136
- ctx.ui.notify(formatUsageReport({ modelLabel, contextUsage, sessionTotals }), "info");
229
+ const reportText = formatUsageReport({ modelLabel, contextUsage, sessionTotals });
230
+ if (ctx.hasUI) {
231
+ try {
232
+ const result = await showUsageDialog(ctx, reportText);
233
+ if (result !== undefined)
234
+ return;
235
+ }
236
+ catch {
237
+ // Fall back to text notify below when custom overlays are unavailable.
238
+ }
239
+ }
240
+ ctx.ui.notify(reportText, "info");
137
241
  }
@@ -7,6 +7,7 @@
7
7
  * Opened via `/gsd show-config` or `/gsd config`.
8
8
  */
9
9
  import { matchesKey, Key, truncateToWidth } from "@gsd/pi-tui";
10
+ import { renderDialogFrame, renderKeyHints } from "./tui/render-kit.js";
10
11
  import { loadEffectiveGSDPreferences, loadGlobalGSDPreferences, loadProjectGSDPreferences, getGlobalGSDPreferencesPath, getProjectGSDPreferencesPath, resolveDynamicRoutingConfig, resolveEffectiveProfile, resolveModelWithFallbacksForUnit, resolveAutoSupervisorConfig, } from "./preferences.js";
11
12
  function collectConfigSections() {
12
13
  const sections = [];
@@ -227,6 +228,7 @@ export class GSDConfigOverlay {
227
228
  onClose;
228
229
  sections;
229
230
  cachedLines;
231
+ cachedWidth;
230
232
  scrollOffset = 0;
231
233
  disposed = false;
232
234
  constructor(tui, theme, onClose) {
@@ -237,6 +239,7 @@ export class GSDConfigOverlay {
237
239
  }
238
240
  invalidate() {
239
241
  this.cachedLines = undefined;
242
+ this.cachedWidth = undefined;
240
243
  }
241
244
  dispose() {
242
245
  this.disposed = true;
@@ -273,14 +276,12 @@ export class GSDConfigOverlay {
273
276
  }
274
277
  }
275
278
  render(width) {
276
- if (this.cachedLines)
279
+ if (this.cachedLines && this.cachedWidth === width)
277
280
  return this.cachedLines;
278
281
  const t = this.theme;
279
- const w = Math.max(width, 50);
282
+ const w = Math.max(1, width);
283
+ const contentWidth = Math.max(1, w - 4);
280
284
  const allLines = [];
281
- // Header
282
- allLines.push(t.bold(t.fg("accent", " GSD Configuration ")));
283
- allLines.push(t.fg("muted", "\u2500".repeat(w)));
284
285
  // Find max label width for alignment
285
286
  let maxLabel = 0;
286
287
  for (const section of this.sections) {
@@ -291,22 +292,27 @@ export class GSDConfigOverlay {
291
292
  }
292
293
  const labelPad = Math.min(maxLabel + 2, 24);
293
294
  for (const section of this.sections) {
294
- allLines.push("");
295
+ if (allLines.length > 0)
296
+ allLines.push("");
295
297
  allLines.push(t.bold(t.fg("accent", ` ${section.title}`)));
296
298
  for (const row of section.rows) {
297
299
  const label = t.fg("muted", ` ${row.label.padEnd(labelPad)}`);
298
300
  const value = row.accent ? t.bold(row.value) : row.value;
299
- allLines.push(truncateToWidth(`${label}${value}`, w));
301
+ allLines.push(truncateToWidth(`${label}${value}`, contentWidth));
300
302
  }
301
303
  }
302
- allLines.push("");
303
- allLines.push(t.fg("muted", ` ${"\u2500".repeat(w - 4)}`));
304
- allLines.push(t.fg("muted", " esc/q close \u2502 \u2191\u2193/jk scroll \u2502 /gsd prefs to edit"));
305
304
  // Apply scroll
306
- const maxScroll = Math.max(0, allLines.length - 20);
305
+ const terminalRows = process.stdout.rows || 32;
306
+ const maxBodyRows = Math.max(1, Math.min(allLines.length, terminalRows - 12));
307
+ const maxScroll = Math.max(0, allLines.length - maxBodyRows);
307
308
  this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
308
- const visible = allLines.slice(this.scrollOffset);
309
- this.cachedLines = visible;
310
- return visible;
309
+ const visible = allLines.slice(this.scrollOffset, this.scrollOffset + maxBodyRows);
310
+ const footer = renderKeyHints(t, ["esc/q close", "\u2191\u2193/jk scroll", "/gsd prefs to edit"], contentWidth);
311
+ this.cachedLines = renderDialogFrame(t, "GSD Configuration", visible, w, {
312
+ footer,
313
+ scroll: { offset: this.scrollOffset, visibleRows: maxBodyRows, totalRows: allLines.length },
314
+ });
315
+ this.cachedWidth = width;
316
+ return this.cachedLines;
311
317
  }
312
318
  }
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { matchesKey, Key, truncateToWidth } from "@gsd/pi-tui";
5
5
  import { formatTokenCount } from "./metrics.js";
6
- import { renderProgressBar, rightAlign } from "./tui/render-kit.js";
6
+ import { renderDialogFrame, renderKeyHints, renderProgressBar, rightAlign } from "./tui/render-kit.js";
7
7
  const SECTION_COLORS = ["accent", "success", "warning", "borderAccent", "text"];
8
8
  export function getContextChartTotals(report) {
9
9
  const systemTokens = report.systemSections.reduce((sum, section) => sum + section.tokens, 0);
@@ -47,6 +47,7 @@ export class GSDContextOverlay {
47
47
  onClose;
48
48
  report;
49
49
  cachedLines;
50
+ cachedWidth;
50
51
  scrollOffset = 0;
51
52
  disposed = false;
52
53
  constructor(tui, theme, report, onClose) {
@@ -57,6 +58,7 @@ export class GSDContextOverlay {
57
58
  }
58
59
  invalidate() {
59
60
  this.cachedLines = undefined;
61
+ this.cachedWidth = undefined;
60
62
  }
61
63
  dispose() {
62
64
  this.disposed = true;
@@ -92,21 +94,20 @@ export class GSDContextOverlay {
92
94
  }
93
95
  }
94
96
  render(width) {
95
- if (this.cachedLines)
97
+ if (this.cachedLines && this.cachedWidth === width)
96
98
  return this.cachedLines;
97
99
  const theme = this.theme;
98
- const w = Math.max(width, 60);
100
+ const w = Math.max(1, width);
101
+ const contentWidth = Math.max(1, w - 4);
99
102
  const totals = getContextChartTotals(this.report);
100
103
  const chartTotal = Math.max(totals.inContext, totals.estimated, 1);
101
104
  const lines = [];
102
- lines.push(theme.bold(theme.fg("accent", " Context Breakdown ")));
103
- lines.push(theme.fg("muted", "─".repeat(w)));
104
105
  if (this.report.modelLabel) {
105
- lines.push(rightAlign(theme.fg("muted", "Model"), theme.fg("text", this.report.modelLabel), w));
106
+ lines.push(rightAlign(theme.fg("muted", "Model"), theme.fg("text", this.report.modelLabel), contentWidth));
106
107
  }
107
108
  lines.push("");
108
109
  if (totals.window != null) {
109
- const usageBarWidth = Math.max(20, w - 28);
110
+ const usageBarWidth = Math.max(8, contentWidth - 28);
110
111
  const usageBar = renderProgressBar(theme, totals.inContext, totals.window, usageBarWidth, {
111
112
  filledColor: totals.inContext / totals.window > 0.85 ? "warning" : "accent",
112
113
  });
@@ -115,7 +116,7 @@ export class GSDContextOverlay {
115
116
  else {
116
117
  lines.push(` ${theme.fg("muted", "Estimated")} ${theme.fg("text", formatTokenCount(chartTotal))}`);
117
118
  }
118
- const splitWidth = Math.max(16, Math.floor((w - 8) / 2));
119
+ const splitWidth = Math.max(8, Math.floor((contentWidth - 8) / 2));
119
120
  const systemBar = renderProgressBar(theme, totals.systemTokens, chartTotal, splitWidth, { filledColor: "accent" });
120
121
  const convBar = renderProgressBar(theme, totals.conversationTokens, chartTotal, splitWidth, { filledColor: "success" });
121
122
  lines.push("");
@@ -123,17 +124,17 @@ export class GSDContextOverlay {
123
124
  lines.push(` ${theme.fg("success", "History")} ${convBar} ${theme.fg("muted", formatPct(totals.conversationTokens, chartTotal))}`);
124
125
  lines.push("");
125
126
  lines.push(theme.bold(theme.fg("accent", " System prompt")));
126
- lines.push(...renderSectionBars(theme, this.report.systemSections, chartTotal, w, 22));
127
+ lines.push(...renderSectionBars(theme, this.report.systemSections, chartTotal, contentWidth, 22));
127
128
  lines.push("");
128
129
  lines.push(theme.bold(theme.fg("accent", " Conversation")));
129
- lines.push(...renderSectionBars(theme, this.report.conversationSections, chartTotal, w, 22));
130
+ lines.push(...renderSectionBars(theme, this.report.conversationSections, chartTotal, contentWidth, 22));
130
131
  lines.push("");
131
132
  lines.push(theme.bold(theme.fg("accent", " Skills")));
132
133
  const { skills } = this.report;
133
134
  if (skills.available.length > 0) {
134
135
  lines.push(` ${theme.fg("muted", "Available")} ${theme.fg("text", `${skills.available.length}`)}`);
135
136
  const preview = skills.available.slice(0, 8).join(", ");
136
- lines.push(truncateToWidth(` ${preview}${skills.available.length > 8 ? "…" : ""}`, w));
137
+ lines.push(truncateToWidth(` ${preview}${skills.available.length > 8 ? "…" : ""}`, contentWidth));
137
138
  }
138
139
  else {
139
140
  lines.push(theme.fg("dim", " none in prompt"));
@@ -147,12 +148,17 @@ export class GSDContextOverlay {
147
148
  lines.push("");
148
149
  lines.push(theme.bold(theme.fg("accent", " Agents")));
149
150
  lines.push(` ${theme.fg("muted", "Subagent spawns")} ${theme.fg("text", String(this.report.subagentSpawns))}`);
150
- lines.push("");
151
- lines.push(theme.fg("muted", ` ${"─".repeat(Math.max(0, w - 4))}`));
152
- lines.push(theme.fg("muted", " esc/q close │ ↑↓ scroll │ /gsd context --open for browser chart"));
153
- const maxScroll = Math.max(0, lines.length - 24);
151
+ const terminalRows = process.stdout.rows || 32;
152
+ const maxBodyRows = Math.max(1, Math.min(lines.length, terminalRows - 12));
153
+ const maxScroll = Math.max(0, lines.length - maxBodyRows);
154
154
  this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
155
- this.cachedLines = lines.slice(this.scrollOffset);
155
+ const visible = lines.slice(this.scrollOffset, this.scrollOffset + maxBodyRows);
156
+ const footer = renderKeyHints(theme, ["esc/q close", "↑↓ scroll", "/gsd context --open"], contentWidth);
157
+ this.cachedLines = renderDialogFrame(theme, "Context Breakdown", visible, w, {
158
+ footer,
159
+ scroll: { offset: this.scrollOffset, visibleRows: maxBodyRows, totalRows: lines.length },
160
+ });
161
+ this.cachedWidth = width;
156
162
  return this.cachedLines;
157
163
  }
158
164
  }
@@ -6,7 +6,7 @@
6
6
  * Toggled with Ctrl+Alt+G (⌃⌥G on macOS), Ctrl+Shift+G fallback,
7
7
  * or opened from /gsd status.
8
8
  */
9
- import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
9
+ import { truncateToWidth, matchesKey, Key } from "@gsd/pi-tui";
10
10
  import { deriveState } from "./state.js";
11
11
  import { loadFile } from "./files.js";
12
12
  import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
@@ -21,6 +21,7 @@ import { estimateTimeRemaining } from "./auto-dashboard.js";
21
21
  import { computeProgressScore } from "./progress-score.js";
22
22
  import { runEnvironmentChecks } from "./doctor-environment.js";
23
23
  import { formattedShortcutPair } from "./shortcut-defs.js";
24
+ import { renderDialogFrame, renderKeyHints } from "./tui/render-kit.js";
24
25
  export function unitLabel(type) {
25
26
  switch (type) {
26
27
  case "discuss-milestone":
@@ -222,30 +223,20 @@ export class GSDDashboardOverlay {
222
223
  }
223
224
  const content = this.buildContentLines(width);
224
225
  const viewportHeight = Math.max(5, process.stdout.rows ? process.stdout.rows - 8 : 24);
225
- const chromeHeight = 2;
226
- const visibleContentRows = Math.max(1, viewportHeight - chromeHeight);
226
+ const visibleContentRows = Math.max(1, viewportHeight - 4);
227
227
  const maxScroll = Math.max(0, content.length - visibleContentRows);
228
228
  this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
229
229
  const visibleContent = content.slice(this.scrollOffset, this.scrollOffset + visibleContentRows);
230
- const lines = this.wrapInBox(visibleContent, width);
230
+ const contentWidth = Math.max(1, width - 4);
231
+ const footer = renderKeyHints(this.theme, ["↑↓ scroll", "g/G top/end", `Esc/${formattedShortcutPair("dashboard")} close`], contentWidth);
232
+ const lines = renderDialogFrame(this.theme, "GSD Dashboard", visibleContent, width, {
233
+ footer,
234
+ scroll: { offset: this.scrollOffset, visibleRows: visibleContentRows, totalRows: content.length },
235
+ });
231
236
  this.cachedWidth = width;
232
237
  this.cachedLines = lines;
233
238
  return lines;
234
239
  }
235
- wrapInBox(inner, width) {
236
- const th = this.theme;
237
- const border = (s) => th.fg("borderAccent", s);
238
- const innerWidth = width - 4;
239
- const lines = [];
240
- lines.push(border("╭" + "─".repeat(width - 2) + "╮"));
241
- for (const line of inner) {
242
- const truncated = truncateToWidth(line, innerWidth);
243
- const padWidth = Math.max(0, innerWidth - visibleWidth(truncated));
244
- lines.push(border("│") + " " + truncated + " ".repeat(padWidth) + " " + border("│"));
245
- }
246
- lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
247
- return lines;
248
- }
249
240
  buildContentLines(width) {
250
241
  const th = this.theme;
251
242
  const shellWidth = width - 4;
@@ -260,7 +251,6 @@ export class GSDDashboardOverlay {
260
251
  const blank = () => row("");
261
252
  const hr = () => row(th.fg("dim", "─".repeat(contentWidth)));
262
253
  const centered = (content) => row(centerLine(content, contentWidth));
263
- const title = th.fg("accent", th.bold("GSD Dashboard"));
264
254
  const isRemote = !!this.dashData.remoteSession;
265
255
  const status = this.dashData.active
266
256
  ? `${Date.now() % 2000 < 1000 ? th.fg("success", "●") : th.fg("dim", "○")} ${th.fg("success", "AUTO")}`
@@ -287,7 +277,7 @@ export class GSDDashboardOverlay {
287
277
  else if (isRemote) {
288
278
  elapsedParts = th.fg("dim", `since ${this.dashData.remoteSession.startedAt.replace("T", " ").slice(0, 19)}`);
289
279
  }
290
- lines.push(row(joinColumns(`${title} ${status}${worktreeTag}`, elapsedParts, contentWidth)));
280
+ lines.push(row(joinColumns(`${status}${worktreeTag}`, elapsedParts, contentWidth)));
291
281
  // Progress score — traffic light indicator (#1221)
292
282
  if (this.dashData.active || this.dashData.paused) {
293
283
  const progressScore = computeProgressScore();
@@ -528,9 +518,6 @@ export class GSDDashboardOverlay {
528
518
  }
529
519
  }
530
520
  }
531
- lines.push(blank());
532
- lines.push(hr());
533
- lines.push(centered(th.fg("dim", `↑↓ scroll · g/G top/end · Esc/${formattedShortcutPair("dashboard")} close`)));
534
521
  return lines;
535
522
  }
536
523
  renderProgressRow(label, done, total, color, width) {