@sashabogi/foundation 0.1.6 → 0.1.8
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/dist/cli/setup-wizard.d.ts +30 -0
- package/dist/cli/setup-wizard.d.ts.map +1 -0
- package/dist/cli/setup-wizard.js +1645 -0
- package/dist/cli/setup-wizard.js.map +1 -0
- package/dist/cli/test-connection.d.ts +76 -0
- package/dist/cli/test-connection.d.ts.map +1 -0
- package/dist/cli/test-connection.js +697 -0
- package/dist/cli/test-connection.js.map +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +50 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +178 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +514 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/base.d.ts +154 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +227 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/deepseek.d.ts +23 -0
- package/dist/providers/deepseek.d.ts.map +1 -0
- package/dist/providers/deepseek.js +31 -0
- package/dist/providers/deepseek.js.map +1 -0
- package/dist/providers/fireworks.d.ts +23 -0
- package/dist/providers/fireworks.d.ts.map +1 -0
- package/dist/providers/fireworks.js +31 -0
- package/dist/providers/fireworks.js.map +1 -0
- package/dist/providers/gemini.d.ts +85 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +414 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/groq.d.ts +23 -0
- package/dist/providers/groq.d.ts.map +1 -0
- package/dist/providers/groq.js +31 -0
- package/dist/providers/groq.js.map +1 -0
- package/dist/providers/index.d.ts +23 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +27 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/kimi-code.d.ts +32 -0
- package/dist/providers/kimi-code.d.ts.map +1 -0
- package/dist/providers/kimi-code.js +46 -0
- package/dist/providers/kimi-code.js.map +1 -0
- package/dist/providers/kimi.d.ts +19 -0
- package/dist/providers/kimi.d.ts.map +1 -0
- package/dist/providers/kimi.js +27 -0
- package/dist/providers/kimi.js.map +1 -0
- package/dist/providers/manager.d.ts +144 -0
- package/dist/providers/manager.d.ts.map +1 -0
- package/dist/providers/manager.js +279 -0
- package/dist/providers/manager.js.map +1 -0
- package/dist/providers/ollama.d.ts +83 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +450 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +91 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +445 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openrouter.d.ts +23 -0
- package/dist/providers/openrouter.d.ts.map +1 -0
- package/dist/providers/openrouter.js +31 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/perplexity.d.ts +34 -0
- package/dist/providers/perplexity.d.ts.map +1 -0
- package/dist/providers/perplexity.js +58 -0
- package/dist/providers/perplexity.js.map +1 -0
- package/dist/providers/together.d.ts +23 -0
- package/dist/providers/together.d.ts.map +1 -0
- package/dist/providers/together.js +31 -0
- package/dist/providers/together.js.map +1 -0
- package/dist/providers/types.d.ts +229 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +73 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/providers/zai.d.ts +19 -0
- package/dist/providers/zai.d.ts.map +1 -0
- package/dist/providers/zai.js +27 -0
- package/dist/providers/zai.js.map +1 -0
- package/dist/services/provider.service.d.ts +28 -0
- package/dist/services/provider.service.d.ts.map +1 -1
- package/dist/services/provider.service.js +137 -13
- package/dist/services/provider.service.js.map +1 -1
- package/dist/tools/demerzel/engine.d.ts +67 -0
- package/dist/tools/demerzel/engine.d.ts.map +1 -0
- package/dist/tools/demerzel/engine.js +401 -0
- package/dist/tools/demerzel/engine.js.map +1 -0
- package/dist/tools/demerzel/enhanced-snapshot.d.ts +67 -0
- package/dist/tools/demerzel/enhanced-snapshot.d.ts.map +1 -0
- package/dist/tools/demerzel/enhanced-snapshot.js +481 -0
- package/dist/tools/demerzel/enhanced-snapshot.js.map +1 -0
- package/dist/tools/demerzel/index.d.ts +11 -0
- package/dist/tools/demerzel/index.d.ts.map +1 -1
- package/dist/tools/demerzel/index.js +656 -85
- package/dist/tools/demerzel/index.js.map +1 -1
- package/dist/tools/demerzel/prompts.d.ts +26 -0
- package/dist/tools/demerzel/prompts.d.ts.map +1 -0
- package/dist/tools/demerzel/prompts.js +181 -0
- package/dist/tools/demerzel/prompts.js.map +1 -0
- package/dist/tools/demerzel/semantic-search.d.ts +54 -0
- package/dist/tools/demerzel/semantic-search.d.ts.map +1 -0
- package/dist/tools/demerzel/semantic-search.js +205 -0
- package/dist/tools/demerzel/semantic-search.js.map +1 -0
- package/dist/tools/demerzel/snapshot.d.ts +30 -0
- package/dist/tools/demerzel/snapshot.d.ts.map +1 -0
- package/dist/tools/demerzel/snapshot.js +169 -0
- package/dist/tools/demerzel/snapshot.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,1645 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive setup wizard for Foundation
|
|
3
|
+
*
|
|
4
|
+
* Foundation is an MCP server for AI-assisted development providing:
|
|
5
|
+
* - Codebase intelligence (Demerzel)
|
|
6
|
+
* - Multi-agent orchestration (Seldon)
|
|
7
|
+
* - Workflow patterns (Gaia)
|
|
8
|
+
*
|
|
9
|
+
* This wizard supports:
|
|
10
|
+
* - Multiple access modes: API keys OR subscription/CLI passthrough
|
|
11
|
+
* - Configurable orchestrator (any provider can orchestrate)
|
|
12
|
+
* - Full flexibility in role assignment
|
|
13
|
+
* - Git worktree isolation for coding agents
|
|
14
|
+
*
|
|
15
|
+
* Updated February 2026 with latest models from all providers.
|
|
16
|
+
*/
|
|
17
|
+
import * as p from "@clack/prompts";
|
|
18
|
+
import color from "picocolors";
|
|
19
|
+
import * as fs from "node:fs";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
import * as os from "node:os";
|
|
22
|
+
import { stringify as yamlStringify } from "yaml";
|
|
23
|
+
import { testOpenAIConnection, testGeminiConnection, testDeepSeekConnection, testZaiConnection, testKimiConnection, testKimiCodeConnection, testOllamaConnection, testAnthropicConnection, testPerplexityConnection, testOpenRouterConnection, testGroqConnection, testTogetherConnection, testFireworksConnection, testConnectionWithSpinner, } from "./test-connection.js";
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Config Paths
|
|
26
|
+
// ============================================================================
|
|
27
|
+
const CONFIG_DIR = path.join(os.homedir(), ".foundation");
|
|
28
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.yaml");
|
|
29
|
+
function getConfigPath() {
|
|
30
|
+
return CONFIG_FILE;
|
|
31
|
+
}
|
|
32
|
+
function getConfigDir() {
|
|
33
|
+
return CONFIG_DIR;
|
|
34
|
+
}
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Constants - Updated February 2026
|
|
37
|
+
// ============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Environment variable names for each provider (API mode)
|
|
40
|
+
*/
|
|
41
|
+
const PROVIDER_ENV_VARS = {
|
|
42
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
43
|
+
openai: "OPENAI_API_KEY",
|
|
44
|
+
google: "GEMINI_API_KEY",
|
|
45
|
+
deepseek: "DEEPSEEK_API_KEY",
|
|
46
|
+
zai: "ZAI_API_KEY",
|
|
47
|
+
kimi: "KIMI_API_KEY",
|
|
48
|
+
"kimi-code": "KIMI_API_KEY", // Uses same API key as regular Kimi
|
|
49
|
+
ollama: "",
|
|
50
|
+
perplexity: "PERPLEXITY_API_KEY",
|
|
51
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
52
|
+
groq: "GROQ_API_KEY",
|
|
53
|
+
together: "TOGETHER_API_KEY",
|
|
54
|
+
fireworks: "FIREWORKS_API_KEY",
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Track which environment variables were configured during setup
|
|
58
|
+
*/
|
|
59
|
+
const configuredEnvVars = new Map();
|
|
60
|
+
/**
|
|
61
|
+
* All available providers with their capabilities
|
|
62
|
+
* Updated February 2026
|
|
63
|
+
*/
|
|
64
|
+
const AVAILABLE_PROVIDERS = [
|
|
65
|
+
{
|
|
66
|
+
value: "anthropic",
|
|
67
|
+
label: "Anthropic (Claude)",
|
|
68
|
+
hint: "Claude Opus/Sonnet 4.5 - Best for orchestration and code",
|
|
69
|
+
supportsSubscription: true,
|
|
70
|
+
subscriptionInfo: "Claude Code session (Max/Pro subscription) - orchestrator only",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
value: "openai",
|
|
74
|
+
label: "OpenAI",
|
|
75
|
+
hint: "GPT-5.2/5.1 - Excellent for coding and critiques",
|
|
76
|
+
supportsSubscription: false,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
value: "google",
|
|
80
|
+
label: "Google Gemini",
|
|
81
|
+
hint: "Gemini 3/2.5 - Great for research and multimodal",
|
|
82
|
+
supportsSubscription: false,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
value: "deepseek",
|
|
86
|
+
label: "DeepSeek",
|
|
87
|
+
hint: "V3.2 Reasoner - Excellent reasoning at 1/10th the cost",
|
|
88
|
+
supportsSubscription: false,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
value: "zai",
|
|
92
|
+
label: "Z.AI (GLM)",
|
|
93
|
+
hint: "GLM-4.7 - Strong agentic coding, very affordable",
|
|
94
|
+
supportsSubscription: false,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
value: "kimi",
|
|
98
|
+
label: "Moonshot (Kimi)",
|
|
99
|
+
hint: "Kimi K2.5 - Excellent coding, 1M context window",
|
|
100
|
+
supportsSubscription: false,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
value: "kimi-code",
|
|
104
|
+
label: "Kimi Code",
|
|
105
|
+
hint: "Dedicated coding endpoint - uses Kimi subscription",
|
|
106
|
+
supportsSubscription: false,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
value: "perplexity",
|
|
110
|
+
label: "Perplexity",
|
|
111
|
+
hint: "Sonar models - Web search grounded, citations",
|
|
112
|
+
supportsSubscription: false,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
value: "openrouter",
|
|
116
|
+
label: "OpenRouter",
|
|
117
|
+
hint: "Gateway to 300+ models via single API",
|
|
118
|
+
supportsSubscription: false,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
value: "groq",
|
|
122
|
+
label: "Groq",
|
|
123
|
+
hint: "Ultra-fast LPU inference - 18x faster",
|
|
124
|
+
supportsSubscription: false,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
value: "together",
|
|
128
|
+
label: "Together AI",
|
|
129
|
+
hint: "200+ open models, sub-100ms latency",
|
|
130
|
+
supportsSubscription: false,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
value: "fireworks",
|
|
134
|
+
label: "Fireworks AI",
|
|
135
|
+
hint: "Fast inference, DeepSeek/Llama hosting",
|
|
136
|
+
supportsSubscription: false,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
value: "ollama",
|
|
140
|
+
label: "Ollama",
|
|
141
|
+
hint: "Local models - free, private, offline",
|
|
142
|
+
supportsSubscription: false,
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// Model Options - Updated February 2026
|
|
147
|
+
// ============================================================================
|
|
148
|
+
const ANTHROPIC_MODELS = [
|
|
149
|
+
{ value: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", hint: "Best balance - recommended" },
|
|
150
|
+
{ value: "claude-opus-4-5-20251101", label: "Claude Opus 4.5", hint: "Most intelligent" },
|
|
151
|
+
{ value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", hint: "Fastest, cheapest" },
|
|
152
|
+
];
|
|
153
|
+
const OPENAI_MODELS = [
|
|
154
|
+
{ value: "gpt-5.1", label: "GPT-5.1", hint: "Best for coding - adaptive reasoning" },
|
|
155
|
+
{ value: "gpt-5.2", label: "GPT-5.2", hint: "Most advanced frontier model" },
|
|
156
|
+
{ value: "gpt-5", label: "GPT-5", hint: "Strong coding, 400K context" },
|
|
157
|
+
{ value: "gpt-5-mini", label: "GPT-5 Mini", hint: "Smaller, cost-effective" },
|
|
158
|
+
{ value: "gpt-4.1", label: "GPT-4.1", hint: "Previous gen, 1M context" },
|
|
159
|
+
{ value: "o3", label: "o3", hint: "Advanced reasoning (slower)" },
|
|
160
|
+
];
|
|
161
|
+
const GEMINI_MODELS = [
|
|
162
|
+
{ value: "gemini-2.5-pro", label: "Gemini 2.5 Pro", hint: "Stable, high capability" },
|
|
163
|
+
{ value: "gemini-2.5-flash", label: "Gemini 2.5 Flash", hint: "Fast, balanced" },
|
|
164
|
+
{ value: "gemini-3-pro-preview", label: "Gemini 3 Pro (Preview)", hint: "Most advanced reasoning" },
|
|
165
|
+
{ value: "gemini-3-flash-preview", label: "Gemini 3 Flash (Preview)", hint: "Fast frontier performance" },
|
|
166
|
+
];
|
|
167
|
+
const DEEPSEEK_MODELS = [
|
|
168
|
+
{ value: "deepseek-reasoner", label: "DeepSeek V3.2 (Thinking)", hint: "Best reasoning - great for critiques" },
|
|
169
|
+
{ value: "deepseek-chat", label: "DeepSeek V3.2 (Non-thinking)", hint: "Fast and very cheap ($0.28/1M)" },
|
|
170
|
+
];
|
|
171
|
+
const ZAI_MODELS = [
|
|
172
|
+
{ value: "glm-4.7", label: "GLM-4.7", hint: "Flagship - best for agentic coding" },
|
|
173
|
+
{ value: "glm-4.7-flashx", label: "GLM-4.7 FlashX", hint: "Fast and affordable" },
|
|
174
|
+
{ value: "glm-4.7-flash", label: "GLM-4.7 Flash", hint: "Free tier" },
|
|
175
|
+
];
|
|
176
|
+
const KIMI_MODELS = [
|
|
177
|
+
{ value: "kimi-k2-5-preview", label: "Kimi K2.5 (Preview)", hint: "Latest flagship - 1M context" },
|
|
178
|
+
{ value: "moonshot-v1-128k", label: "Moonshot V1 128K", hint: "Stable, 128K context" },
|
|
179
|
+
{ value: "moonshot-v1-32k", label: "Moonshot V1 32K", hint: "Fast, 32K context" },
|
|
180
|
+
];
|
|
181
|
+
const KIMI_CODE_MODELS = [
|
|
182
|
+
{ value: "kimi-for-coding", label: "Kimi for Coding", hint: "Optimized for code generation" },
|
|
183
|
+
];
|
|
184
|
+
const PERPLEXITY_MODELS = [
|
|
185
|
+
{ value: "sonar-deep-research", label: "Sonar Deep Research", hint: "Deep research with citations" },
|
|
186
|
+
{ value: "sonar-reasoning-pro", label: "Sonar Reasoning Pro", hint: "Advanced reasoning + search" },
|
|
187
|
+
{ value: "sonar-reasoning", label: "Sonar Reasoning", hint: "Reasoning with web grounding" },
|
|
188
|
+
{ value: "sonar-pro", label: "Sonar Pro", hint: "Enhanced search capabilities" },
|
|
189
|
+
{ value: "sonar", label: "Sonar", hint: "Fast search-grounded responses" },
|
|
190
|
+
{ value: "r1-1776", label: "R1-1776", hint: "Reasoning model" },
|
|
191
|
+
];
|
|
192
|
+
const OPENROUTER_MODELS = [
|
|
193
|
+
{ value: "openrouter/auto", label: "Auto", hint: "Automatically selects best model" },
|
|
194
|
+
{ value: "anthropic/claude-3.5-sonnet", label: "Claude 3.5 Sonnet", hint: "Via OpenRouter" },
|
|
195
|
+
{ value: "openai/gpt-4o", label: "GPT-4o", hint: "Via OpenRouter" },
|
|
196
|
+
{ value: "google/gemini-pro-1.5", label: "Gemini Pro 1.5", hint: "Via OpenRouter" },
|
|
197
|
+
{ value: "meta-llama/llama-3.3-70b-instruct", label: "Llama 3.3 70B", hint: "Via OpenRouter" },
|
|
198
|
+
];
|
|
199
|
+
const GROQ_MODELS = [
|
|
200
|
+
{ value: "llama-3.3-70b-versatile", label: "Llama 3.3 70B Versatile", hint: "Best all-around" },
|
|
201
|
+
{ value: "llama-3.1-8b-instant", label: "Llama 3.1 8B Instant", hint: "Ultra-fast, small" },
|
|
202
|
+
{ value: "mixtral-8x7b-32768", label: "Mixtral 8x7B", hint: "MoE model, 32K context" },
|
|
203
|
+
{ value: "gemma2-9b-it", label: "Gemma 2 9B IT", hint: "Google's open model" },
|
|
204
|
+
];
|
|
205
|
+
const TOGETHER_MODELS = [
|
|
206
|
+
{ value: "meta-llama/Llama-3.3-70B-Instruct-Turbo", label: "Llama 3.3 70B Turbo", hint: "Fast, high quality" },
|
|
207
|
+
{ value: "deepseek-ai/DeepSeek-V3", label: "DeepSeek V3", hint: "Advanced reasoning" },
|
|
208
|
+
{ value: "Qwen/Qwen2.5-72B-Instruct-Turbo", label: "Qwen 2.5 72B Turbo", hint: "Strong multilingual" },
|
|
209
|
+
{ value: "mistralai/Mixtral-8x22B-Instruct-v0.1", label: "Mixtral 8x22B", hint: "Large MoE model" },
|
|
210
|
+
];
|
|
211
|
+
const FIREWORKS_MODELS = [
|
|
212
|
+
{ value: "accounts/fireworks/models/llama-v3p3-70b-instruct", label: "Llama 3.3 70B", hint: "Fast inference" },
|
|
213
|
+
{ value: "accounts/fireworks/models/deepseek-v3", label: "DeepSeek V3", hint: "Advanced reasoning" },
|
|
214
|
+
{ value: "accounts/fireworks/models/mixtral-8x22b-instruct", label: "Mixtral 8x22B", hint: "Large MoE" },
|
|
215
|
+
{ value: "accounts/fireworks/models/qwen2p5-72b-instruct", label: "Qwen 2.5 72B", hint: "Multilingual" },
|
|
216
|
+
];
|
|
217
|
+
const OLLAMA_MODELS = [
|
|
218
|
+
{ value: "llama3.2", label: "Llama 3.2", hint: "Default local model" },
|
|
219
|
+
{ value: "qwen3:32b", label: "Qwen 3 32B", hint: "Strong reasoning" },
|
|
220
|
+
{ value: "codellama", label: "CodeLlama", hint: "Code-focused" },
|
|
221
|
+
{ value: "deepseek-coder-v2", label: "DeepSeek Coder V2", hint: "Excellent for code" },
|
|
222
|
+
];
|
|
223
|
+
const AGENT_ROLES = [
|
|
224
|
+
{
|
|
225
|
+
value: "orchestrator",
|
|
226
|
+
label: "Orchestrator",
|
|
227
|
+
hint: "Main coordinator - routes tasks to other agents",
|
|
228
|
+
defaultProvider: "anthropic",
|
|
229
|
+
defaultModel: "claude-sonnet-4-5-20250929",
|
|
230
|
+
temperature: 0.3,
|
|
231
|
+
systemPrompt: `You are the orchestrating AI agent. Your role is to:
|
|
232
|
+
|
|
233
|
+
1. **Understand the user's intent** and break down complex tasks
|
|
234
|
+
2. **Route tasks** to specialized agents (critic, reviewer, researcher, etc.)
|
|
235
|
+
3. **Synthesize results** from multiple agents into coherent responses
|
|
236
|
+
4. **Maintain context** across the conversation
|
|
237
|
+
|
|
238
|
+
You have access to other AI agents with different specializations.
|
|
239
|
+
Use them strategically to provide the best possible assistance.`,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
value: "critic",
|
|
243
|
+
label: "Critic",
|
|
244
|
+
hint: "Challenge assumptions, find flaws in plans",
|
|
245
|
+
defaultProvider: "deepseek",
|
|
246
|
+
defaultModel: "deepseek-reasoner",
|
|
247
|
+
temperature: 0.3,
|
|
248
|
+
systemPrompt: `You are a skeptical senior architect reviewing a plan.
|
|
249
|
+
Your job is to provide a critical second opinion:
|
|
250
|
+
|
|
251
|
+
1. **Challenge Assumptions**: Don't accept claims at face value. Ask "Why?" and "What if?"
|
|
252
|
+
2. **Identify Risks**: Find failure modes, edge cases, and potential issues
|
|
253
|
+
3. **Question Scope**: Is the solution over-engineered? Under-specified?
|
|
254
|
+
4. **Check Completeness**: What's missing? What hasn't been considered?
|
|
255
|
+
5. **Push for Excellence**: "Good enough" isn't good enough. Find ways to improve.
|
|
256
|
+
|
|
257
|
+
Be constructive but rigorous. Provide specific, actionable feedback.`,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
value: "coder",
|
|
261
|
+
label: "Coder",
|
|
262
|
+
hint: "Write, refactor, and implement code",
|
|
263
|
+
defaultProvider: "anthropic",
|
|
264
|
+
defaultModel: "claude-sonnet-4-5-20250929",
|
|
265
|
+
temperature: 0.2,
|
|
266
|
+
systemPrompt: `You are an expert software engineer. Your role is to:
|
|
267
|
+
|
|
268
|
+
1. **Write Clean Code**: Follow best practices, use clear naming, add appropriate comments
|
|
269
|
+
2. **Implement Features**: Turn requirements into working, well-tested code
|
|
270
|
+
3. **Refactor**: Improve existing code structure without changing behavior
|
|
271
|
+
4. **Debug**: Find and fix bugs systematically
|
|
272
|
+
5. **Optimize**: Improve performance where it matters
|
|
273
|
+
|
|
274
|
+
Always consider:
|
|
275
|
+
- Error handling and edge cases
|
|
276
|
+
- Testing and testability
|
|
277
|
+
- Security implications
|
|
278
|
+
- Performance characteristics
|
|
279
|
+
- Maintainability and readability
|
|
280
|
+
|
|
281
|
+
Provide complete, runnable code with explanations of key decisions.`,
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
value: "reviewer",
|
|
285
|
+
label: "Code Reviewer",
|
|
286
|
+
hint: "Review code for bugs, security, performance",
|
|
287
|
+
defaultProvider: "anthropic",
|
|
288
|
+
defaultModel: "claude-sonnet-4-5-20250929",
|
|
289
|
+
temperature: 0.2,
|
|
290
|
+
systemPrompt: `You are a senior code reviewer. Review code for:
|
|
291
|
+
|
|
292
|
+
1. **Correctness**: Does it work? Are there bugs?
|
|
293
|
+
2. **Security**: SQL injection, XSS, auth issues, data exposure
|
|
294
|
+
3. **Performance**: N+1 queries, unnecessary computations, memory leaks
|
|
295
|
+
4. **Maintainability**: Is it readable? Well-structured? Documented?
|
|
296
|
+
5. **Best Practices**: Follows language/framework conventions?
|
|
297
|
+
6. **Testing**: Is it testable? Are there missing tests?
|
|
298
|
+
|
|
299
|
+
Be specific. Reference line numbers. Suggest improvements with code examples.`,
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
value: "designer",
|
|
303
|
+
label: "Designer",
|
|
304
|
+
hint: "UI/UX feedback and design review",
|
|
305
|
+
defaultProvider: "google",
|
|
306
|
+
defaultModel: "gemini-2.5-pro",
|
|
307
|
+
systemPrompt: `You are a senior UI/UX designer. Focus on:
|
|
308
|
+
|
|
309
|
+
1. **User Experience**: Is it intuitive? Accessible? Delightful?
|
|
310
|
+
2. **Visual Hierarchy**: Does the layout guide the user's eye?
|
|
311
|
+
3. **Component Architecture**: Are components reusable? Maintainable?
|
|
312
|
+
4. **Design Systems**: Does it follow established patterns?
|
|
313
|
+
5. **Responsive Design**: How does it work across devices?
|
|
314
|
+
6. **Accessibility**: WCAG compliance, keyboard navigation, screen readers
|
|
315
|
+
|
|
316
|
+
Provide specific feedback with examples and alternatives.`,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
value: "researcher",
|
|
320
|
+
label: "Researcher",
|
|
321
|
+
hint: "Fact-finding and research tasks",
|
|
322
|
+
defaultProvider: "google",
|
|
323
|
+
defaultModel: "gemini-2.5-pro",
|
|
324
|
+
systemPrompt: `You are a research analyst. Provide:
|
|
325
|
+
|
|
326
|
+
1. Well-researched, factual information
|
|
327
|
+
2. Source citations where possible
|
|
328
|
+
3. Confidence levels for claims
|
|
329
|
+
4. Alternative perspectives or approaches
|
|
330
|
+
5. Current best practices in the field
|
|
331
|
+
|
|
332
|
+
If you're uncertain, say so. Prefer accuracy over completeness.`,
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
value: "verifier",
|
|
336
|
+
label: "Verifier",
|
|
337
|
+
hint: "Verify implementation matches plan",
|
|
338
|
+
defaultProvider: "anthropic",
|
|
339
|
+
defaultModel: "claude-sonnet-4-5-20250929",
|
|
340
|
+
temperature: 0.2,
|
|
341
|
+
systemPrompt: `You are an implementation verifier. Your role is to:
|
|
342
|
+
|
|
343
|
+
1. **Compare implementation to plan**: Does the code match what was intended?
|
|
344
|
+
2. **Check for completeness**: Are all requirements addressed?
|
|
345
|
+
3. **Identify deviations**: Note any differences from the plan
|
|
346
|
+
4. **Verify correctness**: Does the implementation work correctly?
|
|
347
|
+
5. **Check for regressions**: Did we break anything existing?
|
|
348
|
+
|
|
349
|
+
Be thorough and systematic. Report issues with specific file/line references.`,
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
// ============================================================================
|
|
353
|
+
// Display Functions
|
|
354
|
+
// ============================================================================
|
|
355
|
+
function displayBanner() {
|
|
356
|
+
console.log();
|
|
357
|
+
console.log(color.cyan("╭───────────────────────────────────────────────────────╮"));
|
|
358
|
+
console.log(color.cyan("│ │"));
|
|
359
|
+
console.log(color.cyan("│ ") +
|
|
360
|
+
color.bold(color.yellow("🔧")) +
|
|
361
|
+
color.bold(" Foundation Setup") +
|
|
362
|
+
color.cyan(" │"));
|
|
363
|
+
console.log(color.cyan("│ │"));
|
|
364
|
+
console.log(color.cyan("│ ") +
|
|
365
|
+
color.dim("AI-assisted development with multi-agent orchestration") +
|
|
366
|
+
color.cyan("│"));
|
|
367
|
+
console.log(color.cyan("│ │"));
|
|
368
|
+
console.log(color.cyan("╰───────────────────────────────────────────────────────╯"));
|
|
369
|
+
console.log();
|
|
370
|
+
}
|
|
371
|
+
// ============================================================================
|
|
372
|
+
// Validation Functions
|
|
373
|
+
// ============================================================================
|
|
374
|
+
function validateApiKey(value, _provider) {
|
|
375
|
+
if (!value || value.trim().length === 0) {
|
|
376
|
+
return "API key is required";
|
|
377
|
+
}
|
|
378
|
+
if (value.length < 10) {
|
|
379
|
+
return "API key seems too short";
|
|
380
|
+
}
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
383
|
+
// ============================================================================
|
|
384
|
+
// Helper Functions
|
|
385
|
+
// ============================================================================
|
|
386
|
+
function getProviderModels(providerName) {
|
|
387
|
+
switch (providerName) {
|
|
388
|
+
case "anthropic":
|
|
389
|
+
return ANTHROPIC_MODELS;
|
|
390
|
+
case "openai":
|
|
391
|
+
return OPENAI_MODELS;
|
|
392
|
+
case "google":
|
|
393
|
+
return GEMINI_MODELS;
|
|
394
|
+
case "deepseek":
|
|
395
|
+
return DEEPSEEK_MODELS;
|
|
396
|
+
case "zai":
|
|
397
|
+
return ZAI_MODELS;
|
|
398
|
+
case "kimi":
|
|
399
|
+
return KIMI_MODELS;
|
|
400
|
+
case "kimi-code":
|
|
401
|
+
return KIMI_CODE_MODELS;
|
|
402
|
+
case "perplexity":
|
|
403
|
+
return PERPLEXITY_MODELS;
|
|
404
|
+
case "openrouter":
|
|
405
|
+
return OPENROUTER_MODELS;
|
|
406
|
+
case "groq":
|
|
407
|
+
return GROQ_MODELS;
|
|
408
|
+
case "together":
|
|
409
|
+
return TOGETHER_MODELS;
|
|
410
|
+
case "fireworks":
|
|
411
|
+
return FIREWORKS_MODELS;
|
|
412
|
+
case "ollama":
|
|
413
|
+
return OLLAMA_MODELS;
|
|
414
|
+
default:
|
|
415
|
+
return [{ value: "default", label: "Default model" }];
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function getProviderInfo(providerName) {
|
|
419
|
+
return AVAILABLE_PROVIDERS.find((p) => p.value === providerName);
|
|
420
|
+
}
|
|
421
|
+
function getProviderDisplayName(provider) {
|
|
422
|
+
const displayNames = {
|
|
423
|
+
anthropic: "Anthropic (Claude)",
|
|
424
|
+
openai: "OpenAI (GPT)",
|
|
425
|
+
google: "Google (Gemini)",
|
|
426
|
+
deepseek: "DeepSeek",
|
|
427
|
+
zai: "Z.AI (GLM)",
|
|
428
|
+
kimi: "Moonshot (Kimi)",
|
|
429
|
+
"kimi-code": "Kimi Code",
|
|
430
|
+
ollama: "Ollama (Local)",
|
|
431
|
+
perplexity: "Perplexity",
|
|
432
|
+
openrouter: "OpenRouter",
|
|
433
|
+
groq: "Groq",
|
|
434
|
+
together: "Together AI",
|
|
435
|
+
fireworks: "Fireworks AI",
|
|
436
|
+
};
|
|
437
|
+
return displayNames[provider] || provider;
|
|
438
|
+
}
|
|
439
|
+
function getDefaultModelForProvider(provider) {
|
|
440
|
+
const models = getProviderModels(provider);
|
|
441
|
+
return models[0]?.value ?? "default";
|
|
442
|
+
}
|
|
443
|
+
// ============================================================================
|
|
444
|
+
// Provider Configuration Functions
|
|
445
|
+
// ============================================================================
|
|
446
|
+
async function configureAnthropic() {
|
|
447
|
+
p.log.step(color.bold("Anthropic (Claude) Configuration"));
|
|
448
|
+
const provider = getProviderInfo("anthropic");
|
|
449
|
+
if (provider.supportsSubscription) {
|
|
450
|
+
const accessMode = await p.select({
|
|
451
|
+
message: `How do you want to access ${provider.label}?`,
|
|
452
|
+
options: [
|
|
453
|
+
{
|
|
454
|
+
value: "subscription",
|
|
455
|
+
label: "Subscription/CLI",
|
|
456
|
+
hint: provider.subscriptionInfo ?? "Uses CLI tool with subscription",
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
value: "api",
|
|
460
|
+
label: "API Key",
|
|
461
|
+
hint: "Pay-per-token usage",
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
});
|
|
465
|
+
if (p.isCancel(accessMode))
|
|
466
|
+
return null;
|
|
467
|
+
if (accessMode === "subscription") {
|
|
468
|
+
p.note("You're using Claude Code with your existing subscription.\n" +
|
|
469
|
+
"No API key needed - Claude will be accessed through the current session.", "Subscription Mode");
|
|
470
|
+
const defaultModel = await p.select({
|
|
471
|
+
message: "Select default Claude model:",
|
|
472
|
+
options: ANTHROPIC_MODELS,
|
|
473
|
+
});
|
|
474
|
+
if (p.isCancel(defaultModel))
|
|
475
|
+
return null;
|
|
476
|
+
return {
|
|
477
|
+
access_mode: "subscription",
|
|
478
|
+
default_model: defaultModel,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// API mode
|
|
483
|
+
p.note("Get your API key from:\n" +
|
|
484
|
+
color.cyan("https://console.anthropic.com/settings/keys"), "API Key Setup");
|
|
485
|
+
while (true) {
|
|
486
|
+
const apiKey = await p.password({
|
|
487
|
+
message: "Enter your Anthropic API key:",
|
|
488
|
+
validate: (v) => validateApiKey(v, "anthropic"),
|
|
489
|
+
});
|
|
490
|
+
if (p.isCancel(apiKey))
|
|
491
|
+
return null;
|
|
492
|
+
const apiKeyStr = apiKey;
|
|
493
|
+
const result = await testConnectionWithSpinner("Anthropic", () => testAnthropicConnection(apiKeyStr));
|
|
494
|
+
if (result.success) {
|
|
495
|
+
const defaultModel = await p.select({
|
|
496
|
+
message: "Select default Claude model:",
|
|
497
|
+
options: ANTHROPIC_MODELS,
|
|
498
|
+
});
|
|
499
|
+
if (p.isCancel(defaultModel))
|
|
500
|
+
return null;
|
|
501
|
+
const envVarName = PROVIDER_ENV_VARS["anthropic"];
|
|
502
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
503
|
+
return {
|
|
504
|
+
access_mode: "api",
|
|
505
|
+
api_key: "${" + envVarName + "}",
|
|
506
|
+
default_model: defaultModel,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
const action = await p.select({
|
|
510
|
+
message: "Connection test failed. What would you like to do?",
|
|
511
|
+
options: [
|
|
512
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
513
|
+
{ value: "skip", label: "Skip this provider" },
|
|
514
|
+
{ value: "add", label: "Add anyway" },
|
|
515
|
+
],
|
|
516
|
+
});
|
|
517
|
+
if (p.isCancel(action) || action === "skip")
|
|
518
|
+
return null;
|
|
519
|
+
if (action === "add") {
|
|
520
|
+
const envVarName = PROVIDER_ENV_VARS["anthropic"];
|
|
521
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
522
|
+
return {
|
|
523
|
+
access_mode: "api",
|
|
524
|
+
api_key: "${" + envVarName + "}",
|
|
525
|
+
default_model: "claude-sonnet-4-5-20250929",
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async function configureOpenAI() {
|
|
531
|
+
p.log.step(color.bold("OpenAI Configuration"));
|
|
532
|
+
p.note("Get your API key from:\n" +
|
|
533
|
+
color.cyan("https://platform.openai.com/api-keys"), "API Key Setup");
|
|
534
|
+
while (true) {
|
|
535
|
+
const apiKey = await p.password({
|
|
536
|
+
message: "Enter your OpenAI API key:",
|
|
537
|
+
validate: (v) => validateApiKey(v, "openai"),
|
|
538
|
+
});
|
|
539
|
+
if (p.isCancel(apiKey))
|
|
540
|
+
return null;
|
|
541
|
+
const apiKeyStr = apiKey;
|
|
542
|
+
const result = await testConnectionWithSpinner("OpenAI", () => testOpenAIConnection(apiKeyStr));
|
|
543
|
+
if (result.success) {
|
|
544
|
+
const defaultModel = await p.select({
|
|
545
|
+
message: "Select default OpenAI model:",
|
|
546
|
+
options: OPENAI_MODELS,
|
|
547
|
+
});
|
|
548
|
+
if (p.isCancel(defaultModel))
|
|
549
|
+
return null;
|
|
550
|
+
const envVarName = PROVIDER_ENV_VARS["openai"];
|
|
551
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
552
|
+
return {
|
|
553
|
+
access_mode: "api",
|
|
554
|
+
api_key: "${" + envVarName + "}",
|
|
555
|
+
base_url: "https://api.openai.com/v1",
|
|
556
|
+
default_model: defaultModel,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
const action = await p.select({
|
|
560
|
+
message: "Connection test failed. What would you like to do?",
|
|
561
|
+
options: [
|
|
562
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
563
|
+
{ value: "skip", label: "Skip this provider" },
|
|
564
|
+
{ value: "add", label: "Add anyway" },
|
|
565
|
+
],
|
|
566
|
+
});
|
|
567
|
+
if (p.isCancel(action) || action === "skip")
|
|
568
|
+
return null;
|
|
569
|
+
if (action === "add") {
|
|
570
|
+
const envVarName = PROVIDER_ENV_VARS["openai"];
|
|
571
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
572
|
+
return {
|
|
573
|
+
access_mode: "api",
|
|
574
|
+
api_key: "${" + envVarName + "}",
|
|
575
|
+
base_url: "https://api.openai.com/v1",
|
|
576
|
+
default_model: "gpt-5.1",
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async function configureGemini() {
|
|
582
|
+
p.log.step(color.bold("Google Gemini Configuration"));
|
|
583
|
+
p.note("Get your API key from:\n" +
|
|
584
|
+
color.cyan("https://aistudio.google.com/apikey"), "API Key Setup");
|
|
585
|
+
while (true) {
|
|
586
|
+
const apiKey = await p.password({
|
|
587
|
+
message: "Enter your Google Gemini API key:",
|
|
588
|
+
validate: (v) => validateApiKey(v, "google"),
|
|
589
|
+
});
|
|
590
|
+
if (p.isCancel(apiKey))
|
|
591
|
+
return null;
|
|
592
|
+
const apiKeyStr = apiKey;
|
|
593
|
+
const result = await testConnectionWithSpinner("Gemini", () => testGeminiConnection(apiKeyStr));
|
|
594
|
+
if (result.success) {
|
|
595
|
+
const defaultModel = await p.select({
|
|
596
|
+
message: "Select default Gemini model:",
|
|
597
|
+
options: GEMINI_MODELS,
|
|
598
|
+
});
|
|
599
|
+
if (p.isCancel(defaultModel))
|
|
600
|
+
return null;
|
|
601
|
+
const envVarName = PROVIDER_ENV_VARS["google"];
|
|
602
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
603
|
+
return {
|
|
604
|
+
access_mode: "api",
|
|
605
|
+
api_key: "${" + envVarName + "}",
|
|
606
|
+
default_model: defaultModel,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
const action = await p.select({
|
|
610
|
+
message: "Connection test failed. What would you like to do?",
|
|
611
|
+
options: [
|
|
612
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
613
|
+
{ value: "skip", label: "Skip this provider" },
|
|
614
|
+
{ value: "add", label: "Add anyway" },
|
|
615
|
+
],
|
|
616
|
+
});
|
|
617
|
+
if (p.isCancel(action) || action === "skip")
|
|
618
|
+
return null;
|
|
619
|
+
if (action === "add") {
|
|
620
|
+
const envVarName = PROVIDER_ENV_VARS["google"];
|
|
621
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
622
|
+
return {
|
|
623
|
+
access_mode: "api",
|
|
624
|
+
api_key: "${" + envVarName + "}",
|
|
625
|
+
default_model: "gemini-2.5-flash",
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async function configureDeepSeek() {
|
|
631
|
+
p.log.step(color.bold("DeepSeek Configuration"));
|
|
632
|
+
p.note("DeepSeek V3.2 offers excellent reasoning at ~1/10th the cost.\n" +
|
|
633
|
+
color.bold("Pricing: $0.28/1M input, $0.42/1M output") + "\n\n" +
|
|
634
|
+
"Get your API key from:\n" +
|
|
635
|
+
color.cyan("https://platform.deepseek.com/api_keys"), "DeepSeek Setup");
|
|
636
|
+
while (true) {
|
|
637
|
+
const apiKey = await p.password({
|
|
638
|
+
message: "Enter your DeepSeek API key:",
|
|
639
|
+
validate: (v) => validateApiKey(v, "deepseek"),
|
|
640
|
+
});
|
|
641
|
+
if (p.isCancel(apiKey))
|
|
642
|
+
return null;
|
|
643
|
+
const apiKeyStr = apiKey;
|
|
644
|
+
const result = await testConnectionWithSpinner("DeepSeek", () => testDeepSeekConnection(apiKeyStr));
|
|
645
|
+
if (result.success) {
|
|
646
|
+
const defaultModel = await p.select({
|
|
647
|
+
message: "Select default DeepSeek model:",
|
|
648
|
+
options: DEEPSEEK_MODELS,
|
|
649
|
+
});
|
|
650
|
+
if (p.isCancel(defaultModel))
|
|
651
|
+
return null;
|
|
652
|
+
const envVarName = PROVIDER_ENV_VARS["deepseek"];
|
|
653
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
654
|
+
return {
|
|
655
|
+
access_mode: "api",
|
|
656
|
+
api_key: "${" + envVarName + "}",
|
|
657
|
+
base_url: "https://api.deepseek.com",
|
|
658
|
+
default_model: defaultModel,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
const action = await p.select({
|
|
662
|
+
message: "Connection test failed. What would you like to do?",
|
|
663
|
+
options: [
|
|
664
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
665
|
+
{ value: "skip", label: "Skip this provider" },
|
|
666
|
+
{ value: "add", label: "Add anyway" },
|
|
667
|
+
],
|
|
668
|
+
});
|
|
669
|
+
if (p.isCancel(action) || action === "skip")
|
|
670
|
+
return null;
|
|
671
|
+
if (action === "add") {
|
|
672
|
+
const envVarName = PROVIDER_ENV_VARS["deepseek"];
|
|
673
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
674
|
+
return {
|
|
675
|
+
access_mode: "api",
|
|
676
|
+
api_key: "${" + envVarName + "}",
|
|
677
|
+
base_url: "https://api.deepseek.com",
|
|
678
|
+
default_model: "deepseek-reasoner",
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
async function configureZai() {
|
|
684
|
+
p.log.step(color.bold("Z.AI (GLM) Configuration"));
|
|
685
|
+
p.note("GLM-4.7 is excellent for agentic coding tasks.\n" +
|
|
686
|
+
"Get your API key from:\n" +
|
|
687
|
+
color.cyan("https://z.ai/manage-apikey/apikey-list"), "API Key Setup");
|
|
688
|
+
while (true) {
|
|
689
|
+
const apiKey = await p.password({
|
|
690
|
+
message: "Enter your Z.AI API key:",
|
|
691
|
+
validate: (v) => validateApiKey(v, "zai"),
|
|
692
|
+
});
|
|
693
|
+
if (p.isCancel(apiKey))
|
|
694
|
+
return null;
|
|
695
|
+
const apiKeyStr = apiKey;
|
|
696
|
+
const result = await testConnectionWithSpinner("Z.AI", () => testZaiConnection(apiKeyStr));
|
|
697
|
+
if (result.success) {
|
|
698
|
+
const defaultModel = await p.select({
|
|
699
|
+
message: "Select default GLM model:",
|
|
700
|
+
options: ZAI_MODELS,
|
|
701
|
+
});
|
|
702
|
+
if (p.isCancel(defaultModel))
|
|
703
|
+
return null;
|
|
704
|
+
const envVarName = PROVIDER_ENV_VARS["zai"];
|
|
705
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
706
|
+
return {
|
|
707
|
+
access_mode: "api",
|
|
708
|
+
api_key: "${" + envVarName + "}",
|
|
709
|
+
base_url: "https://api.z.ai/api/paas/v4",
|
|
710
|
+
default_model: defaultModel,
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
const action = await p.select({
|
|
714
|
+
message: "Connection test failed. What would you like to do?",
|
|
715
|
+
options: [
|
|
716
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
717
|
+
{ value: "skip", label: "Skip this provider" },
|
|
718
|
+
{ value: "add", label: "Add anyway" },
|
|
719
|
+
],
|
|
720
|
+
});
|
|
721
|
+
if (p.isCancel(action) || action === "skip")
|
|
722
|
+
return null;
|
|
723
|
+
if (action === "add") {
|
|
724
|
+
const envVarName = PROVIDER_ENV_VARS["zai"];
|
|
725
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
726
|
+
return {
|
|
727
|
+
access_mode: "api",
|
|
728
|
+
api_key: "${" + envVarName + "}",
|
|
729
|
+
base_url: "https://api.z.ai/api/paas/v4",
|
|
730
|
+
default_model: "glm-4.7-flash",
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
async function configureKimi() {
|
|
736
|
+
p.log.step(color.bold("Moonshot (Kimi) Configuration"));
|
|
737
|
+
p.note("Kimi K2.5 excels at coding with a 1M context window.\n" +
|
|
738
|
+
color.bold("Pricing: Very affordable") + "\n\n" +
|
|
739
|
+
"Get your API key from:\n" +
|
|
740
|
+
color.cyan("https://platform.moonshot.cn/console/api-keys"), "Moonshot Setup");
|
|
741
|
+
while (true) {
|
|
742
|
+
const apiKey = await p.password({
|
|
743
|
+
message: "Enter your Moonshot API key:",
|
|
744
|
+
validate: (v) => validateApiKey(v, "kimi"),
|
|
745
|
+
});
|
|
746
|
+
if (p.isCancel(apiKey))
|
|
747
|
+
return null;
|
|
748
|
+
const apiKeyStr = apiKey;
|
|
749
|
+
const result = await testConnectionWithSpinner("Moonshot", () => testKimiConnection(apiKeyStr));
|
|
750
|
+
if (result.success) {
|
|
751
|
+
const defaultModel = await p.select({
|
|
752
|
+
message: "Select default Kimi model:",
|
|
753
|
+
options: KIMI_MODELS,
|
|
754
|
+
});
|
|
755
|
+
if (p.isCancel(defaultModel))
|
|
756
|
+
return null;
|
|
757
|
+
const envVarName = PROVIDER_ENV_VARS["kimi"];
|
|
758
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
759
|
+
return {
|
|
760
|
+
access_mode: "api",
|
|
761
|
+
api_key: "${" + envVarName + "}",
|
|
762
|
+
base_url: "https://api.moonshot.ai/v1",
|
|
763
|
+
default_model: defaultModel,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
const action = await p.select({
|
|
767
|
+
message: "Connection test failed. What would you like to do?",
|
|
768
|
+
options: [
|
|
769
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
770
|
+
{ value: "skip", label: "Skip this provider" },
|
|
771
|
+
{ value: "add", label: "Add anyway" },
|
|
772
|
+
],
|
|
773
|
+
});
|
|
774
|
+
if (p.isCancel(action) || action === "skip")
|
|
775
|
+
return null;
|
|
776
|
+
if (action === "add") {
|
|
777
|
+
const envVarName = PROVIDER_ENV_VARS["kimi"];
|
|
778
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
779
|
+
return {
|
|
780
|
+
access_mode: "api",
|
|
781
|
+
api_key: "${" + envVarName + "}",
|
|
782
|
+
base_url: "https://api.moonshot.ai/v1",
|
|
783
|
+
default_model: "kimi-k2-5-preview",
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
async function configureKimiCode() {
|
|
789
|
+
p.log.step(color.bold("Kimi Code Configuration"));
|
|
790
|
+
p.note("Kimi Code is a dedicated coding endpoint.\n" +
|
|
791
|
+
color.bold("Uses your Kimi subscription - no separate API credits needed!") + "\n\n" +
|
|
792
|
+
"Uses your existing Kimi/Moonshot API key.\n" +
|
|
793
|
+
"Get your API key from:\n" +
|
|
794
|
+
color.cyan("https://platform.moonshot.cn/console/api-keys"), "Kimi Code Setup");
|
|
795
|
+
while (true) {
|
|
796
|
+
const apiKey = await p.password({
|
|
797
|
+
message: "Enter your Kimi API key:",
|
|
798
|
+
validate: (v) => validateApiKey(v, "kimi-code"),
|
|
799
|
+
});
|
|
800
|
+
if (p.isCancel(apiKey))
|
|
801
|
+
return null;
|
|
802
|
+
const apiKeyStr = apiKey;
|
|
803
|
+
const result = await testConnectionWithSpinner("Kimi Code", () => testKimiCodeConnection(apiKeyStr));
|
|
804
|
+
if (result.success) {
|
|
805
|
+
const defaultModel = await p.select({
|
|
806
|
+
message: "Select default Kimi Code model:",
|
|
807
|
+
options: KIMI_CODE_MODELS,
|
|
808
|
+
});
|
|
809
|
+
if (p.isCancel(defaultModel))
|
|
810
|
+
return null;
|
|
811
|
+
const envVarName = PROVIDER_ENV_VARS["kimi-code"];
|
|
812
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
813
|
+
return {
|
|
814
|
+
access_mode: "api",
|
|
815
|
+
api_key: "${" + envVarName + "}",
|
|
816
|
+
base_url: "https://api.kimi.com/coding/v1",
|
|
817
|
+
default_model: defaultModel,
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
const action = await p.select({
|
|
821
|
+
message: "Connection test failed. What would you like to do?",
|
|
822
|
+
options: [
|
|
823
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
824
|
+
{ value: "skip", label: "Skip this provider" },
|
|
825
|
+
{ value: "add", label: "Add anyway" },
|
|
826
|
+
],
|
|
827
|
+
});
|
|
828
|
+
if (p.isCancel(action) || action === "skip")
|
|
829
|
+
return null;
|
|
830
|
+
if (action === "add") {
|
|
831
|
+
const envVarName = PROVIDER_ENV_VARS["kimi-code"];
|
|
832
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
833
|
+
return {
|
|
834
|
+
access_mode: "api",
|
|
835
|
+
api_key: "${" + envVarName + "}",
|
|
836
|
+
base_url: "https://api.kimi.com/coding/v1",
|
|
837
|
+
default_model: "kimi-for-coding",
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
async function configurePerplexity() {
|
|
843
|
+
p.log.step(color.bold("Perplexity Configuration"));
|
|
844
|
+
p.note("Perplexity provides web search grounded responses with citations.\n" +
|
|
845
|
+
"Sonar models return real-time web information.\n\n" +
|
|
846
|
+
"Get your API key from:\n" +
|
|
847
|
+
color.cyan("https://www.perplexity.ai/settings/api"), "Perplexity Setup");
|
|
848
|
+
while (true) {
|
|
849
|
+
const apiKey = await p.password({
|
|
850
|
+
message: "Enter your Perplexity API key:",
|
|
851
|
+
validate: (v) => validateApiKey(v, "perplexity"),
|
|
852
|
+
});
|
|
853
|
+
if (p.isCancel(apiKey))
|
|
854
|
+
return null;
|
|
855
|
+
const apiKeyStr = apiKey;
|
|
856
|
+
const result = await testConnectionWithSpinner("Perplexity", () => testPerplexityConnection(apiKeyStr));
|
|
857
|
+
if (result.success) {
|
|
858
|
+
const defaultModel = await p.select({
|
|
859
|
+
message: "Select default Perplexity model:",
|
|
860
|
+
options: PERPLEXITY_MODELS,
|
|
861
|
+
});
|
|
862
|
+
if (p.isCancel(defaultModel))
|
|
863
|
+
return null;
|
|
864
|
+
const envVarName = PROVIDER_ENV_VARS["perplexity"];
|
|
865
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
866
|
+
return {
|
|
867
|
+
access_mode: "api",
|
|
868
|
+
api_key: "${" + envVarName + "}",
|
|
869
|
+
base_url: "https://api.perplexity.ai",
|
|
870
|
+
default_model: defaultModel,
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
const action = await p.select({
|
|
874
|
+
message: "Connection test failed. What would you like to do?",
|
|
875
|
+
options: [
|
|
876
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
877
|
+
{ value: "skip", label: "Skip this provider" },
|
|
878
|
+
{ value: "add", label: "Add anyway" },
|
|
879
|
+
],
|
|
880
|
+
});
|
|
881
|
+
if (p.isCancel(action) || action === "skip")
|
|
882
|
+
return null;
|
|
883
|
+
if (action === "add") {
|
|
884
|
+
const envVarName = PROVIDER_ENV_VARS["perplexity"];
|
|
885
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
886
|
+
return {
|
|
887
|
+
access_mode: "api",
|
|
888
|
+
api_key: "${" + envVarName + "}",
|
|
889
|
+
base_url: "https://api.perplexity.ai",
|
|
890
|
+
default_model: "sonar",
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
async function configureOpenRouter() {
|
|
896
|
+
p.log.step(color.bold("OpenRouter Configuration"));
|
|
897
|
+
p.note("OpenRouter provides access to 300+ models via a single API.\n" +
|
|
898
|
+
"Use 'openrouter/auto' to automatically select the best model.\n\n" +
|
|
899
|
+
"Get your API key from:\n" +
|
|
900
|
+
color.cyan("https://openrouter.ai/keys"), "OpenRouter Setup");
|
|
901
|
+
while (true) {
|
|
902
|
+
const apiKey = await p.password({
|
|
903
|
+
message: "Enter your OpenRouter API key:",
|
|
904
|
+
validate: (v) => validateApiKey(v, "openrouter"),
|
|
905
|
+
});
|
|
906
|
+
if (p.isCancel(apiKey))
|
|
907
|
+
return null;
|
|
908
|
+
const apiKeyStr = apiKey;
|
|
909
|
+
const result = await testConnectionWithSpinner("OpenRouter", () => testOpenRouterConnection(apiKeyStr));
|
|
910
|
+
if (result.success) {
|
|
911
|
+
const defaultModel = await p.select({
|
|
912
|
+
message: "Select default OpenRouter model:",
|
|
913
|
+
options: OPENROUTER_MODELS,
|
|
914
|
+
});
|
|
915
|
+
if (p.isCancel(defaultModel))
|
|
916
|
+
return null;
|
|
917
|
+
const envVarName = PROVIDER_ENV_VARS["openrouter"];
|
|
918
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
919
|
+
return {
|
|
920
|
+
access_mode: "api",
|
|
921
|
+
api_key: "${" + envVarName + "}",
|
|
922
|
+
base_url: "https://openrouter.ai/api/v1",
|
|
923
|
+
default_model: defaultModel,
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
const action = await p.select({
|
|
927
|
+
message: "Connection test failed. What would you like to do?",
|
|
928
|
+
options: [
|
|
929
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
930
|
+
{ value: "skip", label: "Skip this provider" },
|
|
931
|
+
{ value: "add", label: "Add anyway" },
|
|
932
|
+
],
|
|
933
|
+
});
|
|
934
|
+
if (p.isCancel(action) || action === "skip")
|
|
935
|
+
return null;
|
|
936
|
+
if (action === "add") {
|
|
937
|
+
const envVarName = PROVIDER_ENV_VARS["openrouter"];
|
|
938
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
939
|
+
return {
|
|
940
|
+
access_mode: "api",
|
|
941
|
+
api_key: "${" + envVarName + "}",
|
|
942
|
+
base_url: "https://openrouter.ai/api/v1",
|
|
943
|
+
default_model: "openrouter/auto",
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
async function configureGroq() {
|
|
949
|
+
p.log.step(color.bold("Groq Configuration"));
|
|
950
|
+
p.note("Groq provides ultra-fast LPU inference - up to 18x faster than GPU.\n" +
|
|
951
|
+
color.bold("Free tier available!") + "\n\n" +
|
|
952
|
+
"Get your API key from:\n" +
|
|
953
|
+
color.cyan("https://console.groq.com/keys"), "Groq Setup");
|
|
954
|
+
while (true) {
|
|
955
|
+
const apiKey = await p.password({
|
|
956
|
+
message: "Enter your Groq API key:",
|
|
957
|
+
validate: (v) => validateApiKey(v, "groq"),
|
|
958
|
+
});
|
|
959
|
+
if (p.isCancel(apiKey))
|
|
960
|
+
return null;
|
|
961
|
+
const apiKeyStr = apiKey;
|
|
962
|
+
const result = await testConnectionWithSpinner("Groq", () => testGroqConnection(apiKeyStr));
|
|
963
|
+
if (result.success) {
|
|
964
|
+
const defaultModel = await p.select({
|
|
965
|
+
message: "Select default Groq model:",
|
|
966
|
+
options: GROQ_MODELS,
|
|
967
|
+
});
|
|
968
|
+
if (p.isCancel(defaultModel))
|
|
969
|
+
return null;
|
|
970
|
+
const envVarName = PROVIDER_ENV_VARS["groq"];
|
|
971
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
972
|
+
return {
|
|
973
|
+
access_mode: "api",
|
|
974
|
+
api_key: "${" + envVarName + "}",
|
|
975
|
+
base_url: "https://api.groq.com/openai/v1",
|
|
976
|
+
default_model: defaultModel,
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
const action = await p.select({
|
|
980
|
+
message: "Connection test failed. What would you like to do?",
|
|
981
|
+
options: [
|
|
982
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
983
|
+
{ value: "skip", label: "Skip this provider" },
|
|
984
|
+
{ value: "add", label: "Add anyway" },
|
|
985
|
+
],
|
|
986
|
+
});
|
|
987
|
+
if (p.isCancel(action) || action === "skip")
|
|
988
|
+
return null;
|
|
989
|
+
if (action === "add") {
|
|
990
|
+
const envVarName = PROVIDER_ENV_VARS["groq"];
|
|
991
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
992
|
+
return {
|
|
993
|
+
access_mode: "api",
|
|
994
|
+
api_key: "${" + envVarName + "}",
|
|
995
|
+
base_url: "https://api.groq.com/openai/v1",
|
|
996
|
+
default_model: "llama-3.3-70b-versatile",
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
async function configureTogether() {
|
|
1002
|
+
p.log.step(color.bold("Together AI Configuration"));
|
|
1003
|
+
p.note("Together AI provides 200+ open models with sub-100ms latency.\n" +
|
|
1004
|
+
color.bold("$1 free credit to start!") + "\n\n" +
|
|
1005
|
+
"Get your API key from:\n" +
|
|
1006
|
+
color.cyan("https://api.together.xyz/settings/api-keys"), "Together AI Setup");
|
|
1007
|
+
while (true) {
|
|
1008
|
+
const apiKey = await p.password({
|
|
1009
|
+
message: "Enter your Together AI API key:",
|
|
1010
|
+
validate: (v) => validateApiKey(v, "together"),
|
|
1011
|
+
});
|
|
1012
|
+
if (p.isCancel(apiKey))
|
|
1013
|
+
return null;
|
|
1014
|
+
const apiKeyStr = apiKey;
|
|
1015
|
+
const result = await testConnectionWithSpinner("Together AI", () => testTogetherConnection(apiKeyStr));
|
|
1016
|
+
if (result.success) {
|
|
1017
|
+
const defaultModel = await p.select({
|
|
1018
|
+
message: "Select default Together AI model:",
|
|
1019
|
+
options: TOGETHER_MODELS,
|
|
1020
|
+
});
|
|
1021
|
+
if (p.isCancel(defaultModel))
|
|
1022
|
+
return null;
|
|
1023
|
+
const envVarName = PROVIDER_ENV_VARS["together"];
|
|
1024
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
1025
|
+
return {
|
|
1026
|
+
access_mode: "api",
|
|
1027
|
+
api_key: "${" + envVarName + "}",
|
|
1028
|
+
base_url: "https://api.together.xyz/v1",
|
|
1029
|
+
default_model: defaultModel,
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
const action = await p.select({
|
|
1033
|
+
message: "Connection test failed. What would you like to do?",
|
|
1034
|
+
options: [
|
|
1035
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
1036
|
+
{ value: "skip", label: "Skip this provider" },
|
|
1037
|
+
{ value: "add", label: "Add anyway" },
|
|
1038
|
+
],
|
|
1039
|
+
});
|
|
1040
|
+
if (p.isCancel(action) || action === "skip")
|
|
1041
|
+
return null;
|
|
1042
|
+
if (action === "add") {
|
|
1043
|
+
const envVarName = PROVIDER_ENV_VARS["together"];
|
|
1044
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
1045
|
+
return {
|
|
1046
|
+
access_mode: "api",
|
|
1047
|
+
api_key: "${" + envVarName + "}",
|
|
1048
|
+
base_url: "https://api.together.xyz/v1",
|
|
1049
|
+
default_model: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
async function configureFireworks() {
|
|
1055
|
+
p.log.step(color.bold("Fireworks AI Configuration"));
|
|
1056
|
+
p.note("Fireworks AI provides fast inference for DeepSeek, Llama, and other models.\n" +
|
|
1057
|
+
color.bold("$1 free credit to start!") + "\n\n" +
|
|
1058
|
+
"Get your API key from:\n" +
|
|
1059
|
+
color.cyan("https://fireworks.ai/account/api-keys"), "Fireworks AI Setup");
|
|
1060
|
+
while (true) {
|
|
1061
|
+
const apiKey = await p.password({
|
|
1062
|
+
message: "Enter your Fireworks AI API key:",
|
|
1063
|
+
validate: (v) => validateApiKey(v, "fireworks"),
|
|
1064
|
+
});
|
|
1065
|
+
if (p.isCancel(apiKey))
|
|
1066
|
+
return null;
|
|
1067
|
+
const apiKeyStr = apiKey;
|
|
1068
|
+
const result = await testConnectionWithSpinner("Fireworks AI", () => testFireworksConnection(apiKeyStr));
|
|
1069
|
+
if (result.success) {
|
|
1070
|
+
const defaultModel = await p.select({
|
|
1071
|
+
message: "Select default Fireworks AI model:",
|
|
1072
|
+
options: FIREWORKS_MODELS,
|
|
1073
|
+
});
|
|
1074
|
+
if (p.isCancel(defaultModel))
|
|
1075
|
+
return null;
|
|
1076
|
+
const envVarName = PROVIDER_ENV_VARS["fireworks"];
|
|
1077
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
1078
|
+
return {
|
|
1079
|
+
access_mode: "api",
|
|
1080
|
+
api_key: "${" + envVarName + "}",
|
|
1081
|
+
base_url: "https://api.fireworks.ai/inference/v1",
|
|
1082
|
+
default_model: defaultModel,
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
const action = await p.select({
|
|
1086
|
+
message: "Connection test failed. What would you like to do?",
|
|
1087
|
+
options: [
|
|
1088
|
+
{ value: "retry", label: "Re-enter API key" },
|
|
1089
|
+
{ value: "skip", label: "Skip this provider" },
|
|
1090
|
+
{ value: "add", label: "Add anyway" },
|
|
1091
|
+
],
|
|
1092
|
+
});
|
|
1093
|
+
if (p.isCancel(action) || action === "skip")
|
|
1094
|
+
return null;
|
|
1095
|
+
if (action === "add") {
|
|
1096
|
+
const envVarName = PROVIDER_ENV_VARS["fireworks"];
|
|
1097
|
+
configuredEnvVars.set(envVarName, apiKeyStr);
|
|
1098
|
+
return {
|
|
1099
|
+
access_mode: "api",
|
|
1100
|
+
api_key: "${" + envVarName + "}",
|
|
1101
|
+
base_url: "https://api.fireworks.ai/inference/v1",
|
|
1102
|
+
default_model: "accounts/fireworks/models/llama-v3p3-70b-instruct",
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
async function configureOllama() {
|
|
1108
|
+
p.log.step(color.bold("Ollama Configuration"));
|
|
1109
|
+
p.note("Ollama runs models locally - free and private.\n" +
|
|
1110
|
+
"Install from: " + color.cyan("https://ollama.ai") + "\n" +
|
|
1111
|
+
"Then run: " + color.cyan("ollama serve"), "Ollama Setup");
|
|
1112
|
+
while (true) {
|
|
1113
|
+
const baseUrl = await p.text({
|
|
1114
|
+
message: "Enter Ollama base URL:",
|
|
1115
|
+
placeholder: "http://localhost:11434",
|
|
1116
|
+
initialValue: "http://localhost:11434",
|
|
1117
|
+
validate: (v) => {
|
|
1118
|
+
if (!v)
|
|
1119
|
+
return "URL is required";
|
|
1120
|
+
try {
|
|
1121
|
+
new URL(v);
|
|
1122
|
+
return undefined;
|
|
1123
|
+
}
|
|
1124
|
+
catch {
|
|
1125
|
+
return "Invalid URL format";
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
});
|
|
1129
|
+
if (p.isCancel(baseUrl))
|
|
1130
|
+
return null;
|
|
1131
|
+
const baseUrlStr = baseUrl;
|
|
1132
|
+
const result = await testConnectionWithSpinner("Ollama", () => testOllamaConnection(baseUrlStr));
|
|
1133
|
+
if (result.success) {
|
|
1134
|
+
let selectedModel = "llama3.2";
|
|
1135
|
+
if (result.models && result.models.length > 0) {
|
|
1136
|
+
const modelChoice = await p.select({
|
|
1137
|
+
message: "Select default model:",
|
|
1138
|
+
options: result.models.map((m) => ({ value: m, label: m })),
|
|
1139
|
+
});
|
|
1140
|
+
if (p.isCancel(modelChoice))
|
|
1141
|
+
return null;
|
|
1142
|
+
selectedModel = modelChoice;
|
|
1143
|
+
p.log.success(`Found ${result.models.length} installed model(s)`);
|
|
1144
|
+
}
|
|
1145
|
+
else {
|
|
1146
|
+
p.log.warn("No models found. Pull a model with: ollama pull llama3.2");
|
|
1147
|
+
}
|
|
1148
|
+
return {
|
|
1149
|
+
access_mode: "api",
|
|
1150
|
+
base_url: baseUrlStr,
|
|
1151
|
+
default_model: selectedModel,
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
const action = await p.select({
|
|
1155
|
+
message: "Connection test failed. What would you like to do?",
|
|
1156
|
+
options: [
|
|
1157
|
+
{ value: "retry", label: "Re-enter URL" },
|
|
1158
|
+
{ value: "skip", label: "Skip this provider" },
|
|
1159
|
+
{ value: "add", label: "Add anyway" },
|
|
1160
|
+
],
|
|
1161
|
+
});
|
|
1162
|
+
if (p.isCancel(action) || action === "skip")
|
|
1163
|
+
return null;
|
|
1164
|
+
if (action === "add") {
|
|
1165
|
+
return {
|
|
1166
|
+
access_mode: "api",
|
|
1167
|
+
base_url: baseUrlStr,
|
|
1168
|
+
default_model: "llama3.2",
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
// ============================================================================
|
|
1174
|
+
// Role Configuration
|
|
1175
|
+
// ============================================================================
|
|
1176
|
+
async function configureRoles(configuredProviders, orchestratorProvider) {
|
|
1177
|
+
const roles = {};
|
|
1178
|
+
// Auto-configure orchestrator role
|
|
1179
|
+
const orchestratorRole = AGENT_ROLES.find((r) => r.value === "orchestrator");
|
|
1180
|
+
if (orchestratorRole) {
|
|
1181
|
+
const defaultModel = getProviderModels(orchestratorProvider)[0]?.value ?? "default";
|
|
1182
|
+
roles["orchestrator"] = {
|
|
1183
|
+
provider: orchestratorProvider,
|
|
1184
|
+
model: defaultModel,
|
|
1185
|
+
system_prompt: orchestratorRole.systemPrompt,
|
|
1186
|
+
...(orchestratorRole.temperature !== undefined ? { temperature: orchestratorRole.temperature } : {}),
|
|
1187
|
+
};
|
|
1188
|
+
p.log.success(`Orchestrator -> ${orchestratorProvider}/${defaultModel}`);
|
|
1189
|
+
}
|
|
1190
|
+
// Filter out orchestrator from selectable roles
|
|
1191
|
+
const selectableRoles = AGENT_ROLES.filter((r) => r.value !== "orchestrator");
|
|
1192
|
+
const selectedRoles = await p.multiselect({
|
|
1193
|
+
message: "Which agent roles would you like to enable?",
|
|
1194
|
+
options: selectableRoles.map((role) => ({
|
|
1195
|
+
value: role.value,
|
|
1196
|
+
label: role.label,
|
|
1197
|
+
...(role.hint ? { hint: role.hint } : {}),
|
|
1198
|
+
})),
|
|
1199
|
+
required: false,
|
|
1200
|
+
});
|
|
1201
|
+
if (p.isCancel(selectedRoles) || selectedRoles.length === 0) {
|
|
1202
|
+
p.log.warn("No additional roles selected. You can add roles later.");
|
|
1203
|
+
return roles;
|
|
1204
|
+
}
|
|
1205
|
+
for (const roleValue of selectedRoles) {
|
|
1206
|
+
const role = selectableRoles.find((r) => r.value === roleValue);
|
|
1207
|
+
if (!role)
|
|
1208
|
+
continue;
|
|
1209
|
+
console.log();
|
|
1210
|
+
p.log.step(color.cyan(`${role.label}`));
|
|
1211
|
+
console.log(color.dim(` ${role.hint}`));
|
|
1212
|
+
const providerOptions = configuredProviders.map((provider) => {
|
|
1213
|
+
const models = getProviderModels(provider);
|
|
1214
|
+
const defaultModel = models[0]?.label ?? "default";
|
|
1215
|
+
return {
|
|
1216
|
+
value: provider,
|
|
1217
|
+
label: provider,
|
|
1218
|
+
hint: `Default: ${defaultModel}`,
|
|
1219
|
+
};
|
|
1220
|
+
});
|
|
1221
|
+
const selectedProvider = await p.select({
|
|
1222
|
+
message: `Which provider for ${role.label.toLowerCase()}?`,
|
|
1223
|
+
options: providerOptions,
|
|
1224
|
+
});
|
|
1225
|
+
if (p.isCancel(selectedProvider))
|
|
1226
|
+
continue;
|
|
1227
|
+
const modelOptions = getProviderModels(selectedProvider);
|
|
1228
|
+
const selectedModel = await p.select({
|
|
1229
|
+
message: "Select model:",
|
|
1230
|
+
options: modelOptions,
|
|
1231
|
+
});
|
|
1232
|
+
if (p.isCancel(selectedModel))
|
|
1233
|
+
continue;
|
|
1234
|
+
roles[role.value] = {
|
|
1235
|
+
provider: selectedProvider,
|
|
1236
|
+
model: selectedModel,
|
|
1237
|
+
system_prompt: role.systemPrompt,
|
|
1238
|
+
...(role.temperature !== undefined ? { temperature: role.temperature } : {}),
|
|
1239
|
+
};
|
|
1240
|
+
console.log(color.green(` ✓ ${role.value}`) +
|
|
1241
|
+
color.dim(` -> ${selectedProvider}/${selectedModel}`));
|
|
1242
|
+
}
|
|
1243
|
+
return roles;
|
|
1244
|
+
}
|
|
1245
|
+
// ============================================================================
|
|
1246
|
+
// Shell Profile & Config Saving
|
|
1247
|
+
// ============================================================================
|
|
1248
|
+
function getShellProfilePath() {
|
|
1249
|
+
const shell = process.env["SHELL"] ?? "";
|
|
1250
|
+
if (shell.includes("zsh")) {
|
|
1251
|
+
return path.join(os.homedir(), ".zshrc");
|
|
1252
|
+
}
|
|
1253
|
+
return path.join(os.homedir(), ".bashrc");
|
|
1254
|
+
}
|
|
1255
|
+
async function addEnvVarsToShellProfile() {
|
|
1256
|
+
if (configuredEnvVars.size === 0)
|
|
1257
|
+
return;
|
|
1258
|
+
const profilePath = getShellProfilePath();
|
|
1259
|
+
const profileName = path.basename(profilePath);
|
|
1260
|
+
const shouldAdd = await p.confirm({
|
|
1261
|
+
message: `Add API keys to ~/${profileName}?`,
|
|
1262
|
+
initialValue: true,
|
|
1263
|
+
});
|
|
1264
|
+
if (p.isCancel(shouldAdd) || !shouldAdd) {
|
|
1265
|
+
p.log.info("Set environment variables manually:");
|
|
1266
|
+
for (const [envVar] of configuredEnvVars) {
|
|
1267
|
+
console.log(` ${color.cyan(`export ${envVar}="your-api-key"`)}`);
|
|
1268
|
+
}
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
try {
|
|
1272
|
+
let profileContent = "";
|
|
1273
|
+
if (fs.existsSync(profilePath)) {
|
|
1274
|
+
profileContent = fs.readFileSync(profilePath, "utf-8");
|
|
1275
|
+
}
|
|
1276
|
+
const envVarsToAdd = [];
|
|
1277
|
+
for (const [envVar, value] of configuredEnvVars) {
|
|
1278
|
+
const existsPattern = new RegExp(`^\\s*export\\s+${envVar}=`, "m");
|
|
1279
|
+
if (!existsPattern.test(profileContent)) {
|
|
1280
|
+
envVarsToAdd.push({ name: envVar, value });
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
if (envVarsToAdd.length === 0) {
|
|
1284
|
+
p.log.info(`Environment variables already exist in ~/${profileName}`);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
const exportLines = ["", "# Foundation"];
|
|
1288
|
+
for (const { name, value } of envVarsToAdd) {
|
|
1289
|
+
exportLines.push(`export ${name}="${value}"`);
|
|
1290
|
+
}
|
|
1291
|
+
let prefix = "";
|
|
1292
|
+
if (profileContent.length > 0 && !profileContent.endsWith("\n")) {
|
|
1293
|
+
prefix = "\n";
|
|
1294
|
+
}
|
|
1295
|
+
fs.appendFileSync(profilePath, prefix + exportLines.join("\n") + "\n", "utf-8");
|
|
1296
|
+
p.log.success(`Added environment variables to ~/${profileName}`);
|
|
1297
|
+
p.log.info(`Run ${color.cyan(`source ~/${profileName}`)} to apply changes`);
|
|
1298
|
+
}
|
|
1299
|
+
catch (error) {
|
|
1300
|
+
p.log.error(`Failed to update ${profilePath}`);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
function generateConfig(providers, roles) {
|
|
1304
|
+
return {
|
|
1305
|
+
version: "1.0",
|
|
1306
|
+
defaults: {
|
|
1307
|
+
temperature: 0.7,
|
|
1308
|
+
max_tokens: 4096,
|
|
1309
|
+
timeout_ms: 60000,
|
|
1310
|
+
},
|
|
1311
|
+
providers,
|
|
1312
|
+
roles,
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
async function saveConfig(config) {
|
|
1316
|
+
const configPath = getConfigPath();
|
|
1317
|
+
const configDir = getConfigDir();
|
|
1318
|
+
try {
|
|
1319
|
+
if (!fs.existsSync(configDir)) {
|
|
1320
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
1321
|
+
}
|
|
1322
|
+
if (fs.existsSync(configPath)) {
|
|
1323
|
+
const backupPath = `${configPath}.backup.${Date.now()}`;
|
|
1324
|
+
fs.copyFileSync(configPath, backupPath);
|
|
1325
|
+
p.log.info(`Backed up existing config to: ${color.dim(backupPath)}`);
|
|
1326
|
+
}
|
|
1327
|
+
const header = `# Foundation Configuration
|
|
1328
|
+
# Generated: ${new Date().toISOString()}
|
|
1329
|
+
# Documentation: https://github.com/sashabogi/foundation
|
|
1330
|
+
#
|
|
1331
|
+
# This configures AI-assisted development with multi-agent orchestration.
|
|
1332
|
+
# Supports both API keys and subscription/CLI access modes.
|
|
1333
|
+
#
|
|
1334
|
+
# Environment variables: \${VAR_NAME} syntax
|
|
1335
|
+
# Access modes: "api" (pay-per-token) or "subscription" (CLI tools)
|
|
1336
|
+
|
|
1337
|
+
`;
|
|
1338
|
+
const yamlContent = yamlStringify(config, { indent: 2, lineWidth: 100 });
|
|
1339
|
+
fs.writeFileSync(configPath, header + yamlContent, "utf-8");
|
|
1340
|
+
return true;
|
|
1341
|
+
}
|
|
1342
|
+
catch (error) {
|
|
1343
|
+
p.log.error(`Failed to save config: ${error instanceof Error ? error.message : "Unknown"}`);
|
|
1344
|
+
return false;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
// ============================================================================
|
|
1348
|
+
// Provider Configuration Router
|
|
1349
|
+
// ============================================================================
|
|
1350
|
+
async function configureProviderWithAPIKey(providerType) {
|
|
1351
|
+
switch (providerType) {
|
|
1352
|
+
case "anthropic":
|
|
1353
|
+
return configureAnthropic();
|
|
1354
|
+
case "openai":
|
|
1355
|
+
return configureOpenAI();
|
|
1356
|
+
case "google":
|
|
1357
|
+
return configureGemini();
|
|
1358
|
+
case "deepseek":
|
|
1359
|
+
return configureDeepSeek();
|
|
1360
|
+
case "zai":
|
|
1361
|
+
return configureZai();
|
|
1362
|
+
case "kimi":
|
|
1363
|
+
return configureKimi();
|
|
1364
|
+
case "kimi-code":
|
|
1365
|
+
return configureKimiCode();
|
|
1366
|
+
case "perplexity":
|
|
1367
|
+
return configurePerplexity();
|
|
1368
|
+
case "openrouter":
|
|
1369
|
+
return configureOpenRouter();
|
|
1370
|
+
case "groq":
|
|
1371
|
+
return configureGroq();
|
|
1372
|
+
case "together":
|
|
1373
|
+
return configureTogether();
|
|
1374
|
+
case "fireworks":
|
|
1375
|
+
return configureFireworks();
|
|
1376
|
+
case "ollama":
|
|
1377
|
+
return configureOllama();
|
|
1378
|
+
default:
|
|
1379
|
+
return null;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
// ============================================================================
|
|
1383
|
+
// Main Setup Wizard
|
|
1384
|
+
// ============================================================================
|
|
1385
|
+
/**
|
|
1386
|
+
* Detect which CLI environment we're running from
|
|
1387
|
+
*/
|
|
1388
|
+
function detectCurrentCLI() {
|
|
1389
|
+
if (process.env["CLAUDE_CODE"] || process.env["ANTHROPIC_API_KEY"] === "claude-code-session") {
|
|
1390
|
+
return "anthropic";
|
|
1391
|
+
}
|
|
1392
|
+
if (process.env["OPENAI_CODEX_CLI"]) {
|
|
1393
|
+
return "openai";
|
|
1394
|
+
}
|
|
1395
|
+
if (process.env["GEMINI_CLI"]) {
|
|
1396
|
+
return "google";
|
|
1397
|
+
}
|
|
1398
|
+
return "anthropic"; // Default assumption
|
|
1399
|
+
}
|
|
1400
|
+
export async function runSetupWizard() {
|
|
1401
|
+
displayBanner();
|
|
1402
|
+
p.intro(color.bgCyan(color.black(" Foundation Setup ")));
|
|
1403
|
+
const currentCLI = detectCurrentCLI();
|
|
1404
|
+
// Step 1: Choose Orchestrator
|
|
1405
|
+
p.note("The " + color.bold("orchestrator") + " is the main AI that coordinates all other agents.\n" +
|
|
1406
|
+
"It runs IN your current CLI session and routes tasks to specialized agents.\n\n" +
|
|
1407
|
+
"Since you're running from " + color.cyan("Claude Code") + ", Anthropic can use your\n" +
|
|
1408
|
+
"subscription. All other providers require API keys.", "Step 1: Choose Your Orchestrator");
|
|
1409
|
+
const orchestratorProvider = await p.select({
|
|
1410
|
+
message: "Which provider should be your orchestrator?",
|
|
1411
|
+
options: AVAILABLE_PROVIDERS.map((provider) => ({
|
|
1412
|
+
value: provider.value,
|
|
1413
|
+
label: provider.label,
|
|
1414
|
+
hint: provider.value === currentCLI
|
|
1415
|
+
? "Can use subscription (no API key needed)"
|
|
1416
|
+
: "Requires API key",
|
|
1417
|
+
})),
|
|
1418
|
+
});
|
|
1419
|
+
if (p.isCancel(orchestratorProvider)) {
|
|
1420
|
+
p.cancel("Setup cancelled");
|
|
1421
|
+
process.exit(0);
|
|
1422
|
+
}
|
|
1423
|
+
const providers = {};
|
|
1424
|
+
const configuredProviderNames = [];
|
|
1425
|
+
let orchestratorConfig = null;
|
|
1426
|
+
let orchestratorAccessMode = "api";
|
|
1427
|
+
// If orchestrator matches current CLI, offer subscription mode
|
|
1428
|
+
if (orchestratorProvider === currentCLI) {
|
|
1429
|
+
const accessChoice = await p.select({
|
|
1430
|
+
message: `How do you want to access ${orchestratorProvider}?`,
|
|
1431
|
+
options: [
|
|
1432
|
+
{
|
|
1433
|
+
value: "subscription",
|
|
1434
|
+
label: "Use my subscription (no API key)",
|
|
1435
|
+
hint: "Recommended - uses your current Claude Code session",
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
value: "api",
|
|
1439
|
+
label: "Use API key",
|
|
1440
|
+
hint: "Pay-per-token, separate from subscription",
|
|
1441
|
+
},
|
|
1442
|
+
],
|
|
1443
|
+
});
|
|
1444
|
+
if (p.isCancel(accessChoice)) {
|
|
1445
|
+
p.cancel("Setup cancelled");
|
|
1446
|
+
process.exit(0);
|
|
1447
|
+
}
|
|
1448
|
+
orchestratorAccessMode = accessChoice;
|
|
1449
|
+
}
|
|
1450
|
+
// Configure the orchestrator provider
|
|
1451
|
+
if (orchestratorAccessMode === "subscription") {
|
|
1452
|
+
p.note("Your orchestrator will use Claude Code's session.\n" +
|
|
1453
|
+
"No API key needed - requests pass through your subscription.", "Subscription Mode");
|
|
1454
|
+
const model = await p.select({
|
|
1455
|
+
message: "Select default Claude model:",
|
|
1456
|
+
options: ANTHROPIC_MODELS.map((m) => ({
|
|
1457
|
+
value: m.value,
|
|
1458
|
+
label: m.label,
|
|
1459
|
+
...(m.hint ? { hint: m.hint } : {}),
|
|
1460
|
+
})),
|
|
1461
|
+
});
|
|
1462
|
+
if (p.isCancel(model)) {
|
|
1463
|
+
p.cancel("Setup cancelled");
|
|
1464
|
+
process.exit(0);
|
|
1465
|
+
}
|
|
1466
|
+
orchestratorConfig = {
|
|
1467
|
+
access_mode: "subscription",
|
|
1468
|
+
default_model: model,
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
else {
|
|
1472
|
+
orchestratorConfig = await configureProviderWithAPIKey(orchestratorProvider);
|
|
1473
|
+
}
|
|
1474
|
+
if (orchestratorConfig) {
|
|
1475
|
+
providers[orchestratorProvider] = orchestratorConfig;
|
|
1476
|
+
configuredProviderNames.push(orchestratorProvider);
|
|
1477
|
+
p.log.success(`Orchestrator configured: ${orchestratorProvider}`);
|
|
1478
|
+
}
|
|
1479
|
+
else {
|
|
1480
|
+
p.cancel("Failed to configure orchestrator");
|
|
1481
|
+
process.exit(1);
|
|
1482
|
+
}
|
|
1483
|
+
// Step 2: Add Agent Providers
|
|
1484
|
+
const remainingProviders = AVAILABLE_PROVIDERS.filter((prov) => prov.value !== orchestratorProvider);
|
|
1485
|
+
p.note("Now add providers for your agent roles (coder, critic, reviewer, etc.).\n" +
|
|
1486
|
+
"These are called " + color.bold("via API") + " by your orchestrator.\n\n" +
|
|
1487
|
+
color.yellow("All providers below require API keys.") + "\n" +
|
|
1488
|
+
"Your orchestrator will make API calls to these providers.", "Step 2: Add Agent Providers");
|
|
1489
|
+
const selectedAgentProviders = await p.multiselect({
|
|
1490
|
+
message: "Which additional providers do you want for agent roles?",
|
|
1491
|
+
options: remainingProviders.map((provider) => ({
|
|
1492
|
+
value: provider.value,
|
|
1493
|
+
label: provider.label,
|
|
1494
|
+
...(provider.hint ? { hint: provider.hint } : {}),
|
|
1495
|
+
})),
|
|
1496
|
+
required: false,
|
|
1497
|
+
});
|
|
1498
|
+
if (p.isCancel(selectedAgentProviders)) {
|
|
1499
|
+
p.cancel("Setup cancelled");
|
|
1500
|
+
process.exit(0);
|
|
1501
|
+
}
|
|
1502
|
+
// Step 3: Configure API Keys
|
|
1503
|
+
if (selectedAgentProviders.length > 0) {
|
|
1504
|
+
p.note("Configure API keys for each selected provider.\n" +
|
|
1505
|
+
"Keys are stored in your config and/or shell profile.", "Step 3: Configure API Keys");
|
|
1506
|
+
for (const providerType of selectedAgentProviders) {
|
|
1507
|
+
const config = await configureProviderWithAPIKey(providerType);
|
|
1508
|
+
if (config) {
|
|
1509
|
+
providers[providerType] = config;
|
|
1510
|
+
configuredProviderNames.push(providerType);
|
|
1511
|
+
p.log.success(`${providerType} configured`);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
// Step 4: Assign Roles
|
|
1516
|
+
p.note("Assign providers to agent roles.\n" +
|
|
1517
|
+
"Each role can use any configured provider.", "Step 4: Assign Agent Roles");
|
|
1518
|
+
const roles = await configureRoles(configuredProviderNames, orchestratorProvider);
|
|
1519
|
+
// Save Configuration
|
|
1520
|
+
const config = generateConfig(providers, roles);
|
|
1521
|
+
const saved = await saveConfig(config);
|
|
1522
|
+
if (saved) {
|
|
1523
|
+
await addEnvVarsToShellProfile();
|
|
1524
|
+
p.outro(color.green("✓ Foundation configured successfully!"));
|
|
1525
|
+
console.log();
|
|
1526
|
+
console.log(color.bold(" Summary"));
|
|
1527
|
+
console.log();
|
|
1528
|
+
console.log(` Orchestrator: ${color.cyan(orchestratorProvider)} (${orchestratorAccessMode})`);
|
|
1529
|
+
console.log(` Agent Providers: ${configuredProviderNames.filter(prov => prov !== orchestratorProvider).join(", ") || "none"}`);
|
|
1530
|
+
console.log(` Roles: ${Object.keys(roles).join(", ")}`);
|
|
1531
|
+
console.log();
|
|
1532
|
+
console.log(` Config: ${color.dim(getConfigPath())}`);
|
|
1533
|
+
console.log();
|
|
1534
|
+
}
|
|
1535
|
+
else {
|
|
1536
|
+
p.cancel("Failed to save configuration");
|
|
1537
|
+
process.exit(1);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
// ============================================================================
|
|
1541
|
+
// Provider Management Functions (for CLI subcommands)
|
|
1542
|
+
// ============================================================================
|
|
1543
|
+
/**
|
|
1544
|
+
* Add a single provider interactively
|
|
1545
|
+
*/
|
|
1546
|
+
export async function addProvider(providerName) {
|
|
1547
|
+
displayBanner();
|
|
1548
|
+
p.intro(color.bgCyan(color.black(" Add Provider ")));
|
|
1549
|
+
let selectedProvider;
|
|
1550
|
+
if (providerName) {
|
|
1551
|
+
const validProvider = AVAILABLE_PROVIDERS.find((p) => p.value.toLowerCase() === providerName.toLowerCase());
|
|
1552
|
+
if (!validProvider) {
|
|
1553
|
+
p.log.error(`Unknown provider: ${providerName}`);
|
|
1554
|
+
p.log.info(`Available providers: ${AVAILABLE_PROVIDERS.map((p) => p.value).join(", ")}`);
|
|
1555
|
+
process.exit(1);
|
|
1556
|
+
}
|
|
1557
|
+
selectedProvider = validProvider.value;
|
|
1558
|
+
}
|
|
1559
|
+
else {
|
|
1560
|
+
const choice = await p.select({
|
|
1561
|
+
message: "Which provider would you like to add?",
|
|
1562
|
+
options: AVAILABLE_PROVIDERS.map((provider) => ({
|
|
1563
|
+
value: provider.value,
|
|
1564
|
+
label: provider.label,
|
|
1565
|
+
...(provider.hint ? { hint: provider.hint } : {}),
|
|
1566
|
+
})),
|
|
1567
|
+
});
|
|
1568
|
+
if (p.isCancel(choice)) {
|
|
1569
|
+
p.cancel("Cancelled");
|
|
1570
|
+
process.exit(0);
|
|
1571
|
+
}
|
|
1572
|
+
selectedProvider = choice;
|
|
1573
|
+
}
|
|
1574
|
+
const config = await configureProviderWithAPIKey(selectedProvider);
|
|
1575
|
+
if (config) {
|
|
1576
|
+
p.log.success(`${selectedProvider} configured`);
|
|
1577
|
+
await addEnvVarsToShellProfile();
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Test provider connection(s)
|
|
1582
|
+
*/
|
|
1583
|
+
export async function testProvider(providerName) {
|
|
1584
|
+
p.intro(color.bgCyan(color.black(" Test Provider Connection ")));
|
|
1585
|
+
const configPath = getConfigPath();
|
|
1586
|
+
if (!fs.existsSync(configPath)) {
|
|
1587
|
+
p.log.error("No configuration file found.");
|
|
1588
|
+
p.log.info("Run 'foundation setup' first to configure providers.");
|
|
1589
|
+
process.exit(1);
|
|
1590
|
+
}
|
|
1591
|
+
if (providerName) {
|
|
1592
|
+
const provider = AVAILABLE_PROVIDERS.find((p) => p.value.toLowerCase() === providerName.toLowerCase());
|
|
1593
|
+
if (!provider) {
|
|
1594
|
+
p.log.error(`Unknown provider: ${providerName}`);
|
|
1595
|
+
p.log.info(`Available providers: ${AVAILABLE_PROVIDERS.map((p) => p.value).join(", ")}`);
|
|
1596
|
+
process.exit(1);
|
|
1597
|
+
}
|
|
1598
|
+
p.log.info(`Testing ${provider.label}...`);
|
|
1599
|
+
p.log.warn("Provider testing requires API keys in environment or config.");
|
|
1600
|
+
}
|
|
1601
|
+
else {
|
|
1602
|
+
p.log.info("Testing all configured providers...");
|
|
1603
|
+
p.log.warn("Provider testing requires API keys in environment or config.");
|
|
1604
|
+
}
|
|
1605
|
+
p.outro("Test complete");
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* List configured providers
|
|
1609
|
+
*/
|
|
1610
|
+
export async function listProviders() {
|
|
1611
|
+
const configPath = getConfigPath();
|
|
1612
|
+
if (!fs.existsSync(configPath)) {
|
|
1613
|
+
console.log("No configuration file found.");
|
|
1614
|
+
console.log("Run 'foundation setup' to configure providers.");
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
try {
|
|
1618
|
+
const configContent = fs.readFileSync(configPath, "utf-8");
|
|
1619
|
+
const { parse } = await import("yaml");
|
|
1620
|
+
const config = parse(configContent);
|
|
1621
|
+
if (!config.providers || Object.keys(config.providers).length === 0) {
|
|
1622
|
+
console.log("No providers configured.");
|
|
1623
|
+
console.log("Run 'foundation setup' to add providers.");
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
console.log();
|
|
1627
|
+
console.log(color.bold("Configured Providers:"));
|
|
1628
|
+
console.log();
|
|
1629
|
+
for (const [name, providerConfig] of Object.entries(config.providers)) {
|
|
1630
|
+
const accessMode = providerConfig.access_mode ?? "api";
|
|
1631
|
+
const model = providerConfig.default_model ?? "default";
|
|
1632
|
+
const hasApiKey = providerConfig.api_key ? "✓" : "✗";
|
|
1633
|
+
console.log(` ${color.cyan(name.padEnd(12))} ${accessMode.padEnd(14)} ${model}`);
|
|
1634
|
+
if (accessMode === "api") {
|
|
1635
|
+
console.log(` ${color.dim(`API Key: ${hasApiKey}`)}`);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
console.log();
|
|
1639
|
+
}
|
|
1640
|
+
catch (error) {
|
|
1641
|
+
console.error("Error reading configuration:", error instanceof Error ? error.message : error);
|
|
1642
|
+
process.exit(1);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
//# sourceMappingURL=setup-wizard.js.map
|