contextos-mcp 0.4.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 (223) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +260 -0
  3. package/bin/mcp.mjs +61 -0
  4. package/bin/swarm.mjs +45 -0
  5. package/dist/agents/aider.d.ts +12 -0
  6. package/dist/agents/aider.js +192 -0
  7. package/dist/agents/aider.js.map +1 -0
  8. package/dist/agents/claude-code.d.ts +9 -0
  9. package/dist/agents/claude-code.js +236 -0
  10. package/dist/agents/claude-code.js.map +1 -0
  11. package/dist/agents/codex.d.ts +14 -0
  12. package/dist/agents/codex.js +203 -0
  13. package/dist/agents/codex.js.map +1 -0
  14. package/dist/agents/direct-llm.d.ts +22 -0
  15. package/dist/agents/direct-llm.js +254 -0
  16. package/dist/agents/direct-llm.js.map +1 -0
  17. package/dist/agents/mock.d.ts +10 -0
  18. package/dist/agents/mock.js +170 -0
  19. package/dist/agents/mock.js.map +1 -0
  20. package/dist/agents/opencode.d.ts +23 -0
  21. package/dist/agents/opencode.js +631 -0
  22. package/dist/agents/opencode.js.map +1 -0
  23. package/dist/agents/provider.d.ts +11 -0
  24. package/dist/agents/provider.js +31 -0
  25. package/dist/agents/provider.js.map +1 -0
  26. package/dist/compression/compressor.d.ts +28 -0
  27. package/dist/compression/compressor.js +268 -0
  28. package/dist/compression/compressor.js.map +1 -0
  29. package/dist/config.d.ts +43 -0
  30. package/dist/config.js +172 -0
  31. package/dist/config.js.map +1 -0
  32. package/dist/contextos/loader.d.ts +46 -0
  33. package/dist/contextos/loader.js +120 -0
  34. package/dist/contextos/loader.js.map +1 -0
  35. package/dist/contextos/selector.d.ts +46 -0
  36. package/dist/contextos/selector.js +80 -0
  37. package/dist/contextos/selector.js.map +1 -0
  38. package/dist/core/action-dispatcher.d.ts +15 -0
  39. package/dist/core/action-dispatcher.js +28 -0
  40. package/dist/core/action-dispatcher.js.map +1 -0
  41. package/dist/core/action-schema.d.ts +72 -0
  42. package/dist/core/action-schema.js +86 -0
  43. package/dist/core/action-schema.js.map +1 -0
  44. package/dist/core/repl-interface.d.ts +48 -0
  45. package/dist/core/repl-interface.js +5 -0
  46. package/dist/core/repl-interface.js.map +1 -0
  47. package/dist/core/repl.d.ts +59 -0
  48. package/dist/core/repl.js +353 -0
  49. package/dist/core/repl.js.map +1 -0
  50. package/dist/core/rlm.d.ts +65 -0
  51. package/dist/core/rlm.js +564 -0
  52. package/dist/core/rlm.js.map +1 -0
  53. package/dist/core/runtime.py +470 -0
  54. package/dist/core/types.d.ts +265 -0
  55. package/dist/core/types.js +19 -0
  56. package/dist/core/types.js.map +1 -0
  57. package/dist/env.d.ts +10 -0
  58. package/dist/env.js +75 -0
  59. package/dist/env.js.map +1 -0
  60. package/dist/hooks/runner.d.ts +43 -0
  61. package/dist/hooks/runner.js +173 -0
  62. package/dist/hooks/runner.js.map +1 -0
  63. package/dist/labs/cli.d.ts +15 -0
  64. package/dist/labs/cli.js +295 -0
  65. package/dist/labs/cli.js.map +1 -0
  66. package/dist/labs/episodic-memory.d.ts +132 -0
  67. package/dist/labs/episodic-memory.js +391 -0
  68. package/dist/labs/episodic-memory.js.map +1 -0
  69. package/dist/labs/interactive-swarm.d.ts +20 -0
  70. package/dist/labs/interactive-swarm.js +1173 -0
  71. package/dist/labs/interactive-swarm.js.map +1 -0
  72. package/dist/labs/interactive.d.ts +10 -0
  73. package/dist/labs/interactive.js +2099 -0
  74. package/dist/labs/interactive.js.map +1 -0
  75. package/dist/labs/ts-analyzer.d.ts +74 -0
  76. package/dist/labs/ts-analyzer.js +379 -0
  77. package/dist/labs/ts-analyzer.js.map +1 -0
  78. package/dist/labs/viewer.d.ts +12 -0
  79. package/dist/labs/viewer.js +1284 -0
  80. package/dist/labs/viewer.js.map +1 -0
  81. package/dist/main.d.ts +15 -0
  82. package/dist/main.js +247 -0
  83. package/dist/main.js.map +1 -0
  84. package/dist/mcp/server.d.ts +16 -0
  85. package/dist/mcp/server.js +192775 -0
  86. package/dist/mcp/server.js.map +1 -0
  87. package/dist/mcp/session.d.ts +117 -0
  88. package/dist/mcp/session.js +450 -0
  89. package/dist/mcp/session.js.map +1 -0
  90. package/dist/mcp/state.d.ts +42 -0
  91. package/dist/mcp/state.js +537 -0
  92. package/dist/mcp/state.js.map +1 -0
  93. package/dist/mcp/tools/contextos.d.ts +17 -0
  94. package/dist/mcp/tools/contextos.js +583 -0
  95. package/dist/mcp/tools/contextos.js.map +1 -0
  96. package/dist/mcp/tools.d.ts +15 -0
  97. package/dist/mcp/tools.js +393 -0
  98. package/dist/mcp/tools.js.map +1 -0
  99. package/dist/orchestration/reviewer-gate.d.ts +43 -0
  100. package/dist/orchestration/reviewer-gate.js +280 -0
  101. package/dist/orchestration/reviewer-gate.js.map +1 -0
  102. package/dist/orchestration/reviewer-prompt.d.ts +18 -0
  103. package/dist/orchestration/reviewer-prompt.js +67 -0
  104. package/dist/orchestration/reviewer-prompt.js.map +1 -0
  105. package/dist/orchestration/verification-runner.d.ts +53 -0
  106. package/dist/orchestration/verification-runner.js +352 -0
  107. package/dist/orchestration/verification-runner.js.map +1 -0
  108. package/dist/plugins/adapter.d.ts +11 -0
  109. package/dist/plugins/adapter.js +11 -0
  110. package/dist/plugins/adapter.js.map +1 -0
  111. package/dist/plugins/core/archive.d.ts +9 -0
  112. package/dist/plugins/core/archive.js +128 -0
  113. package/dist/plugins/core/archive.js.map +1 -0
  114. package/dist/plugins/core/digest.d.ts +11 -0
  115. package/dist/plugins/core/digest.js +70 -0
  116. package/dist/plugins/core/digest.js.map +1 -0
  117. package/dist/plugins/core/grants.d.ts +22 -0
  118. package/dist/plugins/core/grants.js +90 -0
  119. package/dist/plugins/core/grants.js.map +1 -0
  120. package/dist/plugins/core/index.d.ts +10 -0
  121. package/dist/plugins/core/index.js +11 -0
  122. package/dist/plugins/core/index.js.map +1 -0
  123. package/dist/plugins/core/pinning.d.ts +15 -0
  124. package/dist/plugins/core/pinning.js +86 -0
  125. package/dist/plugins/core/pinning.js.map +1 -0
  126. package/dist/plugins/core/types.d.ts +105 -0
  127. package/dist/plugins/core/types.js +22 -0
  128. package/dist/plugins/core/types.js.map +1 -0
  129. package/dist/plugins/core/updater.d.ts +16 -0
  130. package/dist/plugins/core/updater.js +64 -0
  131. package/dist/plugins/core/updater.js.map +1 -0
  132. package/dist/plugins/loader.d.ts +65 -0
  133. package/dist/plugins/loader.js +110 -0
  134. package/dist/plugins/loader.js.map +1 -0
  135. package/dist/prompts/orchestrator.d.ts +6 -0
  136. package/dist/prompts/orchestrator.js +114 -0
  137. package/dist/prompts/orchestrator.js.map +1 -0
  138. package/dist/routing/model-resolver.d.ts +27 -0
  139. package/dist/routing/model-resolver.js +265 -0
  140. package/dist/routing/model-resolver.js.map +1 -0
  141. package/dist/routing/model-router.d.ts +130 -0
  142. package/dist/routing/model-router.js +515 -0
  143. package/dist/routing/model-router.js.map +1 -0
  144. package/dist/runtime/attestations.cjs +260 -0
  145. package/dist/runtime/reviewer-pipeline.cjs +319 -0
  146. package/dist/runtime/sandbox.cjs +468 -0
  147. package/dist/runtime/state-machine.cjs +885 -0
  148. package/dist/runtime/thread-store.cjs +175 -0
  149. package/dist/runtime/transactional-git.cjs +315 -0
  150. package/dist/runtime/verification-pipeline.cjs +435 -0
  151. package/dist/sandbox/execution.d.ts +165 -0
  152. package/dist/sandbox/execution.js +497 -0
  153. package/dist/sandbox/execution.js.map +1 -0
  154. package/dist/security/file-policy.d.ts +22 -0
  155. package/dist/security/file-policy.js +53 -0
  156. package/dist/security/file-policy.js.map +1 -0
  157. package/dist/security/repository-boundary.d.ts +21 -0
  158. package/dist/security/repository-boundary.js +119 -0
  159. package/dist/security/repository-boundary.js.map +1 -0
  160. package/dist/security/secret-filter.d.ts +36 -0
  161. package/dist/security/secret-filter.js +149 -0
  162. package/dist/security/secret-filter.js.map +1 -0
  163. package/dist/swarm.d.ts +14 -0
  164. package/dist/swarm.js +624 -0
  165. package/dist/swarm.js.map +1 -0
  166. package/dist/threads/cache.d.ts +64 -0
  167. package/dist/threads/cache.js +241 -0
  168. package/dist/threads/cache.js.map +1 -0
  169. package/dist/threads/manager.d.ts +94 -0
  170. package/dist/threads/manager.js +914 -0
  171. package/dist/threads/manager.js.map +1 -0
  172. package/dist/ui/banner.d.ts +14 -0
  173. package/dist/ui/banner.js +42 -0
  174. package/dist/ui/banner.js.map +1 -0
  175. package/dist/ui/dashboard.d.ts +33 -0
  176. package/dist/ui/dashboard.js +135 -0
  177. package/dist/ui/dashboard.js.map +1 -0
  178. package/dist/ui/index.d.ts +11 -0
  179. package/dist/ui/index.js +12 -0
  180. package/dist/ui/index.js.map +1 -0
  181. package/dist/ui/log.d.ts +39 -0
  182. package/dist/ui/log.js +126 -0
  183. package/dist/ui/log.js.map +1 -0
  184. package/dist/ui/onboarding.d.ts +14 -0
  185. package/dist/ui/onboarding.js +755 -0
  186. package/dist/ui/onboarding.js.map +1 -0
  187. package/dist/ui/run-log.d.ts +50 -0
  188. package/dist/ui/run-log.js +60 -0
  189. package/dist/ui/run-log.js.map +1 -0
  190. package/dist/ui/spinner.d.ts +41 -0
  191. package/dist/ui/spinner.js +172 -0
  192. package/dist/ui/spinner.js.map +1 -0
  193. package/dist/ui/streaming-feed.d.ts +72 -0
  194. package/dist/ui/streaming-feed.js +386 -0
  195. package/dist/ui/streaming-feed.js.map +1 -0
  196. package/dist/ui/summary.d.ts +18 -0
  197. package/dist/ui/summary.js +113 -0
  198. package/dist/ui/summary.js.map +1 -0
  199. package/dist/ui/text-input.d.ts +19 -0
  200. package/dist/ui/text-input.js +266 -0
  201. package/dist/ui/text-input.js.map +1 -0
  202. package/dist/ui/theme.d.ts +63 -0
  203. package/dist/ui/theme.js +97 -0
  204. package/dist/ui/theme.js.map +1 -0
  205. package/dist/utils/command-exists.d.ts +11 -0
  206. package/dist/utils/command-exists.js +41 -0
  207. package/dist/utils/command-exists.js.map +1 -0
  208. package/dist/utils/process-security.d.ts +19 -0
  209. package/dist/utils/process-security.js +90 -0
  210. package/dist/utils/process-security.js.map +1 -0
  211. package/dist/utils/rolling-buffer.d.ts +16 -0
  212. package/dist/utils/rolling-buffer.js +30 -0
  213. package/dist/utils/rolling-buffer.js.map +1 -0
  214. package/dist/utils/safe-spawn.d.ts +13 -0
  215. package/dist/utils/safe-spawn.js +49 -0
  216. package/dist/utils/safe-spawn.js.map +1 -0
  217. package/dist/worktree/manager.d.ts +69 -0
  218. package/dist/worktree/manager.js +593 -0
  219. package/dist/worktree/manager.js.map +1 -0
  220. package/dist/worktree/merge.d.ts +58 -0
  221. package/dist/worktree/merge.js +389 -0
  222. package/dist/worktree/merge.js.map +1 -0
  223. package/package.json +63 -0
@@ -0,0 +1,755 @@
1
+ /**
2
+ * First-run onboarding — welcome screen + interactive agent setup wizard.
3
+ *
4
+ * Two-column layout: left has greeting + swarm mesh art + version info,
5
+ * right has tips + environment checks. Then an interactive wizard to:
6
+ * 1. Pick default coding agent from detected backends
7
+ * 2. Configure API keys for required providers
8
+ * 3. Choose default model
9
+ * 4. Save preferences to ~/.swarm/config.yaml
10
+ *
11
+ * Triggered once on first `swarm --dir` invocation. Saves ~/.swarm/.initialized marker.
12
+ */
13
+ import { execFileSync, execSync, spawn, spawn as spawnChild } from "node:child_process";
14
+ import * as fs from "node:fs";
15
+ import * as http from "node:http";
16
+ import * as os from "node:os";
17
+ import * as path from "node:path";
18
+ import * as readline from "node:readline";
19
+ import { fileURLToPath } from "node:url";
20
+ import { commandExists } from "../utils/command-exists.js";
21
+ import { getLogLevel, isJsonMode } from "./log.js";
22
+ import { bold, coral, cyan, dim, green, isTTY, red, stripAnsi, symbols, termWidth, yellow } from "./theme.js";
23
+ // ── Constants ─────────────────────────────────────────────────────────────────
24
+ const SWARM_DIR = path.join(os.homedir(), ".swarm");
25
+ const MARKER_FILE = path.join(SWARM_DIR, ".initialized");
26
+ const CRED_FILE = path.join(SWARM_DIR, "credentials");
27
+ const USER_CONFIG_FILE = path.join(SWARM_DIR, "config.yaml");
28
+ // Read version from package.json instead of hardcoding
29
+ function getVersion() {
30
+ try {
31
+ const __dir = path.dirname(fileURLToPath(import.meta.url));
32
+ const pkgPath = path.join(__dir, "..", "..", "package.json");
33
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
34
+ return pkg.version || "0.0.0";
35
+ }
36
+ catch {
37
+ return "0.0.0";
38
+ }
39
+ }
40
+ const VERSION = getVersion();
41
+ /** Which API key each agent backend requires (or supports). */
42
+ const AGENT_PROVIDERS = {
43
+ opencode: {
44
+ required: ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY"],
45
+ description: "Supports all providers (Anthropic, OpenAI, Google)",
46
+ install: "npm i -g opencode",
47
+ },
48
+ "claude-code": {
49
+ required: ["ANTHROPIC_API_KEY"],
50
+ description: "Anthropic's Claude Code CLI",
51
+ install: "npm i -g @anthropic-ai/claude-code",
52
+ },
53
+ codex: {
54
+ required: ["OPENAI_API_KEY"],
55
+ description: "OpenAI's Codex CLI",
56
+ install: "npm i -g @openai/codex",
57
+ },
58
+ aider: {
59
+ required: ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
60
+ description: "Git-aware AI pair programmer",
61
+ install: "pip install aider-chat",
62
+ },
63
+ "direct-llm": {
64
+ required: ["ANTHROPIC_API_KEY"],
65
+ description: "Direct LLM calls (no coding agent)",
66
+ install: "(built-in)",
67
+ },
68
+ };
69
+ /** Provider display info. */
70
+ const PROVIDERS = [
71
+ { name: "Anthropic", envVar: "ANTHROPIC_API_KEY", prefix: "sk-ant-", modelDefault: "anthropic/claude-sonnet-4-6" },
72
+ { name: "OpenAI", envVar: "OPENAI_API_KEY", prefix: "sk-", modelDefault: "openai/gpt-4o" },
73
+ { name: "Google", envVar: "GEMINI_API_KEY", prefix: "AI", modelDefault: "google/gemini-2.5-flash" },
74
+ ];
75
+ // ── Marker ────────────────────────────────────────────────────────────────────
76
+ export function isFirstRun() {
77
+ return !fs.existsSync(MARKER_FILE);
78
+ }
79
+ function markInitialized() {
80
+ try {
81
+ fs.mkdirSync(SWARM_DIR, { recursive: true });
82
+ fs.writeFileSync(MARKER_FILE, `${new Date().toISOString()}\n`, "utf-8");
83
+ }
84
+ catch {
85
+ // Non-fatal — onboarding will just run again next time
86
+ }
87
+ }
88
+ // commandExists imported from ../utils/command-exists.js (cross-platform: uses where.exe on Windows)
89
+ async function gitVersion() {
90
+ return new Promise((resolve) => {
91
+ const proc = spawn("git", ["--version"], { stdio: "pipe" });
92
+ let out = "";
93
+ proc.stdout?.on("data", (d) => {
94
+ out += d.toString();
95
+ });
96
+ proc.on("close", (code) => {
97
+ if (code === 0) {
98
+ const match = out.match(/git version (\S+)/);
99
+ resolve(match ? match[1] : "unknown");
100
+ }
101
+ else {
102
+ resolve(null);
103
+ }
104
+ });
105
+ proc.on("error", () => resolve(null));
106
+ });
107
+ }
108
+ function detectApiKeys() {
109
+ const keys = new Map();
110
+ for (const p of PROVIDERS) {
111
+ const val = process.env[p.envVar];
112
+ if (val && val.length > 8) {
113
+ keys.set(p.envVar, val);
114
+ }
115
+ }
116
+ return keys;
117
+ }
118
+ function maskKey(val) {
119
+ if (val.length <= 12)
120
+ return "***";
121
+ return `${val.slice(0, 7)}...${val.slice(-4)}`;
122
+ }
123
+ async function checkAgentBackends() {
124
+ const agents = [
125
+ ["opencode", "opencode"],
126
+ ["claude-code", "claude"],
127
+ ["codex", "codex"],
128
+ ["aider", "aider"],
129
+ ];
130
+ const results = [];
131
+ for (const [name, cmd] of agents) {
132
+ const exists = await commandExists(cmd);
133
+ results.push({ name, ok: exists, detail: exists ? "installed" : "not found" });
134
+ }
135
+ // direct-llm is always available
136
+ results.push({ name: "direct-llm", ok: true, detail: "built-in" });
137
+ return results;
138
+ }
139
+ // ── Ollama helpers ────────────────────────────────────────────────────────────
140
+ function isOllamaInstalled() {
141
+ try {
142
+ execFileSync("ollama", ["--version"], { stdio: ["ignore", "pipe", "pipe"], timeout: 5000 });
143
+ return true;
144
+ }
145
+ catch {
146
+ return false;
147
+ }
148
+ }
149
+ function isOllamaModelAvailable(model) {
150
+ try {
151
+ const output = execFileSync("ollama", ["list"], {
152
+ encoding: "utf-8",
153
+ stdio: ["ignore", "pipe", "pipe"],
154
+ timeout: 10000,
155
+ });
156
+ return output.includes(model);
157
+ }
158
+ catch {
159
+ return false;
160
+ }
161
+ }
162
+ async function installOllama() {
163
+ process.stderr.write(`\n ${bold("Installing Ollama...")}\n\n`);
164
+ if (process.platform === "darwin") {
165
+ try {
166
+ execFileSync("brew", ["--version"], { stdio: "ignore", timeout: 5000 });
167
+ process.stderr.write(` ${dim("Using Homebrew...")}\n`);
168
+ try {
169
+ execSync("brew install ollama", { stdio: "inherit", timeout: 120000 });
170
+ return true;
171
+ }
172
+ catch {
173
+ process.stderr.write(` ${dim("Homebrew install failed, trying curl installer...")}\n`);
174
+ }
175
+ }
176
+ catch {
177
+ // No brew — fall through
178
+ }
179
+ }
180
+ // Direct user to official install page (avoiding unverified pipe-to-shell)
181
+ process.stderr.write(` ${dim("Please install Ollama from official site: https://ollama.com/download")}\n`);
182
+ return false;
183
+ }
184
+ function isOllamaServing() {
185
+ return new Promise((resolve) => {
186
+ try {
187
+ const req = http.get("http://127.0.0.1:11434/api/tags", { timeout: 3000 }, (res) => {
188
+ res.resume();
189
+ resolve(res.statusCode === 200);
190
+ });
191
+ req.on("error", () => resolve(false));
192
+ req.on("timeout", () => {
193
+ req.destroy();
194
+ resolve(false);
195
+ });
196
+ }
197
+ catch {
198
+ resolve(false);
199
+ }
200
+ });
201
+ }
202
+ function startOllamaServe() {
203
+ const child = spawnChild("ollama", ["serve"], { stdio: "ignore", detached: true });
204
+ child.unref();
205
+ }
206
+ async function pullOllamaModel(model) {
207
+ process.stderr.write(`\n ${bold(`Pulling ${model}...`)} ${dim("(this may take a few minutes)")}\n\n`);
208
+ return new Promise((resolve) => {
209
+ const child = spawnChild("ollama", ["pull", model], { stdio: "inherit" });
210
+ child.on("close", (code) => resolve(code === 0));
211
+ child.on("error", () => resolve(false));
212
+ });
213
+ }
214
+ async function ensureOllamaSetup(promptFn, model) {
215
+ const shortModel = model.replace("ollama/", "");
216
+ if (!isOllamaInstalled()) {
217
+ process.stderr.write(`\n ${dim("Ollama is not installed. It's needed to run open-source models locally.")}\n`);
218
+ const prompt = promptFn();
219
+ const install = await prompt.ask(` ${coral(symbols.arrow)} Install Ollama now? [Y/n]: `);
220
+ prompt.close();
221
+ if (install.toLowerCase() !== "n" && install.toLowerCase() !== "no") {
222
+ const ok = await installOllama();
223
+ if (!ok) {
224
+ process.stderr.write(`\n ${red("Failed to install Ollama.")}\n`);
225
+ process.stderr.write(` ${dim("Install manually from https://ollama.com/download")}\n\n`);
226
+ return false;
227
+ }
228
+ process.stderr.write(` ${green(symbols.check)} Ollama installed\n`);
229
+ }
230
+ else {
231
+ process.stderr.write(`\n ${dim("Install later from https://ollama.com/download")}\n\n`);
232
+ return false;
233
+ }
234
+ }
235
+ else {
236
+ process.stderr.write(` ${green(symbols.check)} Ollama installed\n`);
237
+ }
238
+ if (!(await isOllamaServing())) {
239
+ process.stderr.write(` ${dim("Starting Ollama server...")}\n`);
240
+ startOllamaServe();
241
+ let retries = 10;
242
+ while (retries > 0 && !(await isOllamaServing())) {
243
+ await new Promise((r) => setTimeout(r, 1000));
244
+ retries--;
245
+ }
246
+ if (await isOllamaServing()) {
247
+ process.stderr.write(` ${green(symbols.check)} Ollama server running\n`);
248
+ }
249
+ else {
250
+ process.stderr.write(` ${yellow(symbols.warn)} Could not start Ollama server. Run ${bold("ollama serve")} manually.\n`);
251
+ return false;
252
+ }
253
+ }
254
+ else {
255
+ process.stderr.write(` ${green(symbols.check)} Ollama server running\n`);
256
+ }
257
+ if (isOllamaModelAvailable(shortModel)) {
258
+ process.stderr.write(` ${green(symbols.check)} Model ${bold(shortModel)} ready\n\n`);
259
+ return true;
260
+ }
261
+ process.stderr.write(` ${dim(`Model ${shortModel} not found locally.`)}\n`);
262
+ const prompt = promptFn();
263
+ const pull = await prompt.ask(`\n ${coral(symbols.arrow)} Pull ${bold(shortModel)} now? [Y/n]: `);
264
+ prompt.close();
265
+ if (pull.toLowerCase() !== "n" && pull.toLowerCase() !== "no") {
266
+ const ok = await pullOllamaModel(shortModel);
267
+ if (!ok) {
268
+ process.stderr.write(`\n ${red(`Failed to pull ${shortModel}.`)}\n`);
269
+ process.stderr.write(` ${dim(`Try manually: ollama pull ${shortModel}`)}\n\n`);
270
+ return false;
271
+ }
272
+ process.stderr.write(` ${green(symbols.check)} Model ${bold(shortModel)} ready\n\n`);
273
+ return true;
274
+ }
275
+ process.stderr.write(`\n ${dim(`Run later: ollama pull ${shortModel}`)}\n\n`);
276
+ return false;
277
+ }
278
+ // ── Interactive helpers ───────────────────────────────────────────────────────
279
+ function createPrompt() {
280
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
281
+ return {
282
+ ask: (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim()))),
283
+ close: () => rl.close(),
284
+ };
285
+ }
286
+ async function readHiddenInput(prompt) {
287
+ return new Promise((resolve) => {
288
+ let input = "";
289
+ const origRawMode = process.stdin.isRaw;
290
+ if (process.stdin.isTTY)
291
+ process.stdin.setRawMode(true);
292
+ process.stdin.resume();
293
+ process.stderr.write(prompt);
294
+ const cleanup = () => {
295
+ process.stdin.removeListener("data", onData);
296
+ if (process.stdin.isTTY && origRawMode !== undefined) {
297
+ process.stdin.setRawMode(origRawMode);
298
+ }
299
+ process.stdin.pause();
300
+ };
301
+ const onData = (buf) => {
302
+ const ch = buf.toString();
303
+ if (ch === "\n" || ch === "\r") {
304
+ process.stderr.write("\n");
305
+ cleanup();
306
+ resolve(input);
307
+ }
308
+ else if (ch === "\x7f" || ch === "\b") {
309
+ if (input.length > 0) {
310
+ input = input.slice(0, -1);
311
+ process.stderr.write("\b \b");
312
+ }
313
+ }
314
+ else if (ch === "\x03") {
315
+ process.stderr.write("\n");
316
+ cleanup();
317
+ resolve("");
318
+ }
319
+ else if (ch >= " ") {
320
+ input += ch;
321
+ process.stderr.write(dim("*"));
322
+ }
323
+ };
324
+ process.stdin.on("data", onData);
325
+ });
326
+ }
327
+ function saveCredential(envVar, key) {
328
+ try {
329
+ fs.mkdirSync(SWARM_DIR, { recursive: true });
330
+ let existing = "";
331
+ if (fs.existsSync(CRED_FILE)) {
332
+ existing = fs.readFileSync(CRED_FILE, "utf-8");
333
+ existing = existing
334
+ .split("\n")
335
+ .filter((l) => !l.startsWith(`${envVar}=`))
336
+ .join("\n");
337
+ if (existing && !existing.endsWith("\n"))
338
+ existing += "\n";
339
+ }
340
+ fs.writeFileSync(CRED_FILE, `${existing}${envVar}=${key}\n`, { mode: 0o600 });
341
+ }
342
+ catch {
343
+ // Fall through — key is still set in process.env
344
+ }
345
+ process.env[envVar] = key;
346
+ }
347
+ function saveUserConfig(agent, model) {
348
+ try {
349
+ fs.mkdirSync(SWARM_DIR, { recursive: true });
350
+ const lines = [
351
+ "# Swarm user preferences (generated by onboarding)",
352
+ `# Created: ${new Date().toISOString()}`,
353
+ "",
354
+ `default_agent: ${agent}`,
355
+ `default_model: ${model}`,
356
+ "",
357
+ ];
358
+ fs.writeFileSync(USER_CONFIG_FILE, lines.join("\n"), "utf-8");
359
+ }
360
+ catch {
361
+ // Non-fatal
362
+ }
363
+ }
364
+ // ── Swarm mesh art ────────────────────────────────────────────────────────────
365
+ const n = (s) => cyan(s); // node
366
+ const c = (s) => dim(s); // connection
367
+ const o = (s) => coral(s); // orchestrator (center)
368
+ function buildSwarmArt() {
369
+ const COLS = 7;
370
+ const CENTER = 3;
371
+ function nodeRow(showCenter) {
372
+ const parts = [];
373
+ for (let i = 0; i < COLS; i++) {
374
+ if (i > 0)
375
+ parts.push(c("\u2500\u2500\u2500"));
376
+ parts.push(showCenter && i === CENTER ? o("\u25C6") : n("\u25CF"));
377
+ }
378
+ return parts.join("");
379
+ }
380
+ function diagRow(startBackslash) {
381
+ const parts = [];
382
+ for (let i = 0; i < COLS; i++) {
383
+ parts.push(c("\u2502"));
384
+ if (i < COLS - 1) {
385
+ const back = (i % 2 === 0) === startBackslash;
386
+ parts.push(` ${c(back ? "\u2572" : "\u2571")} `);
387
+ }
388
+ }
389
+ return parts.join("");
390
+ }
391
+ return [nodeRow(false), diagRow(true), nodeRow(true), diagRow(false), nodeRow(false)];
392
+ }
393
+ const SWARM_ART = buildSwarmArt();
394
+ // ── Two-column renderer ───────────────────────────────────────────────────────
395
+ function padRight(text, width) {
396
+ const visible = stripAnsi(text).length;
397
+ if (visible >= width)
398
+ return text;
399
+ return text + " ".repeat(width - visible);
400
+ }
401
+ function renderTwoColumn(left, right, leftWidth) {
402
+ const sep = ` ${dim(symbols.vertLine)} `;
403
+ const maxLines = Math.max(left.length, right.length);
404
+ for (let i = 0; i < maxLines; i++) {
405
+ const l = padRight(left[i] || "", leftWidth);
406
+ const r = right[i] || "";
407
+ process.stderr.write(`${l}${sep}${r}\n`);
408
+ }
409
+ }
410
+ // ── Section header helper ─────────────────────────────────────────────────────
411
+ function sectionHeader(title, w) {
412
+ const label = ` ${title} `;
413
+ const dashes = Math.max(0, w - stripAnsi(label).length - 4);
414
+ const left = symbols.horizontal.repeat(2);
415
+ const right = symbols.horizontal.repeat(Math.max(0, dashes));
416
+ process.stderr.write(`\n ${dim(left)}${coral(bold(label))}${dim(right)}\n\n`);
417
+ }
418
+ // ── Onboarding flow ───────────────────────────────────────────────────────────
419
+ export async function runOnboarding() {
420
+ if (!isFirstRun())
421
+ return;
422
+ if (isJsonMode()) {
423
+ markInitialized();
424
+ return;
425
+ }
426
+ if (getLogLevel() === "quiet") {
427
+ markInitialized();
428
+ return;
429
+ }
430
+ const w = Math.min(termWidth(), 80);
431
+ // ── Gather environment info ──────────────────────────────────────────
432
+ const username = os.userInfo().username || "there";
433
+ const gitVer = await gitVersion();
434
+ const apiKeys = detectApiKeys();
435
+ const agents = await checkAgentBackends();
436
+ const availableAgents = agents.filter((a) => a.ok);
437
+ const missingAgents = agents.filter((a) => !a.ok);
438
+ // ── Header line ──────────────────────────────────────────────────────
439
+ if (isTTY) {
440
+ const label = ` swarm v${VERSION} `;
441
+ const dashCount = Math.max(0, w - label.length - 4);
442
+ const leftDash = symbols.horizontal.repeat(2);
443
+ const rightDash = symbols.horizontal.repeat(Math.max(0, dashCount));
444
+ process.stderr.write(`\n ${dim(`${leftDash}${bold(coral(label))}${dim(rightDash)}`)}\n`);
445
+ }
446
+ else {
447
+ process.stderr.write(`\n swarm v${VERSION}\n`);
448
+ }
449
+ if (!isTTY) {
450
+ process.stderr.write(` Welcome to swarm, ${username}!\n\n`);
451
+ process.stderr.write(` Usage: swarm --dir ./project "your task"\n`);
452
+ process.stderr.write(` Docs: https://github.com/kok-o/contextos-agents\n\n`);
453
+ markInitialized();
454
+ return;
455
+ }
456
+ // ── Build left column ────────────────────────────────────────────────
457
+ const LEFT_W = 36;
458
+ const left = [];
459
+ left.push(` ${bold("Welcome to swarm, ")}${bold(coral(username))}${bold("!")}`);
460
+ left.push("");
461
+ for (const artLine of SWARM_ART) {
462
+ const artVisible = stripAnsi(artLine).length;
463
+ const artPad = Math.max(0, Math.floor((LEFT_W - artVisible) / 2));
464
+ left.push(" ".repeat(artPad) + artLine);
465
+ }
466
+ left.push("");
467
+ const primaryAgent = availableAgents.length > 0 ? availableAgents[0].name : "opencode";
468
+ left.push(` ${dim(`v${VERSION}`)} ${dim(symbols.dot)} ${dim(`${primaryAgent} agent`)}`);
469
+ left.push(` ${dim(process.cwd())}`);
470
+ // ── Build right column ───────────────────────────────────────────────
471
+ const right = [];
472
+ right.push(coral(bold("Tips for getting started")));
473
+ right.push(`Point swarm at any git repo with a task:`);
474
+ right.push(`${yellow("$")} swarm --dir ./project ${dim('"your task"')}`);
475
+ right.push(`Use ${cyan("--dry-run")} to plan without executing`);
476
+ right.push(`Use ${cyan("--verbose")} for detailed progress`);
477
+ right.push("");
478
+ right.push(coral(bold("Environment")));
479
+ if (gitVer) {
480
+ right.push(`${green(symbols.check)} git ${dim(`v${gitVer}`)}`);
481
+ }
482
+ else {
483
+ right.push(`${red(symbols.cross)} git ${dim("not found (required)")}`);
484
+ }
485
+ if (apiKeys.size > 0) {
486
+ for (const p of PROVIDERS) {
487
+ const val = apiKeys.get(p.envVar);
488
+ if (val)
489
+ right.push(`${green(symbols.check)} ${p.name} ${dim(maskKey(val))}`);
490
+ }
491
+ }
492
+ else {
493
+ right.push(`${yellow(symbols.warn)} ${dim("No API keys configured")}`);
494
+ }
495
+ for (const a of availableAgents) {
496
+ right.push(`${green(symbols.check)} ${a.name} ${dim("agent")}`);
497
+ }
498
+ for (const a of missingAgents) {
499
+ right.push(`${dim(symbols.dash)} ${dim(a.name)} ${dim("not found")}`);
500
+ }
501
+ // ── Render two-column layout ─────────────────────────────────────────
502
+ renderTwoColumn(left, right, LEFT_W);
503
+ // ══════════════════════════════════════════════════════════════════════
504
+ // AGENT SETUP WIZARD
505
+ // ══════════════════════════════════════════════════════════════════════
506
+ sectionHeader("Agent Setup", w);
507
+ // ── Step 1: Pick default agent ───────────────────────────────────────
508
+ let chosenAgent = "opencode";
509
+ if (availableAgents.length > 1) {
510
+ process.stderr.write(` ${bold("Choose your default coding agent:")}\n\n`);
511
+ const agentChoices = availableAgents.map((a, i) => {
512
+ const info = AGENT_PROVIDERS[a.name];
513
+ const desc = info ? dim(info.description) : "";
514
+ const rec = a.name === "opencode" ? ` ${coral("(recommended)")}` : "";
515
+ return { idx: i + 1, name: a.name, line: ` ${cyan(String(i + 1))} ${bold(a.name)}${rec} ${desc}` };
516
+ });
517
+ for (const c of agentChoices)
518
+ process.stderr.write(`${c.line}\n`);
519
+ process.stderr.write("\n");
520
+ const prompt = createPrompt();
521
+ const agentChoice = await prompt.ask(` ${coral(symbols.arrow)} Choice [1]: `);
522
+ prompt.close();
523
+ const idx = parseInt(agentChoice, 10);
524
+ if (idx >= 1 && idx <= agentChoices.length) {
525
+ chosenAgent = agentChoices[idx - 1].name;
526
+ }
527
+ else if (agentChoice === "") {
528
+ chosenAgent = agentChoices[0].name;
529
+ }
530
+ process.stderr.write(` ${green(symbols.check)} Default agent: ${bold(chosenAgent)}\n`);
531
+ }
532
+ else if (availableAgents.length === 1) {
533
+ chosenAgent = availableAgents[0].name;
534
+ process.stderr.write(` ${green(symbols.check)} Default agent: ${bold(chosenAgent)} ${dim("(only available backend)")}\n`);
535
+ }
536
+ else {
537
+ // No agents installed — show install instructions
538
+ process.stderr.write(` ${yellow(symbols.warn)} No coding agents found. Install at least one:\n\n`);
539
+ for (const [name, info] of Object.entries(AGENT_PROVIDERS)) {
540
+ if (name === "direct-llm")
541
+ continue;
542
+ process.stderr.write(` ${cyan(symbols.arrow)} ${bold(name)} ${dim(info.install)}\n`);
543
+ process.stderr.write(` ${dim(info.description)}\n`);
544
+ }
545
+ process.stderr.write(`\n ${dim("Using direct-llm (no coding agent) as fallback.")}\n`);
546
+ chosenAgent = "direct-llm";
547
+ }
548
+ // ── Step 2: Configure backend / API keys ────────────────────────────
549
+ const agentInfo = AGENT_PROVIDERS[chosenAgent];
550
+ const neededKeys = agentInfo?.required ?? ["ANTHROPIC_API_KEY"];
551
+ const needsAnyKey = neededKeys.length > 1;
552
+ const hasAnyNeeded = neededKeys.some((k) => apiKeys.has(k));
553
+ let chosenModel = "anthropic/claude-sonnet-4-6";
554
+ let usesOllama = false;
555
+ let usesOpenRouter = false;
556
+ // OpenCode with no API keys → offer Ollama (default) or OpenRouter
557
+ if (chosenAgent === "opencode" && !hasAnyNeeded) {
558
+ sectionHeader("Backend", w);
559
+ process.stderr.write(` ${bold("OpenCode")} ${dim("— choose your backend:")}\n\n`);
560
+ process.stderr.write(` ${cyan("1")} Ollama ${dim("Run models locally (free, requires download)")}\n`);
561
+ process.stderr.write(` ${cyan("2")} OpenRouter ${dim("Cloud API for 200+ models (requires API key)")}\n`);
562
+ process.stderr.write(` ${cyan("3")} API Key ${dim("Configure Anthropic/OpenAI/Google directly")}\n`);
563
+ process.stderr.write("\n");
564
+ const prompt = createPrompt();
565
+ const backendChoice = await prompt.ask(` ${coral(symbols.arrow)} Backend [1]: `);
566
+ prompt.close();
567
+ const choice = backendChoice.trim();
568
+ if (choice === "2") {
569
+ // OpenRouter setup
570
+ process.stderr.write("\n");
571
+ const keyPrompt = createPrompt();
572
+ const orKey = await keyPrompt.ask(` ${coral(symbols.arrow)} OPENROUTER_API_KEY: `);
573
+ keyPrompt.close();
574
+ if (orKey?.trim()) {
575
+ process.env.OPENROUTER_API_KEY = orKey.trim();
576
+ saveCredential("OPENROUTER_API_KEY", orKey.trim());
577
+ process.stderr.write(` ${green(symbols.check)} OpenRouter configured\n`);
578
+ chosenModel = "openrouter/auto";
579
+ usesOpenRouter = true;
580
+ }
581
+ else {
582
+ process.stderr.write(` ${dim("No key provided — you can set OPENROUTER_API_KEY in .env later")}\n`);
583
+ }
584
+ }
585
+ else if (choice === "3") {
586
+ // Fall through to standard API key setup below
587
+ }
588
+ else {
589
+ // Default: Ollama setup
590
+ process.stderr.write("\n");
591
+ const ok = await ensureOllamaSetup(createPrompt, "ollama/deepseek-coder-v2");
592
+ if (ok) {
593
+ usesOllama = true;
594
+ chosenModel = "ollama/deepseek-coder-v2";
595
+ }
596
+ }
597
+ // If choice was "3", do the standard API key flow
598
+ if (choice === "3") {
599
+ sectionHeader("API Keys", w);
600
+ process.stderr.write(` ${bold(chosenAgent)} supports multiple providers. Configure at least one:\n\n`);
601
+ const missingKeys = neededKeys.filter((k) => !apiKeys.has(k));
602
+ for (const envVar of missingKeys) {
603
+ const provider = PROVIDERS.find((p) => p.envVar === envVar);
604
+ if (!provider)
605
+ continue;
606
+ const kp = createPrompt();
607
+ const yn = await kp.ask(` ${coral(symbols.arrow)} Configure ${bold(provider.name)} (${dim(envVar)})? [y/n]: `);
608
+ kp.close();
609
+ if (yn.toLowerCase() !== "y" && yn.toLowerCase() !== "yes") {
610
+ process.stderr.write(` ${dim(symbols.dash)} Skipped ${provider.name}\n`);
611
+ continue;
612
+ }
613
+ process.stderr.write(` ${dim(`Paste your ${provider.name} API key (input hidden):`)}\n`);
614
+ const key = await readHiddenInput(` ${coral(symbols.arrow)} `);
615
+ if (key && key.length >= 10) {
616
+ saveCredential(envVar, key);
617
+ apiKeys.set(envVar, key);
618
+ process.stderr.write(` ${green(symbols.check)} Saved ${provider.name} key to ${dim("~/.swarm/credentials")}\n\n`);
619
+ }
620
+ else {
621
+ process.stderr.write(` ${dim("Skipped — set later in .env or ~/.swarm/credentials")}\n\n`);
622
+ }
623
+ if (apiKeys.has(envVar))
624
+ break;
625
+ }
626
+ }
627
+ }
628
+ else if (!hasAnyNeeded) {
629
+ // Non-opencode agent with missing keys — standard API key flow
630
+ const missingKeys = neededKeys.filter((k) => !apiKeys.has(k));
631
+ if (missingKeys.length > 0) {
632
+ sectionHeader("API Keys", w);
633
+ if (needsAnyKey) {
634
+ process.stderr.write(` ${bold(chosenAgent)} supports multiple providers. Configure at least one:\n\n`);
635
+ }
636
+ else {
637
+ process.stderr.write(` ${bold(chosenAgent)} needs the following API key(s):\n\n`);
638
+ }
639
+ for (const envVar of missingKeys) {
640
+ const provider = PROVIDERS.find((p) => p.envVar === envVar);
641
+ if (!provider)
642
+ continue;
643
+ const prompt = createPrompt();
644
+ const yn = await prompt.ask(` ${coral(symbols.arrow)} Configure ${bold(provider.name)} (${dim(envVar)})? [y/n]: `);
645
+ prompt.close();
646
+ if (yn.toLowerCase() !== "y" && yn.toLowerCase() !== "yes") {
647
+ process.stderr.write(` ${dim(symbols.dash)} Skipped ${provider.name}\n`);
648
+ continue;
649
+ }
650
+ process.stderr.write(` ${dim(`Paste your ${provider.name} API key (input hidden):`)}\n`);
651
+ const key = await readHiddenInput(` ${coral(symbols.arrow)} `);
652
+ if (key && key.length >= 10) {
653
+ saveCredential(envVar, key);
654
+ apiKeys.set(envVar, key);
655
+ process.stderr.write(` ${green(symbols.check)} Saved ${provider.name} key to ${dim("~/.swarm/credentials")}\n\n`);
656
+ }
657
+ else {
658
+ process.stderr.write(` ${dim("Skipped — set later in .env or ~/.swarm/credentials")}\n\n`);
659
+ }
660
+ if (needsAnyKey && apiKeys.has(envVar))
661
+ break;
662
+ }
663
+ }
664
+ }
665
+ else {
666
+ process.stderr.write(` ${green(symbols.check)} API keys already configured\n`);
667
+ }
668
+ // ── Step 3: Choose default model ─────────────────────────────────────
669
+ if (!usesOllama && !usesOpenRouter) {
670
+ const configuredProviders = PROVIDERS.filter((p) => apiKeys.has(p.envVar));
671
+ if (configuredProviders.length > 0) {
672
+ sectionHeader("Default Model", w);
673
+ const modelOptions = configuredProviders.flatMap((p) => {
674
+ const models = [];
675
+ if (p.envVar === "ANTHROPIC_API_KEY") {
676
+ models.push({
677
+ label: `claude-sonnet-4-6 ${dim("(fast, capable)")}`,
678
+ value: "anthropic/claude-sonnet-4-6",
679
+ recommended: true,
680
+ }, {
681
+ label: `claude-opus-4-6 ${dim("(most capable)")}`,
682
+ value: "anthropic/claude-opus-4-6",
683
+ });
684
+ }
685
+ else if (p.envVar === "OPENAI_API_KEY") {
686
+ models.push({ label: `gpt-4o ${dim("(fast, versatile)")}`, value: "openai/gpt-4o" }, { label: `o3 ${dim("(reasoning)")}`, value: "openai/o3" });
687
+ }
688
+ else if (p.envVar === "GEMINI_API_KEY") {
689
+ models.push({
690
+ label: `gemini-2.5-flash ${dim("(fast, cheap)")}`,
691
+ value: "google/gemini-2.5-flash",
692
+ }, { label: `gemini-2.5-pro ${dim("(capable)")}`, value: "google/gemini-2.5-pro" });
693
+ }
694
+ return models;
695
+ });
696
+ if (modelOptions.length > 0) {
697
+ process.stderr.write(` ${bold("Pick a default model for coding threads:")}\n\n`);
698
+ for (let i = 0; i < modelOptions.length; i++) {
699
+ const opt = modelOptions[i];
700
+ const rec = opt.recommended ? ` ${coral("(recommended)")}` : "";
701
+ process.stderr.write(` ${cyan(String(i + 1))} ${opt.label}${rec}\n`);
702
+ }
703
+ process.stderr.write("\n");
704
+ const prompt = createPrompt();
705
+ const modelChoice = await prompt.ask(` ${coral(symbols.arrow)} Choice [1]: `);
706
+ prompt.close();
707
+ const idx = parseInt(modelChoice, 10);
708
+ if (idx >= 1 && idx <= modelOptions.length) {
709
+ chosenModel = modelOptions[idx - 1].value;
710
+ }
711
+ else {
712
+ chosenModel = modelOptions[0].value;
713
+ }
714
+ process.stderr.write(` ${green(symbols.check)} Default model: ${bold(chosenModel)}\n`);
715
+ }
716
+ }
717
+ }
718
+ // ── Step 4: Save config ──────────────────────────────────────────────
719
+ saveUserConfig(chosenAgent, chosenModel);
720
+ // ── Final summary ────────────────────────────────────────────────────
721
+ sectionHeader("Ready", w);
722
+ process.stderr.write(` ${green(symbols.check)} Agent: ${bold(chosenAgent)}\n`);
723
+ process.stderr.write(` ${green(symbols.check)} Model: ${bold(chosenModel)}\n`);
724
+ if (usesOllama) {
725
+ process.stderr.write(` ${green(symbols.check)} Backend: ${bold("Ollama")} ${dim("(local)")}\n`);
726
+ }
727
+ else if (usesOpenRouter) {
728
+ process.stderr.write(` ${green(symbols.check)} Backend: ${bold("OpenRouter")}\n`);
729
+ }
730
+ else {
731
+ const keyNames = [...apiKeys.keys()].map((k) => {
732
+ const p = PROVIDERS.find((pr) => pr.envVar === k);
733
+ return p?.name ?? k;
734
+ });
735
+ if (keyNames.length > 0) {
736
+ process.stderr.write(` ${green(symbols.check)} Keys: ${bold(keyNames.join(", "))}\n`);
737
+ }
738
+ }
739
+ process.stderr.write(` ${green(symbols.check)} Config: ${dim("~/.swarm/config.yaml")}\n`);
740
+ process.stderr.write("\n");
741
+ // If still missing critical deps, show warnings
742
+ if (!gitVer) {
743
+ process.stderr.write(` ${red(symbols.cross)} ${bold("git is required.")} Install it before using swarm.\n`);
744
+ }
745
+ if (apiKeys.size === 0 && !usesOllama && !usesOpenRouter) {
746
+ process.stderr.write(` ${yellow(symbols.warn)} ${bold("No API keys configured.")}\n`);
747
+ process.stderr.write(` ${dim("Add keys to")} ${cyan("~/.swarm/credentials")} ${dim("or")} ${cyan(".env")}${dim(":")}\n`);
748
+ process.stderr.write(` ${dim("ANTHROPIC_API_KEY=sk-ant-...")}\n`);
749
+ process.stderr.write(` ${dim("OPENAI_API_KEY=sk-...")}\n`);
750
+ process.stderr.write(` ${dim("GEMINI_API_KEY=AI...")}\n`);
751
+ }
752
+ process.stderr.write("\n");
753
+ markInitialized();
754
+ }
755
+ //# sourceMappingURL=onboarding.js.map