@sashabogi/foundation 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/cli/setup-wizard.d.ts +30 -0
  2. package/dist/cli/setup-wizard.d.ts.map +1 -0
  3. package/dist/cli/setup-wizard.js +1645 -0
  4. package/dist/cli/setup-wizard.js.map +1 -0
  5. package/dist/cli/test-connection.d.ts +76 -0
  6. package/dist/cli/test-connection.d.ts.map +1 -0
  7. package/dist/cli/test-connection.js +697 -0
  8. package/dist/cli/test-connection.js.map +1 -0
  9. package/dist/cli.d.ts +3 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +47 -4
  12. package/dist/cli.js.map +1 -1
  13. package/dist/providers/anthropic.d.ts +178 -0
  14. package/dist/providers/anthropic.d.ts.map +1 -0
  15. package/dist/providers/anthropic.js +514 -0
  16. package/dist/providers/anthropic.js.map +1 -0
  17. package/dist/providers/base.d.ts +154 -0
  18. package/dist/providers/base.d.ts.map +1 -0
  19. package/dist/providers/base.js +227 -0
  20. package/dist/providers/base.js.map +1 -0
  21. package/dist/providers/deepseek.d.ts +23 -0
  22. package/dist/providers/deepseek.d.ts.map +1 -0
  23. package/dist/providers/deepseek.js +31 -0
  24. package/dist/providers/deepseek.js.map +1 -0
  25. package/dist/providers/fireworks.d.ts +23 -0
  26. package/dist/providers/fireworks.d.ts.map +1 -0
  27. package/dist/providers/fireworks.js +31 -0
  28. package/dist/providers/fireworks.js.map +1 -0
  29. package/dist/providers/gemini.d.ts +85 -0
  30. package/dist/providers/gemini.d.ts.map +1 -0
  31. package/dist/providers/gemini.js +414 -0
  32. package/dist/providers/gemini.js.map +1 -0
  33. package/dist/providers/groq.d.ts +23 -0
  34. package/dist/providers/groq.d.ts.map +1 -0
  35. package/dist/providers/groq.js +31 -0
  36. package/dist/providers/groq.js.map +1 -0
  37. package/dist/providers/index.d.ts +23 -0
  38. package/dist/providers/index.d.ts.map +1 -0
  39. package/dist/providers/index.js +27 -0
  40. package/dist/providers/index.js.map +1 -0
  41. package/dist/providers/kimi-code.d.ts +32 -0
  42. package/dist/providers/kimi-code.d.ts.map +1 -0
  43. package/dist/providers/kimi-code.js +46 -0
  44. package/dist/providers/kimi-code.js.map +1 -0
  45. package/dist/providers/kimi.d.ts +19 -0
  46. package/dist/providers/kimi.d.ts.map +1 -0
  47. package/dist/providers/kimi.js +27 -0
  48. package/dist/providers/kimi.js.map +1 -0
  49. package/dist/providers/manager.d.ts +144 -0
  50. package/dist/providers/manager.d.ts.map +1 -0
  51. package/dist/providers/manager.js +279 -0
  52. package/dist/providers/manager.js.map +1 -0
  53. package/dist/providers/ollama.d.ts +83 -0
  54. package/dist/providers/ollama.d.ts.map +1 -0
  55. package/dist/providers/ollama.js +450 -0
  56. package/dist/providers/ollama.js.map +1 -0
  57. package/dist/providers/openai.d.ts +91 -0
  58. package/dist/providers/openai.d.ts.map +1 -0
  59. package/dist/providers/openai.js +445 -0
  60. package/dist/providers/openai.js.map +1 -0
  61. package/dist/providers/openrouter.d.ts +23 -0
  62. package/dist/providers/openrouter.d.ts.map +1 -0
  63. package/dist/providers/openrouter.js +31 -0
  64. package/dist/providers/openrouter.js.map +1 -0
  65. package/dist/providers/perplexity.d.ts +34 -0
  66. package/dist/providers/perplexity.d.ts.map +1 -0
  67. package/dist/providers/perplexity.js +58 -0
  68. package/dist/providers/perplexity.js.map +1 -0
  69. package/dist/providers/together.d.ts +23 -0
  70. package/dist/providers/together.d.ts.map +1 -0
  71. package/dist/providers/together.js +31 -0
  72. package/dist/providers/together.js.map +1 -0
  73. package/dist/providers/types.d.ts +229 -0
  74. package/dist/providers/types.d.ts.map +1 -0
  75. package/dist/providers/types.js +73 -0
  76. package/dist/providers/types.js.map +1 -0
  77. package/dist/providers/zai.d.ts +19 -0
  78. package/dist/providers/zai.d.ts.map +1 -0
  79. package/dist/providers/zai.js +27 -0
  80. package/dist/providers/zai.js.map +1 -0
  81. package/dist/services/provider.service.d.ts +28 -0
  82. package/dist/services/provider.service.d.ts.map +1 -1
  83. package/dist/services/provider.service.js +137 -13
  84. package/dist/services/provider.service.js.map +1 -1
  85. package/dist/tools/demerzel/engine.d.ts +67 -0
  86. package/dist/tools/demerzel/engine.d.ts.map +1 -0
  87. package/dist/tools/demerzel/engine.js +401 -0
  88. package/dist/tools/demerzel/engine.js.map +1 -0
  89. package/dist/tools/demerzel/enhanced-snapshot.d.ts +67 -0
  90. package/dist/tools/demerzel/enhanced-snapshot.d.ts.map +1 -0
  91. package/dist/tools/demerzel/enhanced-snapshot.js +481 -0
  92. package/dist/tools/demerzel/enhanced-snapshot.js.map +1 -0
  93. package/dist/tools/demerzel/index.d.ts +11 -0
  94. package/dist/tools/demerzel/index.d.ts.map +1 -1
  95. package/dist/tools/demerzel/index.js +656 -85
  96. package/dist/tools/demerzel/index.js.map +1 -1
  97. package/dist/tools/demerzel/prompts.d.ts +26 -0
  98. package/dist/tools/demerzel/prompts.d.ts.map +1 -0
  99. package/dist/tools/demerzel/prompts.js +181 -0
  100. package/dist/tools/demerzel/prompts.js.map +1 -0
  101. package/dist/tools/demerzel/semantic-search.d.ts +54 -0
  102. package/dist/tools/demerzel/semantic-search.d.ts.map +1 -0
  103. package/dist/tools/demerzel/semantic-search.js +205 -0
  104. package/dist/tools/demerzel/semantic-search.js.map +1 -0
  105. package/dist/tools/demerzel/snapshot.d.ts +30 -0
  106. package/dist/tools/demerzel/snapshot.d.ts.map +1 -0
  107. package/dist/tools/demerzel/snapshot.js +169 -0
  108. package/dist/tools/demerzel/snapshot.js.map +1 -0
  109. 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