@phuetz/code-buddy 1.0.0 → 1.2.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/README.md +160 -164
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +101 -13
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/lesson-auto-proposer.js +10 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/session-duration.d.ts +36 -0
- package/dist/agent/middleware/session-duration.js +78 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/session-end-flush.d.ts +71 -0
- package/dist/agent/session-end-flush.js +233 -0
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +130 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +1 -1
- package/dist/codebuddy/tools.js +27 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/native-engine-commands.js +107 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +5 -0
- package/dist/commands/goal-cli.d.ts +71 -0
- package/dist/commands/goal-cli.js +280 -0
- package/dist/commands/handlers/goal-handler.d.ts +34 -0
- package/dist/commands/handlers/goal-handler.js +155 -0
- package/dist/commands/handlers/index.d.ts +1 -0
- package/dist/commands/handlers/index.js +2 -0
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/slash/builtin-commands.js +20 -0
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +770 -0
- package/dist/config/feature-flags.js +7 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/context-manager-v2.d.ts +39 -0
- package/dist/context/context-manager-v2.js +91 -0
- package/dist/daemon/agent-task-executor.js +12 -1
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +5 -3
- package/dist/daemon/autonomous-loop.d.ts +21 -1
- package/dist/daemon/autonomous-loop.js +69 -0
- package/dist/daemon/colab-goal.d.ts +38 -0
- package/dist/daemon/colab-goal.js +81 -0
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.d.ts +21 -0
- package/dist/fleet/colab-store.js +28 -0
- package/dist/fleet/peer-session-bridge.d.ts +1 -1
- package/dist/fleet/peer-session-bridge.js +243 -2
- package/dist/fleet/peer-session-store.d.ts +3 -0
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/fleet/privacy-lint.d.ts +8 -0
- package/dist/fleet/privacy-lint.js +22 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +38 -0
- package/dist/goals/goal-judge.js +158 -0
- package/dist/goals/goal-loop.d.ts +37 -0
- package/dist/goals/goal-loop.js +101 -0
- package/dist/goals/goal-manager.d.ts +77 -0
- package/dist/goals/goal-manager.js +289 -0
- package/dist/goals/goal-state.d.ts +99 -0
- package/dist/goals/goal-state.js +315 -0
- package/dist/goals/goal-store.d.ts +28 -0
- package/dist/goals/goal-store.js +87 -0
- package/dist/goals/index.d.ts +5 -0
- package/dist/goals/index.js +6 -0
- package/dist/hooks/use-input-handler.js +36 -1
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/index.js +506 -25
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/observability/run-store.d.ts +1 -1
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/server/index.js +17 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.d.ts +13 -1
- package/dist/server/websocket/fleet-bridge.js +16 -0
- package/dist/services/prompt-builder.d.ts +1 -0
- package/dist/services/prompt-builder.js +49 -8
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +7 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- package/dist/utils/kokoro-tts.js +0 -67
|
@@ -22,8 +22,8 @@ export declare function formatTokenUsage(usage: TokenUsageInfo): string;
|
|
|
22
22
|
* Uses a simple heuristic; for accurate cost tracking use CostTracker.
|
|
23
23
|
*
|
|
24
24
|
* Special-case: returns 0 when `model` indicates a ChatGPT subscription
|
|
25
|
-
* call (`gpt-5.2`, `gpt-5.5*`, `*-codex*`, `codex-1`)
|
|
26
|
-
*
|
|
27
|
-
* spend would be misleading.
|
|
25
|
+
* call (`gpt-5.2`, `gpt-5.5*`, `*-codex*`, `codex-1`) or a local Ollama /
|
|
26
|
+
* LM Studio model. Those are not billed per token through Code Buddy, so
|
|
27
|
+
* reporting USD spend would be misleading.
|
|
28
28
|
*/
|
|
29
29
|
export declare function estimateCost(inputTokens: number, outputTokens: number, inputPricePer1k?: number, outputPricePer1k?: number, model?: string): number;
|
|
@@ -42,12 +42,12 @@ function formatCost(cost) {
|
|
|
42
42
|
* Uses a simple heuristic; for accurate cost tracking use CostTracker.
|
|
43
43
|
*
|
|
44
44
|
* Special-case: returns 0 when `model` indicates a ChatGPT subscription
|
|
45
|
-
* call (`gpt-5.2`, `gpt-5.5*`, `*-codex*`, `codex-1`)
|
|
46
|
-
*
|
|
47
|
-
* spend would be misleading.
|
|
45
|
+
* call (`gpt-5.2`, `gpt-5.5*`, `*-codex*`, `codex-1`) or a local Ollama /
|
|
46
|
+
* LM Studio model. Those are not billed per token through Code Buddy, so
|
|
47
|
+
* reporting USD spend would be misleading.
|
|
48
48
|
*/
|
|
49
49
|
export function estimateCost(inputTokens, outputTokens, inputPricePer1k = 0.003, outputPricePer1k = 0.015, model) {
|
|
50
|
-
if (model && isChatGptSubscriptionModel(model))
|
|
50
|
+
if (model && (isChatGptSubscriptionModel(model) || isLocalNoCostModel(model)))
|
|
51
51
|
return 0;
|
|
52
52
|
return (inputTokens / 1000) * inputPricePer1k + (outputTokens / 1000) * outputPricePer1k;
|
|
53
53
|
}
|
|
@@ -60,4 +60,22 @@ function isChatGptSubscriptionModel(model) {
|
|
|
60
60
|
m === 'codex-1' ||
|
|
61
61
|
m.startsWith('codex-mini'));
|
|
62
62
|
}
|
|
63
|
+
function isLocalNoCostModel(model) {
|
|
64
|
+
const m = model.toLowerCase();
|
|
65
|
+
return (m === 'ollama' ||
|
|
66
|
+
m.startsWith('ollama/') ||
|
|
67
|
+
m === 'lmstudio' ||
|
|
68
|
+
m === 'local-model' ||
|
|
69
|
+
m.startsWith('llama') ||
|
|
70
|
+
m.startsWith('qwen') ||
|
|
71
|
+
m.startsWith('gemma') ||
|
|
72
|
+
m.startsWith('phi') ||
|
|
73
|
+
m.startsWith('codellama') ||
|
|
74
|
+
m.startsWith('deepseek') ||
|
|
75
|
+
m.startsWith('command-r') ||
|
|
76
|
+
m === 'mistral' ||
|
|
77
|
+
m.startsWith('mistral:') ||
|
|
78
|
+
m === 'mixtral' ||
|
|
79
|
+
m.startsWith('mixtral:'));
|
|
80
|
+
}
|
|
63
81
|
//# sourceMappingURL=token-display.js.map
|
|
@@ -4,8 +4,47 @@ export interface OnboardingResult {
|
|
|
4
4
|
model: string;
|
|
5
5
|
ttsEnabled: boolean;
|
|
6
6
|
ttsProvider?: string;
|
|
7
|
+
authMode?: OnboardingAuthMode;
|
|
8
|
+
recommendedNextCommands?: string[];
|
|
9
|
+
}
|
|
10
|
+
export type OnboardingAuthMode = 'oauth' | 'api-key' | 'local';
|
|
11
|
+
export interface OnboardingProviderGuide {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
authMode: OnboardingAuthMode;
|
|
15
|
+
envVar: string;
|
|
16
|
+
defaultModel: string;
|
|
17
|
+
/** Inference base URL — persisted so the chosen provider resolves on the
|
|
18
|
+
* next run (the provider strategy in client.ts is picked from baseURL). */
|
|
19
|
+
baseURL?: string;
|
|
20
|
+
setupCommand?: string;
|
|
21
|
+
verifyCommand: string;
|
|
22
|
+
help: string;
|
|
23
|
+
}
|
|
24
|
+
export interface OnboardingPhase {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
hermesPhase: string;
|
|
28
|
+
codeBuddyAction: string;
|
|
29
|
+
successCheck: string;
|
|
7
30
|
}
|
|
8
31
|
export declare const PROVIDER_ENV_MAP: Record<string, string>;
|
|
9
32
|
export declare const PROVIDER_DEFAULT_MODEL: Record<string, string>;
|
|
33
|
+
export declare const PROVIDER_AUTH_MODE: Record<string, OnboardingAuthMode>;
|
|
34
|
+
export declare const PROVIDER_GUIDES: OnboardingProviderGuide[];
|
|
35
|
+
export declare const ONBOARDING_PHASES: OnboardingPhase[];
|
|
36
|
+
/**
|
|
37
|
+
* Persist the chosen provider so the session works now AND on the next run:
|
|
38
|
+
* - provider + baseURL + model → `~/.codebuddy/user-settings.json`
|
|
39
|
+
* (loadBaseURL/loadModel read it; the provider strategy is picked from baseURL).
|
|
40
|
+
* - an entered API key → encrypted credential store (never plaintext in settings),
|
|
41
|
+
* plus `process.env[envVar]` so the current process resolves immediately.
|
|
42
|
+
* - ollama → default `OLLAMA_HOST` so auto-detection picks it up.
|
|
43
|
+
* All writes are best-effort; the wizard summary still prints on failure.
|
|
44
|
+
*/
|
|
45
|
+
export declare function persistProviderSelection(guide: OnboardingProviderGuide, model: string, apiKey: string): Promise<void>;
|
|
10
46
|
export declare function writeConfig(configDir: string, result: OnboardingResult): void;
|
|
47
|
+
export declare function getProviderGuide(provider: string): OnboardingProviderGuide;
|
|
48
|
+
export declare function buildRecommendedNextCommands(result: Pick<OnboardingResult, 'provider' | 'model' | 'apiKey'>): string[];
|
|
49
|
+
export declare function renderOnboardingRoadmap(result?: Pick<OnboardingResult, 'provider' | 'model' | 'apiKey'>): string;
|
|
11
50
|
export declare function runOnboarding(): Promise<OnboardingResult>;
|
|
@@ -1,23 +1,157 @@
|
|
|
1
1
|
import * as readline from 'readline';
|
|
2
2
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
+
import { getValidationConfigForGuide, validateProviderKey, } from './provider-onboarding.js';
|
|
4
5
|
export const PROVIDER_ENV_MAP = {
|
|
6
|
+
chatgpt: '',
|
|
5
7
|
grok: 'GROK_API_KEY',
|
|
6
8
|
claude: 'ANTHROPIC_API_KEY',
|
|
7
|
-
chatgpt: 'OPENAI_API_KEY',
|
|
8
9
|
gemini: 'GEMINI_API_KEY',
|
|
10
|
+
openai: 'OPENAI_API_KEY',
|
|
11
|
+
openrouter: 'OPENROUTER_API_KEY',
|
|
9
12
|
ollama: '',
|
|
10
13
|
lmstudio: '',
|
|
11
14
|
};
|
|
12
15
|
export const PROVIDER_DEFAULT_MODEL = {
|
|
16
|
+
chatgpt: 'gpt-5.5',
|
|
13
17
|
grok: 'grok-3',
|
|
14
18
|
claude: 'claude-sonnet-4-20250514',
|
|
15
|
-
chatgpt: 'gpt-4o',
|
|
16
19
|
gemini: 'gemini-2.0-flash',
|
|
20
|
+
openai: 'gpt-4o',
|
|
21
|
+
openrouter: 'openai/gpt-4o',
|
|
17
22
|
ollama: 'llama3',
|
|
18
23
|
lmstudio: 'default',
|
|
19
24
|
};
|
|
20
|
-
const
|
|
25
|
+
export const PROVIDER_AUTH_MODE = {
|
|
26
|
+
chatgpt: 'oauth',
|
|
27
|
+
grok: 'api-key',
|
|
28
|
+
claude: 'api-key',
|
|
29
|
+
gemini: 'api-key',
|
|
30
|
+
openai: 'api-key',
|
|
31
|
+
openrouter: 'api-key',
|
|
32
|
+
ollama: 'local',
|
|
33
|
+
lmstudio: 'local',
|
|
34
|
+
};
|
|
35
|
+
export const PROVIDER_GUIDES = [
|
|
36
|
+
{
|
|
37
|
+
id: 'chatgpt',
|
|
38
|
+
label: 'ChatGPT subscription (OAuth)',
|
|
39
|
+
authMode: 'oauth',
|
|
40
|
+
envVar: '',
|
|
41
|
+
defaultModel: 'gpt-5.5',
|
|
42
|
+
setupCommand: 'buddy login',
|
|
43
|
+
verifyCommand: 'buddy whoami',
|
|
44
|
+
help: 'One browser login unlocks the ChatGPT-backed Codex route; no OPENAI_API_KEY is required.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'grok',
|
|
48
|
+
label: 'Grok / xAI API key',
|
|
49
|
+
authMode: 'api-key',
|
|
50
|
+
envVar: 'GROK_API_KEY',
|
|
51
|
+
defaultModel: 'grok-3',
|
|
52
|
+
baseURL: 'https://api.x.ai/v1',
|
|
53
|
+
verifyCommand: 'buddy doctor',
|
|
54
|
+
help: 'Set GROK_API_KEY in your shell or secret manager.',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'claude',
|
|
58
|
+
label: 'Anthropic Claude API key',
|
|
59
|
+
authMode: 'api-key',
|
|
60
|
+
envVar: 'ANTHROPIC_API_KEY',
|
|
61
|
+
defaultModel: 'claude-sonnet-4-20250514',
|
|
62
|
+
baseURL: 'https://api.anthropic.com/v1',
|
|
63
|
+
verifyCommand: 'buddy doctor',
|
|
64
|
+
help: 'Set ANTHROPIC_API_KEY in your shell or secret manager.',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'gemini',
|
|
68
|
+
label: 'Google Gemini API key',
|
|
69
|
+
authMode: 'api-key',
|
|
70
|
+
envVar: 'GEMINI_API_KEY',
|
|
71
|
+
defaultModel: 'gemini-2.0-flash',
|
|
72
|
+
baseURL: 'https://generativelanguage.googleapis.com/v1beta',
|
|
73
|
+
verifyCommand: 'buddy doctor',
|
|
74
|
+
help: 'Set GEMINI_API_KEY in your shell or secret manager.',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'openai',
|
|
78
|
+
label: 'OpenAI API key',
|
|
79
|
+
authMode: 'api-key',
|
|
80
|
+
envVar: 'OPENAI_API_KEY',
|
|
81
|
+
defaultModel: 'gpt-4o',
|
|
82
|
+
baseURL: 'https://api.openai.com/v1',
|
|
83
|
+
verifyCommand: 'buddy doctor',
|
|
84
|
+
help: 'Set OPENAI_API_KEY (https://platform.openai.com/api-keys).',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'openrouter',
|
|
88
|
+
label: 'OpenRouter API key',
|
|
89
|
+
authMode: 'api-key',
|
|
90
|
+
envVar: 'OPENROUTER_API_KEY',
|
|
91
|
+
defaultModel: 'openai/gpt-4o',
|
|
92
|
+
baseURL: 'https://openrouter.ai/api/v1',
|
|
93
|
+
verifyCommand: 'buddy doctor',
|
|
94
|
+
help: 'Set OPENROUTER_API_KEY (https://openrouter.ai/keys).',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'ollama',
|
|
98
|
+
label: 'Ollama local model',
|
|
99
|
+
authMode: 'local',
|
|
100
|
+
envVar: '',
|
|
101
|
+
defaultModel: 'llama3',
|
|
102
|
+
baseURL: 'http://localhost:11434/v1',
|
|
103
|
+
setupCommand: 'ollama serve',
|
|
104
|
+
verifyCommand: 'curl http://localhost:11434/api/tags',
|
|
105
|
+
help: 'Run Ollama locally and pull the model you selected.',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'lmstudio',
|
|
109
|
+
label: 'LM Studio local server',
|
|
110
|
+
authMode: 'local',
|
|
111
|
+
envVar: '',
|
|
112
|
+
defaultModel: 'default',
|
|
113
|
+
baseURL: 'http://localhost:1234/v1',
|
|
114
|
+
verifyCommand: 'curl http://localhost:1234/v1/models',
|
|
115
|
+
help: 'Start the LM Studio local server before launching Code Buddy.',
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
export const ONBOARDING_PHASES = [
|
|
119
|
+
{
|
|
120
|
+
id: 'install',
|
|
121
|
+
title: 'Install and diagnose',
|
|
122
|
+
hermesPhase: 'Install Hermes Agent, then run doctor when anything looks off.',
|
|
123
|
+
codeBuddyAction: 'Install Code Buddy, then run buddy doctor.',
|
|
124
|
+
successCheck: 'doctor reports Node.js and core dependencies as usable.',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'provider',
|
|
128
|
+
title: 'Choose provider and authenticate',
|
|
129
|
+
hermesPhase: 'Choose a provider; the fastest path is hermes setup --portal.',
|
|
130
|
+
codeBuddyAction: 'Prefer buddy login for ChatGPT OAuth, or configure an API/local provider.',
|
|
131
|
+
successCheck: 'buddy whoami or buddy doctor confirms the selected credential source.',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'first-chat',
|
|
135
|
+
title: 'Run a verifiable first chat',
|
|
136
|
+
hermesPhase: 'Start the CLI/TUI and ask a specific prompt with observable success.',
|
|
137
|
+
codeBuddyAction: 'Run buddy with a repo summary prompt.',
|
|
138
|
+
successCheck: 'the answer names files or tools from the current workspace.',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'session-resume',
|
|
142
|
+
title: 'Verify session resume',
|
|
143
|
+
hermesPhase: 'Run --continue before moving to advanced workflows.',
|
|
144
|
+
codeBuddyAction: 'Run buddy --continue or buddy session list.',
|
|
145
|
+
successCheck: 'the previous session is visible and resumable.',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: 'next-layer',
|
|
149
|
+
title: 'Add the next layer',
|
|
150
|
+
hermesPhase: 'Only after base chat works, add tools, skills, gateway, MCP, voice, or sandboxing.',
|
|
151
|
+
codeBuddyAction: 'Pick one: buddy --init, buddy server, buddy skills, Cowork, Fleet, companion, or sandbox mode.',
|
|
152
|
+
successCheck: 'the selected layer has a doctor/status command or a focused smoke prompt.',
|
|
153
|
+
},
|
|
154
|
+
];
|
|
21
155
|
const TTS_PROVIDERS = ['edge-tts', 'espeak', 'audioreader'];
|
|
22
156
|
function ask(rl, question, defaultValue) {
|
|
23
157
|
return new Promise((resolve) => {
|
|
@@ -38,18 +172,217 @@ function askChoice(rl, question, choices, defaultIdx) {
|
|
|
38
172
|
});
|
|
39
173
|
});
|
|
40
174
|
}
|
|
175
|
+
/** Read a secret without echoing it (masks keystrokes with '*'). The captured
|
|
176
|
+
* value is always correct regardless of echo; masking is cosmetic, and falls
|
|
177
|
+
* back to a visible prompt if the terminal doesn't support muting. */
|
|
178
|
+
function askSecret(rl, question) {
|
|
179
|
+
return new Promise((resolve) => {
|
|
180
|
+
const rlAny = rl;
|
|
181
|
+
const original = rlAny._writeToOutput?.bind(rl);
|
|
182
|
+
let muted = false;
|
|
183
|
+
if (typeof original === 'function') {
|
|
184
|
+
rlAny._writeToOutput = (str) => {
|
|
185
|
+
if (!muted || /[\r\n]/.test(str))
|
|
186
|
+
original(str);
|
|
187
|
+
else
|
|
188
|
+
original('*'.repeat(str.length || 1));
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
rl.question(` ${question}: `, (answer) => {
|
|
192
|
+
if (typeof original === 'function')
|
|
193
|
+
rlAny._writeToOutput = original;
|
|
194
|
+
resolve(answer.trim());
|
|
195
|
+
});
|
|
196
|
+
muted = true;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Persist the chosen provider so the session works now AND on the next run:
|
|
201
|
+
* - provider + baseURL + model → `~/.codebuddy/user-settings.json`
|
|
202
|
+
* (loadBaseURL/loadModel read it; the provider strategy is picked from baseURL).
|
|
203
|
+
* - an entered API key → encrypted credential store (never plaintext in settings),
|
|
204
|
+
* plus `process.env[envVar]` so the current process resolves immediately.
|
|
205
|
+
* - ollama → default `OLLAMA_HOST` so auto-detection picks it up.
|
|
206
|
+
* All writes are best-effort; the wizard summary still prints on failure.
|
|
207
|
+
*/
|
|
208
|
+
export async function persistProviderSelection(guide, model, apiKey) {
|
|
209
|
+
try {
|
|
210
|
+
const { getSettingsManager } = await import('../utils/settings-manager.js');
|
|
211
|
+
getSettingsManager().saveUserSettings({
|
|
212
|
+
provider: guide.id,
|
|
213
|
+
defaultModel: model,
|
|
214
|
+
model,
|
|
215
|
+
...(guide.baseURL ? { baseURL: guide.baseURL } : {}),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch { /* non-fatal */ }
|
|
219
|
+
if (guide.authMode === 'api-key' && apiKey) {
|
|
220
|
+
try {
|
|
221
|
+
const { getCredentialManager } = await import('../security/credential-manager.js');
|
|
222
|
+
getCredentialManager().setApiKey(apiKey);
|
|
223
|
+
}
|
|
224
|
+
catch { /* non-fatal */ }
|
|
225
|
+
if (guide.envVar)
|
|
226
|
+
process.env[guide.envVar] = apiKey;
|
|
227
|
+
}
|
|
228
|
+
if (guide.id === 'ollama' && !process.env.OLLAMA_HOST) {
|
|
229
|
+
process.env.OLLAMA_HOST = 'http://localhost:11434';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
41
232
|
export function writeConfig(configDir, result) {
|
|
42
233
|
mkdirSync(configDir, { recursive: true });
|
|
234
|
+
const authMode = result.authMode ?? getProviderGuide(result.provider).authMode;
|
|
235
|
+
const recommendedNextCommands = result.recommendedNextCommands ?? buildRecommendedNextCommands(result);
|
|
43
236
|
const config = {
|
|
44
237
|
provider: result.provider,
|
|
45
238
|
model: result.model,
|
|
239
|
+
authMode,
|
|
46
240
|
ttsEnabled: result.ttsEnabled,
|
|
241
|
+
onboarding: {
|
|
242
|
+
version: 1,
|
|
243
|
+
phases: ONBOARDING_PHASES.map((phase) => phase.id),
|
|
244
|
+
recommendedNextCommands,
|
|
245
|
+
},
|
|
47
246
|
};
|
|
48
247
|
if (result.ttsProvider) {
|
|
49
248
|
config.ttsProvider = result.ttsProvider;
|
|
50
249
|
}
|
|
51
250
|
writeFileSync(join(configDir, 'config.json'), JSON.stringify(config, null, 2) + '\n');
|
|
52
251
|
}
|
|
252
|
+
export function getProviderGuide(provider) {
|
|
253
|
+
return (PROVIDER_GUIDES.find((guide) => guide.id === provider)
|
|
254
|
+
?? PROVIDER_GUIDES.find((guide) => guide.id === 'chatgpt')
|
|
255
|
+
?? {
|
|
256
|
+
id: 'chatgpt',
|
|
257
|
+
label: 'ChatGPT subscription (OAuth)',
|
|
258
|
+
authMode: 'oauth',
|
|
259
|
+
envVar: '',
|
|
260
|
+
defaultModel: 'gpt-5.5',
|
|
261
|
+
setupCommand: 'buddy login',
|
|
262
|
+
verifyCommand: 'buddy whoami',
|
|
263
|
+
help: 'Use ChatGPT OAuth.',
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
export function buildRecommendedNextCommands(result) {
|
|
267
|
+
const guide = getProviderGuide(result.provider);
|
|
268
|
+
const commands = [];
|
|
269
|
+
if (guide.authMode === 'oauth') {
|
|
270
|
+
commands.push(guide.setupCommand ?? 'buddy login');
|
|
271
|
+
commands.push(guide.verifyCommand);
|
|
272
|
+
}
|
|
273
|
+
else if (guide.authMode === 'api-key' && guide.envVar && !result.apiKey) {
|
|
274
|
+
commands.push(`export ${guide.envVar}=<your_api_key>`);
|
|
275
|
+
commands.push(guide.verifyCommand);
|
|
276
|
+
}
|
|
277
|
+
else if (guide.authMode === 'local') {
|
|
278
|
+
if (guide.setupCommand)
|
|
279
|
+
commands.push(guide.setupCommand);
|
|
280
|
+
commands.push(guide.verifyCommand);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
commands.push(guide.verifyCommand);
|
|
284
|
+
}
|
|
285
|
+
commands.push(`buddy --model ${result.model} -p "Summarize this repo in 5 bullets and name the main entry point."`);
|
|
286
|
+
commands.push('buddy --continue');
|
|
287
|
+
commands.push('buddy --init');
|
|
288
|
+
return Array.from(new Set(commands));
|
|
289
|
+
}
|
|
290
|
+
export function renderOnboardingRoadmap(result) {
|
|
291
|
+
const nextCommands = result ? buildRecommendedNextCommands(result) : [];
|
|
292
|
+
const lines = [
|
|
293
|
+
' Hermes-style onboarding phases:',
|
|
294
|
+
...ONBOARDING_PHASES.map((phase, index) => ` ${index + 1}. ${phase.title} — ${phase.codeBuddyAction}`),
|
|
295
|
+
];
|
|
296
|
+
if (nextCommands.length) {
|
|
297
|
+
lines.push('', ' Recommended next commands:');
|
|
298
|
+
nextCommands.forEach((command) => lines.push(` ${command}`));
|
|
299
|
+
}
|
|
300
|
+
return lines.join('\n');
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Capture an API key and verify it against the provider's `/models` endpoint
|
|
304
|
+
* before we persist it (Hermes parity: a bad key is rejected here, not saved to
|
|
305
|
+
* silently break the first chat). Up to 3 attempts. On a connectivity error
|
|
306
|
+
* (not an auth rejection) the last-entered key can be kept and verified later,
|
|
307
|
+
* so a transient network blip doesn't discard an otherwise-good key.
|
|
308
|
+
*/
|
|
309
|
+
async function captureAndValidateKey(rl, guide, config) {
|
|
310
|
+
const maxAttempts = 3;
|
|
311
|
+
let lastKey = '';
|
|
312
|
+
let lastErrorWasAuth = true;
|
|
313
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
314
|
+
const key = await askSecret(rl, `Enter your ${guide.envVar} (or press Enter to set it later)`);
|
|
315
|
+
if (!key)
|
|
316
|
+
break;
|
|
317
|
+
lastKey = key;
|
|
318
|
+
if (!config) {
|
|
319
|
+
// No known validation endpoint — accept as entered, can't probe.
|
|
320
|
+
return { apiKey: key, verified: false };
|
|
321
|
+
}
|
|
322
|
+
console.log(' Validating…');
|
|
323
|
+
const result = await validateProviderKey(config, key);
|
|
324
|
+
if (result.valid) {
|
|
325
|
+
console.log(` ✓ Key verified — ${guide.label} reachable.`);
|
|
326
|
+
return { apiKey: key, verified: true, ...(result.models ? { models: result.models } : {}) };
|
|
327
|
+
}
|
|
328
|
+
lastErrorWasAuth = (result.error ?? '').toLowerCase().includes('invalid api key');
|
|
329
|
+
const left = maxAttempts - attempt;
|
|
330
|
+
console.log(` ✗ ${result.error}`);
|
|
331
|
+
if (left > 0) {
|
|
332
|
+
console.log(` Try again (${left} attempt${left === 1 ? '' : 's'} left), or press Enter to skip.`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Exhausted/skipped. If the failure was connectivity (not a rejected key),
|
|
336
|
+
// offer to keep the entered key and verify on the next run.
|
|
337
|
+
if (lastKey && !lastErrorWasAuth) {
|
|
338
|
+
const keep = (await ask(rl, 'Could not reach the provider. Save this key anyway and verify later? (y/N)', 'n'))
|
|
339
|
+
.toLowerCase();
|
|
340
|
+
if (keep === 'y' || keep === 'yes') {
|
|
341
|
+
return { apiKey: lastKey, verified: false };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return { apiKey: '', verified: false };
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Pick a model. When the provider handed us a real model list (from the
|
|
348
|
+
* validation probe), choose from it; otherwise fall back to free-text entry.
|
|
349
|
+
*/
|
|
350
|
+
async function selectModel(rl, guide, availableModels) {
|
|
351
|
+
const models = (availableModels ?? []).filter(Boolean);
|
|
352
|
+
if (models.length === 0) {
|
|
353
|
+
return ask(rl, 'Which model do you want to use?', guide.defaultModel);
|
|
354
|
+
}
|
|
355
|
+
const CUSTOM = '✏️ Enter a custom model id…';
|
|
356
|
+
const MAX_SHOWN = 30;
|
|
357
|
+
const shown = models.slice(0, MAX_SHOWN);
|
|
358
|
+
if (models.length > MAX_SHOWN) {
|
|
359
|
+
console.log(`\n (${models.length} models available; showing the first ${MAX_SHOWN} — pick "custom" to type any id.)`);
|
|
360
|
+
}
|
|
361
|
+
const choices = [...shown, CUSTOM];
|
|
362
|
+
const defaultIdx = shown.indexOf(guide.defaultModel) >= 0 ? shown.indexOf(guide.defaultModel) : 0;
|
|
363
|
+
const picked = await askChoice(rl, 'Which model do you want to use?', choices, defaultIdx);
|
|
364
|
+
if (picked === CUSTOM) {
|
|
365
|
+
return ask(rl, 'Enter the model id', guide.defaultModel);
|
|
366
|
+
}
|
|
367
|
+
return picked;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* A light "what's enabled now / add later" capability snapshot for the closing
|
|
371
|
+
* summary — the Hermes-style reassurance that the agent is ready, derived from
|
|
372
|
+
* env vars actually present. Kept deliberately minimal (getting-started scope).
|
|
373
|
+
*/
|
|
374
|
+
function renderCapabilitiesFooter() {
|
|
375
|
+
const has = (vars) => vars.some((v) => Boolean(process.env[v]));
|
|
376
|
+
const webSearch = has(['BRAVE_API_KEY', 'EXA_API_KEY', 'PERPLEXITY_API_KEY', 'TAVILY_API_KEY']);
|
|
377
|
+
return [
|
|
378
|
+
' Capabilities:',
|
|
379
|
+
' ✓ Code tools — file edit, shell, search',
|
|
380
|
+
' ✓ Text-to-speech (edge-tts, offline fallback)',
|
|
381
|
+
webSearch
|
|
382
|
+
? ' ✓ Web search'
|
|
383
|
+
: ' ○ Web search — add BRAVE_API_KEY or EXA_API_KEY to enable',
|
|
384
|
+
].join('\n');
|
|
385
|
+
}
|
|
53
386
|
export async function runOnboarding() {
|
|
54
387
|
const rl = readline.createInterface({
|
|
55
388
|
input: process.stdin,
|
|
@@ -63,21 +396,72 @@ export async function runOnboarding() {
|
|
|
63
396
|
console.log('');
|
|
64
397
|
console.log(' This wizard will help you configure Code Buddy.');
|
|
65
398
|
console.log('');
|
|
399
|
+
console.log(renderOnboardingRoadmap());
|
|
400
|
+
console.log('');
|
|
66
401
|
// 1. Provider selection
|
|
67
|
-
const provider = await askChoice(rl, 'Which AI provider do you want to use?',
|
|
68
|
-
|
|
69
|
-
const
|
|
402
|
+
const provider = await askChoice(rl, 'Which AI provider do you want to use?', PROVIDER_GUIDES.map((guide) => `${guide.id} — ${guide.label}`), 0).then((choice) => choice.split(/\s+—\s+/)[0] ?? choice);
|
|
403
|
+
const guide = getProviderGuide(provider);
|
|
404
|
+
const validationConfig = getValidationConfigForGuide(provider);
|
|
405
|
+
// 2. Authentication — captured AND verified inline so you leave the wizard
|
|
406
|
+
// ready to chat. A rejected key never gets persisted; a successful probe
|
|
407
|
+
// also hands us the provider's real model list for step 3.
|
|
408
|
+
const envVar = guide.envVar;
|
|
70
409
|
let apiKey = '';
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
410
|
+
let oauthDone = false;
|
|
411
|
+
let verified = false;
|
|
412
|
+
let availableModels;
|
|
413
|
+
if (guide.authMode === 'oauth') {
|
|
414
|
+
const doNow = (await ask(rl, 'Sign in with your ChatGPT account now? (Y/n)', 'y')).toLowerCase();
|
|
415
|
+
if (doNow === 'y' || doNow === 'yes') {
|
|
416
|
+
try {
|
|
417
|
+
const { loginInteractive } = await import('../providers/codex-oauth.js');
|
|
418
|
+
rl.pause();
|
|
419
|
+
const auth = await loginInteractive();
|
|
420
|
+
rl.resume();
|
|
421
|
+
oauthDone = true;
|
|
422
|
+
verified = true;
|
|
423
|
+
console.log(`\n ✅ Signed in${auth.email ? ` as ${auth.email}` : ''}.`);
|
|
424
|
+
}
|
|
425
|
+
catch (err) {
|
|
426
|
+
try {
|
|
427
|
+
rl.resume();
|
|
428
|
+
}
|
|
429
|
+
catch { /* ignore */ }
|
|
430
|
+
console.log(`\n ⚠️ Sign-in didn't complete (${err instanceof Error ? err.message : String(err)}).`);
|
|
431
|
+
console.log(` Finish later with: ${guide.setupCommand ?? 'buddy login'}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
console.log(` No problem — run \`${guide.setupCommand ?? 'buddy login'}\` when you're ready.`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
else if (guide.authMode === 'api-key' && envVar) {
|
|
439
|
+
console.log(`\n ${guide.help}`);
|
|
440
|
+
const captured = await captureAndValidateKey(rl, guide, validationConfig);
|
|
441
|
+
apiKey = captured.apiKey;
|
|
442
|
+
verified = captured.verified;
|
|
443
|
+
availableModels = captured.models;
|
|
444
|
+
}
|
|
445
|
+
else if (guide.authMode === 'local' && validationConfig) {
|
|
446
|
+
// Local providers: probe connectivity and list installed models. This is
|
|
447
|
+
// the real round-trip for the free path (no key required).
|
|
448
|
+
console.log(`\n Checking ${guide.label}…`);
|
|
449
|
+
const probe = await validateProviderKey(validationConfig, '');
|
|
450
|
+
if (probe.valid) {
|
|
451
|
+
verified = true;
|
|
452
|
+
availableModels = probe.models;
|
|
453
|
+
console.log(` ✓ ${guide.label} is running (${probe.models?.length ?? 0} model(s) available).`);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
console.log(` ⚠️ Could not reach ${guide.label}: ${probe.error}`);
|
|
457
|
+
console.log(` ${guide.help}`);
|
|
458
|
+
}
|
|
74
459
|
}
|
|
75
460
|
else {
|
|
76
|
-
console.log(`\n
|
|
461
|
+
console.log(`\n ${guide.help}`);
|
|
77
462
|
}
|
|
78
|
-
// 3. Model selection
|
|
79
|
-
const
|
|
80
|
-
const model = await ask(rl, 'Which model do you want to use?', defaultModel);
|
|
463
|
+
// 3. Model selection — from the real list when the probe gave us one.
|
|
464
|
+
const model = await selectModel(rl, guide, availableModels);
|
|
81
465
|
// 4. TTS setup
|
|
82
466
|
const ttsAnswer = await ask(rl, 'Enable text-to-speech? (y/n)', 'n');
|
|
83
467
|
const ttsEnabled = ttsAnswer.toLowerCase() === 'y' || ttsAnswer.toLowerCase() === 'yes';
|
|
@@ -85,23 +469,51 @@ export async function runOnboarding() {
|
|
|
85
469
|
if (ttsEnabled) {
|
|
86
470
|
ttsProvider = await askChoice(rl, 'Which TTS provider?', TTS_PROVIDERS, 0);
|
|
87
471
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
472
|
+
// 5. Persist credentials + provider so the session works now and next run.
|
|
473
|
+
await persistProviderSelection(guide, model, apiKey);
|
|
474
|
+
const result = {
|
|
475
|
+
provider,
|
|
476
|
+
apiKey,
|
|
477
|
+
model,
|
|
478
|
+
ttsEnabled,
|
|
479
|
+
authMode: guide.authMode,
|
|
480
|
+
recommendedNextCommands: buildRecommendedNextCommands({ provider, apiKey, model }),
|
|
481
|
+
...(ttsProvider ? { ttsProvider } : {}),
|
|
482
|
+
};
|
|
483
|
+
// 6. Write project config
|
|
484
|
+
writeConfig(join(process.cwd(), '.codebuddy'), result);
|
|
485
|
+
// 7. Summary
|
|
486
|
+
const ready = verified || (guide.authMode === 'api-key' && Boolean(apiKey));
|
|
93
487
|
console.log('');
|
|
94
|
-
console.log('
|
|
488
|
+
console.log(` ${ready ? 'Setup complete — you\'re ready to go!' : 'Setup saved.'}`);
|
|
95
489
|
console.log('');
|
|
96
490
|
console.log(` Provider: ${provider}`);
|
|
491
|
+
console.log(` Auth: ${guide.authMode}${oauthDone ? ' (signed in)' : guide.authMode === 'api-key' && apiKey ? ' (key saved)' : ''}`);
|
|
97
492
|
console.log(` Model: ${model}`);
|
|
98
493
|
if (ttsEnabled && ttsProvider) {
|
|
99
494
|
console.log(` TTS: ${ttsProvider}`);
|
|
100
495
|
}
|
|
101
|
-
if (
|
|
496
|
+
if (verified) {
|
|
497
|
+
const modelNote = availableModels?.includes(model) ? `, model ${model} available` : '';
|
|
498
|
+
console.log('');
|
|
499
|
+
console.log(` ✓ Verified — ${provider} reachable${modelNote}.`);
|
|
500
|
+
}
|
|
501
|
+
console.log('');
|
|
502
|
+
console.log(renderCapabilitiesFooter());
|
|
503
|
+
if (guide.authMode === 'oauth' && !oauthDone) {
|
|
102
504
|
console.log('');
|
|
103
|
-
console.log(`
|
|
505
|
+
console.log(` Next: run \`${guide.setupCommand ?? 'buddy login'}\` to finish signing in.`);
|
|
104
506
|
}
|
|
507
|
+
else if (guide.authMode === 'api-key' && !apiKey && envVar) {
|
|
508
|
+
console.log('');
|
|
509
|
+
console.log(` Next: set ${envVar} in your environment (or re-run \`buddy onboard\`).`);
|
|
510
|
+
}
|
|
511
|
+
else if (guide.authMode === 'local') {
|
|
512
|
+
console.log('');
|
|
513
|
+
console.log(` Next: make sure your local server is running (${guide.setupCommand ?? guide.verifyCommand}).`);
|
|
514
|
+
}
|
|
515
|
+
console.log('');
|
|
516
|
+
console.log(renderOnboardingRoadmap(result));
|
|
105
517
|
console.log('');
|
|
106
518
|
return result;
|
|
107
519
|
}
|