cursor-route 0.1.8 → 0.1.10
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.
- package/CHANGELOG.md +22 -0
- package/CONTRIBUTING.md +2 -0
- package/README.md +47 -11
- package/SECURITY.md +6 -5
- package/SUPPORT.md +2 -0
- package/dist/adapters/claude-ds.js +18 -0
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/opencode.js +83 -0
- package/dist/cli.js +33 -12
- package/dist/config.js +30 -3
- package/dist/health.js +17 -1
- package/dist/jobs.js +47 -2
- package/docs/briefs/WORKING.md +12 -4
- package/docs/demo-notes.md +3 -2
- package/docs/fixtures/hero-demo.log +3 -2
- package/llms.txt +8 -4
- package/package.json +3 -2
- package/skills/route-orch/SKILL.md +53 -14
- package/src/adapters/claude-ds.ts +19 -0
- package/src/adapters/index.ts +2 -0
- package/src/adapters/opencode.test.ts +238 -0
- package/src/adapters/opencode.ts +89 -0
- package/src/adapters/types.ts +1 -1
- package/src/cli.test.ts +133 -5
- package/src/cli.ts +31 -10
- package/src/config.ts +38 -6
- package/src/health.ts +22 -1
- package/src/jobs.ts +71 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
_(none)_
|
|
6
|
+
|
|
7
|
+
## 0.1.10 — 2026-08-21
|
|
8
|
+
|
|
9
|
+
- Opt-in `--worker opencode`: OpenCode `run` as a coding agent that defaults to Zen free model `opencode/big-pickle` (alias `--model free`). Not a lane default — mid stays `claude-ds`, easy stays OpenRouter chat.
|
|
10
|
+
- Always-approve → `opencode run --auto` (still honors explicit deny rules); `--ask` / `CURSOR_ROUTE_ASK=1` omits `--auto`. Never rewrites `~/.config/opencode/opencode.json`.
|
|
11
|
+
- `--model provider/model` for opencode (env `CURSOR_ROUTE_OPENCODE_MODEL`); `flash|pro` still DeepSeek-only.
|
|
12
|
+
- Health: `worker:opencode` ✓ when `opencode` is on PATH (override `CURSOR_ROUTE_OPENCODE_BIN`). Auth is `opencode auth login` at first start (same pattern as grok).
|
|
13
|
+
- Dry-run works without opencode installed (falls back to plain `opencode` in the printed command); real starts stay gated by health.
|
|
14
|
+
- Docs / `route-orch`: OpenCode is an opt-in usage-reduction worker, not a second mid harness. Free Zen models may log/train — non-secret prompts only.
|
|
15
|
+
|
|
16
|
+
## 0.1.9 — 2026-08-18
|
|
17
|
+
|
|
18
|
+
- Health proves mid DeepSeek: dedicated `lane:mid` check is ✓ only when mid is a `claude-ds` / `deepseek-claude` shim or stock `claude` with DeepSeek `ANTHROPIC_BASE_URL`. `CURSOR_ROUTE_ALLOW_ANTHROPIC=1` is not proof. Overall `ok` still uses the existing OR-gate (any worker + `CURSOR_ROUTE_RELAXED=1`); grok-only installs stay OK with `lane:mid` ✗ plus a fix tip.
|
|
19
|
+
- Health JSON includes `lanes.mid.{ worker, deepseek, detail }` (`worker` is always `"claude-ds"`).
|
|
20
|
+
- `status --json` adds an evidence tree (`spawn` / `execute` / `verify`). `verify.claim` is always `"unverified"` — the parent must capture; status never auto-greens.
|
|
21
|
+
- Headless pid liveness: if `ps` is unreadable (`EPERM` / non-zero), treat the pid as **alive** (fail-safe), not dead — avoids false-completed kills on sandboxed `ps`.
|
|
22
|
+
- `route-orch` skill: health-before-mid, evidence-tree closeout, always-approve is coding-worktrees-only (not LIVE Discord/trading), do not fork the mid harness; `--worker deepseek` stays experimental / cheap-to-abandon.
|
|
23
|
+
- Docs: `route-orch` **Eval & skill hygiene** (folded from Unreleased) — external eval contract (AutoDesign): never rewrite Verify / Success criteria mid-run to make a failing job look green (capture + exit status are the contract); skill misevolution: no auto-edit / promote skill variants from worker trajectories without operator HITL (write-time approval ≠ safe retrieval later); verify fail → prefer reconsidering the plan/definition over grinding the same tactic, with stage attribution when possible (spawn vs execute vs verify). Mid stays `claude-ds`.
|
|
24
|
+
|
|
3
25
|
## 0.1.8 — 2026-08-14
|
|
4
26
|
|
|
5
27
|
- Experimental `--worker deepseek`: official DeepSeek Harness (`dsh`, `@deepseek-ai/dsh`) runs headless — `dsh --profile headless` with a per-job Cordis patch pinning the model. Not a mid default: `laneWorkers.mid` stays `claude-ds`.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -12,6 +12,7 @@ CURSOR_ROUTE_RELAXED=1 ./bin/cursor-route health # without tmux
|
|
|
12
12
|
./bin/cursor-route start --dry-run --lane mid --model flash "ping"
|
|
13
13
|
./bin/cursor-route start --dry-run --lane mid --model pro "ping"
|
|
14
14
|
./bin/cursor-route start --dry-run --worker deepseek --model flash "ping" # needs dsh + DEEPSEEK_API_KEY for real starts
|
|
15
|
+
./bin/cursor-route start --dry-run --worker opencode --model free "ping" # needs opencode for real starts
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
Keep `skills/route-orch/SKILL.md` and `.cursor/skills/route-orch/SKILL.md` identical when editing the skill.
|
|
@@ -23,6 +24,7 @@ Keep `skills/route-orch/SKILL.md` and `.cursor/skills/route-orch/SKILL.md` ident
|
|
|
23
24
|
- Prefer adapters over rewriting the job core
|
|
24
25
|
- Always-approve defaults stay documented + opt-out (`--ask` / `CURSOR_ROUTE_ASK=1`)
|
|
25
26
|
- Mid default is Flash; document Pro only as harder mid / Grok **usage** stand-in (not missing `grok login`)
|
|
27
|
+
- OpenCode is opt-in (`--worker opencode`); do not make it a lane default — mid stays claude-ds
|
|
26
28
|
|
|
27
29
|
## Release checklist
|
|
28
30
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# cursor-route
|
|
2
2
|
|
|
3
|
-
**Cursor stays the brain. Grok CLI + DeepSeek (claude-ds) + OpenRouter (easy) are the parallel army.**
|
|
3
|
+
**Cursor stays the brain. Grok CLI + DeepSeek (claude-ds) + OpenRouter (easy) + OpenCode (opt-in free) are the parallel army.**
|
|
4
4
|
|
|
5
5
|
Lane-aware `/route` orchestration in tmux — not another multi-provider fleet, not a Codex clone.
|
|
6
6
|
|
|
@@ -17,7 +17,7 @@ cursor-route capture <jobId>
|
|
|
17
17
|
|---------|-------|---------|------|
|
|
18
18
|
| [codex-orchestrator](https://github.com/kingbootoshi/codex-orchestrator) | Claude Code | Codex | Viral tmux panes |
|
|
19
19
|
| [CAO](https://github.com/awslabs/cli-agent-orchestrator) | Supervisor CLI | Many (incl. Cursor CLI) | Enterprise MCP + Web UI |
|
|
20
|
-
| **cursor-route** | **Cursor Agent** | **Grok CLI + claude-ds + OpenRouter (easy)** | Cost-aware lanes you already pay for |
|
|
20
|
+
| **cursor-route** | **Cursor Agent** | **Grok CLI + claude-ds + OpenRouter (easy) + OpenCode (opt-in)** | Cost-aware lanes you already pay for |
|
|
21
21
|
|
|
22
22
|
If you already live in Cursor, X Premium (Grok CLI), and DeepSeek — stop paying a third coding agent just to parallelize.
|
|
23
23
|
|
|
@@ -29,6 +29,7 @@ If you already live in Cursor, X Premium (Grok CLI), and DeepSeek — stop payin
|
|
|
29
29
|
| Grok CLI | X Premium | `--lane hard` implement |
|
|
30
30
|
| DeepSeek via claude-ds | DeepSeek API / plan | `--lane mid` implement (**Flash** default; `--model pro` when needed) |
|
|
31
31
|
| OpenRouter free models | OpenRouter API (free tier) | `--lane easy` wording/drafts — non-secret prompts only (see Security) |
|
|
32
|
+
| OpenCode (opt-in) | OpenCode Zen free models | `--worker opencode` implement on free Zen (default `opencode/big-pickle`) |
|
|
32
33
|
| Codex / extra Claude | Optional | Not required for v0 |
|
|
33
34
|
|
|
34
35
|
Exact dollars vary — the point is **reuse subscriptions you already have**.
|
|
@@ -43,6 +44,7 @@ Exact dollars vary — the point is **reuse subscriptions you already have**.
|
|
|
43
44
|
| [Bun](https://bun.sh) *(or Node 20+)* | Runs the CLI |
|
|
44
45
|
| [Grok CLI](https://x.ai/cli) and/or Claude Code + DeepSeek (`claude-ds`) | Workers |
|
|
45
46
|
| OpenRouter API key (`OPENROUTER_API_KEY`) | Easy lane (`--lane easy` / `--worker openrouter`) |
|
|
47
|
+
| [OpenCode](https://opencode.ai) (`opencode` on PATH) | Opt-in `--worker opencode` (free Zen models) |
|
|
46
48
|
| `script(1)` | Job logs (macOS/Linux) |
|
|
47
49
|
|
|
48
50
|
```bash
|
|
@@ -57,6 +59,7 @@ npm i -g cursor-route
|
|
|
57
59
|
grok login # if using Grok
|
|
58
60
|
# configure claude-ds — see DeepSeek setup below
|
|
59
61
|
export OPENROUTER_API_KEY=... # if using the easy lane (see OpenRouter setup below)
|
|
62
|
+
# optional: npm i -g opencode-ai && opencode auth login # --worker opencode
|
|
60
63
|
|
|
61
64
|
cursor-route health
|
|
62
65
|
# without tmux / workers (CI / headless infra smoke):
|
|
@@ -91,10 +94,10 @@ Working notes for this repo (edit in place): [docs/briefs/WORKING.md](./docs/bri
|
|
|
91
94
|
|
|
92
95
|
| Command | Description |
|
|
93
96
|
|---------|-------------|
|
|
94
|
-
| `health` | Gate #1 — tmux, runtime, workers |
|
|
97
|
+
| `health` | Gate #1 — tmux, runtime, workers; proves `lane:mid` is DeepSeek (`lanes.mid` in `--json`) |
|
|
95
98
|
| `start <prompt>` | Spawn worker job (`--worker` / `--lane` / `--model` / `--dir` / `--ask` / `--dry-run` / `--no-tmux`) |
|
|
96
99
|
| `jobs [--json]` | List jobs |
|
|
97
|
-
| `status <id>` | Job + sessionAlive |
|
|
100
|
+
| `status <id>` | Job + sessionAlive; `--json` includes evidence tree (`spawn` / `execute` / `verify`) |
|
|
98
101
|
| `capture <id> [n]` | Last n pane/log lines |
|
|
99
102
|
| `send <id> <msg>` | Mid-task redirect (tmux send-keys) |
|
|
100
103
|
| `attach <id>` | Print `tmux attach` hint |
|
|
@@ -113,10 +116,10 @@ docs/fixtures/generate-hero-demo.sh # regenerate docs/fixtures/hero-demo.lo
|
|
|
113
116
|
|
|
114
117
|
```text
|
|
115
118
|
$ cursor-route --version
|
|
116
|
-
0.1.
|
|
119
|
+
0.1.9
|
|
117
120
|
|
|
118
121
|
$ CURSOR_ROUTE_RELAXED=1 cursor-route health
|
|
119
|
-
cursor-route v0.1.
|
|
122
|
+
cursor-route v0.1.9
|
|
120
123
|
health: OK
|
|
121
124
|
|
|
122
125
|
$ cursor-route start --lane mid --model flash --dry-run "Add a unit test for shellQuote"
|
|
@@ -140,7 +143,7 @@ A real hero GIF is still pending — recording steps live in [docs/DEMO_GIF.md](
|
|
|
140
143
|
| `mid` | `claude-ds` | Default implement on DeepSeek (**Flash** by default) |
|
|
141
144
|
| `hard` | `grok` | Hard implement on Grok CLI |
|
|
142
145
|
|
|
143
|
-
Always-approve is **on** by default. Opt out: `--ask` or `CURSOR_ROUTE_ASK=1`.
|
|
146
|
+
Always-approve is **on** by default for **coding worktrees only**. It does not authorize LIVE Discord, trading, or irreversible SaaS. Opt out: `--ask` or `CURSOR_ROUTE_ASK=1`.
|
|
144
147
|
|
|
145
148
|
### Mid models (Flash vs Pro)
|
|
146
149
|
|
|
@@ -163,7 +166,7 @@ export CURSOR_ROUTE_DS_MODEL=pro # also honors ANTHROPIC_MODEL; --model overri
|
|
|
163
166
|
|
|
164
167
|
## DeepSeek setup (the cheap mid-lane — this is the point)
|
|
165
168
|
|
|
166
|
-
`cursor-route` mid lane runs **DeepSeek**, not Anthropic. Claude Code is only the terminal harness; bills go to DeepSeek when `ANTHROPIC_BASE_URL` points at them.
|
|
169
|
+
`cursor-route` mid lane runs **DeepSeek**, not Anthropic. Claude Code is only the terminal harness; bills go to DeepSeek when `ANTHROPIC_BASE_URL` points at them. `cursor-route health` shows `lane:mid` ✓ only when DeepSeek is proven (`claude-ds` / `deepseek-claude` shim, or stock `claude` with DeepSeek `ANTHROPIC_BASE_URL`) — `CURSOR_ROUTE_ALLOW_ANTHROPIC=1` is not proof.
|
|
167
170
|
|
|
168
171
|
**Recommended (official DeepSeek → Claude Code):**
|
|
169
172
|
|
|
@@ -176,7 +179,7 @@ export ANTHROPIC_AUTH_TOKEN=YOUR_DEEPSEEK_API_KEY # from platform.deepseek.com
|
|
|
176
179
|
export ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-v4-flash
|
|
177
180
|
export CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash
|
|
178
181
|
|
|
179
|
-
cursor-route health # worker:claude-ds should be ✓
|
|
182
|
+
cursor-route health # worker:claude-ds should be ✓; `lane:mid` is ✓ only when DeepSeek is proven
|
|
180
183
|
cursor-route start --lane mid "…" # Flash (default)
|
|
181
184
|
cursor-route start --lane mid --model pro "…" # Pro when you need it
|
|
182
185
|
```
|
|
@@ -221,6 +224,36 @@ cursor-route start --lane easy "Rewrite this FAQ answer in 3 sentences"
|
|
|
221
224
|
**wording/drafts without credentials**. The same refuse gate as every lane blocks
|
|
222
225
|
key-shaped material in `start` / `send`, and the runner re-checks the prompt file.
|
|
223
226
|
|
|
227
|
+
## OpenCode setup (opt-in free coding worker)
|
|
228
|
+
|
|
229
|
+
`--worker opencode` runs [OpenCode](https://opencode.ai) as a **coding agent** on
|
|
230
|
+
OpenCode Zen free models (default `opencode/big-pickle`). This is **not** a lane
|
|
231
|
+
default — mid stays `claude-ds`; easy stays OpenRouter chat (no tools). Use it
|
|
232
|
+
to burn fewer Grok / DeepSeek tokens on implement work.
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npm i -g opencode-ai # or: brew install opencode
|
|
236
|
+
opencode auth login # connect OpenCode Zen (or another provider)
|
|
237
|
+
# optional:
|
|
238
|
+
export CURSOR_ROUTE_OPENCODE_MODEL=opencode/big-pickle # default; alias --model free
|
|
239
|
+
|
|
240
|
+
cursor-route health # worker:opencode should be ✓
|
|
241
|
+
cursor-route start --worker opencode "…" # Big Pickle (free)
|
|
242
|
+
cursor-route start --worker opencode --model free "…"
|
|
243
|
+
cursor-route start --worker opencode --model opencode/hy3-free "…"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The adapter launches `opencode run --dir <cwd> --model <id>` with the prompt via
|
|
247
|
+
`cat` (never interpolated). Always-approve maps to `--auto` (explicit `"deny"`
|
|
248
|
+
rules still apply); `--ask` omits `--auto`. It never rewrites
|
|
249
|
+
`~/.config/opencode/opencode.json`, so parallel jobs don't race. Override the
|
|
250
|
+
binary with `CURSOR_ROUTE_OPENCODE_BIN`.
|
|
251
|
+
|
|
252
|
+
**Non-secret prompts:** several Zen free models may log or train on prompts during
|
|
253
|
+
their free period (see [OpenCode Zen](https://opencode.ai/docs/zen/) privacy notes).
|
|
254
|
+
The same refuse gate as every lane still applies. `opencode/x-preview-f-free`
|
|
255
|
+
(Ox Alpha) is the zero-retention free option if you need it.
|
|
256
|
+
|
|
224
257
|
## Jobs directory
|
|
225
258
|
|
|
226
259
|
Jobs default to `~/.local/share/cursor-route/jobs` (override with `CURSOR_ROUTE_JOBS_DIR`).
|
|
@@ -235,7 +268,7 @@ This is **not** inside a git clone of this repo.
|
|
|
235
268
|
## FAQ
|
|
236
269
|
|
|
237
270
|
**What is cursor-route?**
|
|
238
|
-
cursor-route is a public MIT CLI and Cursor skill that runs parallel coding workers in tmux while Cursor remains the planner. DeepSeek handles the mid lane, Grok CLI handles the hard lane,
|
|
271
|
+
cursor-route is a public MIT CLI and Cursor skill that runs parallel coding workers in tmux while Cursor remains the planner. DeepSeek handles the mid lane, Grok CLI handles the hard lane, OpenRouter free models handle the easy lane for wording/drafts, and `--worker opencode` is an opt-in coding agent on OpenCode Zen free models.
|
|
239
272
|
|
|
240
273
|
**How is this different from Codex orchestrator?**
|
|
241
274
|
It uses the familiar strategist and worker-pane shape, but it is not a Codex clone. cursor-route uses Cursor as the planner and DeepSeek plus Grok CLI as workers. Codex is not required.
|
|
@@ -247,7 +280,7 @@ No. The mid worker is DeepSeek. Claude Code is the harness, configured with `ANT
|
|
|
247
280
|
Run `npm i -g cursor-route`, install tmux if needed, then run `cursor-route health`. The package is available at https://www.npmjs.com/package/cursor-route, and the source is at https://github.com/cemini23/cursor-route.
|
|
248
281
|
|
|
249
282
|
**Is it free?**
|
|
250
|
-
The cursor-route code is open source under MIT. It does not make the worker services free. Your costs depend on Cursor, DeepSeek API usage, and the Grok access or balance available to you. The easy lane can be free on OpenRouter's free-model route (`openrouter/free`).
|
|
283
|
+
The cursor-route code is open source under MIT. It does not make the worker services free. Your costs depend on Cursor, DeepSeek API usage, and the Grok access or balance available to you. The easy lane can be free on OpenRouter's free-model route (`openrouter/free`). `--worker opencode` can run OpenCode Zen free models (default `opencode/big-pickle`).
|
|
251
284
|
|
|
252
285
|
## Related
|
|
253
286
|
|
|
@@ -276,9 +309,11 @@ If you’d like to tip, use the **donation-only** addresses below (not trading o
|
|
|
276
309
|
|
|
277
310
|
| Chain family | Address |
|
|
278
311
|
|--------------|---------|
|
|
312
|
+
| **X Money** (fiat, US) | Request [@Cemini23](https://x.com/Cemini23) in the X app — scan the Request QR |
|
|
279
313
|
| **EVM** (Ethereum, Polygon, Base, Arbitrum, …) | `0x444C5C2eC439E0382aa5a17F70313c536BcC5D58` |
|
|
280
314
|
| **Solana / SVM** | `J4zNn4hK9jTrKBFY8sbAGJHLoZvXvQf4B9pQSbSrocZE` |
|
|
281
315
|
| **Polymarket** (referral) | [polymarket.com/?r=Cemini23](https://polymarket.com/?r=Cemini23) |
|
|
316
|
+
| **Hyperliquid** (referral) | [app.hyperliquid.xyz/join/CEMINI23](https://app.hyperliquid.xyz/join/CEMINI23) |
|
|
282
317
|
|
|
283
318
|
Full wallet note: [SUPPORT.md](SUPPORT.md) · canon also in [CCC SUPPORT.md](https://github.com/cemini23/cemini-claude-code-CCC/blob/main/SUPPORT.md).
|
|
284
319
|
|
|
@@ -292,6 +327,7 @@ MIT © Cemini — see [LICENSE](LICENSE).
|
|
|
292
327
|
|
|
293
328
|
- Homebrew tap
|
|
294
329
|
- Stabilize the DeepSeek harness adapter (experimental `--worker deepseek` since 0.1.8)
|
|
330
|
+
- Stabilize the OpenCode adapter (`--worker opencode` since 0.1.10)
|
|
295
331
|
- Codebase map injection (`--map`)
|
|
296
332
|
- Cursor CLI `agent` as alternate supervisor
|
|
297
333
|
- Web UI / CAO-style MCP supervisor
|
package/SECURITY.md
CHANGED
|
@@ -15,14 +15,15 @@ Do **not** open a public issue for credential leaks or RCE-class bugs.
|
|
|
15
15
|
|
|
16
16
|
1. **No secrets in prompts or job files.** Do not paste API keys, SSH private keys, Discord tokens, or `.env` contents into `cursor-route start` / `send` prompts.
|
|
17
17
|
2. **Free OpenRouter easy-lane models may log prompts.** `--lane easy` / `--worker openrouter` is for **non-secret** wording/drafts only. The refuse gate (`start` / `send`, plus the runner's re-check) applies to every lane — never send key-shaped material to the easy lane.
|
|
18
|
-
3. **
|
|
19
|
-
4. **
|
|
20
|
-
5. **
|
|
21
|
-
6. **
|
|
18
|
+
3. **OpenCode Zen free models may log or train during their free period.** `--worker opencode` defaults to `opencode/big-pickle`. Treat it like the easy lane for secrets. Prefer `opencode/x-preview-f-free` (Ox Alpha) when you need the zero-retention free option.
|
|
19
|
+
4. **Always-approve is powerful.** Default headless workers skip interactive permission prompts (`--always-approve` / `--dangerously-skip-permissions` / OpenCode `--auto`). Opt out with `--ask` / `CURSOR_ROUTE_ASK=1` when you need HITL.
|
|
20
|
+
5. **Install from known channels only.** Prefer `npm i -g cursor-route` or a git clone of this repo. Do not pipe unknown `curl | bash` installers as the primary path.
|
|
21
|
+
6. **Workers inherit your auth.** Grok CLI, Claude Code / claude-ds, and OpenCode use *your* local login. Treat worker panes like you would treat your own shell.
|
|
22
|
+
7. **No LIVE Discord / trading egress demos** from this tool. Orchestration is local.
|
|
22
23
|
|
|
23
24
|
## Supply chain
|
|
24
25
|
|
|
25
|
-
- Declared runtime npm dependencies: **none**. System needs: Node ≥20 or Bun, `tmux`, worker CLIs (Grok / claude-ds), and an OpenRouter API key for the easy lane.
|
|
26
|
+
- Declared runtime npm dependencies: **none**. System needs: Node ≥20 or Bun, `tmux`, worker CLIs (Grok / claude-ds / optional OpenCode), and an OpenRouter API key for the easy lane.
|
|
26
27
|
- The npm package ships a compiled `dist/` (plain JS, no loader). The bin shim and the completion hook run the compiled JS via `node`; there is **no network fetch of `tsx` on the happy path**.
|
|
27
28
|
- Running from a git clone without a build uses Bun to execute `src/` directly (Bun runs TypeScript natively) — also offline-friendly.
|
|
28
29
|
- Review `package.json` before upgrading.
|
package/SUPPORT.md
CHANGED
|
@@ -6,9 +6,11 @@ Voluntary tips fund wikis, OSS tooling, and public research. **Donation-only add
|
|
|
6
6
|
|
|
7
7
|
| Chain family | Address |
|
|
8
8
|
|--------------|---------|
|
|
9
|
+
| **X Money** (fiat, US) | Request [@Cemini23](https://x.com/Cemini23) in the X app — scan the Request QR |
|
|
9
10
|
| **EVM** (Ethereum, Polygon, Base, Arbitrum, …) | `0x444C5C2eC439E0382aa5a17F70313c536BcC5D58` |
|
|
10
11
|
| **Solana / SVM** | `J4zNn4hK9jTrKBFY8sbAGJHLoZvXvQf4B9pQSbSrocZE` |
|
|
11
12
|
| **Polymarket** (referral) | [polymarket.com/?r=Cemini23](https://polymarket.com/?r=Cemini23) |
|
|
13
|
+
| **Hyperliquid** (referral) | [app.hyperliquid.xyz/join/CEMINI23](https://app.hyperliquid.xyz/join/CEMINI23) |
|
|
12
14
|
|
|
13
15
|
If you’d rather follow along or try something we ship:
|
|
14
16
|
|
|
@@ -108,6 +108,24 @@ function resolveClaudeDs() {
|
|
|
108
108
|
}
|
|
109
109
|
return null;
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Mid lane is proven DeepSeek only via shim (`claude-ds` / `deepseek-claude`)
|
|
113
|
+
* or stock `claude` routed to DeepSeek. The Anthropic escape hatch is not proof.
|
|
114
|
+
*/
|
|
115
|
+
export function isMidDeepSeekProven() {
|
|
116
|
+
const resolved = resolveClaudeDs();
|
|
117
|
+
if (!resolved)
|
|
118
|
+
return false;
|
|
119
|
+
return !resolved.mode.startsWith("claude → Anthropic");
|
|
120
|
+
}
|
|
121
|
+
/** Human detail for health `lane:mid` (mode when proven; install hint otherwise). */
|
|
122
|
+
export function midDeepSeekProofDetail() {
|
|
123
|
+
const resolved = resolveClaudeDs();
|
|
124
|
+
if (!resolved || resolved.mode.startsWith("claude → Anthropic")) {
|
|
125
|
+
return "mid not proven DeepSeek — install claude-ds / set ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic. CURSOR_ROUTE_ALLOW_ANTHROPIC=1 is not proof.";
|
|
126
|
+
}
|
|
127
|
+
return `DeepSeek proven (${resolved.mode})`;
|
|
128
|
+
}
|
|
111
129
|
function pickModel(requested, modelId) {
|
|
112
130
|
if (modelId) {
|
|
113
131
|
const alias = requested ?? resolveDsModel(modelId).alias;
|
package/dist/adapters/index.js
CHANGED
|
@@ -2,11 +2,13 @@ import { grokAdapter } from "./grok.js";
|
|
|
2
2
|
import { claudeDsAdapter } from "./claude-ds.js";
|
|
3
3
|
import { openRouterAdapter } from "./openrouter.js";
|
|
4
4
|
import { deepseekAdapter } from "./deepseek.js";
|
|
5
|
+
import { opencodeAdapter } from "./opencode.js";
|
|
5
6
|
const registry = {
|
|
6
7
|
grok: grokAdapter,
|
|
7
8
|
"claude-ds": claudeDsAdapter,
|
|
8
9
|
openrouter: openRouterAdapter,
|
|
9
10
|
deepseek: deepseekAdapter,
|
|
11
|
+
opencode: opencodeAdapter,
|
|
10
12
|
};
|
|
11
13
|
export function getAdapter(worker) {
|
|
12
14
|
const a = registry[worker];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { shellQuote } from "../util.js";
|
|
4
|
+
import { openCodeModel, OPENCODE_DEFAULT_MODEL } from "../config.js";
|
|
5
|
+
/**
|
|
6
|
+
* Opt-in OpenCode worker (`opencode run`) — a coding agent that can use
|
|
7
|
+
* OpenCode Zen free models (default `opencode/big-pickle`) to cut Grok /
|
|
8
|
+
* DeepSeek usage. Not a lane default: mid stays claude-ds; easy stays
|
|
9
|
+
* OpenRouter chat (no tools).
|
|
10
|
+
*
|
|
11
|
+
* We never rewrite ~/.config/opencode/opencode.json (parallel jobs would
|
|
12
|
+
* race). Always-approve maps to `opencode run --auto` (still honors explicit
|
|
13
|
+
* deny rules). Prompt is inlined via cat — never interpolated.
|
|
14
|
+
*/
|
|
15
|
+
function findOpencode() {
|
|
16
|
+
// Env override lets tests pin a fake opencode — but it must exist, so a
|
|
17
|
+
// stale override cannot pass health with a dangling path.
|
|
18
|
+
const override = process.env.CURSOR_ROUTE_OPENCODE_BIN;
|
|
19
|
+
if (override)
|
|
20
|
+
return existsSync(override) ? override : null;
|
|
21
|
+
try {
|
|
22
|
+
return (execSync("command -v opencode", {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
25
|
+
env: { ...process.env },
|
|
26
|
+
}).trim() || null);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export const opencodeAdapter = {
|
|
33
|
+
kind: "opencode",
|
|
34
|
+
label: "OpenCode (free Zen / other models)",
|
|
35
|
+
health() {
|
|
36
|
+
const binary = findOpencode();
|
|
37
|
+
if (!binary) {
|
|
38
|
+
return {
|
|
39
|
+
worker: "opencode",
|
|
40
|
+
ok: false,
|
|
41
|
+
binary: null,
|
|
42
|
+
detail: "opencode not found — install: npm i -g opencode-ai (or brew install opencode). Then: opencode auth login. Mid default remains claude-ds.",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
let defaultModel = OPENCODE_DEFAULT_MODEL;
|
|
46
|
+
try {
|
|
47
|
+
defaultModel = openCodeModel();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* invalid env — health still ok; startJob will fail loud */
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
worker: "opencode",
|
|
54
|
+
ok: true,
|
|
55
|
+
binary,
|
|
56
|
+
detail: `ok (opencode run; default model ${defaultModel}; auth at first start — opencode auth login if jobs fail; mid default remains claude-ds)`,
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
buildLaunch({ promptFile, cwd, alwaysApprove, modelId }) {
|
|
60
|
+
// Missing binary is tolerated here so `--dry-run` can still print the
|
|
61
|
+
// command; real starts are gated by the health preflight.
|
|
62
|
+
const binary = findOpencode() || "opencode";
|
|
63
|
+
const id = openCodeModel(modelId);
|
|
64
|
+
const ask = process.env.CURSOR_ROUTE_ASK === "1";
|
|
65
|
+
const skip = alwaysApprove && !ask;
|
|
66
|
+
const parts = [
|
|
67
|
+
shellQuote(binary),
|
|
68
|
+
"run",
|
|
69
|
+
"--dir",
|
|
70
|
+
shellQuote(cwd),
|
|
71
|
+
"--model",
|
|
72
|
+
shellQuote(id),
|
|
73
|
+
];
|
|
74
|
+
if (skip)
|
|
75
|
+
parts.push("--auto");
|
|
76
|
+
parts.push(`"$(cat ${shellQuote(promptFile)})"`);
|
|
77
|
+
return {
|
|
78
|
+
worker: "opencode",
|
|
79
|
+
command: `cd ${shellQuote(cwd)} && ${parts.join(" ")}`,
|
|
80
|
+
alwaysApprove: skip,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* cursor-route CLI — Cursor brain, Grok + DeepSeek + OpenRouter (easy) workers in tmux.
|
|
3
|
+
* cursor-route CLI — Cursor brain, Grok + DeepSeek + OpenRouter (easy) + OpenCode (opt-in) workers in tmux.
|
|
4
4
|
*/
|
|
5
5
|
import { readFileSync, existsSync, realpathSync, statSync } from "node:fs";
|
|
6
6
|
import { resolve, basename } from "node:path";
|
|
7
|
-
import { config, WORKERS, LANES, resolveDsModel, } from "./config.js";
|
|
7
|
+
import { config, WORKERS, LANES, resolveDsModel, openCodeModel, } from "./config.js";
|
|
8
8
|
import { runHealth, printHealth } from "./health.js";
|
|
9
|
-
import { startJob, listJobs, readJob, killJob, cleanJobs, jobPaths, refreshStatus, } from "./jobs.js";
|
|
9
|
+
import { startJob, listJobs, readJob, killJob, cleanJobs, jobPaths, refreshStatus, jobEvidence, } from "./jobs.js";
|
|
10
10
|
import { capturePane, sendKeys, attachHint, listManagedSessions, sessionExists, } from "./tmux.js";
|
|
11
11
|
import { looksLikeSecretMaterial, redactSecrets } from "./secrets.js";
|
|
12
12
|
function usage(exitCode = 0) {
|
|
13
13
|
console.log(`cursor-route v${config.version}
|
|
14
14
|
|
|
15
|
-
Cursor stays the brain. Grok CLI + DeepSeek (claude-ds) + OpenRouter (easy) are the parallel army.
|
|
15
|
+
Cursor stays the brain. Grok CLI + DeepSeek (claude-ds) + OpenRouter (easy) + OpenCode (opt-in free) are the parallel army.
|
|
16
16
|
|
|
17
17
|
Usage:
|
|
18
18
|
cursor-route --version
|
|
19
|
-
cursor-route health [--json]
|
|
19
|
+
cursor-route health [--json] # JSON includes lanes.mid (DeepSeek proof)
|
|
20
20
|
cursor-route start <prompt> [options]
|
|
21
21
|
cursor-route start --prompt-file <path> [options]
|
|
22
22
|
cursor-route jobs [--json] [--limit N]
|
|
23
|
-
cursor-route status <jobId> [--json]
|
|
23
|
+
cursor-route status <jobId> [--json] # JSON includes evidence spawn/execute/verify
|
|
24
24
|
cursor-route capture <jobId> [lines]
|
|
25
25
|
cursor-route send <jobId> <message>
|
|
26
26
|
cursor-route attach <jobId>
|
|
@@ -29,9 +29,9 @@ Usage:
|
|
|
29
29
|
cursor-route clean [--days N]
|
|
30
30
|
|
|
31
31
|
Start options:
|
|
32
|
-
--worker <grok|claude-ds|openrouter|deepseek> Worker adapter (default: grok; deepseek =
|
|
32
|
+
--worker <grok|claude-ds|openrouter|deepseek|opencode> Worker adapter (default: grok; deepseek/opencode = opt-in)
|
|
33
33
|
--lane <easy|mid|hard> Lane → worker (easy=openrouter, mid=claude-ds, hard=grok)
|
|
34
|
-
--model <flash|pro>
|
|
34
|
+
--model <flash|pro|free|provider/model> claude-ds/deepseek: flash|pro. opencode: free (default opencode/big-pickle) or provider/model
|
|
35
35
|
--dir <path> Working directory (default: cwd)
|
|
36
36
|
--ask Disable always-approve for this job
|
|
37
37
|
--dry-run Print launch command; do not start
|
|
@@ -48,10 +48,12 @@ Env:
|
|
|
48
48
|
CURSOR_ROUTE_GROK_BIN Override the grok binary path (tests / power users)
|
|
49
49
|
CURSOR_ROUTE_CLAUDE_DS_BIN Override the claude-ds binary path (tests / power users)
|
|
50
50
|
CURSOR_ROUTE_DSH_BIN Override the dsh binary path (tests / power users)
|
|
51
|
+
CURSOR_ROUTE_OPENCODE_BIN Override the opencode binary path (tests / power users)
|
|
51
52
|
DEEPSEEK_API_KEY DeepSeek API key (required for --worker deepseek)
|
|
52
53
|
OPENROUTER_API_KEY OpenRouter key (required for --worker openrouter / --lane easy)
|
|
53
54
|
CURSOR_ROUTE_OPENROUTER_MODEL OpenRouter model (default: openrouter/free)
|
|
54
55
|
OPENROUTER_BASE_URL OpenRouter API base (default: https://openrouter.ai/api/v1)
|
|
56
|
+
CURSOR_ROUTE_OPENCODE_MODEL OpenCode model (default: opencode/big-pickle); --model overrides
|
|
55
57
|
`);
|
|
56
58
|
process.exit(exitCode);
|
|
57
59
|
}
|
|
@@ -150,6 +152,14 @@ function asDsModelChoice(v) {
|
|
|
150
152
|
throw new Error(`Invalid --model; expected flash|pro`);
|
|
151
153
|
return resolveDsModel(v);
|
|
152
154
|
}
|
|
155
|
+
function asOpenCodeModel(v) {
|
|
156
|
+
if (v === undefined || v === true)
|
|
157
|
+
return undefined;
|
|
158
|
+
if (typeof v !== "string") {
|
|
159
|
+
throw new Error(`Invalid --model; expected provider/model (e.g. opencode/big-pickle) or free`);
|
|
160
|
+
}
|
|
161
|
+
return openCodeModel(v);
|
|
162
|
+
}
|
|
153
163
|
function refuseSecrets(text, context) {
|
|
154
164
|
if (looksLikeSecretMaterial(text)) {
|
|
155
165
|
console.error(`Refusing ${context}: looks like secret key material. Remove tokens/keys and retry.`);
|
|
@@ -223,8 +233,8 @@ async function main() {
|
|
|
223
233
|
const resolvedWorker = worker ?? (lane ? config.laneWorkers[lane] : config.defaultWorker);
|
|
224
234
|
let model;
|
|
225
235
|
let modelId;
|
|
226
|
-
// --model
|
|
227
|
-
// ignore (do not validate) for
|
|
236
|
+
// --model: DeepSeek flash|pro for claude-ds/deepseek; provider/model (or free) for opencode;
|
|
237
|
+
// ignore (do not validate) for grok/openrouter
|
|
228
238
|
if (f.model !== undefined && (resolvedWorker === "claude-ds" || resolvedWorker === "deepseek")) {
|
|
229
239
|
try {
|
|
230
240
|
const choice = asDsModelChoice(f.model);
|
|
@@ -238,6 +248,15 @@ async function main() {
|
|
|
238
248
|
process.exit(2);
|
|
239
249
|
}
|
|
240
250
|
}
|
|
251
|
+
else if (f.model !== undefined && resolvedWorker === "opencode") {
|
|
252
|
+
try {
|
|
253
|
+
modelId = asOpenCodeModel(f.model);
|
|
254
|
+
}
|
|
255
|
+
catch (e) {
|
|
256
|
+
console.error(e.message);
|
|
257
|
+
process.exit(2);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
241
260
|
let prompt = "";
|
|
242
261
|
if (promptFile) {
|
|
243
262
|
const p = resolve(promptFile);
|
|
@@ -327,7 +346,7 @@ async function main() {
|
|
|
327
346
|
else {
|
|
328
347
|
for (const j of jobs) {
|
|
329
348
|
const age = j.startedAt || j.createdAt;
|
|
330
|
-
const modelCol = j.model ? j.model.padEnd(
|
|
349
|
+
const modelCol = j.model ? j.model.padEnd(22) : "".padEnd(22);
|
|
331
350
|
console.log(`${j.id} ${j.status.padEnd(10)} ${j.worker.padEnd(10)} ${modelCol} ${age} ${j.prompt.slice(0, 48).replace(/\n/g, " ")}`);
|
|
332
351
|
}
|
|
333
352
|
}
|
|
@@ -356,11 +375,13 @@ async function main() {
|
|
|
356
375
|
}
|
|
357
376
|
})())
|
|
358
377
|
: sessionExists(job.tmuxSession);
|
|
359
|
-
const
|
|
378
|
+
const evidence = jobEvidence(job, alive);
|
|
379
|
+
const view = { ...job, sessionAlive: alive, evidence };
|
|
360
380
|
if (json)
|
|
361
381
|
console.log(JSON.stringify(view, null, 2));
|
|
362
382
|
else {
|
|
363
383
|
console.log(`${job.id} ${job.status} worker=${job.worker}${job.model ? ` model=${job.model}` : ""} sessionAlive=${alive}`);
|
|
384
|
+
console.log("evidence: spawn/execute/verify (claim=unverified)");
|
|
364
385
|
if (job.error)
|
|
365
386
|
console.log(`error: ${job.error}`);
|
|
366
387
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { defaultJobsDir } from "./runtime.js";
|
|
4
|
-
export const WORKERS = ["grok", "claude-ds", "openrouter", "deepseek"];
|
|
4
|
+
export const WORKERS = ["grok", "claude-ds", "openrouter", "deepseek", "opencode"];
|
|
5
5
|
export const LANES = ["easy", "mid", "hard"];
|
|
6
6
|
export const DS_MODELS = ["flash", "pro"];
|
|
7
7
|
export const DS_MODEL_IDS = {
|
|
@@ -49,6 +49,33 @@ export function openRouterModel() {
|
|
|
49
49
|
export function openRouterBaseUrl() {
|
|
50
50
|
return process.env.OPENROUTER_BASE_URL || "https://openrouter.ai/api/v1";
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Default OpenCode model: OpenCode Zen Big Pickle (free, limited-time).
|
|
54
|
+
* Override: CURSOR_ROUTE_OPENCODE_MODEL or `--model provider/model`.
|
|
55
|
+
* Alias `free` resolves to this default (or the env override).
|
|
56
|
+
*/
|
|
57
|
+
export const OPENCODE_DEFAULT_MODEL = "opencode/big-pickle";
|
|
58
|
+
/** Whitelist OpenCode `provider/model` ids (no spaces / injection). */
|
|
59
|
+
export function assertOpenCodeModel(id) {
|
|
60
|
+
if (!/^[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9][a-z0-9._\-:]+)+$/i.test(id)) {
|
|
61
|
+
throw new Error(`Invalid OpenCode model ${id}; expected provider/model (e.g. opencode/big-pickle) or free`);
|
|
62
|
+
}
|
|
63
|
+
return id;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve OpenCode `--model` / env to a concrete `provider/model` id.
|
|
67
|
+
* Empty or `free` → CURSOR_ROUTE_OPENCODE_MODEL, else OPENCODE_DEFAULT_MODEL.
|
|
68
|
+
*/
|
|
69
|
+
export function openCodeModel(raw) {
|
|
70
|
+
const v = (raw ?? "").trim();
|
|
71
|
+
if (!v || v.toLowerCase() === "free") {
|
|
72
|
+
const env = (process.env.CURSOR_ROUTE_OPENCODE_MODEL ?? "").trim();
|
|
73
|
+
if (!env || env.toLowerCase() === "free")
|
|
74
|
+
return OPENCODE_DEFAULT_MODEL;
|
|
75
|
+
return assertOpenCodeModel(env);
|
|
76
|
+
}
|
|
77
|
+
return assertOpenCodeModel(v);
|
|
78
|
+
}
|
|
52
79
|
function maxConcurrentJobsFromEnv() {
|
|
53
80
|
const raw = process.env.CURSOR_ROUTE_MAX_JOBS;
|
|
54
81
|
if (raw) {
|
|
@@ -65,7 +92,7 @@ function maxConcurrentJobsFromEnv() {
|
|
|
65
92
|
*/
|
|
66
93
|
export const config = {
|
|
67
94
|
product: "cursor-route",
|
|
68
|
-
version: "0.1.
|
|
95
|
+
version: "0.1.10",
|
|
69
96
|
get jobsDir() {
|
|
70
97
|
return defaultJobsDir();
|
|
71
98
|
},
|
|
@@ -73,7 +100,7 @@ export const config = {
|
|
|
73
100
|
defaultWorker: "grok",
|
|
74
101
|
/**
|
|
75
102
|
* Lane → default worker (Cemini /route public core).
|
|
76
|
-
* `deepseek`
|
|
103
|
+
* `deepseek` and `opencode` are opt-in only — mid stays on claude-ds.
|
|
77
104
|
*/
|
|
78
105
|
laneWorkers: {
|
|
79
106
|
easy: "openrouter",
|
package/dist/health.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { allAdapters } from "./adapters/index.js";
|
|
3
|
+
import { isMidDeepSeekProven, midDeepSeekProofDetail } from "./adapters/claude-ds.js";
|
|
3
4
|
import { config } from "./config.js";
|
|
4
5
|
import { isTmuxAvailable } from "./tmux.js";
|
|
5
6
|
import { commandExists } from "./util.js";
|
|
@@ -42,6 +43,14 @@ export function runHealth() {
|
|
|
42
43
|
detail: h.binary ? `${h.detail} @ ${h.binary}` : h.detail,
|
|
43
44
|
});
|
|
44
45
|
}
|
|
46
|
+
// Informational: mid is proven DeepSeek (not the overall OR-gate).
|
|
47
|
+
const midOk = isMidDeepSeekProven();
|
|
48
|
+
const midDetail = midDeepSeekProofDetail();
|
|
49
|
+
checks.push({
|
|
50
|
+
name: "lane:mid",
|
|
51
|
+
ok: midOk,
|
|
52
|
+
detail: midDetail,
|
|
53
|
+
});
|
|
45
54
|
// Optional supervisor probe (v0 skill-only; Cursor CLI agent is informational)
|
|
46
55
|
const agentBin = (commandExists("agent") && "agent") ||
|
|
47
56
|
(commandExists("cursor-agent") && "cursor-agent") ||
|
|
@@ -78,6 +87,9 @@ export function runHealth() {
|
|
|
78
87
|
product: config.product,
|
|
79
88
|
version: config.version,
|
|
80
89
|
checks,
|
|
90
|
+
lanes: {
|
|
91
|
+
mid: { worker: "claude-ds", deepseek: midOk, detail: midDetail },
|
|
92
|
+
},
|
|
81
93
|
};
|
|
82
94
|
}
|
|
83
95
|
export function printHealth(report, asJson) {
|
|
@@ -95,6 +107,10 @@ export function printHealth(report, asJson) {
|
|
|
95
107
|
if (!report.ok) {
|
|
96
108
|
console.log("");
|
|
97
109
|
console.log("Fix the ✗ items, then re-run: cursor-route health");
|
|
98
|
-
console.log("Tip: start with one worker (grok OR claude-ds) before parallel demos.");
|
|
110
|
+
console.log("Tip: start with one worker (grok OR claude-ds OR opencode) before parallel demos.");
|
|
111
|
+
}
|
|
112
|
+
else if (report.checks.some((c) => c.name === "lane:mid" && !c.ok)) {
|
|
113
|
+
console.log("");
|
|
114
|
+
console.log("Tip: health OK without a DeepSeek mid — --lane mid will fail until lane:mid is ✓.");
|
|
99
115
|
}
|
|
100
116
|
}
|