@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
@@ -4,10 +4,12 @@
4
4
  * Shows current LLM context window usage and session token totals.
5
5
  */
6
6
 
7
- import type { ExtensionCommandContext, ContextUsage, SessionEntry } from "@gsd/pi-coding-agent";
7
+ import type { ExtensionCommandContext, ContextUsage, SessionEntry, Theme } from "@gsd/pi-coding-agent";
8
+ import { Key, matchesKey } from "@gsd/pi-tui";
8
9
 
9
10
  import { formatCost, formatPercent, formatTokenCount } from "./metrics.js";
10
11
  import { loadEffectiveGSDPreferences } from "./preferences.js";
12
+ import { renderDialogFrame, renderKeyHints } from "./tui/render-kit.js";
11
13
 
12
14
  export interface SessionTokenTotals {
13
15
  input: number;
@@ -160,6 +162,101 @@ export function formatUsageReport(options: {
160
162
  return lines.join("\n");
161
163
  }
162
164
 
165
+ async function showUsageDialog(
166
+ ctx: ExtensionCommandContext,
167
+ reportText: string,
168
+ ): Promise<boolean | undefined> {
169
+ return ctx.ui.custom<boolean>((tui, theme: Theme, _kb, done) => {
170
+ let cachedLines: string[] | undefined;
171
+ let cachedWidth: number | undefined;
172
+ let cachedRows: number | undefined;
173
+ let cachedScrollOffset: number | undefined;
174
+ let scrollOffset = 0;
175
+ let lastMaxScroll = 0;
176
+ let lastVisibleRows = 1;
177
+
178
+ function render(width: number): string[] {
179
+ const terminalRows = process.stdout.rows || 0;
180
+ if (
181
+ cachedLines &&
182
+ cachedWidth === width &&
183
+ cachedRows === terminalRows &&
184
+ cachedScrollOffset === scrollOffset
185
+ ) {
186
+ return cachedLines;
187
+ }
188
+
189
+ const contentWidth = Math.max(1, width - 4);
190
+ const body = reportText.split("\n");
191
+ if (body[0] === "Context Usage") body.shift();
192
+ while (body[0] === "") body.shift();
193
+ const maxOverlayRows = terminalRows > 0 ? Math.max(5, Math.floor(terminalRows * 0.8)) : 24;
194
+ const frameRows = 4;
195
+ const visibleRows = Math.max(1, maxOverlayRows - frameRows);
196
+ const maxScroll = Math.max(0, body.length - visibleRows);
197
+ scrollOffset = Math.min(Math.max(scrollOffset, 0), maxScroll);
198
+ lastMaxScroll = maxScroll;
199
+ lastVisibleRows = visibleRows;
200
+ const visible = body.slice(scrollOffset, scrollOffset + visibleRows);
201
+ const scrollable = body.length > visibleRows;
202
+
203
+ cachedLines = renderDialogFrame(theme, "Context Usage", visible, width, {
204
+ footer: renderKeyHints(theme, scrollable ? ["↑↓ scroll", "any key close"] : ["any key close"], contentWidth),
205
+ scroll: { offset: scrollOffset, visibleRows, totalRows: body.length },
206
+ });
207
+ cachedWidth = width;
208
+ cachedRows = terminalRows;
209
+ cachedScrollOffset = scrollOffset;
210
+ return cachedLines;
211
+ }
212
+
213
+ function scrollBy(delta: number): boolean {
214
+ if (lastMaxScroll <= 0) return false;
215
+ const nextOffset = Math.min(Math.max(scrollOffset + delta, 0), lastMaxScroll);
216
+ if (nextOffset !== scrollOffset) {
217
+ scrollOffset = nextOffset;
218
+ cachedLines = undefined;
219
+ cachedScrollOffset = undefined;
220
+ tui.requestRender();
221
+ }
222
+ return true;
223
+ }
224
+
225
+ return {
226
+ render,
227
+ invalidate: () => {
228
+ cachedLines = undefined;
229
+ cachedWidth = undefined;
230
+ cachedRows = undefined;
231
+ cachedScrollOffset = undefined;
232
+ },
233
+ handleInput: (data: string) => {
234
+ if (matchesKey(data, Key.down) || matchesKey(data, "j")) {
235
+ if (scrollBy(1)) return;
236
+ }
237
+ if (matchesKey(data, Key.up) || matchesKey(data, "k")) {
238
+ if (scrollBy(-1)) return;
239
+ }
240
+ if (matchesKey(data, Key.pageDown)) {
241
+ if (scrollBy(lastVisibleRows)) return;
242
+ }
243
+ if (matchesKey(data, Key.pageUp)) {
244
+ if (scrollBy(-lastVisibleRows)) return;
245
+ }
246
+ done(true);
247
+ },
248
+ };
249
+ }, {
250
+ overlay: true,
251
+ overlayOptions: {
252
+ width: "70%",
253
+ minWidth: 64,
254
+ maxHeight: "80%",
255
+ anchor: "center",
256
+ },
257
+ });
258
+ }
259
+
163
260
  export async function handleUsage(args: string, ctx: ExtensionCommandContext): Promise<void> {
164
261
  const contextUsage = ctx.getContextUsage?.();
165
262
  const sessionTotals = scanSessionTokenTotals(ctx.sessionManager.getEntries());
@@ -187,8 +284,16 @@ export async function handleUsage(args: string, ctx: ExtensionCommandContext): P
187
284
  return;
188
285
  }
189
286
 
190
- ctx.ui.notify(
191
- formatUsageReport({ modelLabel, contextUsage, sessionTotals }),
192
- "info",
193
- );
287
+ const reportText = formatUsageReport({ modelLabel, contextUsage, sessionTotals });
288
+
289
+ if (ctx.hasUI) {
290
+ try {
291
+ const result = await showUsageDialog(ctx, reportText);
292
+ if (result !== undefined) return;
293
+ } catch {
294
+ // Fall back to text notify below when custom overlays are unavailable.
295
+ }
296
+ }
297
+
298
+ ctx.ui.notify(reportText, "info");
194
299
  }
@@ -10,6 +10,7 @@
10
10
  import type { Theme } from "@gsd/pi-coding-agent";
11
11
  import { matchesKey, Key, truncateToWidth } from "@gsd/pi-tui";
12
12
 
13
+ import { renderDialogFrame, renderKeyHints } from "./tui/render-kit.js";
13
14
  import {
14
15
  loadEffectiveGSDPreferences,
15
16
  loadGlobalGSDPreferences,
@@ -231,6 +232,7 @@ export class GSDConfigOverlay {
231
232
  private onClose: () => void;
232
233
  private sections: ConfigSection[];
233
234
  private cachedLines?: string[];
235
+ private cachedWidth?: number;
234
236
  private scrollOffset = 0;
235
237
  private disposed = false;
236
238
 
@@ -247,6 +249,7 @@ export class GSDConfigOverlay {
247
249
 
248
250
  invalidate(): void {
249
251
  this.cachedLines = undefined;
252
+ this.cachedWidth = undefined;
250
253
  }
251
254
 
252
255
  dispose(): void {
@@ -286,16 +289,13 @@ export class GSDConfigOverlay {
286
289
  }
287
290
 
288
291
  render(width: number): string[] {
289
- if (this.cachedLines) return this.cachedLines;
292
+ if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
290
293
 
291
294
  const t = this.theme;
292
- const w = Math.max(width, 50);
295
+ const w = Math.max(1, width);
296
+ const contentWidth = Math.max(1, w - 4);
293
297
  const allLines: string[] = [];
294
298
 
295
- // Header
296
- allLines.push(t.bold(t.fg("accent", " GSD Configuration ")));
297
- allLines.push(t.fg("muted", "\u2500".repeat(w)));
298
-
299
299
  // Find max label width for alignment
300
300
  let maxLabel = 0;
301
301
  for (const section of this.sections) {
@@ -306,26 +306,29 @@ export class GSDConfigOverlay {
306
306
  const labelPad = Math.min(maxLabel + 2, 24);
307
307
 
308
308
  for (const section of this.sections) {
309
- allLines.push("");
309
+ if (allLines.length > 0) allLines.push("");
310
310
  allLines.push(t.bold(t.fg("accent", ` ${section.title}`)));
311
311
 
312
312
  for (const row of section.rows) {
313
313
  const label = t.fg("muted", ` ${row.label.padEnd(labelPad)}`);
314
314
  const value = row.accent ? t.bold(row.value) : row.value;
315
- allLines.push(truncateToWidth(`${label}${value}`, w));
315
+ allLines.push(truncateToWidth(`${label}${value}`, contentWidth));
316
316
  }
317
317
  }
318
318
 
319
- allLines.push("");
320
- allLines.push(t.fg("muted", ` ${"\u2500".repeat(w - 4)}`));
321
- allLines.push(t.fg("muted", " esc/q close \u2502 \u2191\u2193/jk scroll \u2502 /gsd prefs to edit"));
322
-
323
319
  // Apply scroll
324
- const maxScroll = Math.max(0, allLines.length - 20);
320
+ const terminalRows = process.stdout.rows || 32;
321
+ const maxBodyRows = Math.max(1, Math.min(allLines.length, terminalRows - 12));
322
+ const maxScroll = Math.max(0, allLines.length - maxBodyRows);
325
323
  this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
326
- const visible = allLines.slice(this.scrollOffset);
324
+ const visible = allLines.slice(this.scrollOffset, this.scrollOffset + maxBodyRows);
325
+ const footer = renderKeyHints(t, ["esc/q close", "\u2191\u2193/jk scroll", "/gsd prefs to edit"], contentWidth);
327
326
 
328
- this.cachedLines = visible;
329
- return visible;
327
+ this.cachedLines = renderDialogFrame(t, "GSD Configuration", visible, w, {
328
+ footer,
329
+ scroll: { offset: this.scrollOffset, visibleRows: maxBodyRows, totalRows: allLines.length },
330
+ });
331
+ this.cachedWidth = width;
332
+ return this.cachedLines;
330
333
  }
331
334
  }
@@ -3,11 +3,11 @@
3
3
  */
4
4
 
5
5
  import type { Theme, ThemeColor } from "@gsd/pi-coding-agent";
6
- import { matchesKey, Key, truncateToWidth, visibleWidth } from "@gsd/pi-tui";
6
+ import { matchesKey, Key, truncateToWidth } from "@gsd/pi-tui";
7
7
 
8
8
  import type { ContextBreakdownReport, ContextSectionBreakdown } from "./commands-context.js";
9
9
  import { formatTokenCount } from "./metrics.js";
10
- import { renderProgressBar, rightAlign } from "./tui/render-kit.js";
10
+ import { renderDialogFrame, renderKeyHints, renderProgressBar, rightAlign } from "./tui/render-kit.js";
11
11
 
12
12
  const SECTION_COLORS: ThemeColor[] = ["accent", "success", "warning", "borderAccent", "text"];
13
13
 
@@ -70,6 +70,7 @@ export class GSDContextOverlay {
70
70
  private onClose: () => void;
71
71
  private report: ContextBreakdownReport;
72
72
  private cachedLines?: string[];
73
+ private cachedWidth?: number;
73
74
  private scrollOffset = 0;
74
75
  private disposed = false;
75
76
 
@@ -87,6 +88,7 @@ export class GSDContextOverlay {
87
88
 
88
89
  invalidate(): void {
89
90
  this.cachedLines = undefined;
91
+ this.cachedWidth = undefined;
90
92
  }
91
93
 
92
94
  dispose(): void {
@@ -125,24 +127,22 @@ export class GSDContextOverlay {
125
127
  }
126
128
 
127
129
  render(width: number): string[] {
128
- if (this.cachedLines) return this.cachedLines;
130
+ if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
129
131
 
130
132
  const theme = this.theme;
131
- const w = Math.max(width, 60);
133
+ const w = Math.max(1, width);
134
+ const contentWidth = Math.max(1, w - 4);
132
135
  const totals = getContextChartTotals(this.report);
133
136
  const chartTotal = Math.max(totals.inContext, totals.estimated, 1);
134
137
  const lines: string[] = [];
135
138
 
136
- lines.push(theme.bold(theme.fg("accent", " Context Breakdown ")));
137
- lines.push(theme.fg("muted", "─".repeat(w)));
138
-
139
139
  if (this.report.modelLabel) {
140
- lines.push(rightAlign(theme.fg("muted", "Model"), theme.fg("text", this.report.modelLabel), w));
140
+ lines.push(rightAlign(theme.fg("muted", "Model"), theme.fg("text", this.report.modelLabel), contentWidth));
141
141
  }
142
142
 
143
143
  lines.push("");
144
144
  if (totals.window != null) {
145
- const usageBarWidth = Math.max(20, w - 28);
145
+ const usageBarWidth = Math.max(8, contentWidth - 28);
146
146
  const usageBar = renderProgressBar(theme, totals.inContext, totals.window, usageBarWidth, {
147
147
  filledColor: totals.inContext / totals.window > 0.85 ? "warning" : "accent",
148
148
  });
@@ -151,7 +151,7 @@ export class GSDContextOverlay {
151
151
  lines.push(` ${theme.fg("muted", "Estimated")} ${theme.fg("text", formatTokenCount(chartTotal))}`);
152
152
  }
153
153
 
154
- const splitWidth = Math.max(16, Math.floor((w - 8) / 2));
154
+ const splitWidth = Math.max(8, Math.floor((contentWidth - 8) / 2));
155
155
  const systemBar = renderProgressBar(theme, totals.systemTokens, chartTotal, splitWidth, { filledColor: "accent" });
156
156
  const convBar = renderProgressBar(theme, totals.conversationTokens, chartTotal, splitWidth, { filledColor: "success" });
157
157
  lines.push("");
@@ -160,11 +160,11 @@ export class GSDContextOverlay {
160
160
 
161
161
  lines.push("");
162
162
  lines.push(theme.bold(theme.fg("accent", " System prompt")));
163
- lines.push(...renderSectionBars(theme, this.report.systemSections, chartTotal, w, 22));
163
+ lines.push(...renderSectionBars(theme, this.report.systemSections, chartTotal, contentWidth, 22));
164
164
 
165
165
  lines.push("");
166
166
  lines.push(theme.bold(theme.fg("accent", " Conversation")));
167
- lines.push(...renderSectionBars(theme, this.report.conversationSections, chartTotal, w, 22));
167
+ lines.push(...renderSectionBars(theme, this.report.conversationSections, chartTotal, contentWidth, 22));
168
168
 
169
169
  lines.push("");
170
170
  lines.push(theme.bold(theme.fg("accent", " Skills")));
@@ -172,7 +172,7 @@ export class GSDContextOverlay {
172
172
  if (skills.available.length > 0) {
173
173
  lines.push(` ${theme.fg("muted", "Available")} ${theme.fg("text", `${skills.available.length}`)}`);
174
174
  const preview = skills.available.slice(0, 8).join(", ");
175
- lines.push(truncateToWidth(` ${preview}${skills.available.length > 8 ? "…" : ""}`, w));
175
+ lines.push(truncateToWidth(` ${preview}${skills.available.length > 8 ? "…" : ""}`, contentWidth));
176
176
  } else {
177
177
  lines.push(theme.fg("dim", " none in prompt"));
178
178
  }
@@ -187,13 +187,18 @@ export class GSDContextOverlay {
187
187
  lines.push(theme.bold(theme.fg("accent", " Agents")));
188
188
  lines.push(` ${theme.fg("muted", "Subagent spawns")} ${theme.fg("text", String(this.report.subagentSpawns))}`);
189
189
 
190
- lines.push("");
191
- lines.push(theme.fg("muted", ` ${"─".repeat(Math.max(0, w - 4))}`));
192
- lines.push(theme.fg("muted", " esc/q close │ ↑↓ scroll │ /gsd context --open for browser chart"));
193
-
194
- const maxScroll = Math.max(0, lines.length - 24);
190
+ const terminalRows = process.stdout.rows || 32;
191
+ const maxBodyRows = Math.max(1, Math.min(lines.length, terminalRows - 12));
192
+ const maxScroll = Math.max(0, lines.length - maxBodyRows);
195
193
  this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
196
- this.cachedLines = lines.slice(this.scrollOffset);
194
+ const visible = lines.slice(this.scrollOffset, this.scrollOffset + maxBodyRows);
195
+ const footer = renderKeyHints(theme, ["esc/q close", "↑↓ scroll", "/gsd context --open"], contentWidth);
196
+
197
+ this.cachedLines = renderDialogFrame(theme, "Context Breakdown", visible, w, {
198
+ footer,
199
+ scroll: { offset: this.scrollOffset, visibleRows: maxBodyRows, totalRows: lines.length },
200
+ });
201
+ this.cachedWidth = width;
197
202
  return this.cachedLines;
198
203
  }
199
204
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import type { Theme } from "@gsd/pi-coding-agent";
11
- import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
11
+ import { truncateToWidth, matchesKey, Key } from "@gsd/pi-tui";
12
12
  import { deriveState } from "./state.js";
13
13
  import { loadFile } from "./files.js";
14
14
  import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
@@ -29,6 +29,7 @@ import { estimateTimeRemaining } from "./auto-dashboard.js";
29
29
  import { computeProgressScore, formatProgressLine } from "./progress-score.js";
30
30
  import { runEnvironmentChecks, type EnvironmentCheckResult } from "./doctor-environment.js";
31
31
  import { formattedShortcutPair } from "./shortcut-defs.js";
32
+ import { renderDialogFrame, renderKeyHints } from "./tui/render-kit.js";
32
33
 
33
34
  export function unitLabel(type: string): string {
34
35
  switch (type) {
@@ -258,35 +259,26 @@ export class GSDDashboardOverlay {
258
259
 
259
260
  const content = this.buildContentLines(width);
260
261
  const viewportHeight = Math.max(5, process.stdout.rows ? process.stdout.rows - 8 : 24);
261
- const chromeHeight = 2;
262
- const visibleContentRows = Math.max(1, viewportHeight - chromeHeight);
262
+ const visibleContentRows = Math.max(1, viewportHeight - 4);
263
263
  const maxScroll = Math.max(0, content.length - visibleContentRows);
264
264
  this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
265
265
  const visibleContent = content.slice(this.scrollOffset, this.scrollOffset + visibleContentRows);
266
-
267
- const lines = this.wrapInBox(visibleContent, width);
266
+ const contentWidth = Math.max(1, width - 4);
267
+ const footer = renderKeyHints(
268
+ this.theme,
269
+ ["↑↓ scroll", "g/G top/end", `Esc/${formattedShortcutPair("dashboard")} close`],
270
+ contentWidth,
271
+ );
272
+ const lines = renderDialogFrame(this.theme, "GSD Dashboard", visibleContent, width, {
273
+ footer,
274
+ scroll: { offset: this.scrollOffset, visibleRows: visibleContentRows, totalRows: content.length },
275
+ });
268
276
 
269
277
  this.cachedWidth = width;
270
278
  this.cachedLines = lines;
271
279
  return lines;
272
280
  }
273
281
 
274
- private wrapInBox(inner: string[], width: number): string[] {
275
- const th = this.theme;
276
- const border = (s: string) => th.fg("borderAccent", s);
277
- const innerWidth = width - 4;
278
- const lines: string[] = [];
279
-
280
- lines.push(border("╭" + "─".repeat(width - 2) + "╮"));
281
- for (const line of inner) {
282
- const truncated = truncateToWidth(line, innerWidth);
283
- const padWidth = Math.max(0, innerWidth - visibleWidth(truncated));
284
- lines.push(border("│") + " " + truncated + " ".repeat(padWidth) + " " + border("│"));
285
- }
286
- lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
287
- return lines;
288
- }
289
-
290
282
  private buildContentLines(width: number): string[] {
291
283
  const th = this.theme;
292
284
  const shellWidth = width - 4;
@@ -303,7 +295,6 @@ export class GSDDashboardOverlay {
303
295
  const hr = () => row(th.fg("dim", "─".repeat(contentWidth)));
304
296
  const centered = (content: string) => row(centerLine(content, contentWidth));
305
297
 
306
- const title = th.fg("accent", th.bold("GSD Dashboard"));
307
298
  const isRemote = !!this.dashData.remoteSession;
308
299
  const status = this.dashData.active
309
300
  ? `${Date.now() % 2000 < 1000 ? th.fg("success", "●") : th.fg("dim", "○")} ${th.fg("success", "AUTO")}`
@@ -328,7 +319,7 @@ export class GSDDashboardOverlay {
328
319
  } else if (isRemote) {
329
320
  elapsedParts = th.fg("dim", `since ${this.dashData.remoteSession!.startedAt.replace("T", " ").slice(0, 19)}`);
330
321
  }
331
- lines.push(row(joinColumns(`${title} ${status}${worktreeTag}`, elapsedParts, contentWidth)));
322
+ lines.push(row(joinColumns(`${status}${worktreeTag}`, elapsedParts, contentWidth)));
332
323
 
333
324
  // Progress score — traffic light indicator (#1221)
334
325
  if (this.dashData.active || this.dashData.paused) {
@@ -610,10 +601,6 @@ export class GSDDashboardOverlay {
610
601
  }
611
602
  }
612
603
 
613
- lines.push(blank());
614
- lines.push(hr());
615
- lines.push(centered(th.fg("dim", `↑↓ scroll · g/G top/end · Esc/${formattedShortcutPair("dashboard")} close`)));
616
-
617
604
  return lines;
618
605
  }
619
606
 
@@ -16,7 +16,7 @@ import { delimiter, join } from "node:path";
16
16
  import { AuthStorage } from "@gsd/pi-coding-agent";
17
17
  import { getEnvApiKey } from "@gsd/pi-ai";
18
18
  import { loadEffectiveGSDPreferences } from "./preferences.js";
19
- import { getAuthPath, PROVIDER_REGISTRY, type ProviderCategory } from "./key-manager.js";
19
+ import { getAuthPath, PROVIDER_REGISTRY, supportsBrowserOAuth, type ProviderCategory } from "./key-manager.js";
20
20
  import { homedir } from "node:os";
21
21
 
22
22
  // ── Types ──────────────────────────────────────────────────────────────────────
@@ -42,11 +42,10 @@ export interface ProviderCheckResult {
42
42
 
43
43
  /**
44
44
  * Providers that use external CLI authentication (not API keys).
45
- * These are always considered "found" the host CLI handles auth.
45
+ * When explicitly selected, the provider's own CLI/session owns auth.
46
46
  */
47
47
  const CLI_AUTH_PROVIDERS = new Set([
48
48
  "claude-code",
49
- "openai-codex",
50
49
  "google-gemini-cli",
51
50
  "google-antigravity",
52
51
  ]);
@@ -156,26 +155,30 @@ interface KeyLookup {
156
155
  * Map of CLI provider IDs to their binary names on disk.
157
156
  * Used for lightweight binary-presence checks (PATH scan, no subprocess).
158
157
  */
159
- const CLI_BINARY_MAP: Record<string, string> = {
160
- "claude-code": "claude",
161
- "openai-codex": "codex",
162
- "google-gemini-cli": "gemini",
163
- "google-antigravity": "antigravity",
158
+ const CLI_BINARY_MAP: Record<string, string[]> = {
159
+ "claude-code": ["claude", "claude-code"],
160
+ "google-gemini-cli": ["gemini"],
161
+ "google-antigravity": ["agy"],
164
162
  };
165
163
 
164
+ const CLI_AUTH_PATH_CHECK_PROVIDERS = new Set([
165
+ "google-gemini-cli",
166
+ "google-antigravity",
167
+ ]);
168
+
166
169
  /**
167
170
  * Check if a CLI provider's binary exists anywhere in PATH.
168
171
  * Fast filesystem scan — no subprocess, no network, sub-1ms.
169
172
  */
170
173
  function isCliBinaryInPath(providerId: string): boolean {
171
- const binary = CLI_BINARY_MAP[providerId];
172
- if (!binary) return false;
174
+ const binaries = CLI_BINARY_MAP[providerId];
175
+ if (!binaries) return false;
173
176
 
174
177
  const pathDirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
175
178
 
176
179
  // On Windows, command shims are commonly installed as .cmd/.exe/.bat/.com.
177
180
  // Scan PATHEXT candidates in addition to the bare binary name.
178
- const executableNames: string[] = [binary];
181
+ const executableNames: string[] = [...binaries];
179
182
  if (process.platform === "win32") {
180
183
  const rawPathExt = process.env.PATHEXT
181
184
  ?.split(";")
@@ -185,9 +188,11 @@ function isCliBinaryInPath(providerId: string): boolean {
185
188
  ext.startsWith(".") ? ext.toLowerCase() : `.${ext.toLowerCase()}`,
186
189
  );
187
190
  const defaultExt = [".exe", ".cmd", ".bat", ".com"];
188
- for (const ext of [...normalizedPathExt, ...defaultExt]) {
189
- const candidate = `${binary}${ext}`;
190
- if (!executableNames.includes(candidate)) executableNames.push(candidate);
191
+ for (const binary of binaries) {
192
+ for (const ext of [...normalizedPathExt, ...defaultExt]) {
193
+ const candidate = `${binary}${ext}`;
194
+ if (!executableNames.includes(candidate)) executableNames.push(candidate);
195
+ }
191
196
  }
192
197
  }
193
198
 
@@ -224,12 +229,6 @@ function hasModelsJsonApiKey(providerId: string): boolean {
224
229
  function resolveKey(providerId: string): KeyLookup {
225
230
  const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
226
231
 
227
- // claude-code never stores credentials in auth.json — GSD delegates entirely to
228
- // the local CLI binary. Presence of the binary in PATH is the only signal.
229
- if (providerId === "claude-code") {
230
- return { found: isCliBinaryInPath("claude-code"), source: "env", backedOff: false };
231
- }
232
-
233
232
  if (providerId === "anthropic-vertex" && process.env.ANTHROPIC_VERTEX_PROJECT_ID) {
234
233
  return { found: true, source: "env", backedOff: false };
235
234
  }
@@ -242,9 +241,15 @@ function resolveKey(providerId: string): KeyLookup {
242
241
  const creds = auth.getCredentialsForProvider(providerId);
243
242
  if (creds.length > 0) {
244
243
  // Filter out empty placeholder keys (from skipped onboarding)
245
- const hasRealKey = creds.some(c =>
246
- c.type === "oauth" || (c.type === "api_key" && (c as { key?: string }).key)
247
- );
244
+ const hasRealKey = creds.some(c => {
245
+ if (c.type === "oauth") return true;
246
+ if (c.type !== "api_key") return false;
247
+
248
+ const key = (c as { key?: string }).key?.trim();
249
+ if (!key) return false;
250
+
251
+ return !(CLI_AUTH_PROVIDERS.has(providerId) && key === "cli");
252
+ });
248
253
  if (hasRealKey) {
249
254
  return {
250
255
  found: true,
@@ -275,6 +280,12 @@ function resolveKey(providerId: string): KeyLookup {
275
280
  return { found: true, source: "models.json", backedOff: false };
276
281
  }
277
282
 
283
+ // Cross-provider routes can use a local CLI when it is installed. Explicit
284
+ // external CLI provider selections are handled in checkLlmProviders() below.
285
+ if (CLI_AUTH_PROVIDERS.has(providerId) && isCliBinaryInPath(providerId)) {
286
+ return { found: true, source: "env", backedOff: false };
287
+ }
288
+
278
289
  return { found: false, source: "none", backedOff: false };
279
290
  }
280
291
 
@@ -285,15 +296,32 @@ function checkLlmProviders(): ProviderCheckResult[] {
285
296
  const results: ProviderCheckResult[] = [];
286
297
 
287
298
  for (const providerId of required) {
288
- // CLI-authenticated providers don't need API keys skip key check
299
+ // CLI-authenticated providers don't need API keys. The provider's own
300
+ // request path validates CLI sessions when it is used.
289
301
  if (CLI_AUTH_PROVIDERS.has(providerId)) {
290
302
  const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
303
+ const label = info?.label ?? providerId;
304
+ if (CLI_AUTH_PATH_CHECK_PROVIDERS.has(providerId) && !isCliBinaryInPath(providerId)) {
305
+ const binaries = CLI_BINARY_MAP[providerId]?.map(binary => `\`${binary}\``).join(" or ");
306
+ results.push({
307
+ name: providerId,
308
+ label,
309
+ category: "llm",
310
+ status: "error",
311
+ message: `${label} — CLI not found`,
312
+ detail: binaries
313
+ ? `Install ${label} and ensure ${binaries} is on PATH`
314
+ : `Install ${label} and ensure its CLI is on PATH`,
315
+ required: true,
316
+ });
317
+ continue;
318
+ }
291
319
  results.push({
292
320
  name: providerId,
293
- label: info?.label ?? providerId,
321
+ label,
294
322
  category: "llm",
295
323
  status: "ok",
296
- message: `${info?.label ?? providerId} — CLI auth (no key needed)`,
324
+ message: `${label} — CLI auth (no key needed)`,
297
325
  required: true,
298
326
  });
299
327
  continue;
@@ -333,7 +361,7 @@ function checkLlmProviders(): ProviderCheckResult[] {
333
361
  message: `${label} — not configured`,
334
362
  detail: providerId === "anthropic-vertex"
335
363
  ? "Set ANTHROPIC_VERTEX_PROJECT_ID and authenticate with Google ADC"
336
- : info?.hasOAuth
364
+ : info && supportsBrowserOAuth(info)
337
365
  ? `Run /gsd keys to authenticate`
338
366
  : `Set ${envVar} or run /gsd keys`,
339
367
  required: true,
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { existsSync } from "node:fs";
6
- import { join } from "node:path";
6
+ import { dirname, join, resolve } from "node:path";
7
7
 
8
8
  import { autoResolveSafeConflictPaths } from "./git-conflict-resolve.js";
9
9
  import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
@@ -14,6 +14,21 @@ function splitZeroDelimited(output: string): string[] {
14
14
  return output.split("\0").filter(Boolean);
15
15
  }
16
16
 
17
+ function hasGitMarker(basePath: string): boolean {
18
+ try {
19
+ let dir = resolve(basePath);
20
+ for (let i = 0; i < 30; i++) {
21
+ if (existsSync(join(dir, ".git"))) return true;
22
+ const parent = dirname(dir);
23
+ if (parent === dir) break;
24
+ dir = parent;
25
+ }
26
+ } catch {
27
+ // Fall through to the git probes, which will report unknown on failure.
28
+ }
29
+ return false;
30
+ }
31
+
17
32
  export function listUnmergedGitPaths(basePath: string): string[] | null {
18
33
  try {
19
34
  const output = spawnSync("git", ["diff", "--name-only", "--diff-filter=U", "-z"], {
@@ -75,6 +90,15 @@ export interface GitConflictProbeResult {
75
90
  }
76
91
 
77
92
  export function probeGitConflictState(basePath: string): GitConflictProbeResult {
93
+ if (!hasGitMarker(basePath)) {
94
+ return {
95
+ status: "clean",
96
+ unmerged: [],
97
+ checkFailures: [],
98
+ mergeStateBlockers: [],
99
+ };
100
+ }
101
+
78
102
  const unmerged = listUnmergedGitPaths(basePath);
79
103
  if (unmerged === null) {
80
104
  return {
@@ -1358,7 +1358,7 @@ function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, _basePa
1358
1358
  * Run preparation phase if enabled, then build the discuss prompt.
1359
1359
  * Preparation analyzes the codebase and prior context, injecting the results
1360
1360
  * as supplementary context into the standard discuss template. The discuss
1361
- * template drives the conversation (asks "What's the vision?" first), while
1361
+ * template drives the conversation with a variable vision opener, while
1362
1362
  * the preparation briefs give the agent grounding in the existing codebase.
1363
1363
  *
1364
1364
  * @param ctx - Extension command context with UI for progress notifications