mcp-agent-foundry 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +22 -2
  2. package/dist/cli/setup-wizard.d.ts +4 -2
  3. package/dist/cli/setup-wizard.d.ts.map +1 -1
  4. package/dist/cli/setup-wizard.js +1024 -37
  5. package/dist/cli/setup-wizard.js.map +1 -1
  6. package/dist/cli/test-connection.d.ts +34 -2
  7. package/dist/cli/test-connection.d.ts.map +1 -1
  8. package/dist/cli/test-connection.js +384 -2
  9. package/dist/cli/test-connection.js.map +1 -1
  10. package/dist/cli.d.ts +15 -3
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +198 -30
  13. package/dist/cli.js.map +1 -1
  14. package/dist/config/defaults.d.ts +2 -2
  15. package/dist/config/defaults.js +16 -16
  16. package/dist/config/defaults.js.map +1 -1
  17. package/dist/config/validator.d.ts +113 -0
  18. package/dist/config/validator.d.ts.map +1 -1
  19. package/dist/config/validator.js +113 -0
  20. package/dist/config/validator.js.map +1 -1
  21. package/dist/failover/health-tracker.d.ts +175 -0
  22. package/dist/failover/health-tracker.d.ts.map +1 -0
  23. package/dist/failover/health-tracker.js +350 -0
  24. package/dist/failover/health-tracker.js.map +1 -0
  25. package/dist/failover/index.d.ts +9 -0
  26. package/dist/failover/index.d.ts.map +1 -0
  27. package/dist/failover/index.js +9 -0
  28. package/dist/failover/index.js.map +1 -0
  29. package/dist/failover/orchestrator.d.ts +189 -0
  30. package/dist/failover/orchestrator.d.ts.map +1 -0
  31. package/dist/failover/orchestrator.js +488 -0
  32. package/dist/failover/orchestrator.js.map +1 -0
  33. package/dist/failover/pricing.d.ts +115 -0
  34. package/dist/failover/pricing.d.ts.map +1 -0
  35. package/dist/failover/pricing.js +283 -0
  36. package/dist/failover/pricing.js.map +1 -0
  37. package/dist/persistence/state-schema.d.ts +50 -0
  38. package/dist/persistence/state-schema.d.ts.map +1 -1
  39. package/dist/persistence/state-schema.js +2 -0
  40. package/dist/persistence/state-schema.js.map +1 -1
  41. package/dist/providers/fireworks.d.ts +23 -0
  42. package/dist/providers/fireworks.d.ts.map +1 -0
  43. package/dist/providers/fireworks.js +31 -0
  44. package/dist/providers/fireworks.js.map +1 -0
  45. package/dist/providers/groq.d.ts +23 -0
  46. package/dist/providers/groq.d.ts.map +1 -0
  47. package/dist/providers/groq.js +31 -0
  48. package/dist/providers/groq.js.map +1 -0
  49. package/dist/providers/kimi-code.d.ts +32 -0
  50. package/dist/providers/kimi-code.d.ts.map +1 -0
  51. package/dist/providers/kimi-code.js +46 -0
  52. package/dist/providers/kimi-code.js.map +1 -0
  53. package/dist/providers/kimi.d.ts +1 -1
  54. package/dist/providers/kimi.js +1 -1
  55. package/dist/providers/openrouter.d.ts +23 -0
  56. package/dist/providers/openrouter.d.ts.map +1 -0
  57. package/dist/providers/openrouter.js +31 -0
  58. package/dist/providers/openrouter.js.map +1 -0
  59. package/dist/providers/perplexity.d.ts +29 -0
  60. package/dist/providers/perplexity.d.ts.map +1 -0
  61. package/dist/providers/perplexity.js +51 -0
  62. package/dist/providers/perplexity.js.map +1 -0
  63. package/dist/providers/together.d.ts +23 -0
  64. package/dist/providers/together.d.ts.map +1 -0
  65. package/dist/providers/together.js +31 -0
  66. package/dist/providers/together.js.map +1 -0
  67. package/dist/router/engine.d.ts +21 -0
  68. package/dist/router/engine.d.ts.map +1 -1
  69. package/dist/router/engine.js +81 -21
  70. package/dist/router/engine.js.map +1 -1
  71. package/dist/server.d.ts.map +1 -1
  72. package/dist/server.js +49 -0
  73. package/dist/server.js.map +1 -1
  74. package/dist/types.d.ts +52 -1
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/types.js +14 -0
  77. package/dist/types.js.map +1 -1
  78. package/package.json +1 -1
@@ -1,11 +1,13 @@
1
1
  /**
2
- * Interactive setup wizard for AgentRouter v2
2
+ * Interactive setup wizard for Agent Foundry
3
3
  *
4
- * AgentRouter is an MCP server for multi-agent orchestration.
4
+ * Agent Foundry is an MCP server for multi-agent orchestration with
5
+ * Git worktree isolation for parallel coding agents.
5
6
  * This version supports:
6
7
  * - Multiple access modes: API keys OR subscription/CLI passthrough
7
8
  * - Configurable orchestrator (any provider can orchestrate)
8
9
  * - Full flexibility in role assignment
10
+ * - Git worktree isolation for coding agents
9
11
  *
10
12
  * Updated January 2026 with latest models from all providers.
11
13
  */
@@ -16,7 +18,7 @@ import * as path from "node:path";
16
18
  import * as os from "node:os";
17
19
  import { stringify as yamlStringify } from "yaml";
18
20
  import { getUserConfigPath, getUserConfigDir } from "../config/defaults.js";
19
- import { testOpenAIConnection, testGeminiConnection, testDeepSeekConnection, testZaiConnection, testOllamaConnection, testAnthropicConnection, testConnectionWithSpinner, } from "./test-connection.js";
21
+ import { testOpenAIConnection, testGeminiConnection, testDeepSeekConnection, testZaiConnection, testKimiConnection, testKimiCodeConnection, testOllamaConnection, testAnthropicConnection, testPerplexityConnection, testOpenRouterConnection, testGroqConnection, testTogetherConnection, testFireworksConnection, testConnectionWithSpinner, } from "./test-connection.js";
20
22
  // ============================================================================
21
23
  // Constants - Updated January 2026
22
24
  // ============================================================================
@@ -29,7 +31,14 @@ const PROVIDER_ENV_VARS = {
29
31
  google: "GEMINI_API_KEY",
30
32
  deepseek: "DEEPSEEK_API_KEY",
31
33
  zai: "ZAI_API_KEY",
34
+ kimi: "KIMI_API_KEY",
35
+ "kimi-code": "KIMI_API_KEY", // Uses same API key as regular Kimi
32
36
  ollama: "",
37
+ perplexity: "PERPLEXITY_API_KEY",
38
+ openrouter: "OPENROUTER_API_KEY",
39
+ groq: "GROQ_API_KEY",
40
+ together: "TOGETHER_API_KEY",
41
+ fireworks: "FIREWORKS_API_KEY",
33
42
  };
34
43
  /**
35
44
  * Track which environment variables were configured during setup
@@ -40,7 +49,7 @@ const configuredEnvVars = new Map();
40
49
  * Updated January 2026
41
50
  *
42
51
  * NOTE: "subscription" mode (CLI passthrough) only works for the interface
43
- * you're currently running FROM. Since AgentRouter typically runs from
52
+ * you're currently running FROM. Since Agent Foundry typically runs from
44
53
  * Claude Code, only Anthropic can use subscription mode for the orchestrator.
45
54
  * All other providers need API keys for agent roles.
46
55
  */
@@ -76,6 +85,48 @@ const AVAILABLE_PROVIDERS = [
76
85
  hint: "GLM-4.7 - Strong agentic coding, very affordable",
77
86
  supportsSubscription: false, // Requires API key when called from Claude Code
78
87
  },
88
+ {
89
+ value: "kimi",
90
+ label: "Moonshot (Kimi)",
91
+ hint: "Kimi K2.5 - Excellent coding, 1M context window",
92
+ supportsSubscription: false,
93
+ },
94
+ {
95
+ value: "kimi-code",
96
+ label: "Kimi Code",
97
+ hint: "Dedicated coding endpoint - uses Kimi subscription",
98
+ supportsSubscription: false,
99
+ },
100
+ {
101
+ value: "perplexity",
102
+ label: "Perplexity",
103
+ hint: "Sonar models - Web search grounded, citations",
104
+ supportsSubscription: false,
105
+ },
106
+ {
107
+ value: "openrouter",
108
+ label: "OpenRouter",
109
+ hint: "Gateway to 300+ models via single API",
110
+ supportsSubscription: false,
111
+ },
112
+ {
113
+ value: "groq",
114
+ label: "Groq",
115
+ hint: "Ultra-fast LPU inference - 18x faster",
116
+ supportsSubscription: false,
117
+ },
118
+ {
119
+ value: "together",
120
+ label: "Together AI",
121
+ hint: "200+ open models, sub-100ms latency",
122
+ supportsSubscription: false,
123
+ },
124
+ {
125
+ value: "fireworks",
126
+ label: "Fireworks AI",
127
+ hint: "Fast inference, DeepSeek/Llama hosting",
128
+ supportsSubscription: false,
129
+ },
79
130
  {
80
131
  value: "ollama",
81
132
  label: "Ollama",
@@ -138,6 +189,82 @@ const ZAI_MODELS = [
138
189
  { value: "glm-4.7-flashx", label: "GLM-4.7 FlashX", hint: "Fast and affordable" },
139
190
  { value: "glm-4.7-flash", label: "GLM-4.7 Flash", hint: "Free tier" },
140
191
  ];
192
+ /**
193
+ * Moonshot Kimi models (January 2026)
194
+ * https://platform.moonshot.cn/docs/api/model-list
195
+ * K2.5 series with 1M context window
196
+ */
197
+ const KIMI_MODELS = [
198
+ { value: "kimi-k2-5-preview", label: "Kimi K2.5 (Preview)", hint: "Latest flagship - 1M context" },
199
+ { value: "moonshot-v1-128k", label: "Moonshot V1 128K", hint: "Stable, 128K context" },
200
+ { value: "moonshot-v1-32k", label: "Moonshot V1 32K", hint: "Fast, 32K context" },
201
+ ];
202
+ /**
203
+ * Kimi Code models (January 2026)
204
+ * Separate API endpoint for coding-focused Kimi
205
+ * Uses Kimi subscription, no separate API credits needed
206
+ */
207
+ const KIMI_CODE_MODELS = [
208
+ { value: "kimi-for-coding", label: "Kimi for Coding", hint: "Optimized for code generation" },
209
+ ];
210
+ /**
211
+ * Perplexity models (January 2026)
212
+ * https://docs.perplexity.ai/
213
+ * Web search grounded responses with citations
214
+ */
215
+ const PERPLEXITY_MODELS = [
216
+ { value: "sonar-deep-research", label: "Sonar Deep Research", hint: "Deep research with citations" },
217
+ { value: "sonar-reasoning-pro", label: "Sonar Reasoning Pro", hint: "Advanced reasoning + search" },
218
+ { value: "sonar-reasoning", label: "Sonar Reasoning", hint: "Reasoning with web grounding" },
219
+ { value: "sonar-pro", label: "Sonar Pro", hint: "Enhanced search capabilities" },
220
+ { value: "sonar", label: "Sonar", hint: "Fast search-grounded responses" },
221
+ { value: "r1-1776", label: "R1-1776", hint: "Reasoning model" },
222
+ ];
223
+ /**
224
+ * OpenRouter models (January 2026)
225
+ * https://openrouter.ai/models
226
+ * Gateway to 300+ models
227
+ */
228
+ const OPENROUTER_MODELS = [
229
+ { value: "openrouter/auto", label: "Auto", hint: "Automatically selects best model" },
230
+ { value: "anthropic/claude-3.5-sonnet", label: "Claude 3.5 Sonnet", hint: "Via OpenRouter" },
231
+ { value: "openai/gpt-4o", label: "GPT-4o", hint: "Via OpenRouter" },
232
+ { value: "google/gemini-pro-1.5", label: "Gemini Pro 1.5", hint: "Via OpenRouter" },
233
+ { value: "meta-llama/llama-3.3-70b-instruct", label: "Llama 3.3 70B", hint: "Via OpenRouter" },
234
+ ];
235
+ /**
236
+ * Groq models (January 2026)
237
+ * https://console.groq.com/docs/models
238
+ * Ultra-fast LPU inference
239
+ */
240
+ const GROQ_MODELS = [
241
+ { value: "llama-3.3-70b-versatile", label: "Llama 3.3 70B Versatile", hint: "Best all-around" },
242
+ { value: "llama-3.1-8b-instant", label: "Llama 3.1 8B Instant", hint: "Ultra-fast, small" },
243
+ { value: "mixtral-8x7b-32768", label: "Mixtral 8x7B", hint: "MoE model, 32K context" },
244
+ { value: "gemma2-9b-it", label: "Gemma 2 9B IT", hint: "Google's open model" },
245
+ ];
246
+ /**
247
+ * Together AI models (January 2026)
248
+ * https://docs.together.ai/docs/inference-models
249
+ * 200+ open models with sub-100ms latency
250
+ */
251
+ const TOGETHER_MODELS = [
252
+ { value: "meta-llama/Llama-3.3-70B-Instruct-Turbo", label: "Llama 3.3 70B Turbo", hint: "Fast, high quality" },
253
+ { value: "deepseek-ai/DeepSeek-V3", label: "DeepSeek V3", hint: "Advanced reasoning" },
254
+ { value: "Qwen/Qwen2.5-72B-Instruct-Turbo", label: "Qwen 2.5 72B Turbo", hint: "Strong multilingual" },
255
+ { value: "mistralai/Mixtral-8x22B-Instruct-v0.1", label: "Mixtral 8x22B", hint: "Large MoE model" },
256
+ ];
257
+ /**
258
+ * Fireworks AI models (January 2026)
259
+ * https://docs.fireworks.ai/models/models
260
+ * Fast inference for open models
261
+ */
262
+ const FIREWORKS_MODELS = [
263
+ { value: "accounts/fireworks/models/llama-v3p3-70b-instruct", label: "Llama 3.3 70B", hint: "Fast inference" },
264
+ { value: "accounts/fireworks/models/deepseek-v3", label: "DeepSeek V3", hint: "Advanced reasoning" },
265
+ { value: "accounts/fireworks/models/mixtral-8x22b-instruct", label: "Mixtral 8x22B", hint: "Large MoE" },
266
+ { value: "accounts/fireworks/models/qwen2p5-72b-instruct", label: "Qwen 2.5 72B", hint: "Multilingual" },
267
+ ];
141
268
  /**
142
269
  * Ollama local models
143
270
  */
@@ -264,18 +391,18 @@ If you're uncertain, say so. Prefer accuracy over completeness.`,
264
391
  // ============================================================================
265
392
  function displayBanner() {
266
393
  console.log();
267
- console.log(color.cyan("╭─────────────────────────────────────────────────╮"));
268
- console.log(color.cyan("│ │"));
394
+ console.log(color.cyan("╭───────────────────────────────────────────────────────╮"));
395
+ console.log(color.cyan("│ │"));
269
396
  console.log(color.cyan("│ ") +
270
- color.bold(color.yellow("🔀")) +
271
- color.bold(" AgentRouter Setup v2") +
272
- color.cyan(" │"));
273
- console.log(color.cyan("│ │"));
397
+ color.bold(color.yellow("🏗️")) +
398
+ color.bold(" Agent Foundry Setup") +
399
+ color.cyan(" │"));
400
+ console.log(color.cyan("│ │"));
274
401
  console.log(color.cyan("│ ") +
275
- color.dim("Multi-agent orchestration for AI coding tools") +
402
+ color.dim("Multi-agent orchestration with worktree isolation") +
276
403
  color.cyan(" │"));
277
- console.log(color.cyan("│ │"));
278
- console.log(color.cyan("╰─────────────────────────────────────────────────╯"));
404
+ console.log(color.cyan("│ │"));
405
+ console.log(color.cyan("╰───────────────────────────────────────────────────────╯"));
279
406
  console.log();
280
407
  }
281
408
  // ============================================================================
@@ -305,6 +432,20 @@ function getProviderModels(providerName) {
305
432
  return DEEPSEEK_MODELS;
306
433
  case "zai":
307
434
  return ZAI_MODELS;
435
+ case "kimi":
436
+ return KIMI_MODELS;
437
+ case "kimi-code":
438
+ return KIMI_CODE_MODELS;
439
+ case "perplexity":
440
+ return PERPLEXITY_MODELS;
441
+ case "openrouter":
442
+ return OPENROUTER_MODELS;
443
+ case "groq":
444
+ return GROQ_MODELS;
445
+ case "together":
446
+ return TOGETHER_MODELS;
447
+ case "fireworks":
448
+ return FIREWORKS_MODELS;
308
449
  case "ollama":
309
450
  return OLLAMA_MODELS;
310
451
  default:
@@ -314,6 +455,34 @@ function getProviderModels(providerName) {
314
455
  function getProviderInfo(providerName) {
315
456
  return AVAILABLE_PROVIDERS.find((p) => p.value === providerName);
316
457
  }
458
+ /**
459
+ * Get display name for a provider.
460
+ */
461
+ function getProviderDisplayName(provider) {
462
+ const displayNames = {
463
+ anthropic: "Anthropic (Claude)",
464
+ openai: "OpenAI (GPT)",
465
+ google: "Google (Gemini)",
466
+ deepseek: "DeepSeek",
467
+ zai: "Z.AI (GLM)",
468
+ kimi: "Moonshot (Kimi)",
469
+ "kimi-code": "Kimi Code",
470
+ ollama: "Ollama (Local)",
471
+ perplexity: "Perplexity",
472
+ openrouter: "OpenRouter",
473
+ groq: "Groq",
474
+ together: "Together AI",
475
+ fireworks: "Fireworks AI",
476
+ };
477
+ return displayNames[provider] || provider;
478
+ }
479
+ /**
480
+ * Get default model for a provider.
481
+ */
482
+ function getDefaultModelForProvider(provider) {
483
+ const models = getProviderModels(provider);
484
+ return models[0]?.value ?? "default";
485
+ }
317
486
  // ============================================================================
318
487
  // Provider Configuration Functions
319
488
  // ============================================================================
@@ -640,28 +809,418 @@ async function configureZai() {
640
809
  // API mode
641
810
  p.note("GLM-4.7 is excellent for agentic coding tasks.\n" +
642
811
  "Get your API key from:\n" +
643
- color.cyan("https://z.ai/manage-apikey/apikey-list"), "API Key Setup");
812
+ color.cyan("https://z.ai/manage-apikey/apikey-list"), "API Key Setup");
813
+ while (true) {
814
+ const apiKey = await p.password({
815
+ message: "Enter your Z.AI API key:",
816
+ validate: (v) => validateApiKey(v, "zai"),
817
+ });
818
+ if (p.isCancel(apiKey))
819
+ return null;
820
+ const result = await testConnectionWithSpinner("Z.AI", () => testZaiConnection(apiKey));
821
+ if (result.success) {
822
+ const defaultModel = await p.select({
823
+ message: "Select default GLM model:",
824
+ options: ZAI_MODELS,
825
+ });
826
+ if (p.isCancel(defaultModel))
827
+ return null;
828
+ const envVarName = PROVIDER_ENV_VARS["zai"];
829
+ configuredEnvVars.set(envVarName, apiKey);
830
+ return {
831
+ access_mode: "api",
832
+ api_key: "${" + envVarName + "}",
833
+ base_url: "https://api.z.ai/api/paas/v4",
834
+ default_model: defaultModel,
835
+ };
836
+ }
837
+ const action = await p.select({
838
+ message: "Connection test failed. What would you like to do?",
839
+ options: [
840
+ { value: "retry", label: "Re-enter API key" },
841
+ { value: "skip", label: "Skip this provider" },
842
+ { value: "add", label: "Add anyway" },
843
+ ],
844
+ });
845
+ if (p.isCancel(action) || action === "skip")
846
+ return null;
847
+ if (action === "add") {
848
+ const envVarName = PROVIDER_ENV_VARS["zai"];
849
+ configuredEnvVars.set(envVarName, apiKey);
850
+ return {
851
+ access_mode: "api",
852
+ api_key: "${" + envVarName + "}",
853
+ base_url: "https://api.z.ai/api/paas/v4",
854
+ default_model: "glm-4.7-flash",
855
+ };
856
+ }
857
+ }
858
+ }
859
+ /**
860
+ * Configure Moonshot (Kimi) provider
861
+ */
862
+ async function configureKimi() {
863
+ p.log.step(color.bold("Moonshot (Kimi) Configuration"));
864
+ p.note("Kimi K2.5 excels at coding with a 1M context window.\n" +
865
+ color.bold("Pricing: Very affordable") + "\n\n" +
866
+ "Get your API key from:\n" +
867
+ color.cyan("https://platform.moonshot.cn/console/api-keys"), "Moonshot Setup");
868
+ while (true) {
869
+ const apiKey = await p.password({
870
+ message: "Enter your Moonshot API key:",
871
+ validate: (v) => validateApiKey(v, "kimi"),
872
+ });
873
+ if (p.isCancel(apiKey))
874
+ return null;
875
+ const result = await testConnectionWithSpinner("Moonshot", () => testKimiConnection(apiKey));
876
+ if (result.success) {
877
+ const defaultModel = await p.select({
878
+ message: "Select default Kimi model:",
879
+ options: KIMI_MODELS,
880
+ });
881
+ if (p.isCancel(defaultModel))
882
+ return null;
883
+ const envVarName = PROVIDER_ENV_VARS["kimi"];
884
+ configuredEnvVars.set(envVarName, apiKey);
885
+ return {
886
+ access_mode: "api",
887
+ api_key: "${" + envVarName + "}",
888
+ base_url: "https://api.moonshot.ai/v1",
889
+ default_model: defaultModel,
890
+ };
891
+ }
892
+ const action = await p.select({
893
+ message: "Connection test failed. What would you like to do?",
894
+ options: [
895
+ { value: "retry", label: "Re-enter API key" },
896
+ { value: "skip", label: "Skip this provider" },
897
+ { value: "add", label: "Add anyway" },
898
+ ],
899
+ });
900
+ if (p.isCancel(action) || action === "skip")
901
+ return null;
902
+ if (action === "add") {
903
+ const envVarName = PROVIDER_ENV_VARS["kimi"];
904
+ configuredEnvVars.set(envVarName, apiKey);
905
+ return {
906
+ access_mode: "api",
907
+ api_key: "${" + envVarName + "}",
908
+ base_url: "https://api.moonshot.ai/v1",
909
+ default_model: "kimi-k2-5-preview",
910
+ };
911
+ }
912
+ }
913
+ }
914
+ /**
915
+ * Configure Kimi Code provider
916
+ *
917
+ * Kimi Code is a separate API endpoint from the regular Moonshot API.
918
+ * Uses the same Kimi subscription/API key but different base URL.
919
+ * CRITICAL: Requires User-Agent: claude-code/1.0 header.
920
+ */
921
+ async function configureKimiCode() {
922
+ p.log.step(color.bold("Kimi Code Configuration"));
923
+ p.note("Kimi Code is a dedicated coding endpoint.\n" +
924
+ color.bold("Uses your Kimi subscription - no separate API credits needed!") + "\n\n" +
925
+ "Uses your existing Kimi/Moonshot API key.\n" +
926
+ "Get your API key from:\n" +
927
+ color.cyan("https://platform.moonshot.cn/console/api-keys"), "Kimi Code Setup");
928
+ while (true) {
929
+ const apiKey = await p.password({
930
+ message: "Enter your Kimi API key:",
931
+ validate: (v) => validateApiKey(v, "kimi-code"),
932
+ });
933
+ if (p.isCancel(apiKey))
934
+ return null;
935
+ const result = await testConnectionWithSpinner("Kimi Code", () => testKimiCodeConnection(apiKey));
936
+ if (result.success) {
937
+ const defaultModel = await p.select({
938
+ message: "Select default Kimi Code model:",
939
+ options: KIMI_CODE_MODELS,
940
+ });
941
+ if (p.isCancel(defaultModel))
942
+ return null;
943
+ const envVarName = PROVIDER_ENV_VARS["kimi-code"];
944
+ configuredEnvVars.set(envVarName, apiKey);
945
+ return {
946
+ access_mode: "api",
947
+ api_key: "${" + envVarName + "}",
948
+ base_url: "https://api.kimi.com/coding/v1",
949
+ default_model: defaultModel,
950
+ };
951
+ }
952
+ const action = await p.select({
953
+ message: "Connection test failed. What would you like to do?",
954
+ options: [
955
+ { value: "retry", label: "Re-enter API key" },
956
+ { value: "skip", label: "Skip this provider" },
957
+ { value: "add", label: "Add anyway" },
958
+ ],
959
+ });
960
+ if (p.isCancel(action) || action === "skip")
961
+ return null;
962
+ if (action === "add") {
963
+ const envVarName = PROVIDER_ENV_VARS["kimi-code"];
964
+ configuredEnvVars.set(envVarName, apiKey);
965
+ return {
966
+ access_mode: "api",
967
+ api_key: "${" + envVarName + "}",
968
+ base_url: "https://api.kimi.com/coding/v1",
969
+ default_model: "kimi-for-coding",
970
+ };
971
+ }
972
+ }
973
+ }
974
+ /**
975
+ * Configure Perplexity provider (API only)
976
+ */
977
+ async function configurePerplexity() {
978
+ p.log.step(color.bold("Perplexity Configuration"));
979
+ p.note("Perplexity provides web search grounded responses with citations.\n" +
980
+ "Sonar models return real-time web information.\n\n" +
981
+ "Get your API key from:\n" +
982
+ color.cyan("https://www.perplexity.ai/settings/api"), "Perplexity Setup");
983
+ while (true) {
984
+ const apiKey = await p.password({
985
+ message: "Enter your Perplexity API key:",
986
+ validate: (v) => validateApiKey(v, "perplexity"),
987
+ });
988
+ if (p.isCancel(apiKey))
989
+ return null;
990
+ const result = await testConnectionWithSpinner("Perplexity", () => testPerplexityConnection(apiKey));
991
+ if (result.success) {
992
+ const defaultModel = await p.select({
993
+ message: "Select default Perplexity model:",
994
+ options: PERPLEXITY_MODELS,
995
+ });
996
+ if (p.isCancel(defaultModel))
997
+ return null;
998
+ const envVarName = PROVIDER_ENV_VARS["perplexity"];
999
+ configuredEnvVars.set(envVarName, apiKey);
1000
+ return {
1001
+ access_mode: "api",
1002
+ api_key: "${" + envVarName + "}",
1003
+ base_url: "https://api.perplexity.ai",
1004
+ default_model: defaultModel,
1005
+ };
1006
+ }
1007
+ const action = await p.select({
1008
+ message: "Connection test failed. What would you like to do?",
1009
+ options: [
1010
+ { value: "retry", label: "Re-enter API key" },
1011
+ { value: "skip", label: "Skip this provider" },
1012
+ { value: "add", label: "Add anyway" },
1013
+ ],
1014
+ });
1015
+ if (p.isCancel(action) || action === "skip")
1016
+ return null;
1017
+ if (action === "add") {
1018
+ const envVarName = PROVIDER_ENV_VARS["perplexity"];
1019
+ configuredEnvVars.set(envVarName, apiKey);
1020
+ return {
1021
+ access_mode: "api",
1022
+ api_key: "${" + envVarName + "}",
1023
+ base_url: "https://api.perplexity.ai",
1024
+ default_model: "sonar",
1025
+ };
1026
+ }
1027
+ }
1028
+ }
1029
+ /**
1030
+ * Configure OpenRouter provider (API only)
1031
+ */
1032
+ async function configureOpenRouter() {
1033
+ p.log.step(color.bold("OpenRouter Configuration"));
1034
+ p.note("OpenRouter provides access to 300+ models via a single API.\n" +
1035
+ "Use 'openrouter/auto' to automatically select the best model.\n\n" +
1036
+ "Get your API key from:\n" +
1037
+ color.cyan("https://openrouter.ai/keys"), "OpenRouter Setup");
1038
+ while (true) {
1039
+ const apiKey = await p.password({
1040
+ message: "Enter your OpenRouter API key:",
1041
+ validate: (v) => validateApiKey(v, "openrouter"),
1042
+ });
1043
+ if (p.isCancel(apiKey))
1044
+ return null;
1045
+ const result = await testConnectionWithSpinner("OpenRouter", () => testOpenRouterConnection(apiKey));
1046
+ if (result.success) {
1047
+ const defaultModel = await p.select({
1048
+ message: "Select default OpenRouter model:",
1049
+ options: OPENROUTER_MODELS,
1050
+ });
1051
+ if (p.isCancel(defaultModel))
1052
+ return null;
1053
+ const envVarName = PROVIDER_ENV_VARS["openrouter"];
1054
+ configuredEnvVars.set(envVarName, apiKey);
1055
+ return {
1056
+ access_mode: "api",
1057
+ api_key: "${" + envVarName + "}",
1058
+ base_url: "https://openrouter.ai/api/v1",
1059
+ default_model: defaultModel,
1060
+ };
1061
+ }
1062
+ const action = await p.select({
1063
+ message: "Connection test failed. What would you like to do?",
1064
+ options: [
1065
+ { value: "retry", label: "Re-enter API key" },
1066
+ { value: "skip", label: "Skip this provider" },
1067
+ { value: "add", label: "Add anyway" },
1068
+ ],
1069
+ });
1070
+ if (p.isCancel(action) || action === "skip")
1071
+ return null;
1072
+ if (action === "add") {
1073
+ const envVarName = PROVIDER_ENV_VARS["openrouter"];
1074
+ configuredEnvVars.set(envVarName, apiKey);
1075
+ return {
1076
+ access_mode: "api",
1077
+ api_key: "${" + envVarName + "}",
1078
+ base_url: "https://openrouter.ai/api/v1",
1079
+ default_model: "openrouter/auto",
1080
+ };
1081
+ }
1082
+ }
1083
+ }
1084
+ /**
1085
+ * Configure Groq provider (API only)
1086
+ */
1087
+ async function configureGroq() {
1088
+ p.log.step(color.bold("Groq Configuration"));
1089
+ p.note("Groq provides ultra-fast LPU inference - up to 18x faster than GPU.\n" +
1090
+ color.bold("Free tier available!") + "\n\n" +
1091
+ "Get your API key from:\n" +
1092
+ color.cyan("https://console.groq.com/keys"), "Groq Setup");
1093
+ while (true) {
1094
+ const apiKey = await p.password({
1095
+ message: "Enter your Groq API key:",
1096
+ validate: (v) => validateApiKey(v, "groq"),
1097
+ });
1098
+ if (p.isCancel(apiKey))
1099
+ return null;
1100
+ const result = await testConnectionWithSpinner("Groq", () => testGroqConnection(apiKey));
1101
+ if (result.success) {
1102
+ const defaultModel = await p.select({
1103
+ message: "Select default Groq model:",
1104
+ options: GROQ_MODELS,
1105
+ });
1106
+ if (p.isCancel(defaultModel))
1107
+ return null;
1108
+ const envVarName = PROVIDER_ENV_VARS["groq"];
1109
+ configuredEnvVars.set(envVarName, apiKey);
1110
+ return {
1111
+ access_mode: "api",
1112
+ api_key: "${" + envVarName + "}",
1113
+ base_url: "https://api.groq.com/openai/v1",
1114
+ default_model: defaultModel,
1115
+ };
1116
+ }
1117
+ const action = await p.select({
1118
+ message: "Connection test failed. What would you like to do?",
1119
+ options: [
1120
+ { value: "retry", label: "Re-enter API key" },
1121
+ { value: "skip", label: "Skip this provider" },
1122
+ { value: "add", label: "Add anyway" },
1123
+ ],
1124
+ });
1125
+ if (p.isCancel(action) || action === "skip")
1126
+ return null;
1127
+ if (action === "add") {
1128
+ const envVarName = PROVIDER_ENV_VARS["groq"];
1129
+ configuredEnvVars.set(envVarName, apiKey);
1130
+ return {
1131
+ access_mode: "api",
1132
+ api_key: "${" + envVarName + "}",
1133
+ base_url: "https://api.groq.com/openai/v1",
1134
+ default_model: "llama-3.3-70b-versatile",
1135
+ };
1136
+ }
1137
+ }
1138
+ }
1139
+ /**
1140
+ * Configure Together AI provider (API only)
1141
+ */
1142
+ async function configureTogether() {
1143
+ p.log.step(color.bold("Together AI Configuration"));
1144
+ p.note("Together AI provides 200+ open models with sub-100ms latency.\n" +
1145
+ color.bold("$1 free credit to start!") + "\n\n" +
1146
+ "Get your API key from:\n" +
1147
+ color.cyan("https://api.together.xyz/settings/api-keys"), "Together AI Setup");
1148
+ while (true) {
1149
+ const apiKey = await p.password({
1150
+ message: "Enter your Together AI API key:",
1151
+ validate: (v) => validateApiKey(v, "together"),
1152
+ });
1153
+ if (p.isCancel(apiKey))
1154
+ return null;
1155
+ const result = await testConnectionWithSpinner("Together AI", () => testTogetherConnection(apiKey));
1156
+ if (result.success) {
1157
+ const defaultModel = await p.select({
1158
+ message: "Select default Together AI model:",
1159
+ options: TOGETHER_MODELS,
1160
+ });
1161
+ if (p.isCancel(defaultModel))
1162
+ return null;
1163
+ const envVarName = PROVIDER_ENV_VARS["together"];
1164
+ configuredEnvVars.set(envVarName, apiKey);
1165
+ return {
1166
+ access_mode: "api",
1167
+ api_key: "${" + envVarName + "}",
1168
+ base_url: "https://api.together.xyz/v1",
1169
+ default_model: defaultModel,
1170
+ };
1171
+ }
1172
+ const action = await p.select({
1173
+ message: "Connection test failed. What would you like to do?",
1174
+ options: [
1175
+ { value: "retry", label: "Re-enter API key" },
1176
+ { value: "skip", label: "Skip this provider" },
1177
+ { value: "add", label: "Add anyway" },
1178
+ ],
1179
+ });
1180
+ if (p.isCancel(action) || action === "skip")
1181
+ return null;
1182
+ if (action === "add") {
1183
+ const envVarName = PROVIDER_ENV_VARS["together"];
1184
+ configuredEnvVars.set(envVarName, apiKey);
1185
+ return {
1186
+ access_mode: "api",
1187
+ api_key: "${" + envVarName + "}",
1188
+ base_url: "https://api.together.xyz/v1",
1189
+ default_model: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
1190
+ };
1191
+ }
1192
+ }
1193
+ }
1194
+ /**
1195
+ * Configure Fireworks AI provider (API only)
1196
+ */
1197
+ async function configureFireworks() {
1198
+ p.log.step(color.bold("Fireworks AI Configuration"));
1199
+ p.note("Fireworks AI provides fast inference for DeepSeek, Llama, and other models.\n" +
1200
+ color.bold("$1 free credit to start!") + "\n\n" +
1201
+ "Get your API key from:\n" +
1202
+ color.cyan("https://fireworks.ai/account/api-keys"), "Fireworks AI Setup");
644
1203
  while (true) {
645
1204
  const apiKey = await p.password({
646
- message: "Enter your Z.AI API key:",
647
- validate: (v) => validateApiKey(v, "zai"),
1205
+ message: "Enter your Fireworks AI API key:",
1206
+ validate: (v) => validateApiKey(v, "fireworks"),
648
1207
  });
649
1208
  if (p.isCancel(apiKey))
650
1209
  return null;
651
- const result = await testConnectionWithSpinner("Z.AI", () => testZaiConnection(apiKey));
1210
+ const result = await testConnectionWithSpinner("Fireworks AI", () => testFireworksConnection(apiKey));
652
1211
  if (result.success) {
653
1212
  const defaultModel = await p.select({
654
- message: "Select default GLM model:",
655
- options: ZAI_MODELS,
1213
+ message: "Select default Fireworks AI model:",
1214
+ options: FIREWORKS_MODELS,
656
1215
  });
657
1216
  if (p.isCancel(defaultModel))
658
1217
  return null;
659
- const envVarName = PROVIDER_ENV_VARS["zai"];
1218
+ const envVarName = PROVIDER_ENV_VARS["fireworks"];
660
1219
  configuredEnvVars.set(envVarName, apiKey);
661
1220
  return {
662
1221
  access_mode: "api",
663
1222
  api_key: "${" + envVarName + "}",
664
- base_url: "https://api.z.ai/api/paas/v4",
1223
+ base_url: "https://api.fireworks.ai/inference/v1",
665
1224
  default_model: defaultModel,
666
1225
  };
667
1226
  }
@@ -676,13 +1235,13 @@ async function configureZai() {
676
1235
  if (p.isCancel(action) || action === "skip")
677
1236
  return null;
678
1237
  if (action === "add") {
679
- const envVarName = PROVIDER_ENV_VARS["zai"];
1238
+ const envVarName = PROVIDER_ENV_VARS["fireworks"];
680
1239
  configuredEnvVars.set(envVarName, apiKey);
681
1240
  return {
682
1241
  access_mode: "api",
683
1242
  api_key: "${" + envVarName + "}",
684
- base_url: "https://api.z.ai/api/paas/v4",
685
- default_model: "glm-4.7-flash",
1243
+ base_url: "https://api.fireworks.ai/inference/v1",
1244
+ default_model: "accounts/fireworks/models/llama-v3p3-70b-instruct",
686
1245
  };
687
1246
  }
688
1247
  }
@@ -826,6 +1385,75 @@ async function configureRoles(configuredProviders, orchestratorProvider) {
826
1385
  return roles;
827
1386
  }
828
1387
  // ============================================================================
1388
+ // Failover Configuration
1389
+ // ============================================================================
1390
+ /**
1391
+ * Configure failover settings.
1392
+ */
1393
+ async function configureFailover(configuredProviders) {
1394
+ p.note("Failover automatically switches to backup providers when errors occur.\n" +
1395
+ "This keeps your coding agents running even during rate limits.", "Failover Configuration");
1396
+ const enableFailover = await p.confirm({
1397
+ message: "Enable automatic failover?",
1398
+ initialValue: true,
1399
+ });
1400
+ if (p.isCancel(enableFailover)) {
1401
+ p.cancel("Setup cancelled");
1402
+ process.exit(0);
1403
+ }
1404
+ if (!enableFailover) {
1405
+ return {
1406
+ enabled: false,
1407
+ preferCostEfficient: false,
1408
+ roleFailbacks: {},
1409
+ };
1410
+ }
1411
+ const preferCostEfficient = await p.confirm({
1412
+ message: "Prefer cheapest healthy provider when no fallback specified?",
1413
+ initialValue: false,
1414
+ });
1415
+ if (p.isCancel(preferCostEfficient)) {
1416
+ p.cancel("Setup cancelled");
1417
+ process.exit(0);
1418
+ }
1419
+ // Configure fallback order for coder role if multiple providers
1420
+ const roleFailbacks = {};
1421
+ if (configuredProviders.length > 1) {
1422
+ const configureCoderFallback = await p.confirm({
1423
+ message: `Configure fallback order for the "coder" role? (${configuredProviders.length} providers available)`,
1424
+ initialValue: true,
1425
+ });
1426
+ if (p.isCancel(configureCoderFallback)) {
1427
+ p.cancel("Setup cancelled");
1428
+ process.exit(0);
1429
+ }
1430
+ if (configureCoderFallback) {
1431
+ // Create options from configured providers
1432
+ const providerOptions = configuredProviders.map((prov) => ({
1433
+ value: prov,
1434
+ label: getProviderDisplayName(prov),
1435
+ }));
1436
+ const fallbackSelection = await p.multiselect({
1437
+ message: "Select fallback providers in order of preference (first = primary, rest = fallbacks):",
1438
+ options: providerOptions,
1439
+ required: true,
1440
+ });
1441
+ if (p.isCancel(fallbackSelection)) {
1442
+ p.cancel("Setup cancelled");
1443
+ process.exit(0);
1444
+ }
1445
+ if (Array.isArray(fallbackSelection) && fallbackSelection.length > 1) {
1446
+ roleFailbacks["coder"] = fallbackSelection;
1447
+ }
1448
+ }
1449
+ }
1450
+ return {
1451
+ enabled: true,
1452
+ preferCostEfficient: preferCostEfficient,
1453
+ roleFailbacks,
1454
+ };
1455
+ }
1456
+ // ============================================================================
829
1457
  // Shell Profile & Config Saving
830
1458
  // ============================================================================
831
1459
  function getShellProfilePath() {
@@ -867,7 +1495,7 @@ async function addEnvVarsToShellProfile() {
867
1495
  p.log.info(`Environment variables already exist in ~/${profileName}`);
868
1496
  return;
869
1497
  }
870
- const exportLines = ["", "# AgentRouter"];
1498
+ const exportLines = ["", "# Agent Foundry"];
871
1499
  for (const { name, value } of envVarsToAdd) {
872
1500
  exportLines.push(`export ${name}="${value}"`);
873
1501
  }
@@ -883,8 +1511,8 @@ async function addEnvVarsToShellProfile() {
883
1511
  p.log.error(`Failed to update ${profilePath}`);
884
1512
  }
885
1513
  }
886
- function generateConfig(providers, roles) {
887
- return {
1514
+ function generateConfig(providers, roles, failoverConfig) {
1515
+ const config = {
888
1516
  version: "2.0",
889
1517
  defaults: {
890
1518
  temperature: 0.7,
@@ -894,6 +1522,31 @@ function generateConfig(providers, roles) {
894
1522
  roles,
895
1523
  providers,
896
1524
  };
1525
+ // Add failover configuration to defaults
1526
+ if (failoverConfig?.enabled) {
1527
+ config.defaults.failover = {
1528
+ enabled: true,
1529
+ prefer_cost_efficient: failoverConfig.preferCostEfficient,
1530
+ health_check_interval_ms: 60000,
1531
+ cooldown_ms: 300000,
1532
+ };
1533
+ // Add fallback chains to roles
1534
+ for (const [roleName, fallbackProviders] of Object.entries(failoverConfig.roleFailbacks)) {
1535
+ if (config.roles[roleName] && fallbackProviders.length > 1) {
1536
+ const [primary, ...rest] = fallbackProviders;
1537
+ // Update primary provider
1538
+ config.roles[roleName].provider = primary;
1539
+ // Add fallback chain
1540
+ config.roles[roleName].fallback_chain = {
1541
+ providers: rest.map((provider) => ({
1542
+ provider,
1543
+ model: getDefaultModelForProvider(provider),
1544
+ })),
1545
+ };
1546
+ }
1547
+ }
1548
+ }
1549
+ return config;
897
1550
  }
898
1551
  async function saveConfig(config) {
899
1552
  const configPath = getUserConfigPath();
@@ -907,11 +1560,11 @@ async function saveConfig(config) {
907
1560
  fs.copyFileSync(configPath, backupPath);
908
1561
  p.log.info(`Backed up existing config to: ${color.dim(backupPath)}`);
909
1562
  }
910
- const header = `# AgentRouter Configuration v2
1563
+ const header = `# Agent Foundry Configuration
911
1564
  # Generated: ${new Date().toISOString()}
912
- # Documentation: https://github.com/hive-dev/agent-router
1565
+ # Documentation: https://github.com/sashabogi/agent-foundry
913
1566
  #
914
- # This configures multi-agent orchestration across AI providers.
1567
+ # This configures multi-agent orchestration with Git worktree isolation.
915
1568
  # Supports both API keys and subscription/CLI access modes.
916
1569
  #
917
1570
  # Environment variables: \${VAR_NAME} syntax
@@ -952,7 +1605,7 @@ function detectCurrentCLI() {
952
1605
  }
953
1606
  export async function runSetupWizard() {
954
1607
  displayBanner();
955
- p.intro(color.bgCyan(color.black(" AgentRouter Setup v2 ")));
1608
+ p.intro(color.bgCyan(color.black(" Agent Foundry Setup ")));
956
1609
  const currentCLI = detectCurrentCLI();
957
1610
  // ============================================================================
958
1611
  // STEP 1: Choose Orchestrator
@@ -1080,19 +1733,26 @@ export async function runSetupWizard() {
1080
1733
  "Each role can use any configured provider.", "Step 4: Assign Agent Roles");
1081
1734
  const roles = await configureRoles(configuredProviderNames, orchestratorProvider);
1082
1735
  // ============================================================================
1736
+ // STEP 5: Configure Failover
1737
+ // ============================================================================
1738
+ p.note("Failover provides automatic recovery when providers fail.\n" +
1739
+ "Configure backup providers to keep agents running.", "Step 5: Failover Settings");
1740
+ const failoverConfig = await configureFailover(configuredProviderNames);
1741
+ // ============================================================================
1083
1742
  // Save Configuration
1084
1743
  // ============================================================================
1085
- const config = generateConfig(providers, roles);
1744
+ const config = generateConfig(providers, roles, failoverConfig);
1086
1745
  const saved = await saveConfig(config);
1087
1746
  if (saved) {
1088
1747
  await addEnvVarsToShellProfile();
1089
- p.outro(color.green("✓ AgentRouter configured successfully!"));
1748
+ p.outro(color.green("✓ Agent Foundry configured successfully!"));
1090
1749
  console.log();
1091
1750
  console.log(color.bold(" Summary"));
1092
1751
  console.log();
1093
1752
  console.log(` Orchestrator: ${color.cyan(orchestratorProvider)} (${orchestratorAccessMode})`);
1094
- console.log(` Agent Providers: ${configuredProviderNames.filter(p => p !== orchestratorProvider).join(", ") || "none"}`);
1753
+ console.log(` Agent Providers: ${configuredProviderNames.filter(prov => prov !== orchestratorProvider).join(", ") || "none"}`);
1095
1754
  console.log(` Roles: ${Object.keys(roles).join(", ")}`);
1755
+ console.log(` Failover: ${failoverConfig.enabled ? color.green("enabled") : color.dim("disabled")}`);
1096
1756
  console.log();
1097
1757
  console.log(` Config: ${color.dim(getUserConfigPath())}`);
1098
1758
  console.log();
@@ -1117,6 +1777,20 @@ async function configureProviderWithAPIKey(providerType) {
1117
1777
  return configureDeepSeek();
1118
1778
  case "zai":
1119
1779
  return configureZaiAPI();
1780
+ case "kimi":
1781
+ return configureKimiAPI();
1782
+ case "kimi-code":
1783
+ return configureKimiCodeAPI();
1784
+ case "perplexity":
1785
+ return configurePerplexityAPI();
1786
+ case "openrouter":
1787
+ return configureOpenRouterAPI();
1788
+ case "groq":
1789
+ return configureGroqAPI();
1790
+ case "together":
1791
+ return configureTogetherAPI();
1792
+ case "fireworks":
1793
+ return configureFireworksAPI();
1120
1794
  case "ollama":
1121
1795
  return configureOllama();
1122
1796
  default:
@@ -1284,6 +1958,298 @@ async function configureZaiAPI() {
1284
1958
  default_model: model,
1285
1959
  };
1286
1960
  }
1961
+ /**
1962
+ * Configure Moonshot (Kimi) with API key (no subscription option)
1963
+ */
1964
+ async function configureKimiAPI() {
1965
+ p.note("Kimi K2.5 excels at coding with 1M context window.\n" +
1966
+ "Get your API key from:\n" +
1967
+ color.cyan("https://platform.moonshot.cn/console/api-keys"), "Moonshot Kimi API Setup");
1968
+ const apiKey = await p.password({
1969
+ message: "Enter your Moonshot API key:",
1970
+ validate: (value) => validateApiKey(value, "kimi"),
1971
+ });
1972
+ if (p.isCancel(apiKey))
1973
+ return null;
1974
+ const result = await testConnectionWithSpinner("Moonshot", () => testKimiConnection(apiKey));
1975
+ if (!result.success) {
1976
+ const continueAnyway = await p.confirm({
1977
+ message: `Connection failed: ${result.error}. Continue anyway?`,
1978
+ initialValue: false,
1979
+ });
1980
+ if (p.isCancel(continueAnyway) || !continueAnyway)
1981
+ return null;
1982
+ }
1983
+ const model = await p.select({
1984
+ message: "Select default Kimi model:",
1985
+ options: KIMI_MODELS.map((m) => ({
1986
+ value: m.value,
1987
+ label: m.label,
1988
+ ...(m.hint ? { hint: m.hint } : {}),
1989
+ })),
1990
+ });
1991
+ if (p.isCancel(model))
1992
+ return null;
1993
+ // Store API key for later shell profile export
1994
+ configuredEnvVars.set("KIMI_API_KEY", apiKey);
1995
+ return {
1996
+ access_mode: "api",
1997
+ api_key: `\${KIMI_API_KEY}`,
1998
+ base_url: "https://api.moonshot.ai/v1",
1999
+ default_model: model,
2000
+ };
2001
+ }
2002
+ /**
2003
+ * Configure Kimi Code with API key (no subscription option)
2004
+ */
2005
+ async function configureKimiCodeAPI() {
2006
+ p.note("Kimi Code is a dedicated coding endpoint.\n" +
2007
+ color.bold("Uses your Kimi subscription - no separate API credits!") + "\n\n" +
2008
+ "Uses your existing Kimi/Moonshot API key.\n" +
2009
+ "Get your API key from:\n" +
2010
+ color.cyan("https://platform.moonshot.cn/console/api-keys"), "Kimi Code API Setup");
2011
+ const apiKey = await p.password({
2012
+ message: "Enter your Kimi API key:",
2013
+ validate: (value) => validateApiKey(value, "kimi-code"),
2014
+ });
2015
+ if (p.isCancel(apiKey))
2016
+ return null;
2017
+ const result = await testConnectionWithSpinner("Kimi Code", () => testKimiCodeConnection(apiKey));
2018
+ if (!result.success) {
2019
+ const continueAnyway = await p.confirm({
2020
+ message: `Connection failed: ${result.error}. Continue anyway?`,
2021
+ initialValue: false,
2022
+ });
2023
+ if (p.isCancel(continueAnyway) || !continueAnyway)
2024
+ return null;
2025
+ }
2026
+ const model = await p.select({
2027
+ message: "Select default Kimi Code model:",
2028
+ options: KIMI_CODE_MODELS.map((m) => ({
2029
+ value: m.value,
2030
+ label: m.label,
2031
+ ...(m.hint ? { hint: m.hint } : {}),
2032
+ })),
2033
+ });
2034
+ if (p.isCancel(model))
2035
+ return null;
2036
+ // Store API key for later shell profile export (uses same key as regular Kimi)
2037
+ configuredEnvVars.set("KIMI_API_KEY", apiKey);
2038
+ return {
2039
+ access_mode: "api",
2040
+ api_key: `\${KIMI_API_KEY}`,
2041
+ base_url: "https://api.kimi.com/coding/v1",
2042
+ default_model: model,
2043
+ };
2044
+ }
2045
+ /**
2046
+ * Configure Perplexity with API key (no subscription option)
2047
+ */
2048
+ async function configurePerplexityAPI() {
2049
+ p.note("Perplexity provides web search grounded responses with citations.\n" +
2050
+ "Get your API key from:\n" +
2051
+ color.cyan("https://www.perplexity.ai/settings/api"), "Perplexity API Setup");
2052
+ const apiKey = await p.password({
2053
+ message: "Enter your Perplexity API key:",
2054
+ validate: (value) => validateApiKey(value, "perplexity"),
2055
+ });
2056
+ if (p.isCancel(apiKey))
2057
+ return null;
2058
+ const result = await testConnectionWithSpinner("Perplexity", () => testPerplexityConnection(apiKey));
2059
+ if (!result.success) {
2060
+ const continueAnyway = await p.confirm({
2061
+ message: `Connection failed: ${result.error}. Continue anyway?`,
2062
+ initialValue: false,
2063
+ });
2064
+ if (p.isCancel(continueAnyway) || !continueAnyway)
2065
+ return null;
2066
+ }
2067
+ const model = await p.select({
2068
+ message: "Select default Perplexity model:",
2069
+ options: PERPLEXITY_MODELS.map((m) => ({
2070
+ value: m.value,
2071
+ label: m.label,
2072
+ ...(m.hint ? { hint: m.hint } : {}),
2073
+ })),
2074
+ });
2075
+ if (p.isCancel(model))
2076
+ return null;
2077
+ // Store API key for later shell profile export
2078
+ configuredEnvVars.set("PERPLEXITY_API_KEY", apiKey);
2079
+ return {
2080
+ access_mode: "api",
2081
+ api_key: `\${PERPLEXITY_API_KEY}`,
2082
+ base_url: "https://api.perplexity.ai",
2083
+ default_model: model,
2084
+ };
2085
+ }
2086
+ /**
2087
+ * Configure OpenRouter with API key (no subscription option)
2088
+ */
2089
+ async function configureOpenRouterAPI() {
2090
+ p.note("OpenRouter provides access to 300+ models via a single API.\n" +
2091
+ "Get your API key from:\n" +
2092
+ color.cyan("https://openrouter.ai/keys"), "OpenRouter API Setup");
2093
+ const apiKey = await p.password({
2094
+ message: "Enter your OpenRouter API key:",
2095
+ validate: (value) => validateApiKey(value, "openrouter"),
2096
+ });
2097
+ if (p.isCancel(apiKey))
2098
+ return null;
2099
+ const result = await testConnectionWithSpinner("OpenRouter", () => testOpenRouterConnection(apiKey));
2100
+ if (!result.success) {
2101
+ const continueAnyway = await p.confirm({
2102
+ message: `Connection failed: ${result.error}. Continue anyway?`,
2103
+ initialValue: false,
2104
+ });
2105
+ if (p.isCancel(continueAnyway) || !continueAnyway)
2106
+ return null;
2107
+ }
2108
+ const model = await p.select({
2109
+ message: "Select default OpenRouter model:",
2110
+ options: OPENROUTER_MODELS.map((m) => ({
2111
+ value: m.value,
2112
+ label: m.label,
2113
+ ...(m.hint ? { hint: m.hint } : {}),
2114
+ })),
2115
+ });
2116
+ if (p.isCancel(model))
2117
+ return null;
2118
+ // Store API key for later shell profile export
2119
+ configuredEnvVars.set("OPENROUTER_API_KEY", apiKey);
2120
+ return {
2121
+ access_mode: "api",
2122
+ api_key: `\${OPENROUTER_API_KEY}`,
2123
+ base_url: "https://openrouter.ai/api/v1",
2124
+ default_model: model,
2125
+ };
2126
+ }
2127
+ /**
2128
+ * Configure Groq with API key (no subscription option)
2129
+ */
2130
+ async function configureGroqAPI() {
2131
+ p.note("Groq provides ultra-fast LPU inference - up to 18x faster than GPU.\n" +
2132
+ color.bold("Free tier available!") + "\n" +
2133
+ "Get your API key from:\n" +
2134
+ color.cyan("https://console.groq.com/keys"), "Groq API Setup");
2135
+ const apiKey = await p.password({
2136
+ message: "Enter your Groq API key:",
2137
+ validate: (value) => validateApiKey(value, "groq"),
2138
+ });
2139
+ if (p.isCancel(apiKey))
2140
+ return null;
2141
+ const result = await testConnectionWithSpinner("Groq", () => testGroqConnection(apiKey));
2142
+ if (!result.success) {
2143
+ const continueAnyway = await p.confirm({
2144
+ message: `Connection failed: ${result.error}. Continue anyway?`,
2145
+ initialValue: false,
2146
+ });
2147
+ if (p.isCancel(continueAnyway) || !continueAnyway)
2148
+ return null;
2149
+ }
2150
+ const model = await p.select({
2151
+ message: "Select default Groq model:",
2152
+ options: GROQ_MODELS.map((m) => ({
2153
+ value: m.value,
2154
+ label: m.label,
2155
+ ...(m.hint ? { hint: m.hint } : {}),
2156
+ })),
2157
+ });
2158
+ if (p.isCancel(model))
2159
+ return null;
2160
+ // Store API key for later shell profile export
2161
+ configuredEnvVars.set("GROQ_API_KEY", apiKey);
2162
+ return {
2163
+ access_mode: "api",
2164
+ api_key: `\${GROQ_API_KEY}`,
2165
+ base_url: "https://api.groq.com/openai/v1",
2166
+ default_model: model,
2167
+ };
2168
+ }
2169
+ /**
2170
+ * Configure Together AI with API key (no subscription option)
2171
+ */
2172
+ async function configureTogetherAPI() {
2173
+ p.note("Together AI provides 200+ open models with sub-100ms latency.\n" +
2174
+ color.bold("$1 free credit to start!") + "\n" +
2175
+ "Get your API key from:\n" +
2176
+ color.cyan("https://api.together.xyz/settings/api-keys"), "Together AI API Setup");
2177
+ const apiKey = await p.password({
2178
+ message: "Enter your Together AI API key:",
2179
+ validate: (value) => validateApiKey(value, "together"),
2180
+ });
2181
+ if (p.isCancel(apiKey))
2182
+ return null;
2183
+ const result = await testConnectionWithSpinner("Together AI", () => testTogetherConnection(apiKey));
2184
+ if (!result.success) {
2185
+ const continueAnyway = await p.confirm({
2186
+ message: `Connection failed: ${result.error}. Continue anyway?`,
2187
+ initialValue: false,
2188
+ });
2189
+ if (p.isCancel(continueAnyway) || !continueAnyway)
2190
+ return null;
2191
+ }
2192
+ const model = await p.select({
2193
+ message: "Select default Together AI model:",
2194
+ options: TOGETHER_MODELS.map((m) => ({
2195
+ value: m.value,
2196
+ label: m.label,
2197
+ ...(m.hint ? { hint: m.hint } : {}),
2198
+ })),
2199
+ });
2200
+ if (p.isCancel(model))
2201
+ return null;
2202
+ // Store API key for later shell profile export
2203
+ configuredEnvVars.set("TOGETHER_API_KEY", apiKey);
2204
+ return {
2205
+ access_mode: "api",
2206
+ api_key: `\${TOGETHER_API_KEY}`,
2207
+ base_url: "https://api.together.xyz/v1",
2208
+ default_model: model,
2209
+ };
2210
+ }
2211
+ /**
2212
+ * Configure Fireworks AI with API key (no subscription option)
2213
+ */
2214
+ async function configureFireworksAPI() {
2215
+ p.note("Fireworks AI provides fast inference for DeepSeek, Llama, and other models.\n" +
2216
+ color.bold("$1 free credit to start!") + "\n" +
2217
+ "Get your API key from:\n" +
2218
+ color.cyan("https://fireworks.ai/account/api-keys"), "Fireworks AI API Setup");
2219
+ const apiKey = await p.password({
2220
+ message: "Enter your Fireworks AI API key:",
2221
+ validate: (value) => validateApiKey(value, "fireworks"),
2222
+ });
2223
+ if (p.isCancel(apiKey))
2224
+ return null;
2225
+ const result = await testConnectionWithSpinner("Fireworks AI", () => testFireworksConnection(apiKey));
2226
+ if (!result.success) {
2227
+ const continueAnyway = await p.confirm({
2228
+ message: `Connection failed: ${result.error}. Continue anyway?`,
2229
+ initialValue: false,
2230
+ });
2231
+ if (p.isCancel(continueAnyway) || !continueAnyway)
2232
+ return null;
2233
+ }
2234
+ const model = await p.select({
2235
+ message: "Select default Fireworks AI model:",
2236
+ options: FIREWORKS_MODELS.map((m) => ({
2237
+ value: m.value,
2238
+ label: m.label,
2239
+ ...(m.hint ? { hint: m.hint } : {}),
2240
+ })),
2241
+ });
2242
+ if (p.isCancel(model))
2243
+ return null;
2244
+ // Store API key for later shell profile export
2245
+ configuredEnvVars.set("FIREWORKS_API_KEY", apiKey);
2246
+ return {
2247
+ access_mode: "api",
2248
+ api_key: `\${FIREWORKS_API_KEY}`,
2249
+ base_url: "https://api.fireworks.ai/inference/v1",
2250
+ default_model: model,
2251
+ };
2252
+ }
1287
2253
  // ============================================================================
1288
2254
  // Provider Management Functions (for CLI subcommands)
1289
2255
  // ============================================================================
@@ -1337,6 +2303,27 @@ export async function addProvider(providerName) {
1337
2303
  case "zai":
1338
2304
  config = await configureZai();
1339
2305
  break;
2306
+ case "kimi":
2307
+ config = await configureKimi();
2308
+ break;
2309
+ case "kimi-code":
2310
+ config = await configureKimiCode();
2311
+ break;
2312
+ case "perplexity":
2313
+ config = await configurePerplexity();
2314
+ break;
2315
+ case "openrouter":
2316
+ config = await configureOpenRouter();
2317
+ break;
2318
+ case "groq":
2319
+ config = await configureGroq();
2320
+ break;
2321
+ case "together":
2322
+ config = await configureTogether();
2323
+ break;
2324
+ case "fireworks":
2325
+ config = await configureFireworks();
2326
+ break;
1340
2327
  case "ollama":
1341
2328
  config = await configureOllama();
1342
2329
  break;
@@ -1356,7 +2343,7 @@ export async function testProvider(providerName) {
1356
2343
  const configPath = getUserConfigPath();
1357
2344
  if (!fs.existsSync(configPath)) {
1358
2345
  p.log.error("No configuration file found.");
1359
- p.log.info("Run 'agent-router setup' first to configure providers.");
2346
+ p.log.info("Run 'agent-foundry setup' first to configure providers.");
1360
2347
  process.exit(1);
1361
2348
  }
1362
2349
  // For now, just test based on provider name
@@ -1383,7 +2370,7 @@ export async function listProviders() {
1383
2370
  const configPath = getUserConfigPath();
1384
2371
  if (!fs.existsSync(configPath)) {
1385
2372
  console.log("No configuration file found.");
1386
- console.log("Run 'agent-router setup' to configure providers.");
2373
+ console.log("Run 'agent-foundry setup' to configure providers.");
1387
2374
  return;
1388
2375
  }
1389
2376
  try {
@@ -1392,7 +2379,7 @@ export async function listProviders() {
1392
2379
  const config = parse(configContent);
1393
2380
  if (!config.providers || Object.keys(config.providers).length === 0) {
1394
2381
  console.log("No providers configured.");
1395
- console.log("Run 'agent-router setup' to add providers.");
2382
+ console.log("Run 'agent-foundry setup' to add providers.");
1396
2383
  return;
1397
2384
  }
1398
2385
  console.log();