@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.
- package/LICENSE +21 -0
- package/README.md +453 -0
- package/dist/app-paths.d.ts +4 -0
- package/dist/app-paths.js +6 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +269 -0
- package/dist/loader.d.ts +2 -0
- package/dist/loader.js +70 -0
- package/dist/logo.d.ts +16 -0
- package/dist/logo.js +25 -0
- package/dist/onboarding.d.ts +43 -0
- package/dist/onboarding.js +418 -0
- package/dist/pi-migration.d.ts +14 -0
- package/dist/pi-migration.js +57 -0
- package/dist/resource-loader.d.ts +22 -0
- package/dist/resource-loader.js +60 -0
- package/dist/tool-bootstrap.d.ts +4 -0
- package/dist/tool-bootstrap.js +74 -0
- package/dist/wizard.d.ts +7 -0
- package/dist/wizard.js +25 -0
- package/package.json +60 -0
- package/patches/@mariozechner+pi-coding-agent+0.57.1.patch +108 -0
- package/patches/@mariozechner+pi-tui+0.57.1.patch +47 -0
- package/pkg/dist/modes/interactive/theme/dark.json +85 -0
- package/pkg/dist/modes/interactive/theme/light.json +84 -0
- package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
- package/pkg/dist/modes/interactive/theme/theme.js +949 -0
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
- package/pkg/package.json +8 -0
- package/scripts/postinstall.js +127 -0
- package/src/resources/GSD-WORKFLOW.md +661 -0
- package/src/resources/agents/researcher.md +29 -0
- package/src/resources/agents/scout.md +56 -0
- package/src/resources/agents/worker.md +31 -0
- package/src/resources/extensions/ask-user-questions.ts +249 -0
- package/src/resources/extensions/bg-shell/index.ts +2808 -0
- package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
- package/src/resources/extensions/browser-tools/core.js +1057 -0
- package/src/resources/extensions/browser-tools/index.ts +4989 -0
- package/src/resources/extensions/browser-tools/package.json +20 -0
- package/src/resources/extensions/context7/index.ts +428 -0
- package/src/resources/extensions/context7/package.json +11 -0
- package/src/resources/extensions/get-secrets-from-user.ts +352 -0
- package/src/resources/extensions/google-search/index.ts +323 -0
- package/src/resources/extensions/google-search/package.json +9 -0
- package/src/resources/extensions/gsd/activity-log.ts +69 -0
- package/src/resources/extensions/gsd/auto.ts +2744 -0
- package/src/resources/extensions/gsd/commands.ts +313 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +85 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +521 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +176 -0
- package/src/resources/extensions/gsd/doctor.ts +690 -0
- package/src/resources/extensions/gsd/files.ts +732 -0
- package/src/resources/extensions/gsd/git-service.ts +597 -0
- package/src/resources/extensions/gsd/gitignore.ts +168 -0
- package/src/resources/extensions/gsd/guided-flow.ts +817 -0
- package/src/resources/extensions/gsd/index.ts +558 -0
- package/src/resources/extensions/gsd/metrics.ts +374 -0
- package/src/resources/extensions/gsd/migrate/command.ts +218 -0
- package/src/resources/extensions/gsd/migrate/index.ts +42 -0
- package/src/resources/extensions/gsd/migrate/parser.ts +323 -0
- package/src/resources/extensions/gsd/migrate/parsers.ts +624 -0
- package/src/resources/extensions/gsd/migrate/preview.ts +48 -0
- package/src/resources/extensions/gsd/migrate/transformer.ts +346 -0
- package/src/resources/extensions/gsd/migrate/types.ts +370 -0
- package/src/resources/extensions/gsd/migrate/validator.ts +55 -0
- package/src/resources/extensions/gsd/migrate/writer.ts +539 -0
- package/src/resources/extensions/gsd/observability-validator.ts +408 -0
- package/src/resources/extensions/gsd/package.json +11 -0
- package/src/resources/extensions/gsd/paths.ts +308 -0
- package/src/resources/extensions/gsd/preferences.ts +757 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +50 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +25 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +29 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +189 -0
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +29 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +61 -0
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +59 -0
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -0
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +23 -0
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -0
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +11 -0
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -0
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +65 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +51 -0
- package/src/resources/extensions/gsd/prompts/queue.md +85 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +48 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +39 -0
- package/src/resources/extensions/gsd/prompts/research-milestone.md +37 -0
- package/src/resources/extensions/gsd/prompts/research-slice.md +28 -0
- package/src/resources/extensions/gsd/prompts/review-migration.md +66 -0
- package/src/resources/extensions/gsd/prompts/run-uat.md +109 -0
- package/src/resources/extensions/gsd/prompts/system.md +187 -0
- package/src/resources/extensions/gsd/prompts/worktree-merge.md +123 -0
- package/src/resources/extensions/gsd/session-forensics.ts +487 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +137 -0
- package/src/resources/extensions/gsd/state.ts +460 -0
- package/src/resources/extensions/gsd/templates/context.md +76 -0
- package/src/resources/extensions/gsd/templates/decisions.md +8 -0
- package/src/resources/extensions/gsd/templates/milestone-summary.md +73 -0
- package/src/resources/extensions/gsd/templates/plan.md +131 -0
- package/src/resources/extensions/gsd/templates/preferences.md +24 -0
- package/src/resources/extensions/gsd/templates/project.md +31 -0
- package/src/resources/extensions/gsd/templates/reassessment.md +28 -0
- package/src/resources/extensions/gsd/templates/requirements.md +81 -0
- package/src/resources/extensions/gsd/templates/research.md +46 -0
- package/src/resources/extensions/gsd/templates/roadmap.md +118 -0
- package/src/resources/extensions/gsd/templates/slice-context.md +58 -0
- package/src/resources/extensions/gsd/templates/slice-summary.md +99 -0
- package/src/resources/extensions/gsd/templates/state.md +19 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +52 -0
- package/src/resources/extensions/gsd/templates/task-summary.md +57 -0
- package/src/resources/extensions/gsd/templates/uat.md +54 -0
- package/src/resources/extensions/gsd/tests/activity-log-prune.test.ts +327 -0
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +53 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +225 -0
- package/src/resources/extensions/gsd/tests/cost-projection.test.ts +160 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +341 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +689 -0
- package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/doctor.test.ts +505 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +1313 -0
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +308 -0
- package/src/resources/extensions/gsd/tests/metrics-io.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +217 -0
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +390 -0
- package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +786 -0
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +657 -0
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +443 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +318 -0
- package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +420 -0
- package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +309 -0
- package/src/resources/extensions/gsd/tests/parsers.test.ts +1351 -0
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +163 -0
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +386 -0
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/remote-status.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +521 -0
- package/src/resources/extensions/gsd/tests/requirements.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +34 -0
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +11 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +348 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +247 -0
- package/src/resources/extensions/gsd/tests/workflow-config.test.mjs +53 -0
- package/src/resources/extensions/gsd/tests/workspace-index.test.ts +94 -0
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +253 -0
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +160 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +264 -0
- package/src/resources/extensions/gsd/types.ts +159 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +184 -0
- package/src/resources/extensions/gsd/workspace-index.ts +203 -0
- package/src/resources/extensions/gsd/worktree-command.ts +845 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +392 -0
- package/src/resources/extensions/gsd/worktree.ts +183 -0
- package/src/resources/extensions/mac-tools/index.ts +852 -0
- package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
- package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
- package/src/resources/extensions/mcporter/index.ts +429 -0
- package/src/resources/extensions/remote-questions/config.ts +81 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +128 -0
- package/src/resources/extensions/remote-questions/format.ts +163 -0
- package/src/resources/extensions/remote-questions/manager.ts +192 -0
- package/src/resources/extensions/remote-questions/remote-command.ts +307 -0
- package/src/resources/extensions/remote-questions/slack-adapter.ts +92 -0
- package/src/resources/extensions/remote-questions/status.ts +31 -0
- package/src/resources/extensions/remote-questions/store.ts +77 -0
- package/src/resources/extensions/remote-questions/types.ts +75 -0
- package/src/resources/extensions/search-the-web/cache.ts +78 -0
- package/src/resources/extensions/search-the-web/command-search-provider.ts +95 -0
- package/src/resources/extensions/search-the-web/format.ts +258 -0
- package/src/resources/extensions/search-the-web/http.ts +238 -0
- package/src/resources/extensions/search-the-web/index.ts +65 -0
- package/src/resources/extensions/search-the-web/native-search.ts +157 -0
- package/src/resources/extensions/search-the-web/provider.ts +118 -0
- package/src/resources/extensions/search-the-web/tavily.ts +116 -0
- package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
- package/src/resources/extensions/search-the-web/tool-llm-context.ts +561 -0
- package/src/resources/extensions/search-the-web/tool-search.ts +576 -0
- package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
- package/src/resources/extensions/shared/confirm-ui.ts +126 -0
- package/src/resources/extensions/shared/interview-ui.ts +613 -0
- package/src/resources/extensions/shared/next-action-ui.ts +197 -0
- package/src/resources/extensions/shared/progress-widget.ts +282 -0
- package/src/resources/extensions/shared/terminal.ts +23 -0
- package/src/resources/extensions/shared/thinking-widget.ts +107 -0
- package/src/resources/extensions/shared/ui.ts +400 -0
- package/src/resources/extensions/shared/wizard-ui.ts +551 -0
- package/src/resources/extensions/slash-commands/audit.ts +88 -0
- package/src/resources/extensions/slash-commands/clear.ts +10 -0
- package/src/resources/extensions/slash-commands/create-extension.ts +297 -0
- package/src/resources/extensions/slash-commands/create-slash-command.ts +234 -0
- package/src/resources/extensions/slash-commands/index.ts +12 -0
- package/src/resources/extensions/subagent/agents.ts +126 -0
- package/src/resources/extensions/subagent/index.ts +1020 -0
- package/src/resources/extensions/voice/index.ts +195 -0
- package/src/resources/extensions/voice/speech-recognizer.swift +154 -0
- package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
- package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
- package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
- package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
- package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
- package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
- package/src/resources/skills/frontend-design/SKILL.md +45 -0
- package/src/resources/skills/swiftui/SKILL.md +208 -0
- package/src/resources/skills/swiftui/references/animations.md +921 -0
- package/src/resources/skills/swiftui/references/architecture.md +1561 -0
- package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
- package/src/resources/skills/swiftui/references/navigation.md +1492 -0
- package/src/resources/skills/swiftui/references/networking-async.md +214 -0
- package/src/resources/skills/swiftui/references/performance.md +1706 -0
- package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
- package/src/resources/skills/swiftui/references/state-management.md +1443 -0
- package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
- package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
- package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
- package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
- package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
- package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
- package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
- package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
- 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,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
|
+
}
|
package/dist/wizard.d.ts
ADDED
|
@@ -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;
|