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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Vipul Maheshwari
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,384 @@
1
+ # swarm-code
2
+
3
+ Open-source swarm-native coding agent orchestrator. Spawns parallel coding agents in isolated git worktrees, orchestrated by a Recursive Language Model (based on [arXiv:2512.24601](https://arxiv.org/abs/2512.24601)).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g swarm-code
9
+ ```
10
+
11
+ Requires **Node.js >= 20** and **Python 3**.
12
+
13
+ ### Supported Providers
14
+
15
+ | Provider | Env Variable | Default Model |
16
+ |----------|-------------|---------------|
17
+ | **Anthropic** | `ANTHROPIC_API_KEY` | `claude-sonnet-4-6` |
18
+ | **OpenAI** | `OPENAI_API_KEY` | `gpt-4o` |
19
+ | **Google** | `GEMINI_API_KEY` | `gemini-2.5-flash` |
20
+
21
+ ```bash
22
+ export ANTHROPIC_API_KEY=sk-ant-...
23
+ ```
24
+
25
+ ### From Source
26
+
27
+ ```bash
28
+ git clone https://github.com/kingjulio8238/swarm-code.git
29
+ cd swarm-code
30
+ npm install
31
+ npm run build
32
+ npm link
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ### Swarm Mode — Parallel Coding Agents
38
+
39
+ Point swarm at a repo with a task. It scans the codebase, decomposes the work, and spawns coding agents in isolated git worktrees:
40
+
41
+ ```bash
42
+ swarm --dir ./my-project "add error handling to all API routes"
43
+ ```
44
+
45
+ The orchestrator LLM writes Python code that calls `thread()` to spawn agents, `asyncio.gather()` for parallelism, and `merge_threads()` to integrate changes:
46
+
47
+ ```bash
48
+ # With auto model routing (picks best agent+model per task)
49
+ swarm --dir ./project --auto-route "migrate from Express to Fastify"
50
+
51
+ # Dry run — plan without executing
52
+ swarm --dir ./project --dry-run "refactor auth module"
53
+
54
+ # Budget cap
55
+ swarm --dir ./project --max-budget 5.00 "add comprehensive tests"
56
+
57
+ # Specific agent backend
58
+ swarm --dir ./project --agent claude-code "review and fix security issues"
59
+
60
+ # Verbose — see routing decisions and memory hints
61
+ swarm --dir ./project --verbose --auto-route "optimize database queries"
62
+ ```
63
+
64
+ ### Agent Backends
65
+
66
+ | Agent | Description | Best for |
67
+ |-------|------------|----------|
68
+ | `opencode` (default) | Open-source, multi-provider, tool-capable | General coding, testing |
69
+ | `claude-code` | Anthropic's Claude Code CLI | Deep analysis, refactoring |
70
+ | `codex` | OpenAI's Codex CLI | Shell commands, OpenAI models |
71
+ | `aider` | Git-aware AI coding assistant | Targeted edits, minimal changes |
72
+ | `direct-llm` | Bare LLM call, no agent wrapper | Analysis, planning, classification |
73
+
74
+ ### RLM Text Mode (inherited)
75
+
76
+ The original RLM text-processing mode is preserved:
77
+
78
+ ```bash
79
+ swarm run --file large-document.txt "summarize the key findings"
80
+ swarm run --url https://example.com/data.txt "extract all dates"
81
+ cat data.txt | swarm run --stdin "count the errors"
82
+ ```
83
+
84
+ ### Interactive Mode
85
+
86
+ Run with `--dir` but no task to enter interactive mode — a persistent REPL with live thread monitoring:
87
+
88
+ ```bash
89
+ swarm --dir ./my-project
90
+ ```
91
+
92
+ Commands:
93
+
94
+ | Command | Description |
95
+ |---------|-------------|
96
+ | `/threads` | List all threads with status, cost, duration |
97
+ | `/thread <id>` | Show thread detail (files changed, diff, result) |
98
+ | `/merge` | Merge all completed thread branches |
99
+ | `/reject <id>` | Reject a thread's changes |
100
+ | `/dag` | Show thread DAG with timing bars |
101
+ | `/budget` | Show budget breakdown |
102
+ | `/status` | Show session stats |
103
+ | `/help` | List commands |
104
+ | `/quit` | Cleanup and exit |
105
+
106
+ Ctrl+C once cancels the current task. Ctrl+C twice exits.
107
+
108
+ ### GitHub Action
109
+
110
+ Trigger swarm from issue comments. Add `.github/workflows/swarm.yml` to your repo (a template is provided in this repo):
111
+
112
+ ```yaml
113
+ name: Swarm Agent
114
+ on:
115
+ issue_comment:
116
+ types: [created]
117
+ workflow_dispatch:
118
+ inputs:
119
+ task:
120
+ description: 'Task for swarm to execute'
121
+ required: true
122
+ type: string
123
+
124
+ jobs:
125
+ swarm:
126
+ runs-on: ubuntu-latest
127
+ timeout-minutes: 30
128
+ if: >
129
+ github.event_name == 'workflow_dispatch' ||
130
+ (github.event_name == 'issue_comment' &&
131
+ contains(github.event.comment.body, '@swarm'))
132
+ permissions:
133
+ contents: write
134
+ pull-requests: write
135
+ issues: write
136
+ steps:
137
+ - uses: actions/checkout@v4
138
+ - uses: kingjulio8238/swarm-code@main
139
+ with:
140
+ task: ${{ github.event.inputs.task || '' }}
141
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
142
+ max_budget: '5.00'
143
+ ```
144
+
145
+ Then comment on any issue:
146
+
147
+ ```
148
+ @swarm fix the auth bug in src/auth.ts
149
+ ```
150
+
151
+ Swarm runs, creates a PR with the changes, and posts a summary back on the issue.
152
+
153
+ **Security**: Only OWNER/MEMBER/COLLABORATOR can trigger. Fork PRs are rejected. Budget hard cap of $50. API keys are masked in logs.
154
+
155
+ **Action inputs**: `task`, `anthropic_api_key`, `openai_api_key`, `gemini_api_key`, `agent`, `model`, `max_budget`
156
+
157
+ **Action outputs**: `success`, `pr_url`, `cost_usd`, `threads_completed`, `threads_failed`, `elapsed_s`, `answer`, `skipped`, `skip_reason`
158
+
159
+ ### MCP Server
160
+
161
+ Expose swarm as tools for Claude Code, Cursor, or any MCP-compatible agent:
162
+
163
+ ```bash
164
+ swarm mcp # Start MCP server (stdio)
165
+ swarm mcp --dir ./my-project # Start with default directory
166
+ ```
167
+
168
+ **Tools exposed:**
169
+
170
+ | Tool | Description |
171
+ |------|-------------|
172
+ | `swarm_run` | Full swarm orchestration — decompose, spawn threads, merge, return result |
173
+ | `swarm_thread` | Spawn a single coding agent in an isolated worktree |
174
+ | `swarm_status` | Get session status — threads, budget, costs |
175
+ | `swarm_merge` | Merge completed thread branches back to main |
176
+ | `swarm_cancel` | Cancel running thread(s) |
177
+ | `swarm_cleanup` | Destroy session and clean up worktrees |
178
+
179
+ **Claude Code setup:**
180
+
181
+ ```bash
182
+ claude mcp add swarm-code -- npx swarm-code mcp
183
+ ```
184
+
185
+ Or add to your project's `.mcp.json`:
186
+
187
+ ```json
188
+ {
189
+ "mcpServers": {
190
+ "swarm-code": {
191
+ "command": "npx",
192
+ "args": ["swarm-code", "mcp", "--dir", "."],
193
+ "env": {
194
+ "ANTHROPIC_API_KEY": "${ANTHROPIC_API_KEY}"
195
+ }
196
+ }
197
+ }
198
+ }
199
+ ```
200
+
201
+ **Cursor setup:** Add to `.cursor/mcp.json` with the same format.
202
+
203
+ Once configured, Claude Code or Cursor can call `swarm_thread` to spawn coding agents in worktrees, check progress with `swarm_status`, and merge with `swarm_merge` — giving the host agent full orchestration control.
204
+
205
+ ### Trajectory Viewer
206
+
207
+ ```bash
208
+ swarm viewer
209
+ ```
210
+
211
+ Browse saved runs in a TUI. View iterations, code, output, sub-queries, and swarm thread DAGs with timing bars and cost breakdowns. Arrow keys to navigate, Enter to drill down into thread details.
212
+
213
+ ### Benchmarks
214
+
215
+ ```bash
216
+ swarm benchmark oolong # Oolong Synth long-context benchmark
217
+ swarm benchmark longbench # LongBench NarrativeQA benchmark
218
+ ```
219
+
220
+ ## How It Works
221
+
222
+ 1. **Scan**: Codebase is scanned and loaded as context
223
+ 2. **Orchestrate**: The RLM loop runs — the LLM writes Python code using swarm primitives
224
+ 3. **Decompose**: Tasks are broken into independent, parallelizable units
225
+ 4. **Spawn**: `thread()` / `async_thread()` spawn coding agents in isolated git worktrees
226
+ 5. **Compress**: Agent output is filtered to successful operations only (episode quality)
227
+ 6. **Merge**: `merge_threads()` integrates worktree branches back to main
228
+ 7. **Verify**: Optional test thread validates the merged result
229
+
230
+ ### Python Primitives
231
+
232
+ ```python
233
+ # Lightweight LLM query (no file changes)
234
+ analysis = llm_query(context[:5000], "List all API endpoints")
235
+
236
+ # Spawn a coding agent in an isolated worktree
237
+ result = thread("Fix the auth bug", files=["src/auth.ts"])
238
+
239
+ # Parallel threads
240
+ import asyncio
241
+ results = await asyncio.gather(
242
+ async_thread("Add validation to POST /users", files=["src/routes/users.ts"]),
243
+ async_thread("Add validation to POST /orders", files=["src/routes/orders.ts"]),
244
+ )
245
+
246
+ # Merge all thread branches back to main
247
+ merge_threads()
248
+
249
+ # Return final answer
250
+ FINAL("Added input validation to all API routes")
251
+ ```
252
+
253
+ ### Thread DAG Composition
254
+
255
+ Thread results compose naturally via Python variable persistence:
256
+
257
+ ```python
258
+ # Stage 1: Research in parallel
259
+ analysis, test_gaps = await asyncio.gather(
260
+ async_thread("Analyze the auth module", files=["src/auth/"]),
261
+ async_thread("Find files with <50% coverage", files=["package.json"]),
262
+ )
263
+
264
+ # Stage 2: Act on Stage 1 results
265
+ await asyncio.gather(
266
+ async_thread("Add rate limiting", context=analysis, files=["src/auth/middleware.ts"]),
267
+ async_thread("Add tests for low-coverage files", context=test_gaps),
268
+ )
269
+
270
+ # Stage 3: Merge and validate
271
+ merge_threads()
272
+ thread("Run full test suite and fix failures")
273
+ ```
274
+
275
+ ## Configuration
276
+
277
+ Create `swarm_config.yaml` in your project root:
278
+
279
+ ```yaml
280
+ # Concurrency
281
+ max_threads: 5 # Max concurrent threads
282
+ max_total_threads: 20 # Max threads per session
283
+ thread_timeout_ms: 300000 # 5min per thread
284
+
285
+ # Budget
286
+ max_thread_budget_usd: 1.00 # Per-thread cost cap
287
+ max_session_budget_usd: 10.00 # Total session cost cap
288
+
289
+ # Agent
290
+ default_agent: opencode # opencode, claude-code, codex, aider, direct-llm
291
+ default_model: anthropic/claude-sonnet-4-6
292
+ auto_model_selection: false # Enable auto-routing
293
+
294
+ # Compression
295
+ compression_strategy: structured # structured, diff-only, truncate, llm-summary
296
+
297
+ # Model slots — override model per task type
298
+ # model_slot_execution: anthropic/claude-sonnet-4-6
299
+ # model_slot_search: anthropic/claude-haiku-4-5
300
+ # model_slot_reasoning: anthropic/claude-opus-4-6
301
+ # model_slot_planning: anthropic/claude-opus-4-6
302
+
303
+ # Episodic memory — cross-session strategy learning
304
+ episodic_memory_enabled: false
305
+ memory_dir: ~/.swarm/memory
306
+
307
+ # Thread cache persistence
308
+ thread_cache_persist: false
309
+ thread_cache_dir: ~/.swarm/cache
310
+ thread_cache_ttl_hours: 24
311
+ ```
312
+
313
+ ## Key Optimizations
314
+
315
+ - **Episode quality**: Compression filters agent output to only successful operations — failed attempts, stack traces, and retries are stripped automatically
316
+ - **Subthread caching**: Identical threads (same task + files + agent + model) are cached in-memory with optional disk persistence and TTL expiry
317
+ - **Named model slots**: Tasks auto-classified into execution/search/reasoning/planning slots, each with preferred agents and optional model overrides
318
+ - **Episodic memory**: Persists successful thread strategies to disk; trigram-based similarity recall informs agent/model selection in future sessions
319
+ - **DAG composition**: Thread results compose via Python variable persistence (T1+T2 → T3); orchestrator prompt teaches multi-stage pipelines and failure re-routing
320
+ - **Failure tracking**: Exponential-decay weighted failure rates per agent/model pair — recent failures penalized more, agents that keep failing get routed around
321
+
322
+ ## Architecture
323
+
324
+ ```
325
+ src/
326
+ ├── main.ts CLI entry point (swarm/run/viewer/benchmark)
327
+ ├── swarm.ts Swarm orchestration (single-shot)
328
+ ├── interactive-swarm.ts Interactive REPL with live monitoring
329
+ ├── cli.ts RLM text mode
330
+ ├── core/
331
+ │ ├── rlm.ts Core RLM loop (Algorithm 1)
332
+ │ ├── repl.ts Python REPL bridge (JSON over stdin/stdout)
333
+ │ ├── runtime.py Python runtime (thread/async_thread/merge)
334
+ │ └── types.ts Shared type definitions
335
+ ├── agents/
336
+ │ ├── provider.ts AgentProvider interface + registry
337
+ │ ├── opencode.ts OpenCode (subprocess + server mode)
338
+ │ ├── claude-code.ts Claude Code CLI backend
339
+ │ ├── codex.ts Codex CLI backend
340
+ │ ├── aider.ts Aider backend
341
+ │ └── direct-llm.ts Bare LLM calls
342
+ ├── threads/
343
+ │ ├── manager.ts Thread lifecycle + concurrency + episodes
344
+ │ └── cache.ts Subthread cache (memory + disk)
345
+ ├── worktree/
346
+ │ ├── manager.ts Git worktree CRUD
347
+ │ └── merge.ts Branch merging
348
+ ├── compression/
349
+ │ └── compressor.ts Result compression strategies
350
+ ├── routing/
351
+ │ └── model-router.ts Auto model/agent selection + failure tracking
352
+ ├── memory/
353
+ │ └── episodic.ts Cross-session strategy learning
354
+ ├── prompts/
355
+ │ └── orchestrator.ts Swarm system prompt
356
+ ├── mcp/
357
+ │ ├── server.ts MCP server entry point (stdio transport)
358
+ │ ├── tools.ts Tool definitions + handlers
359
+ │ └── session.ts Per-directory session state
360
+ ├── ui/
361
+ │ ├── onboarding.ts First-run setup wizard
362
+ │ ├── spinner.ts CLI spinner
363
+ │ ├── dashboard.ts Live progress dashboard
364
+ │ └── summary.ts Session summary + JSON output
365
+ └── viewer.ts Trajectory TUI + DAG viewer
366
+ action/
367
+ ├── entrypoint.ts GitHub Action orchestration
368
+ ├── parse-trigger.ts @swarm comment parsing
369
+ ├── security.ts Auth + fork detection + budget caps
370
+ └── pr.ts PR creation + issue commenting
371
+ ```
372
+
373
+ ## Development
374
+
375
+ ```bash
376
+ npm install # Install deps
377
+ npx tsx src/main.ts --dir . # Run in dev mode
378
+ npm run build # Compile TypeScript
379
+ npm test # Run tests
380
+ ```
381
+
382
+ ## License
383
+
384
+ MIT
package/bin/swarm.mjs ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * swarm — Swarm-Native Coding Agent CLI
5
+ *
6
+ * This shim boots the CLI entry point. It tries the compiled dist first,
7
+ * then falls back to tsx for development.
8
+ */
9
+
10
+ import { fileURLToPath, pathToFileURL } from "node:url";
11
+ import { dirname, join } from "node:path";
12
+ import { existsSync } from "node:fs";
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const distEntry = join(__dirname, "..", "dist", "main.js");
16
+
17
+ if (existsSync(distEntry)) {
18
+ // Production: use compiled JS (pathToFileURL needed for Windows)
19
+ await import(pathToFileURL(distEntry).href);
20
+ } else {
21
+ // Development: use tsx to run TypeScript directly
22
+ const srcEntry = join(__dirname, "..", "src", "main.ts");
23
+ const { register } = await import("node:module");
24
+
25
+ // Try to register tsx loader, then import
26
+ try {
27
+ const tsxPath = join(__dirname, "..", "node_modules", "tsx", "dist", "esm", "index.mjs");
28
+ if (existsSync(tsxPath)) {
29
+ register(pathToFileURL(tsxPath).href);
30
+ }
31
+ await import(pathToFileURL(srcEntry).href);
32
+ } catch {
33
+ // Fallback: spawn tsx as a child process
34
+ const { spawn } = await import("node:child_process");
35
+ const tsxBin = join(__dirname, "..", "node_modules", ".bin", "tsx");
36
+ const child = spawn(tsxBin, [srcEntry, ...process.argv.slice(2)], {
37
+ stdio: "inherit",
38
+ });
39
+ child.on("exit", (code) => process.exit(code ?? 1));
40
+ child.on("error", (err) => {
41
+ console.error(`Failed to start swarm: ${err.message}`);
42
+ process.exit(1);
43
+ });
44
+ }
45
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Aider agent backend.
3
+ *
4
+ * Runs tasks via `aider --yes-always --no-auto-commits --message "prompt"` subprocess.
5
+ * Aider is a git-aware AI coding assistant that makes targeted edits.
6
+ *
7
+ * Output format: Plain text (no JSON mode available).
8
+ * Edit confirmations appear as "Applied edit to <file>" lines.
9
+ */
10
+ import type { AgentProvider } from "../core/types.js";
11
+ declare const aiderProvider: AgentProvider;
12
+ export default aiderProvider;
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Aider agent backend.
3
+ *
4
+ * Runs tasks via `aider --yes-always --no-auto-commits --message "prompt"` subprocess.
5
+ * Aider is a git-aware AI coding assistant that makes targeted edits.
6
+ *
7
+ * Output format: Plain text (no JSON mode available).
8
+ * Edit confirmations appear as "Applied edit to <file>" lines.
9
+ */
10
+ import { spawn } from "node:child_process";
11
+ import * as os from "node:os";
12
+ import { registerAgent } from "./provider.js";
13
+ async function commandExists(cmd) {
14
+ return new Promise((resolve) => {
15
+ const proc = spawn("which", [cmd], { stdio: "pipe" });
16
+ proc.on("close", (code) => resolve(code === 0));
17
+ proc.on("error", () => resolve(false));
18
+ });
19
+ }
20
+ /** Whitelist of env vars safe to pass to agent subprocess. */
21
+ function buildAgentEnv() {
22
+ const homeDir = os.homedir();
23
+ return {
24
+ PATH: process.env.PATH,
25
+ HOME: homeDir,
26
+ USERPROFILE: homeDir,
27
+ SHELL: process.env.SHELL,
28
+ TERM: process.env.TERM,
29
+ LANG: process.env.LANG,
30
+ // Aider supports multiple providers via these keys
31
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
32
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY,
33
+ GEMINI_API_KEY: process.env.GEMINI_API_KEY,
34
+ // Aider-specific env vars
35
+ AIDER_MODEL: process.env.AIDER_MODEL,
36
+ AIDER_DARK_MODE: process.env.AIDER_DARK_MODE,
37
+ // Git config
38
+ GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME,
39
+ GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL,
40
+ GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME,
41
+ GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL,
42
+ // Python (aider is Python-based)
43
+ VIRTUAL_ENV: process.env.VIRTUAL_ENV,
44
+ CONDA_DEFAULT_ENV: process.env.CONDA_DEFAULT_ENV,
45
+ PYTHONPATH: process.env.PYTHONPATH,
46
+ // Windows
47
+ ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}),
48
+ ...(process.env.SYSTEMROOT ? { SYSTEMROOT: process.env.SYSTEMROOT } : {}),
49
+ };
50
+ }
51
+ /** Map provider/model format to aider model flag. */
52
+ function resolveModel(model) {
53
+ // Aider accepts provider/model format natively (e.g., "anthropic/claude-sonnet-4-6")
54
+ // It also has short aliases: sonnet, opus, haiku, 4o, etc.
55
+ return model;
56
+ }
57
+ /** Parse aider output to extract edited file paths. */
58
+ function extractFilesChanged(output) {
59
+ const files = [];
60
+ // Aider outputs lines like:
61
+ // "Applied edit to src/auth.ts"
62
+ // "Wrote src/auth.ts"
63
+ // "Committing src/auth.ts ..."
64
+ // "Created new file src/utils.ts"
65
+ const patterns = [
66
+ /Applied edit to\s+(.+?)(?:\s*$)/gm,
67
+ /Wrote\s+(.+?)(?:\s*$)/gm,
68
+ /Committing\s+(.+?)(?:\s+\.\.\.|\s*$)/gm,
69
+ /(?:Created|Added)\s+new file\s+(.+?)(?:\s*$)/gm,
70
+ ];
71
+ for (const pattern of patterns) {
72
+ let match;
73
+ while ((match = pattern.exec(output)) !== null) {
74
+ const file = match[1].trim();
75
+ if (file) {
76
+ files.push(file);
77
+ }
78
+ }
79
+ }
80
+ return [...new Set(files)];
81
+ }
82
+ const aiderProvider = {
83
+ name: "aider",
84
+ async isAvailable() {
85
+ return commandExists("aider");
86
+ },
87
+ async run(options) {
88
+ const { task, workDir, model, files, signal } = options;
89
+ const startTime = Date.now();
90
+ const args = [
91
+ "--yes-always", // Auto-confirm all prompts (except shell commands)
92
+ "--no-auto-commits", // Don't auto-commit (worktree manager handles commits)
93
+ "--no-pretty", // Disable ANSI colors for clean parsing
94
+ "--no-stream", // Don't stream (capture full output)
95
+ "--no-fancy-input", // Disable prompt toolkit input
96
+ "--no-suggest-shell-commands", // Suppress shell command suggestions
97
+ "--no-detect-urls", // Suppress URL detection prompts
98
+ ];
99
+ if (model) {
100
+ args.push("--model", resolveModel(model));
101
+ }
102
+ // Pass the task via --message (non-interactive mode)
103
+ args.push("--message", task);
104
+ // Add specific files to edit if provided
105
+ if (files && files.length > 0) {
106
+ for (const file of files) {
107
+ args.push("--file", file);
108
+ }
109
+ }
110
+ return new Promise((resolve) => {
111
+ const proc = spawn("aider", args, {
112
+ cwd: workDir,
113
+ stdio: ["ignore", "pipe", "pipe"],
114
+ env: buildAgentEnv(),
115
+ });
116
+ let stdout = "";
117
+ let stderr = "";
118
+ let resolved = false;
119
+ const doResolve = (result) => {
120
+ if (resolved)
121
+ return;
122
+ resolved = true;
123
+ resolve(result);
124
+ };
125
+ proc.stdout?.on("data", (chunk) => {
126
+ const text = chunk.toString();
127
+ stdout += text;
128
+ options.onOutput?.(text);
129
+ });
130
+ proc.stderr?.on("data", (chunk) => {
131
+ stderr += chunk.toString();
132
+ });
133
+ if (signal) {
134
+ const onAbort = () => {
135
+ proc.kill("SIGTERM");
136
+ const killTimer = setTimeout(() => {
137
+ try {
138
+ if (proc.exitCode === null)
139
+ proc.kill("SIGKILL");
140
+ }
141
+ catch {
142
+ /* already dead */
143
+ }
144
+ }, 3000);
145
+ proc.on("exit", () => clearTimeout(killTimer));
146
+ };
147
+ if (signal.aborted) {
148
+ onAbort();
149
+ }
150
+ else {
151
+ signal.addEventListener("abort", onAbort, { once: true });
152
+ proc.on("exit", () => signal.removeEventListener("abort", onAbort));
153
+ }
154
+ }
155
+ proc.on("close", (code) => {
156
+ const durationMs = Date.now() - startTime;
157
+ const filesChanged = extractFilesChanged(stdout);
158
+ doResolve({
159
+ success: code === 0,
160
+ output: stdout || stderr,
161
+ filesChanged,
162
+ diff: "", // Diff is captured separately by worktree manager
163
+ durationMs,
164
+ error: code !== 0 ? `aider exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}` : undefined,
165
+ });
166
+ });
167
+ proc.on("error", (err) => {
168
+ doResolve({
169
+ success: false,
170
+ output: "",
171
+ filesChanged: [],
172
+ diff: "",
173
+ durationMs: Date.now() - startTime,
174
+ error: `Failed to spawn aider: ${err.message}`,
175
+ });
176
+ });
177
+ });
178
+ },
179
+ };
180
+ registerAgent(aiderProvider);
181
+ export default aiderProvider;
182
+ //# sourceMappingURL=aider.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Claude Code agent backend.
3
+ *
4
+ * Runs tasks via `claude -p --output-format json "prompt"` subprocess.
5
+ * Parses structured JSON output for results, cost, and file changes.
6
+ */
7
+ import type { AgentProvider } from "../core/types.js";
8
+ declare const claudeCodeProvider: AgentProvider;
9
+ export default claudeCodeProvider;