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