@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
@@ -0,0 +1,57 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { stripVTControlCharacters } from "node:util";
4
+
5
+ import { visibleWidth } from "@gsd/pi-tui";
6
+
7
+ import { showConfirm } from "../confirm-ui.js";
8
+
9
+ function assertFullOuterBorder(lines: string[], width: number): void {
10
+ assert.ok(lines.length >= 2, "dialog must include top and bottom borders");
11
+ for (const [index, line] of lines.entries()) {
12
+ assert.equal(visibleWidth(line), width, `line ${index} must fill dialog width`);
13
+ }
14
+ const top = stripVTControlCharacters(lines[0] ?? "");
15
+ const bottom = stripVTControlCharacters(lines.at(-1) ?? "");
16
+ assert.match(top, /^[╭┌].*[╮┐]$/);
17
+ assert.match(bottom, /^[╰└].*[╯┘]$/);
18
+ for (let index = 1; index < lines.length - 1; index++) {
19
+ const line = stripVTControlCharacters(lines[index] ?? "");
20
+ assert.match(line, /^[│┃├]/, `line ${index} missing left border: ${line}`);
21
+ assert.match(line, /[│┃┤]$/, `line ${index} missing right border: ${line}`);
22
+ }
23
+ }
24
+
25
+ describe("showConfirm", () => {
26
+ it("renders the confirmation prompt inside a full border", async () => {
27
+ let rendered: string[] = [];
28
+ const theme = {
29
+ fg: (_color: string, text: string) => text,
30
+ bold: (text: string) => text,
31
+ };
32
+
33
+ const ctx = {
34
+ hasUI: true,
35
+ ui: {
36
+ custom: async (factory: any) => {
37
+ let resolved: boolean | undefined;
38
+ const component = factory({ requestRender() {} }, theme, null, (value: boolean) => {
39
+ resolved = value;
40
+ });
41
+ rendered = component.render(80);
42
+ component.handleInput("\r");
43
+ return resolved as never;
44
+ },
45
+ },
46
+ };
47
+
48
+ const result = await showConfirm(ctx as any, {
49
+ title: "Confirm action",
50
+ message: "Proceed with the change?",
51
+ });
52
+
53
+ assert.equal(result, true);
54
+ assertFullOuterBorder(rendered, 80);
55
+ assert.match(stripVTControlCharacters(rendered[0] ?? ""), /Confirm action/);
56
+ });
57
+ });
@@ -0,0 +1,163 @@
1
+ // gsd-pi — Shared interview UI dialog border contract
2
+
3
+ import assert from "node:assert/strict";
4
+ import { before, describe, it } from "node:test";
5
+
6
+ import { visibleWidth } from "@gsd/pi-tui";
7
+ import { initTheme } from "@gsd/pi-coding-agent";
8
+ import {
9
+ showInterviewRound,
10
+ showWrapUpScreen,
11
+ type Question,
12
+ type RoundResult,
13
+ type WrapUpResult,
14
+ } from "../interview-ui.js";
15
+
16
+ const ANSI_PATTERN = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
17
+ const ENTER = "\r";
18
+ const ESC = "\x1b";
19
+
20
+ before(() => { initTheme(); });
21
+
22
+ type RenderWidget = {
23
+ render(width: number): string[];
24
+ handleInput(input: string): void;
25
+ };
26
+
27
+ function stripAnsi(text: string): string {
28
+ return text.replace(ANSI_PATTERN, "");
29
+ }
30
+
31
+ function assertFullOuterBorder(lines: string[], width: number): void {
32
+ assert.ok(lines.length >= 2, "dialog must include top and bottom borders");
33
+
34
+ for (const [index, line] of lines.entries()) {
35
+ assert.equal(visibleWidth(line), width, `line ${index} must fill dialog width`);
36
+ }
37
+
38
+ const top = stripAnsi(lines[0] ?? "");
39
+ const bottom = stripAnsi(lines.at(-1) ?? "");
40
+ assert.match(top, /^╭.*╮$/, `top border missing full corners: ${top}`);
41
+ assert.match(bottom, /^╰.*╯$/, `bottom border missing full corners: ${bottom}`);
42
+
43
+ for (let index = 1; index < lines.length - 1; index++) {
44
+ const line = stripAnsi(lines[index] ?? "");
45
+ assert.match(line, /^[│├]/, `line ${index} missing left border: ${line}`);
46
+ assert.match(line, /[│┤]$/, `line ${index} missing right border: ${line}`);
47
+ }
48
+ }
49
+
50
+ function mockTheme() {
51
+ return {
52
+ fg: (_color: string, text: string) => text,
53
+ bold: (text: string) => text,
54
+ dim: (text: string) => text,
55
+ italic: (text: string) => text,
56
+ strikethrough: (text: string) => text,
57
+ accent: (text: string) => text,
58
+ success: (text: string) => text,
59
+ warning: (text: string) => text,
60
+ error: (text: string) => text,
61
+ info: (text: string) => text,
62
+ muted: (text: string) => text,
63
+ dimmed: (text: string) => text,
64
+ };
65
+ }
66
+
67
+ async function captureInterviewWidget(
68
+ questions: Question[],
69
+ ): Promise<RenderWidget> {
70
+ let widget: RenderWidget | undefined;
71
+
72
+ await showInterviewRound(questions, {}, {
73
+ ui: {
74
+ custom: (factory: any) => {
75
+ widget = factory(
76
+ { requestRender: () => {} },
77
+ mockTheme(),
78
+ {},
79
+ (_result: RoundResult) => {},
80
+ );
81
+ return Promise.resolve({ endInterview: false, answers: {} });
82
+ },
83
+ },
84
+ } as any);
85
+
86
+ assert.ok(widget, "interview widget should be created");
87
+ return widget;
88
+ }
89
+
90
+ async function captureWrapUpWidget(): Promise<RenderWidget> {
91
+ let widget: RenderWidget | undefined;
92
+
93
+ await showWrapUpScreen({
94
+ headline: "Ready to wrap up?",
95
+ progress: "4 questions answered",
96
+ keepGoingLabel: "Keep going",
97
+ satisfiedLabel: "I'm satisfied",
98
+ }, {
99
+ ui: {
100
+ custom: (factory: any) => {
101
+ widget = factory(
102
+ { requestRender: () => {} },
103
+ mockTheme(),
104
+ {},
105
+ (_result: WrapUpResult) => {},
106
+ );
107
+ return Promise.resolve({ satisfied: false });
108
+ },
109
+ },
110
+ } as any);
111
+
112
+ assert.ok(widget, "wrap-up widget should be created");
113
+ return widget;
114
+ }
115
+
116
+ describe("interview-ui dialog borders", () => {
117
+ const questions: Question[] = [
118
+ {
119
+ id: "project_type",
120
+ header: "Project Type",
121
+ question: "What type of project?",
122
+ options: [
123
+ { label: "Web App", description: "Frontend or full-stack" },
124
+ { label: "CLI Tool", description: "Command-line utility" },
125
+ ],
126
+ },
127
+ ];
128
+
129
+ it("renders the main question screen with a full border", async () => {
130
+ const widget = await captureInterviewWidget(questions);
131
+ assertFullOuterBorder(widget.render(80), 80);
132
+ });
133
+
134
+ it("renders the preview split screen with a full border", async () => {
135
+ const widget = await captureInterviewWidget([{
136
+ ...questions[0],
137
+ options: [
138
+ {
139
+ label: "Web App",
140
+ description: "Frontend or full-stack",
141
+ preview: "### Stack\n\nUse React with a typed API boundary.",
142
+ },
143
+ ],
144
+ }]);
145
+ assertFullOuterBorder(widget.render(100), 100);
146
+ });
147
+
148
+ it("renders review and exit confirmation screens with full borders", async () => {
149
+ const widget = await captureInterviewWidget(questions);
150
+
151
+ widget.handleInput(ENTER);
152
+ assertFullOuterBorder(widget.render(80), 80);
153
+
154
+ const exitWidget = await captureInterviewWidget(questions);
155
+ exitWidget.handleInput(ESC);
156
+ assertFullOuterBorder(exitWidget.render(80), 80);
157
+ });
158
+
159
+ it("renders the wrap-up screen with a full border", async () => {
160
+ const widget = await captureWrapUpWidget();
161
+ assertFullOuterBorder(widget.render(80), 80);
162
+ });
163
+ });
@@ -15,9 +15,27 @@
15
15
 
16
16
  import { describe, it } from "node:test";
17
17
  import assert from "node:assert/strict";
18
+ import { stripVTControlCharacters } from "node:util";
19
+ import { visibleWidth } from "@gsd/pi-tui";
18
20
 
19
21
  import { showNextAction } from "../next-action-ui.js";
20
22
 
23
+ function assertFullOuterBorder(lines: string[], width: number): void {
24
+ assert.ok(lines.length >= 2, "dialog must include top and bottom borders");
25
+ for (const [index, line] of lines.entries()) {
26
+ assert.equal(visibleWidth(line), width, `line ${index} must fill dialog width`);
27
+ }
28
+ const top = stripVTControlCharacters(lines[0] ?? "");
29
+ const bottom = stripVTControlCharacters(lines.at(-1) ?? "");
30
+ assert.match(top, /^[╭┌].*[╮┐]$/);
31
+ assert.match(bottom, /^[╰└].*[╯┘]$/);
32
+ for (let index = 1; index < lines.length - 1; index++) {
33
+ const line = stripVTControlCharacters(lines[index] ?? "");
34
+ assert.match(line, /^[│┃├]/, `line ${index} missing left border: ${line}`);
35
+ assert.match(line, /[│┃┤]$/, `line ${index} missing right border: ${line}`);
36
+ }
37
+ }
38
+
21
39
  describe("showNextAction ctx.hasUI guard (#5125 lockup root protection)", () => {
22
40
  it("returns 'not_yet' immediately when ctx.hasUI is false (no UI calls)", async () => {
23
41
  let customCalled = 0;
@@ -152,4 +170,41 @@ describe("showNextAction ctx.hasUI guard (#5125 lockup root protection)", () =>
152
170
  assert.equal(result, "beta", "TUI selection should be returned verbatim");
153
171
  assert.equal(selectCalled, 0, "ctx.ui.select fallback must NOT fire when custom returns a value");
154
172
  });
173
+
174
+ it("renders the interactive next-action menu inside a full border", async () => {
175
+ let rendered: string[] = [];
176
+ const theme = {
177
+ fg: (_color: string, text: string) => text,
178
+ bold: (text: string) => text,
179
+ };
180
+
181
+ const ctx = {
182
+ hasUI: true,
183
+ ui: {
184
+ custom: async (factory: any) => {
185
+ let resolved: string | undefined;
186
+ const component = factory({ requestRender() {} }, theme, null, (value: string) => {
187
+ resolved = value;
188
+ });
189
+ rendered = component.render(80);
190
+ component.handleInput("\r");
191
+ return resolved as never;
192
+ },
193
+ select: async () => undefined,
194
+ },
195
+ };
196
+
197
+ const result = await showNextAction(ctx as any, {
198
+ title: "GSD — test",
199
+ summary: ["summary"],
200
+ actions: [
201
+ { id: "alpha", label: "Alpha", description: "first", recommended: true },
202
+ { id: "beta", label: "Beta", description: "second" },
203
+ ],
204
+ });
205
+
206
+ assert.equal(result, "alpha");
207
+ assertFullOuterBorder(rendered, 80);
208
+ assert.match(stripVTControlCharacters(rendered[0] ?? ""), /GSD Next Action/);
209
+ });
155
210
  });
@@ -0,0 +1,39 @@
1
+ import { homedir } from 'node:os'
2
+ import { join, resolve as resolvePath, sep } from 'node:path'
3
+
4
+ function hasPnpmPath(value: string | undefined): boolean {
5
+ if (!value) return false
6
+ const normalized = value.replace(/\\/g, '/').toLowerCase()
7
+ return (
8
+ normalized.includes('/.pnpm/') ||
9
+ normalized.endsWith('/pnpm') ||
10
+ normalized.endsWith('/pnpm.cjs') ||
11
+ normalized.endsWith('/pnpm.js')
12
+ )
13
+ }
14
+
15
+ function pathStartsWith(pathValue: string | undefined, dir: string): boolean {
16
+ if (!pathValue) return false
17
+ const resolvedPath = resolvePath(pathValue)
18
+ const resolvedDir = resolvePath(dir)
19
+ return resolvedPath === resolvedDir || resolvedPath.startsWith(resolvedDir + sep)
20
+ }
21
+
22
+ // Shared by update-check.ts and gsd command handlers. The JS installer keeps a
23
+ // parallel copy because it runs before TypeScript output exists.
24
+ export function isPnpmInstall(
25
+ argv1: string | undefined = process.argv[1],
26
+ env: NodeJS.ProcessEnv = process.env,
27
+ ): boolean {
28
+ if (env.npm_config_user_agent?.startsWith('pnpm/')) return true
29
+ if (hasPnpmPath(env.npm_execpath)) return true
30
+ if (hasPnpmPath(argv1)) return true
31
+ if (!argv1) return false
32
+
33
+ const pnpmBinDirs: string[] = []
34
+ if (env.PNPM_HOME) pnpmBinDirs.push(env.PNPM_HOME)
35
+ pnpmBinDirs.push(join(homedir(), 'Library', 'pnpm'))
36
+ pnpmBinDirs.push(join(homedir(), '.local', 'share', 'pnpm'))
37
+
38
+ return pnpmBinDirs.some((dir) => pathStartsWith(argv1, dir) || pathStartsWith(env.npm_execpath, dir))
39
+ }