@tuan_son.dinh/gsd 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +453 -0
  3. package/dist/app-paths.d.ts +4 -0
  4. package/dist/app-paths.js +6 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +269 -0
  7. package/dist/loader.d.ts +2 -0
  8. package/dist/loader.js +70 -0
  9. package/dist/logo.d.ts +16 -0
  10. package/dist/logo.js +25 -0
  11. package/dist/onboarding.d.ts +43 -0
  12. package/dist/onboarding.js +418 -0
  13. package/dist/pi-migration.d.ts +14 -0
  14. package/dist/pi-migration.js +57 -0
  15. package/dist/resource-loader.d.ts +22 -0
  16. package/dist/resource-loader.js +60 -0
  17. package/dist/tool-bootstrap.d.ts +4 -0
  18. package/dist/tool-bootstrap.js +74 -0
  19. package/dist/wizard.d.ts +7 -0
  20. package/dist/wizard.js +25 -0
  21. package/package.json +60 -0
  22. package/patches/@mariozechner+pi-coding-agent+0.57.1.patch +108 -0
  23. package/patches/@mariozechner+pi-tui+0.57.1.patch +47 -0
  24. package/pkg/dist/modes/interactive/theme/dark.json +85 -0
  25. package/pkg/dist/modes/interactive/theme/light.json +84 -0
  26. package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
  27. package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
  28. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  29. package/pkg/dist/modes/interactive/theme/theme.js +949 -0
  30. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
  31. package/pkg/package.json +8 -0
  32. package/scripts/postinstall.js +127 -0
  33. package/src/resources/GSD-WORKFLOW.md +661 -0
  34. package/src/resources/agents/researcher.md +29 -0
  35. package/src/resources/agents/scout.md +56 -0
  36. package/src/resources/agents/worker.md +31 -0
  37. package/src/resources/extensions/ask-user-questions.ts +249 -0
  38. package/src/resources/extensions/bg-shell/index.ts +2808 -0
  39. package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  40. package/src/resources/extensions/browser-tools/core.js +1057 -0
  41. package/src/resources/extensions/browser-tools/index.ts +4989 -0
  42. package/src/resources/extensions/browser-tools/package.json +20 -0
  43. package/src/resources/extensions/context7/index.ts +428 -0
  44. package/src/resources/extensions/context7/package.json +11 -0
  45. package/src/resources/extensions/get-secrets-from-user.ts +352 -0
  46. package/src/resources/extensions/google-search/index.ts +323 -0
  47. package/src/resources/extensions/google-search/package.json +9 -0
  48. package/src/resources/extensions/gsd/activity-log.ts +69 -0
  49. package/src/resources/extensions/gsd/auto.ts +2744 -0
  50. package/src/resources/extensions/gsd/commands.ts +313 -0
  51. package/src/resources/extensions/gsd/crash-recovery.ts +85 -0
  52. package/src/resources/extensions/gsd/dashboard-overlay.ts +521 -0
  53. package/src/resources/extensions/gsd/docs/preferences-reference.md +176 -0
  54. package/src/resources/extensions/gsd/doctor.ts +690 -0
  55. package/src/resources/extensions/gsd/files.ts +732 -0
  56. package/src/resources/extensions/gsd/git-service.ts +597 -0
  57. package/src/resources/extensions/gsd/gitignore.ts +168 -0
  58. package/src/resources/extensions/gsd/guided-flow.ts +817 -0
  59. package/src/resources/extensions/gsd/index.ts +558 -0
  60. package/src/resources/extensions/gsd/metrics.ts +374 -0
  61. package/src/resources/extensions/gsd/migrate/command.ts +218 -0
  62. package/src/resources/extensions/gsd/migrate/index.ts +42 -0
  63. package/src/resources/extensions/gsd/migrate/parser.ts +323 -0
  64. package/src/resources/extensions/gsd/migrate/parsers.ts +624 -0
  65. package/src/resources/extensions/gsd/migrate/preview.ts +48 -0
  66. package/src/resources/extensions/gsd/migrate/transformer.ts +346 -0
  67. package/src/resources/extensions/gsd/migrate/types.ts +370 -0
  68. package/src/resources/extensions/gsd/migrate/validator.ts +55 -0
  69. package/src/resources/extensions/gsd/migrate/writer.ts +539 -0
  70. package/src/resources/extensions/gsd/observability-validator.ts +408 -0
  71. package/src/resources/extensions/gsd/package.json +11 -0
  72. package/src/resources/extensions/gsd/paths.ts +308 -0
  73. package/src/resources/extensions/gsd/preferences.ts +757 -0
  74. package/src/resources/extensions/gsd/prompt-loader.ts +50 -0
  75. package/src/resources/extensions/gsd/prompts/complete-milestone.md +25 -0
  76. package/src/resources/extensions/gsd/prompts/complete-slice.md +29 -0
  77. package/src/resources/extensions/gsd/prompts/discuss.md +189 -0
  78. package/src/resources/extensions/gsd/prompts/doctor-heal.md +29 -0
  79. package/src/resources/extensions/gsd/prompts/execute-task.md +61 -0
  80. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -0
  81. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -0
  82. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +59 -0
  83. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -0
  84. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +23 -0
  85. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -0
  86. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +11 -0
  87. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -0
  88. package/src/resources/extensions/gsd/prompts/plan-milestone.md +65 -0
  89. package/src/resources/extensions/gsd/prompts/plan-slice.md +51 -0
  90. package/src/resources/extensions/gsd/prompts/queue.md +85 -0
  91. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +48 -0
  92. package/src/resources/extensions/gsd/prompts/replan-slice.md +39 -0
  93. package/src/resources/extensions/gsd/prompts/research-milestone.md +37 -0
  94. package/src/resources/extensions/gsd/prompts/research-slice.md +28 -0
  95. package/src/resources/extensions/gsd/prompts/review-migration.md +66 -0
  96. package/src/resources/extensions/gsd/prompts/run-uat.md +109 -0
  97. package/src/resources/extensions/gsd/prompts/system.md +187 -0
  98. package/src/resources/extensions/gsd/prompts/worktree-merge.md +123 -0
  99. package/src/resources/extensions/gsd/session-forensics.ts +487 -0
  100. package/src/resources/extensions/gsd/skill-discovery.ts +137 -0
  101. package/src/resources/extensions/gsd/state.ts +460 -0
  102. package/src/resources/extensions/gsd/templates/context.md +76 -0
  103. package/src/resources/extensions/gsd/templates/decisions.md +8 -0
  104. package/src/resources/extensions/gsd/templates/milestone-summary.md +73 -0
  105. package/src/resources/extensions/gsd/templates/plan.md +131 -0
  106. package/src/resources/extensions/gsd/templates/preferences.md +24 -0
  107. package/src/resources/extensions/gsd/templates/project.md +31 -0
  108. package/src/resources/extensions/gsd/templates/reassessment.md +28 -0
  109. package/src/resources/extensions/gsd/templates/requirements.md +81 -0
  110. package/src/resources/extensions/gsd/templates/research.md +46 -0
  111. package/src/resources/extensions/gsd/templates/roadmap.md +118 -0
  112. package/src/resources/extensions/gsd/templates/slice-context.md +58 -0
  113. package/src/resources/extensions/gsd/templates/slice-summary.md +99 -0
  114. package/src/resources/extensions/gsd/templates/state.md +19 -0
  115. package/src/resources/extensions/gsd/templates/task-plan.md +52 -0
  116. package/src/resources/extensions/gsd/templates/task-summary.md +57 -0
  117. package/src/resources/extensions/gsd/templates/uat.md +54 -0
  118. package/src/resources/extensions/gsd/tests/activity-log-prune.test.ts +327 -0
  119. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +56 -0
  120. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +53 -0
  121. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +225 -0
  122. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +160 -0
  123. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +341 -0
  124. package/src/resources/extensions/gsd/tests/derive-state.test.ts +689 -0
  125. package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +38 -0
  126. package/src/resources/extensions/gsd/tests/doctor.test.ts +505 -0
  127. package/src/resources/extensions/gsd/tests/git-service.test.ts +1313 -0
  128. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +308 -0
  129. package/src/resources/extensions/gsd/tests/metrics-io.test.ts +201 -0
  130. package/src/resources/extensions/gsd/tests/metrics.test.ts +217 -0
  131. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +390 -0
  132. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +786 -0
  133. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +657 -0
  134. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +443 -0
  135. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +318 -0
  136. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +420 -0
  137. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +309 -0
  138. package/src/resources/extensions/gsd/tests/parsers.test.ts +1351 -0
  139. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +163 -0
  140. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +386 -0
  141. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +171 -0
  142. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +155 -0
  143. package/src/resources/extensions/gsd/tests/remote-status.test.ts +99 -0
  144. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +521 -0
  145. package/src/resources/extensions/gsd/tests/requirements.test.ts +125 -0
  146. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +34 -0
  147. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +11 -0
  148. package/src/resources/extensions/gsd/tests/run-uat.test.ts +348 -0
  149. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +247 -0
  150. package/src/resources/extensions/gsd/tests/workflow-config.test.mjs +53 -0
  151. package/src/resources/extensions/gsd/tests/workspace-index.test.ts +94 -0
  152. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +253 -0
  153. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +160 -0
  154. package/src/resources/extensions/gsd/tests/worktree.test.ts +264 -0
  155. package/src/resources/extensions/gsd/types.ts +159 -0
  156. package/src/resources/extensions/gsd/unit-runtime.ts +184 -0
  157. package/src/resources/extensions/gsd/workspace-index.ts +203 -0
  158. package/src/resources/extensions/gsd/worktree-command.ts +845 -0
  159. package/src/resources/extensions/gsd/worktree-manager.ts +392 -0
  160. package/src/resources/extensions/gsd/worktree.ts +183 -0
  161. package/src/resources/extensions/mac-tools/index.ts +852 -0
  162. package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
  163. package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
  164. package/src/resources/extensions/mcporter/index.ts +429 -0
  165. package/src/resources/extensions/remote-questions/config.ts +81 -0
  166. package/src/resources/extensions/remote-questions/discord-adapter.ts +128 -0
  167. package/src/resources/extensions/remote-questions/format.ts +163 -0
  168. package/src/resources/extensions/remote-questions/manager.ts +192 -0
  169. package/src/resources/extensions/remote-questions/remote-command.ts +307 -0
  170. package/src/resources/extensions/remote-questions/slack-adapter.ts +92 -0
  171. package/src/resources/extensions/remote-questions/status.ts +31 -0
  172. package/src/resources/extensions/remote-questions/store.ts +77 -0
  173. package/src/resources/extensions/remote-questions/types.ts +75 -0
  174. package/src/resources/extensions/search-the-web/cache.ts +78 -0
  175. package/src/resources/extensions/search-the-web/command-search-provider.ts +95 -0
  176. package/src/resources/extensions/search-the-web/format.ts +258 -0
  177. package/src/resources/extensions/search-the-web/http.ts +238 -0
  178. package/src/resources/extensions/search-the-web/index.ts +65 -0
  179. package/src/resources/extensions/search-the-web/native-search.ts +157 -0
  180. package/src/resources/extensions/search-the-web/provider.ts +118 -0
  181. package/src/resources/extensions/search-the-web/tavily.ts +116 -0
  182. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
  183. package/src/resources/extensions/search-the-web/tool-llm-context.ts +561 -0
  184. package/src/resources/extensions/search-the-web/tool-search.ts +576 -0
  185. package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
  186. package/src/resources/extensions/shared/confirm-ui.ts +126 -0
  187. package/src/resources/extensions/shared/interview-ui.ts +613 -0
  188. package/src/resources/extensions/shared/next-action-ui.ts +197 -0
  189. package/src/resources/extensions/shared/progress-widget.ts +282 -0
  190. package/src/resources/extensions/shared/terminal.ts +23 -0
  191. package/src/resources/extensions/shared/thinking-widget.ts +107 -0
  192. package/src/resources/extensions/shared/ui.ts +400 -0
  193. package/src/resources/extensions/shared/wizard-ui.ts +551 -0
  194. package/src/resources/extensions/slash-commands/audit.ts +88 -0
  195. package/src/resources/extensions/slash-commands/clear.ts +10 -0
  196. package/src/resources/extensions/slash-commands/create-extension.ts +297 -0
  197. package/src/resources/extensions/slash-commands/create-slash-command.ts +234 -0
  198. package/src/resources/extensions/slash-commands/index.ts +12 -0
  199. package/src/resources/extensions/subagent/agents.ts +126 -0
  200. package/src/resources/extensions/subagent/index.ts +1020 -0
  201. package/src/resources/extensions/voice/index.ts +195 -0
  202. package/src/resources/extensions/voice/speech-recognizer.swift +154 -0
  203. package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
  204. package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
  205. package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
  206. package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
  207. package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
  208. package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
  209. package/src/resources/skills/frontend-design/SKILL.md +45 -0
  210. package/src/resources/skills/swiftui/SKILL.md +208 -0
  211. package/src/resources/skills/swiftui/references/animations.md +921 -0
  212. package/src/resources/skills/swiftui/references/architecture.md +1561 -0
  213. package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
  214. package/src/resources/skills/swiftui/references/navigation.md +1492 -0
  215. package/src/resources/skills/swiftui/references/networking-async.md +214 -0
  216. package/src/resources/skills/swiftui/references/performance.md +1706 -0
  217. package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
  218. package/src/resources/skills/swiftui/references/state-management.md +1443 -0
  219. package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
  220. package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
  221. package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
  222. package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
  223. package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
  224. package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
  225. package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
  226. package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
  227. package/src/resources/skills/swiftui/workflows/write-tests.md +235 -0
@@ -0,0 +1,418 @@
1
+ /**
2
+ * Unified first-run onboarding wizard.
3
+ *
4
+ * Replaces the raw API-key-only wizard with a branded, clack-based experience
5
+ * that guides users through LLM provider authentication before the TUI launches.
6
+ *
7
+ * Flow: logo -> choose LLM provider -> authenticate (OAuth or API key) ->
8
+ * optional tool keys -> summary -> TUI launches.
9
+ *
10
+ * All steps are skippable. All errors are recoverable. Never crashes boot.
11
+ */
12
+ import { exec } from 'node:child_process';
13
+ import { renderLogo } from './logo.js';
14
+ // ─── Constants ────────────────────────────────────────────────────────────────
15
+ const TOOL_KEYS = [
16
+ {
17
+ provider: 'brave',
18
+ envVar: 'BRAVE_API_KEY',
19
+ label: 'Brave Search',
20
+ hint: 'web search + search_and_read tools',
21
+ },
22
+ {
23
+ provider: 'brave_answers',
24
+ envVar: 'BRAVE_ANSWERS_KEY',
25
+ label: 'Brave Answers',
26
+ hint: 'AI-summarised search answers',
27
+ },
28
+ {
29
+ provider: 'context7',
30
+ envVar: 'CONTEXT7_API_KEY',
31
+ label: 'Context7',
32
+ hint: 'up-to-date library docs',
33
+ },
34
+ {
35
+ provider: 'jina',
36
+ envVar: 'JINA_API_KEY',
37
+ label: 'Jina AI',
38
+ hint: 'clean web page extraction',
39
+ },
40
+ {
41
+ provider: 'slack_bot',
42
+ envVar: 'SLACK_BOT_TOKEN',
43
+ label: 'Slack Bot',
44
+ hint: 'remote questions in auto-mode',
45
+ },
46
+ {
47
+ provider: 'discord_bot',
48
+ envVar: 'DISCORD_BOT_TOKEN',
49
+ label: 'Discord Bot',
50
+ hint: 'remote questions in auto-mode',
51
+ },
52
+ ];
53
+ /** Known LLM provider IDs that, if authed, mean the user doesn't need onboarding */
54
+ const LLM_PROVIDER_IDS = [
55
+ 'anthropic',
56
+ 'openai',
57
+ 'github-copilot',
58
+ 'openai-codex',
59
+ 'google-gemini-cli',
60
+ 'google-antigravity',
61
+ 'google',
62
+ 'groq',
63
+ 'xai',
64
+ 'openrouter',
65
+ 'mistral',
66
+ ];
67
+ /** API key prefix validation — loose checks to catch obvious mistakes */
68
+ const API_KEY_PREFIXES = {
69
+ anthropic: ['sk-ant-'],
70
+ openai: ['sk-'],
71
+ };
72
+ const OTHER_PROVIDERS = [
73
+ { value: 'google', label: 'Google (Gemini)' },
74
+ { value: 'groq', label: 'Groq' },
75
+ { value: 'xai', label: 'xAI (Grok)' },
76
+ { value: 'openrouter', label: 'OpenRouter' },
77
+ { value: 'mistral', label: 'Mistral' },
78
+ ];
79
+ // ─── Dynamic imports ──────────────────────────────────────────────────────────
80
+ /**
81
+ * Dynamically import @clack/prompts and picocolors.
82
+ * Dynamic import with fallback so the module doesn't crash if they're missing.
83
+ */
84
+ async function loadClack() {
85
+ try {
86
+ return await import('@clack/prompts');
87
+ }
88
+ catch {
89
+ throw new Error('[gsd] @clack/prompts not found — onboarding wizard requires this dependency');
90
+ }
91
+ }
92
+ async function loadPico() {
93
+ try {
94
+ const mod = await import('picocolors');
95
+ return mod.default ?? mod;
96
+ }
97
+ catch {
98
+ // Fallback: return identity functions
99
+ const identity = (s) => s;
100
+ return { cyan: identity, green: identity, yellow: identity, dim: identity, bold: identity, red: identity, reset: identity };
101
+ }
102
+ }
103
+ // ─── Utilities ────────────────────────────────────────────────────────────────
104
+ /** Open a URL in the system browser (best-effort, non-blocking) */
105
+ function openBrowser(url) {
106
+ const cmd = process.platform === 'darwin' ? 'open' :
107
+ process.platform === 'win32' ? 'start' :
108
+ 'xdg-open';
109
+ exec(`${cmd} "${url}"`, () => {
110
+ // Ignore errors — user can manually open the URL
111
+ });
112
+ }
113
+ /** Check if an error is a clack cancel signal */
114
+ function isCancelError(p, err) {
115
+ return p.isCancel(err);
116
+ }
117
+ // ─── Public API ───────────────────────────────────────────────────────────────
118
+ /**
119
+ * Determine if the onboarding wizard should run.
120
+ *
121
+ * Returns true when:
122
+ * - No LLM provider has credentials in authStorage
123
+ * - We're on a TTY (interactive terminal)
124
+ *
125
+ * Returns false (skip wizard) when:
126
+ * - Any LLM provider is already authed (returning user)
127
+ * - Not a TTY (piped input, subagent, CI)
128
+ */
129
+ export function shouldRunOnboarding(authStorage) {
130
+ if (!process.stdin.isTTY)
131
+ return false;
132
+ // Check if any LLM provider has credentials
133
+ const authedProviders = authStorage.list();
134
+ const hasLlmAuth = authedProviders.some(id => LLM_PROVIDER_IDS.includes(id));
135
+ return !hasLlmAuth;
136
+ }
137
+ /**
138
+ * Run the unified onboarding wizard.
139
+ *
140
+ * Walks the user through:
141
+ * 1. Choose LLM provider
142
+ * 2. Authenticate (OAuth or API key)
143
+ * 3. Optional tool API keys
144
+ * 4. Summary
145
+ *
146
+ * All steps are skippable. All errors are recoverable.
147
+ * Writes status to stderr during execution.
148
+ */
149
+ export async function runOnboarding(authStorage) {
150
+ let p;
151
+ let pc;
152
+ try {
153
+ ;
154
+ [p, pc] = await Promise.all([loadClack(), loadPico()]);
155
+ }
156
+ catch (err) {
157
+ // If clack isn't available, fall back silently — don't block boot
158
+ process.stderr.write(`[gsd] Onboarding wizard unavailable: ${err instanceof Error ? err.message : String(err)}\n`);
159
+ return;
160
+ }
161
+ // ── Intro ─────────────────────────────────────────────────────────────────
162
+ process.stderr.write(renderLogo(pc.cyan));
163
+ p.intro(pc.bold('Welcome to GSD — let\'s get you set up'));
164
+ // ── LLM Provider Selection ────────────────────────────────────────────────
165
+ let llmConfigured = false;
166
+ try {
167
+ llmConfigured = await runLlmStep(p, pc, authStorage);
168
+ }
169
+ catch (err) {
170
+ // User cancelled (Ctrl+C in clack throws) or unexpected error
171
+ if (isCancelError(p, err)) {
172
+ p.cancel('Setup cancelled — you can run /login inside GSD later.');
173
+ return;
174
+ }
175
+ p.log.warn(`LLM setup failed: ${err instanceof Error ? err.message : String(err)}`);
176
+ p.log.info('You can configure your LLM provider later with /login inside GSD.');
177
+ }
178
+ // ── Tool API Keys ─────────────────────────────────────────────────────────
179
+ let toolKeyCount = 0;
180
+ try {
181
+ toolKeyCount = await runToolKeysStep(p, pc, authStorage);
182
+ }
183
+ catch (err) {
184
+ if (isCancelError(p, err)) {
185
+ p.cancel('Setup cancelled.');
186
+ return;
187
+ }
188
+ p.log.warn(`Tool key setup failed: ${err instanceof Error ? err.message : String(err)}`);
189
+ }
190
+ // ── Summary ───────────────────────────────────────────────────────────────
191
+ const summaryLines = [];
192
+ if (llmConfigured) {
193
+ // Re-read what provider was stored
194
+ const authed = authStorage.list().filter(id => LLM_PROVIDER_IDS.includes(id));
195
+ if (authed.length > 0) {
196
+ const name = authed[0];
197
+ summaryLines.push(`${pc.green('✓')} LLM provider: ${name}`);
198
+ }
199
+ else {
200
+ summaryLines.push(`${pc.green('✓')} LLM provider configured`);
201
+ }
202
+ }
203
+ else {
204
+ summaryLines.push(`${pc.yellow('↷')} LLM provider: skipped — use /login inside GSD`);
205
+ }
206
+ if (toolKeyCount > 0) {
207
+ summaryLines.push(`${pc.green('✓')} ${toolKeyCount} tool key${toolKeyCount > 1 ? 's' : ''} saved`);
208
+ }
209
+ else {
210
+ summaryLines.push(`${pc.dim('↷')} Tool keys: none configured`);
211
+ }
212
+ p.note(summaryLines.join('\n'), 'Setup complete');
213
+ p.outro(pc.dim('Launching GSD...'));
214
+ }
215
+ // ─── LLM Authentication Step ──────────────────────────────────────────────────
216
+ async function runLlmStep(p, pc, authStorage) {
217
+ // Build the OAuth provider list dynamically from what's registered
218
+ const oauthProviders = authStorage.getOAuthProviders();
219
+ const oauthMap = new Map(oauthProviders.map(op => [op.id, op]));
220
+ const choice = await p.select({
221
+ message: 'Choose your LLM provider',
222
+ options: [
223
+ { value: 'anthropic-oauth', label: 'Anthropic — Claude (OAuth login)', hint: 'recommended' },
224
+ { value: 'anthropic-api-key', label: 'Anthropic — Claude (API key)' },
225
+ { value: 'openai-api-key', label: 'OpenAI (API key)' },
226
+ { value: 'github-copilot-oauth', label: 'GitHub Copilot (OAuth login)' },
227
+ { value: 'openai-codex-oauth', label: 'ChatGPT Plus/Pro — Codex (OAuth login)' },
228
+ { value: 'google-gemini-cli-oauth', label: 'Google Gemini CLI (OAuth login)' },
229
+ { value: 'google-antigravity-oauth', label: 'Antigravity — Gemini 3, Claude, GPT-OSS (OAuth login)' },
230
+ { value: 'other-api-key', label: 'Other provider (API key)' },
231
+ { value: 'skip', label: 'Skip for now', hint: 'use /login inside GSD later' },
232
+ ],
233
+ });
234
+ if (p.isCancel(choice) || choice === 'skip')
235
+ return false;
236
+ // ── OAuth flows ───────────────────────────────────────────────────────────
237
+ if (choice === 'anthropic-oauth') {
238
+ return await runOAuthFlow(p, pc, authStorage, 'anthropic', oauthMap);
239
+ }
240
+ if (choice === 'github-copilot-oauth') {
241
+ return await runOAuthFlow(p, pc, authStorage, 'github-copilot', oauthMap);
242
+ }
243
+ if (choice === 'openai-codex-oauth') {
244
+ return await runOAuthFlow(p, pc, authStorage, 'openai-codex', oauthMap);
245
+ }
246
+ if (choice === 'google-gemini-cli-oauth') {
247
+ return await runOAuthFlow(p, pc, authStorage, 'google-gemini-cli', oauthMap);
248
+ }
249
+ if (choice === 'google-antigravity-oauth') {
250
+ return await runOAuthFlow(p, pc, authStorage, 'google-antigravity', oauthMap);
251
+ }
252
+ // ── API key flows ─────────────────────────────────────────────────────────
253
+ if (choice === 'anthropic-api-key') {
254
+ return await runApiKeyFlow(p, pc, authStorage, 'anthropic', 'Anthropic');
255
+ }
256
+ if (choice === 'openai-api-key') {
257
+ return await runApiKeyFlow(p, pc, authStorage, 'openai', 'OpenAI');
258
+ }
259
+ if (choice === 'other-api-key') {
260
+ return await runOtherProviderFlow(p, pc, authStorage);
261
+ }
262
+ return false;
263
+ }
264
+ // ─── OAuth Flow ───────────────────────────────────────────────────────────────
265
+ async function runOAuthFlow(p, pc, authStorage, providerId, oauthMap) {
266
+ const providerInfo = oauthMap.get(providerId);
267
+ const providerName = providerInfo?.name ?? providerId;
268
+ const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
269
+ const s = p.spinner();
270
+ s.start(`Authenticating with ${providerName}...`);
271
+ try {
272
+ await authStorage.login(providerId, {
273
+ onAuth: (info) => {
274
+ s.stop(`Opening browser for ${providerName}`);
275
+ openBrowser(info.url);
276
+ p.log.info(`${pc.dim('URL:')} ${pc.cyan(info.url)}`);
277
+ if (info.instructions) {
278
+ p.log.info(pc.yellow(info.instructions));
279
+ }
280
+ },
281
+ onPrompt: async (prompt) => {
282
+ const result = await p.text({
283
+ message: prompt.message,
284
+ placeholder: prompt.placeholder,
285
+ });
286
+ if (p.isCancel(result))
287
+ return '';
288
+ return result;
289
+ },
290
+ onProgress: (message) => {
291
+ p.log.step(pc.dim(message));
292
+ },
293
+ onManualCodeInput: usesCallbackServer
294
+ ? async () => {
295
+ const result = await p.text({
296
+ message: 'Paste the redirect URL from your browser:',
297
+ placeholder: 'http://localhost:...',
298
+ });
299
+ if (p.isCancel(result))
300
+ return '';
301
+ return result;
302
+ }
303
+ : undefined,
304
+ });
305
+ p.log.success(`Authenticated with ${pc.green(providerName)}`);
306
+ return true;
307
+ }
308
+ catch (err) {
309
+ s.stop(`${providerName} authentication failed`);
310
+ const errorMsg = err instanceof Error ? err.message : String(err);
311
+ p.log.warn(`OAuth error: ${errorMsg}`);
312
+ // Offer retry or skip
313
+ const retry = await p.select({
314
+ message: 'What would you like to do?',
315
+ options: [
316
+ { value: 'retry', label: 'Try again' },
317
+ { value: 'skip', label: 'Skip — configure later with /login' },
318
+ ],
319
+ });
320
+ if (p.isCancel(retry) || retry === 'skip')
321
+ return false;
322
+ // Recursive retry
323
+ return runOAuthFlow(p, pc, authStorage, providerId, oauthMap);
324
+ }
325
+ }
326
+ // ─── API Key Flow ─────────────────────────────────────────────────────────────
327
+ async function runApiKeyFlow(p, pc, authStorage, providerId, providerLabel) {
328
+ const key = await p.password({
329
+ message: `Paste your ${providerLabel} API key:`,
330
+ mask: '●',
331
+ });
332
+ if (p.isCancel(key) || !key)
333
+ return false;
334
+ const trimmed = key.trim();
335
+ if (!trimmed)
336
+ return false;
337
+ // Basic prefix validation
338
+ const expectedPrefixes = API_KEY_PREFIXES[providerId];
339
+ if (expectedPrefixes && !expectedPrefixes.some(pfx => trimmed.startsWith(pfx))) {
340
+ p.log.warn(`Key doesn't start with expected prefix (${expectedPrefixes.join(' or ')}). Saving anyway.`);
341
+ }
342
+ authStorage.set(providerId, { type: 'api_key', key: trimmed });
343
+ p.log.success(`API key saved for ${pc.green(providerLabel)}`);
344
+ return true;
345
+ }
346
+ // ─── "Other Provider" Sub-Flow ────────────────────────────────────────────────
347
+ async function runOtherProviderFlow(p, pc, authStorage) {
348
+ const provider = await p.select({
349
+ message: 'Select provider',
350
+ options: OTHER_PROVIDERS.map(op => ({
351
+ value: op.value,
352
+ label: op.label,
353
+ })),
354
+ });
355
+ if (p.isCancel(provider))
356
+ return false;
357
+ const label = OTHER_PROVIDERS.find(op => op.value === provider)?.label ?? String(provider);
358
+ return runApiKeyFlow(p, pc, authStorage, provider, label);
359
+ }
360
+ // ─── Tool API Keys Step ───────────────────────────────────────────────────────
361
+ async function runToolKeysStep(p, pc, authStorage) {
362
+ // Filter to keys not already configured
363
+ const missing = TOOL_KEYS.filter(tk => !authStorage.has(tk.provider) && !process.env[tk.envVar]);
364
+ if (missing.length === 0)
365
+ return 0;
366
+ const wantToolKeys = await p.confirm({
367
+ message: 'Set up optional tool API keys? (web search, docs, etc.)',
368
+ initialValue: false,
369
+ });
370
+ if (p.isCancel(wantToolKeys) || !wantToolKeys)
371
+ return 0;
372
+ let savedCount = 0;
373
+ for (const tk of missing) {
374
+ const key = await p.password({
375
+ message: `${tk.label} ${pc.dim(`(${tk.hint})`)} — Enter to skip:`,
376
+ mask: '●',
377
+ });
378
+ if (p.isCancel(key))
379
+ break;
380
+ const trimmed = key?.trim();
381
+ if (trimmed) {
382
+ authStorage.set(tk.provider, { type: 'api_key', key: trimmed });
383
+ process.env[tk.envVar] = trimmed;
384
+ p.log.success(`${tk.label} saved`);
385
+ savedCount++;
386
+ }
387
+ else {
388
+ // Store empty key so wizard doesn't re-ask on next launch
389
+ authStorage.set(tk.provider, { type: 'api_key', key: '' });
390
+ p.log.info(pc.dim(`${tk.label} skipped`));
391
+ }
392
+ }
393
+ return savedCount;
394
+ }
395
+ // ─── Env hydration (migrated from wizard.ts) ─────────────────────────────────
396
+ /**
397
+ * Hydrate process.env from stored auth.json credentials for optional tool keys.
398
+ * Runs on every launch so extensions see Brave/Context7/Jina keys stored via the
399
+ * wizard on prior launches.
400
+ */
401
+ export function loadStoredEnvKeys(authStorage) {
402
+ const providers = [
403
+ ['brave', 'BRAVE_API_KEY'],
404
+ ['brave_answers', 'BRAVE_ANSWERS_KEY'],
405
+ ['context7', 'CONTEXT7_API_KEY'],
406
+ ['jina', 'JINA_API_KEY'],
407
+ ['slack_bot', 'SLACK_BOT_TOKEN'],
408
+ ['discord_bot', 'DISCORD_BOT_TOKEN'],
409
+ ];
410
+ for (const [provider, envVar] of providers) {
411
+ if (!process.env[envVar]) {
412
+ const cred = authStorage.get(provider);
413
+ if (cred?.type === 'api_key' && cred.key) {
414
+ process.env[envVar] = cred.key;
415
+ }
416
+ }
417
+ }
418
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * One-time migration of provider credentials from Pi (~/.pi/agent/auth.json)
3
+ * into GSD's auth storage. Runs when GSD has no LLM providers configured,
4
+ * so users with an existing Pi install skip re-authentication.
5
+ */
6
+ import type { AuthStorage } from '@mariozechner/pi-coding-agent';
7
+ /**
8
+ * Migrate provider credentials from Pi's auth.json into GSD's AuthStorage.
9
+ *
10
+ * Only runs when GSD has no LLM provider configured and Pi's auth.json exists.
11
+ * Copies any credentials GSD doesn't already have. Returns true if an LLM
12
+ * provider was migrated (so onboarding can be skipped).
13
+ */
14
+ export declare function migratePiCredentials(authStorage: AuthStorage): boolean;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * One-time migration of provider credentials from Pi (~/.pi/agent/auth.json)
3
+ * into GSD's auth storage. Runs when GSD has no LLM providers configured,
4
+ * so users with an existing Pi install skip re-authentication.
5
+ */
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { homedir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ const PI_AUTH_PATH = join(homedir(), '.pi', 'agent', 'auth.json');
10
+ const LLM_PROVIDER_IDS = [
11
+ 'anthropic',
12
+ 'openai',
13
+ 'github-copilot',
14
+ 'openai-codex',
15
+ 'google-gemini-cli',
16
+ 'google-antigravity',
17
+ 'google',
18
+ 'groq',
19
+ 'xai',
20
+ 'openrouter',
21
+ 'mistral',
22
+ ];
23
+ /**
24
+ * Migrate provider credentials from Pi's auth.json into GSD's AuthStorage.
25
+ *
26
+ * Only runs when GSD has no LLM provider configured and Pi's auth.json exists.
27
+ * Copies any credentials GSD doesn't already have. Returns true if an LLM
28
+ * provider was migrated (so onboarding can be skipped).
29
+ */
30
+ export function migratePiCredentials(authStorage) {
31
+ try {
32
+ // Only migrate when GSD has no LLM providers
33
+ const existing = authStorage.list();
34
+ const hasLlm = existing.some(id => LLM_PROVIDER_IDS.includes(id));
35
+ if (hasLlm)
36
+ return false;
37
+ if (!existsSync(PI_AUTH_PATH))
38
+ return false;
39
+ const raw = readFileSync(PI_AUTH_PATH, 'utf-8');
40
+ const piData = JSON.parse(raw);
41
+ let migratedLlm = false;
42
+ for (const [providerId, credential] of Object.entries(piData)) {
43
+ if (authStorage.has(providerId))
44
+ continue;
45
+ authStorage.set(providerId, credential);
46
+ const isLlm = LLM_PROVIDER_IDS.includes(providerId);
47
+ if (isLlm)
48
+ migratedLlm = true;
49
+ process.stderr.write(`[gsd] Migrated ${isLlm ? 'LLM provider' : 'credential'}: ${providerId} (from Pi)\n`);
50
+ }
51
+ return migratedLlm;
52
+ }
53
+ catch {
54
+ // Non-fatal — don't block startup
55
+ return false;
56
+ }
57
+ }
@@ -0,0 +1,22 @@
1
+ import { DefaultResourceLoader } from '@mariozechner/pi-coding-agent';
2
+ /**
3
+ * Syncs all bundled resources to agentDir (~/.gsd/agent/) on every launch.
4
+ *
5
+ * - extensions/ → ~/.gsd/agent/extensions/ (always overwrite — ensures updates ship on next launch)
6
+ * - agents/ → ~/.gsd/agent/agents/ (always overwrite)
7
+ * - AGENTS.md → ~/.gsd/agent/AGENTS.md (always overwrite)
8
+ * - GSD-WORKFLOW.md is read directly from bundled path via GSD_WORKFLOW_PATH env var
9
+ *
10
+ * Always-overwrite ensures `npm update -g @glittercowboy/gsd` takes effect immediately.
11
+ * User customizations should go in ~/.gsd/agent/extensions/ subdirs with unique names,
12
+ * not by editing the gsd-managed files.
13
+ *
14
+ * Inspectable: `ls ~/.gsd/agent/extensions/`
15
+ */
16
+ export declare function initResources(agentDir: string): void;
17
+ /**
18
+ * Constructs a DefaultResourceLoader that loads extensions from both
19
+ * ~/.gsd/agent/extensions/ (GSD's default) and ~/.pi/agent/extensions/ (pi's default).
20
+ * This allows users to use extensions from either location.
21
+ */
22
+ export declare function buildResourceLoader(agentDir: string): DefaultResourceLoader;
@@ -0,0 +1,60 @@
1
+ import { DefaultResourceLoader } from '@mariozechner/pi-coding-agent';
2
+ import { homedir } from 'node:os';
3
+ import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ // Resolves to the bundled src/resources/ inside the npm package at runtime:
7
+ // dist/resource-loader.js → .. → package root → src/resources/
8
+ const resourcesDir = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'src', 'resources');
9
+ const bundledExtensionsDir = join(resourcesDir, 'extensions');
10
+ /**
11
+ * Syncs all bundled resources to agentDir (~/.gsd/agent/) on every launch.
12
+ *
13
+ * - extensions/ → ~/.gsd/agent/extensions/ (always overwrite — ensures updates ship on next launch)
14
+ * - agents/ → ~/.gsd/agent/agents/ (always overwrite)
15
+ * - AGENTS.md → ~/.gsd/agent/AGENTS.md (always overwrite)
16
+ * - GSD-WORKFLOW.md is read directly from bundled path via GSD_WORKFLOW_PATH env var
17
+ *
18
+ * Always-overwrite ensures `npm update -g @glittercowboy/gsd` takes effect immediately.
19
+ * User customizations should go in ~/.gsd/agent/extensions/ subdirs with unique names,
20
+ * not by editing the gsd-managed files.
21
+ *
22
+ * Inspectable: `ls ~/.gsd/agent/extensions/`
23
+ */
24
+ export function initResources(agentDir) {
25
+ mkdirSync(agentDir, { recursive: true });
26
+ // Sync extensions — always overwrite so updates land on next launch
27
+ const destExtensions = join(agentDir, 'extensions');
28
+ cpSync(bundledExtensionsDir, destExtensions, { recursive: true, force: true });
29
+ // Sync agents
30
+ const destAgents = join(agentDir, 'agents');
31
+ const srcAgents = join(resourcesDir, 'agents');
32
+ if (existsSync(srcAgents)) {
33
+ cpSync(srcAgents, destAgents, { recursive: true, force: true });
34
+ }
35
+ // Sync skills — always overwrite so updates land on next launch
36
+ const destSkills = join(agentDir, 'skills');
37
+ const srcSkills = join(resourcesDir, 'skills');
38
+ if (existsSync(srcSkills)) {
39
+ cpSync(srcSkills, destSkills, { recursive: true, force: true });
40
+ }
41
+ // Sync AGENTS.md
42
+ const srcAgentsMd = join(resourcesDir, 'AGENTS.md');
43
+ const destAgentsMd = join(agentDir, 'AGENTS.md');
44
+ if (existsSync(srcAgentsMd)) {
45
+ writeFileSync(destAgentsMd, readFileSync(srcAgentsMd));
46
+ }
47
+ }
48
+ /**
49
+ * Constructs a DefaultResourceLoader that loads extensions from both
50
+ * ~/.gsd/agent/extensions/ (GSD's default) and ~/.pi/agent/extensions/ (pi's default).
51
+ * This allows users to use extensions from either location.
52
+ */
53
+ export function buildResourceLoader(agentDir) {
54
+ const piAgentDir = join(homedir(), '.pi', 'agent');
55
+ const piExtensionsDir = join(piAgentDir, 'extensions');
56
+ return new DefaultResourceLoader({
57
+ agentDir,
58
+ additionalExtensionPaths: [piExtensionsDir],
59
+ });
60
+ }
@@ -0,0 +1,4 @@
1
+ type ManagedTool = "fd" | "rg";
2
+ export declare function resolveToolFromPath(tool: ManagedTool, pathValue?: string | undefined): string | null;
3
+ export declare function ensureManagedTools(targetDir: string, pathValue?: string | undefined): string[];
4
+ export {};
@@ -0,0 +1,74 @@
1
+ import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, rmSync, symlinkSync } from "node:fs";
2
+ import { delimiter, join } from "node:path";
3
+ const TOOL_SPECS = {
4
+ fd: {
5
+ targetName: process.platform === "win32" ? "fd.exe" : "fd",
6
+ candidates: process.platform === "win32" ? ["fd.exe", "fd", "fdfind.exe", "fdfind"] : ["fd", "fdfind"],
7
+ },
8
+ rg: {
9
+ targetName: process.platform === "win32" ? "rg.exe" : "rg",
10
+ candidates: process.platform === "win32" ? ["rg.exe", "rg"] : ["rg"],
11
+ },
12
+ };
13
+ function splitPath(pathValue) {
14
+ if (!pathValue)
15
+ return [];
16
+ return pathValue.split(delimiter).map((segment) => segment.trim()).filter(Boolean);
17
+ }
18
+ function getCandidateNames(name) {
19
+ if (process.platform !== "win32")
20
+ return [name];
21
+ const lower = name.toLowerCase();
22
+ if (lower.endsWith(".exe") || lower.endsWith(".cmd") || lower.endsWith(".bat"))
23
+ return [name];
24
+ return [name, `${name}.exe`, `${name}.cmd`, `${name}.bat`];
25
+ }
26
+ function isRegularFile(path) {
27
+ try {
28
+ return lstatSync(path).isFile() || lstatSync(path).isSymbolicLink();
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ export function resolveToolFromPath(tool, pathValue = process.env.PATH) {
35
+ const spec = TOOL_SPECS[tool];
36
+ for (const dir of splitPath(pathValue)) {
37
+ for (const candidate of spec.candidates) {
38
+ for (const name of getCandidateNames(candidate)) {
39
+ const fullPath = join(dir, name);
40
+ if (existsSync(fullPath) && isRegularFile(fullPath)) {
41
+ return fullPath;
42
+ }
43
+ }
44
+ }
45
+ }
46
+ return null;
47
+ }
48
+ function provisionTool(targetDir, tool, sourcePath) {
49
+ const targetPath = join(targetDir, TOOL_SPECS[tool].targetName);
50
+ if (existsSync(targetPath))
51
+ return targetPath;
52
+ mkdirSync(targetDir, { recursive: true });
53
+ try {
54
+ symlinkSync(sourcePath, targetPath);
55
+ }
56
+ catch {
57
+ rmSync(targetPath, { force: true });
58
+ copyFileSync(sourcePath, targetPath);
59
+ chmodSync(targetPath, 0o755);
60
+ }
61
+ return targetPath;
62
+ }
63
+ export function ensureManagedTools(targetDir, pathValue = process.env.PATH) {
64
+ const provisioned = [];
65
+ for (const tool of Object.keys(TOOL_SPECS)) {
66
+ if (existsSync(join(targetDir, TOOL_SPECS[tool].targetName)))
67
+ continue;
68
+ const sourcePath = resolveToolFromPath(tool, pathValue);
69
+ if (!sourcePath)
70
+ continue;
71
+ provisioned.push(provisionTool(targetDir, tool, sourcePath));
72
+ }
73
+ return provisioned;
74
+ }
@@ -0,0 +1,7 @@
1
+ import type { AuthStorage } from '@mariozechner/pi-coding-agent';
2
+ /**
3
+ * Hydrate process.env from stored auth.json credentials for optional tool keys.
4
+ * Runs on every launch so extensions see Brave/Context7/Jina keys stored via the
5
+ * wizard on prior launches.
6
+ */
7
+ export declare function loadStoredEnvKeys(authStorage: AuthStorage): void;