swarm-code 0.1.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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +384 -0
  3. package/bin/swarm.mjs +45 -0
  4. package/dist/agents/aider.d.ts +12 -0
  5. package/dist/agents/aider.js +182 -0
  6. package/dist/agents/claude-code.d.ts +9 -0
  7. package/dist/agents/claude-code.js +216 -0
  8. package/dist/agents/codex.d.ts +14 -0
  9. package/dist/agents/codex.js +193 -0
  10. package/dist/agents/direct-llm.d.ts +9 -0
  11. package/dist/agents/direct-llm.js +78 -0
  12. package/dist/agents/mock.d.ts +9 -0
  13. package/dist/agents/mock.js +77 -0
  14. package/dist/agents/opencode.d.ts +23 -0
  15. package/dist/agents/opencode.js +571 -0
  16. package/dist/agents/provider.d.ts +11 -0
  17. package/dist/agents/provider.js +31 -0
  18. package/dist/cli.d.ts +15 -0
  19. package/dist/cli.js +285 -0
  20. package/dist/compression/compressor.d.ts +28 -0
  21. package/dist/compression/compressor.js +265 -0
  22. package/dist/config.d.ts +42 -0
  23. package/dist/config.js +170 -0
  24. package/dist/core/repl.d.ts +69 -0
  25. package/dist/core/repl.js +336 -0
  26. package/dist/core/rlm.d.ts +63 -0
  27. package/dist/core/rlm.js +409 -0
  28. package/dist/core/runtime.py +335 -0
  29. package/dist/core/types.d.ts +131 -0
  30. package/dist/core/types.js +19 -0
  31. package/dist/env.d.ts +10 -0
  32. package/dist/env.js +75 -0
  33. package/dist/interactive-swarm.d.ts +20 -0
  34. package/dist/interactive-swarm.js +1041 -0
  35. package/dist/interactive.d.ts +10 -0
  36. package/dist/interactive.js +1765 -0
  37. package/dist/main.d.ts +15 -0
  38. package/dist/main.js +242 -0
  39. package/dist/mcp/server.d.ts +15 -0
  40. package/dist/mcp/server.js +72 -0
  41. package/dist/mcp/session.d.ts +73 -0
  42. package/dist/mcp/session.js +184 -0
  43. package/dist/mcp/tools.d.ts +15 -0
  44. package/dist/mcp/tools.js +377 -0
  45. package/dist/memory/episodic.d.ts +132 -0
  46. package/dist/memory/episodic.js +390 -0
  47. package/dist/prompts/orchestrator.d.ts +5 -0
  48. package/dist/prompts/orchestrator.js +191 -0
  49. package/dist/routing/model-router.d.ts +130 -0
  50. package/dist/routing/model-router.js +515 -0
  51. package/dist/swarm.d.ts +14 -0
  52. package/dist/swarm.js +557 -0
  53. package/dist/threads/cache.d.ts +58 -0
  54. package/dist/threads/cache.js +198 -0
  55. package/dist/threads/manager.d.ts +85 -0
  56. package/dist/threads/manager.js +659 -0
  57. package/dist/ui/banner.d.ts +14 -0
  58. package/dist/ui/banner.js +42 -0
  59. package/dist/ui/dashboard.d.ts +33 -0
  60. package/dist/ui/dashboard.js +151 -0
  61. package/dist/ui/index.d.ts +10 -0
  62. package/dist/ui/index.js +11 -0
  63. package/dist/ui/log.d.ts +39 -0
  64. package/dist/ui/log.js +126 -0
  65. package/dist/ui/onboarding.d.ts +14 -0
  66. package/dist/ui/onboarding.js +518 -0
  67. package/dist/ui/spinner.d.ts +25 -0
  68. package/dist/ui/spinner.js +113 -0
  69. package/dist/ui/summary.d.ts +18 -0
  70. package/dist/ui/summary.js +113 -0
  71. package/dist/ui/theme.d.ts +63 -0
  72. package/dist/ui/theme.js +97 -0
  73. package/dist/viewer.d.ts +12 -0
  74. package/dist/viewer.js +1284 -0
  75. package/dist/worktree/manager.d.ts +45 -0
  76. package/dist/worktree/manager.js +266 -0
  77. package/dist/worktree/merge.d.ts +28 -0
  78. package/dist/worktree/merge.js +138 -0
  79. package/package.json +69 -0
@@ -0,0 +1,571 @@
1
+ /**
2
+ * OpenCode agent backend.
3
+ *
4
+ * Three execution modes (in priority order):
5
+ * 1. Server mode: Connects to a managed `opencode serve` instance via HTTP API.
6
+ * Avoids cold-start overhead. Server is shared across threads.
7
+ * 2. Attach mode: Uses `opencode run --attach <url>` to connect to a running server.
8
+ * Falls back here if direct HTTP API call fails.
9
+ * 3. Subprocess mode: Cold-starts `opencode run --format json` per invocation.
10
+ * Always-available fallback.
11
+ *
12
+ * The server pool manages lifecycle of `opencode serve` processes, starting them
13
+ * lazily and shutting them down when the session ends.
14
+ */
15
+ import { spawn } from "node:child_process";
16
+ import * as http from "node:http";
17
+ import * as os from "node:os";
18
+ import { registerAgent } from "./provider.js";
19
+ // ── Helpers ──────────────────────────────────────────────────────────────────
20
+ async function commandExists(cmd) {
21
+ return new Promise((resolve) => {
22
+ const proc = spawn("which", [cmd], { stdio: "pipe" });
23
+ proc.on("close", (code) => resolve(code === 0));
24
+ proc.on("error", () => resolve(false));
25
+ });
26
+ }
27
+ /** Extract JSON object from mixed stdout (may contain non-JSON lines before/after). */
28
+ function extractJson(raw) {
29
+ try {
30
+ return JSON.parse(raw);
31
+ }
32
+ catch {
33
+ /* mixed output — try extraction */
34
+ }
35
+ const firstBrace = raw.indexOf("{");
36
+ const lastBrace = raw.lastIndexOf("}");
37
+ if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace)
38
+ return null;
39
+ try {
40
+ return JSON.parse(raw.slice(firstBrace, lastBrace + 1));
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ /** Extract output text, file changes, and token usage from parsed JSON. */
47
+ function extractFromParsed(parsed) {
48
+ if (parsed.error) {
49
+ return { output: parsed.error, filesChanged: [], error: parsed.error };
50
+ }
51
+ let output = "";
52
+ const filesChanged = [];
53
+ if (parsed.messages) {
54
+ const assistantMsgs = parsed.messages.filter((m) => m.role === "assistant");
55
+ if (assistantMsgs.length > 0) {
56
+ output = assistantMsgs[assistantMsgs.length - 1].content;
57
+ }
58
+ for (const msg of parsed.messages) {
59
+ if (msg.tool_calls) {
60
+ for (const tc of msg.tool_calls) {
61
+ if (tc.name === "write" || tc.name === "edit") {
62
+ const filePath = tc.input.file_path || tc.input.path;
63
+ if (typeof filePath === "string") {
64
+ filesChanged.push(filePath);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ // Extract token usage from various possible locations
72
+ let usage;
73
+ const u = (parsed.usage || parsed.total_usage);
74
+ if (u) {
75
+ const inputTokens = u.input_tokens ?? u.prompt_tokens ?? 0;
76
+ const outputTokens = u.output_tokens ?? u.completion_tokens ?? 0;
77
+ if (inputTokens > 0 || outputTokens > 0) {
78
+ usage = {
79
+ inputTokens,
80
+ outputTokens,
81
+ totalTokens: u.total_tokens || inputTokens + outputTokens,
82
+ };
83
+ }
84
+ }
85
+ return { output, filesChanged: [...new Set(filesChanged)], usage };
86
+ }
87
+ /** Whitelist of env vars safe to pass to agent subprocess. */
88
+ function buildAgentEnv() {
89
+ const homeDir = os.homedir();
90
+ return {
91
+ PATH: process.env.PATH,
92
+ HOME: homeDir,
93
+ USERPROFILE: homeDir,
94
+ SHELL: process.env.SHELL,
95
+ TERM: process.env.TERM,
96
+ LANG: process.env.LANG,
97
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
98
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY,
99
+ GEMINI_API_KEY: process.env.GEMINI_API_KEY,
100
+ GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME,
101
+ GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL,
102
+ GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME,
103
+ GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL,
104
+ ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}),
105
+ ...(process.env.SYSTEMROOT ? { SYSTEMROOT: process.env.SYSTEMROOT } : {}),
106
+ };
107
+ }
108
+ /**
109
+ * Manages a pool of `opencode serve` processes.
110
+ * One server per unique working directory (worktree).
111
+ * Servers are started lazily and reused across thread invocations.
112
+ */
113
+ class OpenCodeServerPool {
114
+ servers = new Map();
115
+ pendingStarts = new Map();
116
+ nextPort = 14096; // Start from a high port to avoid conflicts
117
+ shuttingDown = false;
118
+ /**
119
+ * Get or create a server for the given working directory.
120
+ * Returns the server URL if successful, null if server mode is unavailable.
121
+ * Uses pendingStarts to prevent race conditions from concurrent getServer() calls.
122
+ */
123
+ async getServer(cwd) {
124
+ if (this.shuttingDown)
125
+ return null;
126
+ const existing = this.servers.get(cwd);
127
+ if (existing) {
128
+ // Wait for it to be ready
129
+ try {
130
+ await existing.readyPromise;
131
+ // Verify still alive
132
+ if (existing.process.exitCode === null) {
133
+ return existing.url;
134
+ }
135
+ // Dead — clean up and retry
136
+ this.servers.delete(cwd);
137
+ }
138
+ catch {
139
+ this.servers.delete(cwd);
140
+ }
141
+ }
142
+ // Prevent concurrent startServer for the same cwd
143
+ const pending = this.pendingStarts.get(cwd);
144
+ if (pending)
145
+ return pending;
146
+ const startPromise = this.startServer(cwd).finally(() => {
147
+ this.pendingStarts.delete(cwd);
148
+ });
149
+ this.pendingStarts.set(cwd, startPromise);
150
+ return startPromise;
151
+ }
152
+ async startServer(cwd) {
153
+ const port = this.nextPort++;
154
+ const hostname = "127.0.0.1";
155
+ let resolveReady;
156
+ let rejectReady;
157
+ const readyPromise = new Promise((resolve, reject) => {
158
+ resolveReady = resolve;
159
+ rejectReady = reject;
160
+ });
161
+ const proc = spawn("opencode", ["serve", "--port", String(port), "--hostname", hostname], {
162
+ cwd,
163
+ stdio: ["ignore", "pipe", "pipe"],
164
+ env: buildAgentEnv(),
165
+ });
166
+ const server = {
167
+ process: proc,
168
+ url: `http://${hostname}:${port}`,
169
+ port,
170
+ cwd,
171
+ ready: false,
172
+ readyPromise,
173
+ };
174
+ this.servers.set(cwd, server);
175
+ // Listen for stderr to detect "listening on" or ready signal
176
+ let stderr = "";
177
+ proc.stderr?.on("data", (chunk) => {
178
+ stderr += chunk.toString();
179
+ });
180
+ proc.on("error", () => {
181
+ rejectReady(new Error("Failed to start opencode serve"));
182
+ this.servers.delete(cwd);
183
+ });
184
+ proc.on("exit", () => {
185
+ if (!server.ready) {
186
+ rejectReady(new Error(`opencode serve exited early: ${stderr.slice(0, 200)}`));
187
+ }
188
+ this.servers.delete(cwd);
189
+ });
190
+ // Poll for health endpoint readiness (up to 10s)
191
+ const startTime = Date.now();
192
+ const maxWait = 10000;
193
+ const pollInterval = 200;
194
+ while (Date.now() - startTime < maxWait) {
195
+ if (proc.exitCode !== null) {
196
+ rejectReady(new Error("opencode serve exited during startup"));
197
+ return null;
198
+ }
199
+ try {
200
+ const healthy = await this.healthCheck(server.url);
201
+ if (healthy) {
202
+ server.ready = true;
203
+ resolveReady();
204
+ return server.url;
205
+ }
206
+ }
207
+ catch {
208
+ // Not ready yet
209
+ }
210
+ await new Promise((r) => setTimeout(r, pollInterval));
211
+ }
212
+ // Timeout — kill and return null (will fall back to subprocess mode)
213
+ this.killServer(server);
214
+ rejectReady(new Error("opencode serve startup timeout"));
215
+ return null;
216
+ }
217
+ healthCheck(baseUrl) {
218
+ return new Promise((resolve) => {
219
+ const req = http.get(`${baseUrl}/global/health`, { timeout: 2000 }, (res) => {
220
+ let body = "";
221
+ res.on("data", (d) => {
222
+ body += d;
223
+ });
224
+ res.on("end", () => {
225
+ try {
226
+ const data = JSON.parse(body);
227
+ resolve(data.healthy === true);
228
+ }
229
+ catch {
230
+ resolve(false);
231
+ }
232
+ });
233
+ });
234
+ req.on("error", () => resolve(false));
235
+ req.on("timeout", () => {
236
+ req.destroy();
237
+ resolve(false);
238
+ });
239
+ });
240
+ }
241
+ killServer(server) {
242
+ try {
243
+ server.process.kill("SIGTERM");
244
+ setTimeout(() => {
245
+ try {
246
+ if (server.process.exitCode === null)
247
+ server.process.kill("SIGKILL");
248
+ }
249
+ catch {
250
+ /* ok */
251
+ }
252
+ }, 3000);
253
+ }
254
+ catch {
255
+ /* already dead */
256
+ }
257
+ this.servers.delete(server.cwd);
258
+ }
259
+ /** Shut down all managed servers. */
260
+ async shutdown() {
261
+ this.shuttingDown = true;
262
+ for (const [, server] of this.servers) {
263
+ this.killServer(server);
264
+ }
265
+ this.servers.clear();
266
+ }
267
+ get activeCount() {
268
+ return this.servers.size;
269
+ }
270
+ }
271
+ // Module-level server pool (shared across all opencode agent invocations)
272
+ const serverPool = new OpenCodeServerPool();
273
+ // ── HTTP API execution ───────────────────────────────────────────────────────
274
+ /**
275
+ * Execute a task via the OpenCode HTTP API (server mode).
276
+ * Creates a session, sends the message, waits for response.
277
+ */
278
+ async function runViaHttpApi(serverUrl, task, model, signal) {
279
+ try {
280
+ // 1. Create session
281
+ const sessionRes = await httpPost(`${serverUrl}/session`, {});
282
+ if (!sessionRes || !sessionRes.id)
283
+ return null;
284
+ const sessionId = sessionRes.id;
285
+ // 2. Send message
286
+ const msgBody = {
287
+ parts: [{ type: "text", text: task }],
288
+ };
289
+ if (model)
290
+ msgBody.model = model;
291
+ const msgRes = await httpPost(`${serverUrl}/session/${sessionId}/message`, msgBody, signal);
292
+ if (!msgRes)
293
+ return null;
294
+ // 3. Extract output from response
295
+ let output = "";
296
+ const filesChanged = [];
297
+ if (msgRes.parts) {
298
+ for (const part of msgRes.parts) {
299
+ if (part.type === "text" && typeof part.text === "string") {
300
+ output += part.text;
301
+ }
302
+ if (part.type === "tool_use" || part.type === "tool_call") {
303
+ const name = part.name;
304
+ const input = part.input;
305
+ if ((name === "write" || name === "edit") && input) {
306
+ const fp = input.file_path || input.path;
307
+ if (typeof fp === "string")
308
+ filesChanged.push(fp);
309
+ }
310
+ }
311
+ }
312
+ }
313
+ // Also check info.content for assistant messages
314
+ const info = msgRes.info;
315
+ if (info?.content && typeof info.content === "string") {
316
+ if (!output)
317
+ output = info.content;
318
+ }
319
+ // Extract token usage from response (may be in usage, total_usage, or info.usage)
320
+ let usage;
321
+ const u = (msgRes.usage ?? msgRes.total_usage ?? info?.usage);
322
+ if (u) {
323
+ const inputTokens = u.input_tokens ?? u.prompt_tokens ?? 0;
324
+ const outputTokens = u.output_tokens ?? u.completion_tokens ?? 0;
325
+ if (inputTokens > 0 || outputTokens > 0) {
326
+ usage = {
327
+ inputTokens,
328
+ outputTokens,
329
+ totalTokens: u.total_tokens ?? inputTokens + outputTokens,
330
+ };
331
+ }
332
+ }
333
+ return { output, filesChanged: [...new Set(filesChanged)], sessionId: sessionId, usage };
334
+ }
335
+ catch {
336
+ return null;
337
+ }
338
+ }
339
+ function httpPost(url, body, signal) {
340
+ return new Promise((resolve) => {
341
+ const data = JSON.stringify(body);
342
+ const parsed = new URL(url);
343
+ const req = http.request({
344
+ hostname: parsed.hostname,
345
+ port: parsed.port,
346
+ path: parsed.pathname,
347
+ method: "POST",
348
+ headers: {
349
+ "Content-Type": "application/json",
350
+ "Content-Length": Buffer.byteLength(data),
351
+ },
352
+ timeout: 300000, // 5 min for long tasks
353
+ }, (res) => {
354
+ let responseBody = "";
355
+ res.on("data", (d) => {
356
+ responseBody += d;
357
+ });
358
+ res.on("end", () => {
359
+ // Reject non-2xx status codes
360
+ const status = res.statusCode ?? 0;
361
+ if (status < 200 || status >= 300) {
362
+ resolve(null);
363
+ return;
364
+ }
365
+ try {
366
+ resolve(JSON.parse(responseBody));
367
+ }
368
+ catch {
369
+ resolve(null);
370
+ }
371
+ });
372
+ });
373
+ req.on("error", () => resolve(null));
374
+ req.on("timeout", () => {
375
+ req.destroy();
376
+ resolve(null);
377
+ });
378
+ if (signal) {
379
+ const onAbort = () => req.destroy();
380
+ if (signal.aborted) {
381
+ req.destroy();
382
+ resolve(null);
383
+ return;
384
+ }
385
+ signal.addEventListener("abort", onAbort, { once: true });
386
+ req.on("close", () => signal.removeEventListener("abort", onAbort));
387
+ }
388
+ req.write(data);
389
+ req.end();
390
+ });
391
+ }
392
+ // ── Attach mode execution ────────────────────────────────────────────────────
393
+ /**
394
+ * Execute via `opencode run --attach <url>` — connects to a running server
395
+ * but uses the CLI's output parsing.
396
+ */
397
+ function runViaAttach(serverUrl, task, workDir, model, signal, onOutput) {
398
+ const startTime = Date.now();
399
+ const args = ["run", "--attach", serverUrl, "--format", "json"];
400
+ if (model)
401
+ args.push("--model", model);
402
+ args.push(task);
403
+ return runSubprocess(args, workDir, startTime, signal, onOutput);
404
+ }
405
+ // ── Subprocess mode execution ────────────────────────────────────────────────
406
+ /**
407
+ * Execute via cold-start `opencode run` subprocess.
408
+ * Always-available fallback.
409
+ */
410
+ function runViaSubprocess(task, workDir, model, signal, onOutput) {
411
+ const startTime = Date.now();
412
+ const args = ["run", "--format", "json"];
413
+ if (model)
414
+ args.push("--model", model);
415
+ args.push(task);
416
+ return runSubprocess(args, workDir, startTime, signal, onOutput);
417
+ }
418
+ /** Shared subprocess runner for both attach and cold-start modes. */
419
+ function runSubprocess(args, workDir, startTime, signal, onOutput) {
420
+ return new Promise((resolve) => {
421
+ const proc = spawn("opencode", args, {
422
+ cwd: workDir,
423
+ stdio: ["ignore", "pipe", "pipe"],
424
+ env: buildAgentEnv(),
425
+ });
426
+ let stdout = "";
427
+ let stderr = "";
428
+ let resolved = false;
429
+ const doResolve = (result) => {
430
+ if (resolved)
431
+ return;
432
+ resolved = true;
433
+ resolve(result);
434
+ };
435
+ proc.stdout?.on("data", (chunk) => {
436
+ const text = chunk.toString();
437
+ stdout += text;
438
+ onOutput?.(text);
439
+ });
440
+ proc.stderr?.on("data", (chunk) => {
441
+ stderr += chunk.toString();
442
+ });
443
+ if (signal) {
444
+ const onAbort = () => {
445
+ proc.kill("SIGTERM");
446
+ const killTimer = setTimeout(() => {
447
+ try {
448
+ if (proc.exitCode === null)
449
+ proc.kill("SIGKILL");
450
+ }
451
+ catch {
452
+ /* dead */
453
+ }
454
+ }, 3000);
455
+ proc.on("exit", () => clearTimeout(killTimer));
456
+ };
457
+ if (signal.aborted) {
458
+ onAbort();
459
+ }
460
+ else {
461
+ signal.addEventListener("abort", onAbort, { once: true });
462
+ proc.on("exit", () => signal.removeEventListener("abort", onAbort));
463
+ }
464
+ }
465
+ proc.on("close", (code) => {
466
+ const durationMs = Date.now() - startTime;
467
+ let filesChanged = [];
468
+ let output = stdout;
469
+ const parsed = extractJson(stdout);
470
+ let usage;
471
+ if (parsed) {
472
+ const extracted = extractFromParsed(parsed);
473
+ if (extracted.error) {
474
+ doResolve({
475
+ success: false,
476
+ output: extracted.error,
477
+ filesChanged: [],
478
+ diff: "",
479
+ durationMs,
480
+ error: extracted.error,
481
+ usage: extracted.usage,
482
+ });
483
+ return;
484
+ }
485
+ if (extracted.output)
486
+ output = extracted.output;
487
+ filesChanged = extracted.filesChanged;
488
+ usage = extracted.usage;
489
+ }
490
+ doResolve({
491
+ success: code === 0,
492
+ output,
493
+ filesChanged,
494
+ diff: "",
495
+ durationMs,
496
+ error: code !== 0 ? `opencode exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}` : undefined,
497
+ usage,
498
+ });
499
+ });
500
+ proc.on("error", (err) => {
501
+ doResolve({
502
+ success: false,
503
+ output: "",
504
+ filesChanged: [],
505
+ diff: "",
506
+ durationMs: Date.now() - startTime,
507
+ error: `Failed to spawn opencode: ${err.message}`,
508
+ });
509
+ });
510
+ });
511
+ }
512
+ // ── Provider ─────────────────────────────────────────────────────────────────
513
+ // Whether server mode is enabled (set via enableServerMode)
514
+ let _serverModeEnabled = false;
515
+ /** Enable server mode for the OpenCode agent. Call before spawning threads. */
516
+ export function enableServerMode() {
517
+ _serverModeEnabled = true;
518
+ }
519
+ /** Disable server mode and shut down all managed servers. */
520
+ export async function disableServerMode() {
521
+ _serverModeEnabled = false;
522
+ await serverPool.shutdown();
523
+ }
524
+ /** Get the number of active server instances. */
525
+ export function getActiveServerCount() {
526
+ return serverPool.activeCount;
527
+ }
528
+ const openCodeProvider = {
529
+ name: "opencode",
530
+ async isAvailable() {
531
+ return commandExists("opencode");
532
+ },
533
+ async run(options) {
534
+ const { task, workDir, model, signal, onOutput } = options;
535
+ const startTime = Date.now();
536
+ // Strategy 1: Server mode (HTTP API)
537
+ if (_serverModeEnabled) {
538
+ try {
539
+ const serverUrl = await serverPool.getServer(workDir);
540
+ if (serverUrl) {
541
+ const apiResult = await runViaHttpApi(serverUrl, task, model, signal);
542
+ if (apiResult) {
543
+ return {
544
+ success: true,
545
+ output: apiResult.output,
546
+ filesChanged: apiResult.filesChanged,
547
+ diff: "",
548
+ durationMs: Date.now() - startTime,
549
+ usage: apiResult.usage,
550
+ };
551
+ }
552
+ // API call failed — try attach mode with the same server
553
+ try {
554
+ return await runViaAttach(serverUrl, task, workDir, model, signal, onOutput);
555
+ }
556
+ catch {
557
+ // Fall through to subprocess
558
+ }
559
+ }
560
+ }
561
+ catch {
562
+ // Server mode unavailable — fall through
563
+ }
564
+ }
565
+ // Strategy 2: Subprocess mode (always-available fallback)
566
+ return runViaSubprocess(task, workDir, model, signal, onOutput);
567
+ },
568
+ };
569
+ registerAgent(openCodeProvider);
570
+ export default openCodeProvider;
571
+ //# sourceMappingURL=opencode.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Agent provider interface and registry.
3
+ *
4
+ * Each agent backend (OpenCode, Claude Code, etc.) implements AgentProvider.
5
+ * The registry maps backend names to providers.
6
+ */
7
+ import type { AgentProvider } from "../core/types.js";
8
+ export declare function registerAgent(provider: AgentProvider): void;
9
+ export declare function getAgent(name: string): AgentProvider;
10
+ export declare function listAgents(): string[];
11
+ export declare function getAvailableAgents(): Promise<string[]>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Agent provider interface and registry.
3
+ *
4
+ * Each agent backend (OpenCode, Claude Code, etc.) implements AgentProvider.
5
+ * The registry maps backend names to providers.
6
+ */
7
+ const registry = new Map();
8
+ export function registerAgent(provider) {
9
+ registry.set(provider.name, provider);
10
+ }
11
+ export function getAgent(name) {
12
+ const provider = registry.get(name);
13
+ if (!provider) {
14
+ const available = [...registry.keys()].join(", ");
15
+ throw new Error(`Unknown agent backend "${name}". Available: ${available || "none"}`);
16
+ }
17
+ return provider;
18
+ }
19
+ export function listAgents() {
20
+ return [...registry.keys()];
21
+ }
22
+ export async function getAvailableAgents() {
23
+ const results = [];
24
+ for (const [name, provider] of registry) {
25
+ if (await provider.isAvailable()) {
26
+ results.push(name);
27
+ }
28
+ }
29
+ return results;
30
+ }
31
+ //# sourceMappingURL=provider.js.map
package/dist/cli.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Standalone RLM CLI — run Recursive Language Model queries from the terminal.
4
+ *
5
+ * Usage:
6
+ * npx tsx src/cli.ts --file large-file.txt "What are the main themes?"
7
+ * npx tsx src/cli.ts --url https://example.com/big.txt "Summarize this"
8
+ * cat data.txt | npx tsx src/cli.ts --stdin "Count the errors"
9
+ *
10
+ * Environment (pick one):
11
+ * ANTHROPIC_API_KEY — for Anthropic models
12
+ * OPENAI_API_KEY — for OpenAI models
13
+ * GEMINI_API_KEY — for Google models
14
+ */
15
+ import "./env.js";