cursor-route 0.1.9 → 0.1.11
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 +15 -0
- package/CONTRIBUTING.md +2 -0
- package/README.md +42 -4
- package/SECURITY.md +6 -5
- package/SUPPORT.md +1 -0
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/opencode.js +78 -0
- package/dist/cli.js +27 -8
- package/dist/config.js +4 -3
- package/dist/health.js +1 -1
- package/dist/jobs.js +21 -2
- package/dist/zen-free.js +237 -0
- package/docs/briefs/WORKING.md +11 -4
- package/llms.txt +8 -4
- package/package.json +3 -2
- package/skills/route-orch/SKILL.md +22 -7
- package/src/adapters/index.ts +2 -0
- package/src/adapters/opencode.test.ts +297 -0
- package/src/adapters/opencode.ts +85 -0
- package/src/adapters/types.ts +1 -1
- package/src/cli.test.ts +28 -3
- package/src/cli.ts +25 -7
- package/src/config.ts +14 -6
- package/src/health.ts +1 -1
- package/src/jobs.ts +23 -6
- package/src/zen-free.test.ts +137 -0
- package/src/zen-free.ts +258 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
_(none)_
|
|
6
6
|
|
|
7
|
+
## 0.1.11 — 2026-08-21
|
|
8
|
+
|
|
9
|
+
- `--worker opencode --model free` now **ranks the live OpenCode Zen catalog** (`GET https://opencode.ai/zen/v1/models`, cached ~15 min) instead of hardcoding `opencode/big-pickle`. Ox Alpha (`opencode/x-preview-f-free`) wins while it is listed and free; coding `-free` models next; `big-pickle` last among non-contributor free. Fetch-fail fallback is Ox Alpha.
|
|
10
|
+
- Pin with `--model provider/model` or `CURSOR_ROUTE_OPENCODE_MODEL`. Tests/CI: `CURSOR_ROUTE_ZEN_OFFLINE=1` or `CURSOR_ROUTE_ZEN_CATALOG_JSON` (health never fetches — cache or fallback only).
|
|
11
|
+
- Mid stays `claude-ds`; easy stays OpenRouter chat. OpenCode is still opt-in, not a lane default.
|
|
12
|
+
|
|
13
|
+
## 0.1.10 — 2026-08-21
|
|
14
|
+
|
|
15
|
+
- 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.
|
|
16
|
+
- Always-approve → `opencode run --auto` (still honors explicit deny rules); `--ask` / `CURSOR_ROUTE_ASK=1` omits `--auto`. Never rewrites `~/.config/opencode/opencode.json`.
|
|
17
|
+
- `--model provider/model` for opencode (env `CURSOR_ROUTE_OPENCODE_MODEL`); `flash|pro` still DeepSeek-only.
|
|
18
|
+
- 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).
|
|
19
|
+
- Dry-run works without opencode installed (falls back to plain `opencode` in the printed command); real starts stay gated by health.
|
|
20
|
+
- 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.
|
|
21
|
+
|
|
7
22
|
## 0.1.9 — 2026-08-18
|
|
8
23
|
|
|
9
24
|
- 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.
|
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 live Zen free pick (Ox Alpha while listed) |
|
|
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):
|
|
@@ -221,6 +224,39 @@ 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
|
+
live OpenCode Zen free models (`--model free` fetches the catalog and ranks,
|
|
231
|
+
same idea as the OpenRouter free picker). Ox Alpha (`opencode/x-preview-f-free`)
|
|
232
|
+
wins while it is listed and free. This is **not** a lane default — mid stays
|
|
233
|
+
`claude-ds`; easy stays OpenRouter chat (no tools). Use it to burn fewer Grok /
|
|
234
|
+
DeepSeek tokens on implement work.
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
npm i -g opencode-ai # or: brew install opencode
|
|
238
|
+
opencode auth login # connect OpenCode Zen (or another provider)
|
|
239
|
+
# optional pin (skips the live catalog pick):
|
|
240
|
+
export CURSOR_ROUTE_OPENCODE_MODEL=opencode/hy3-free
|
|
241
|
+
|
|
242
|
+
cursor-route health # worker:opencode should be ✓
|
|
243
|
+
cursor-route start --worker opencode "…" # live Zen free pick
|
|
244
|
+
cursor-route start --worker opencode --model free "…"
|
|
245
|
+
cursor-route start --worker opencode --model opencode/hy3-free "…"
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The adapter launches `opencode run --dir <cwd> --model <id>` with the prompt via
|
|
249
|
+
`cat` (never interpolated). Always-approve maps to `--auto` (explicit `"deny"`
|
|
250
|
+
rules still apply); `--ask` omits `--auto`. It never rewrites
|
|
251
|
+
`~/.config/opencode/opencode.json`, so parallel jobs don't race. Override the
|
|
252
|
+
binary with `CURSOR_ROUTE_OPENCODE_BIN`. `--model free` caches the ranked catalog
|
|
253
|
+
for ~15 minutes (`CURSOR_ROUTE_ZEN_CACHE_MINUTES`).
|
|
254
|
+
|
|
255
|
+
**Non-secret prompts:** several Zen free models may log or train on prompts during
|
|
256
|
+
their free period (see [OpenCode Zen](https://opencode.ai/docs/zen/) privacy notes).
|
|
257
|
+
The same refuse gate as every lane still applies. `opencode/x-preview-f-free`
|
|
258
|
+
(Ox Alpha) is the zero-retention free option if you need it.
|
|
259
|
+
|
|
224
260
|
## Jobs directory
|
|
225
261
|
|
|
226
262
|
Jobs default to `~/.local/share/cursor-route/jobs` (override with `CURSOR_ROUTE_JOBS_DIR`).
|
|
@@ -235,7 +271,7 @@ This is **not** inside a git clone of this repo.
|
|
|
235
271
|
## FAQ
|
|
236
272
|
|
|
237
273
|
**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,
|
|
274
|
+
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
275
|
|
|
240
276
|
**How is this different from Codex orchestrator?**
|
|
241
277
|
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 +283,7 @@ No. The mid worker is DeepSeek. Claude Code is the harness, configured with `ANT
|
|
|
247
283
|
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
284
|
|
|
249
285
|
**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`).
|
|
286
|
+
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 live OpenCode Zen free models (`--model free` ranks the catalog).
|
|
251
287
|
|
|
252
288
|
## Related
|
|
253
289
|
|
|
@@ -280,6 +316,7 @@ If you’d like to tip, use the **donation-only** addresses below (not trading o
|
|
|
280
316
|
| **EVM** (Ethereum, Polygon, Base, Arbitrum, …) | `0x444C5C2eC439E0382aa5a17F70313c536BcC5D58` |
|
|
281
317
|
| **Solana / SVM** | `J4zNn4hK9jTrKBFY8sbAGJHLoZvXvQf4B9pQSbSrocZE` |
|
|
282
318
|
| **Polymarket** (referral) | [polymarket.com/?r=Cemini23](https://polymarket.com/?r=Cemini23) |
|
|
319
|
+
| **Hyperliquid** (referral) | [app.hyperliquid.xyz/join/CEMINI23](https://app.hyperliquid.xyz/join/CEMINI23) |
|
|
283
320
|
|
|
284
321
|
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).
|
|
285
322
|
|
|
@@ -293,6 +330,7 @@ MIT © Cemini — see [LICENSE](LICENSE).
|
|
|
293
330
|
|
|
294
331
|
- Homebrew tap
|
|
295
332
|
- Stabilize the DeepSeek harness adapter (experimental `--worker deepseek` since 0.1.8)
|
|
333
|
+
- Stabilize the OpenCode adapter (`--worker opencode` since 0.1.10)
|
|
296
334
|
- Codebase map injection (`--map`)
|
|
297
335
|
- Cursor CLI `agent` as alternate supervisor
|
|
298
336
|
- 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 --model free` picks from the live Zen catalog (Ox Alpha / `x-preview-f-free` currently ranks first and is zero-retention). Treat other free Zen models like the easy lane for secrets. Pin `CURSOR_ROUTE_OPENCODE_MODEL` when you need a specific id.
|
|
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
|
@@ -10,6 +10,7 @@ Voluntary tips fund wikis, OSS tooling, and public research. **Donation-only add
|
|
|
10
10
|
| **EVM** (Ethereum, Polygon, Base, Arbitrum, …) | `0x444C5C2eC439E0382aa5a17F70313c536BcC5D58` |
|
|
11
11
|
| **Solana / SVM** | `J4zNn4hK9jTrKBFY8sbAGJHLoZvXvQf4B9pQSbSrocZE` |
|
|
12
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) |
|
|
13
14
|
|
|
14
15
|
If you’d rather follow along or try something we ship:
|
|
15
16
|
|
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,78 @@
|
|
|
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, cachedZenFreePick } from "../config.js";
|
|
5
|
+
/**
|
|
6
|
+
* Opt-in OpenCode worker (`opencode run`) — a coding agent on live OpenCode
|
|
7
|
+
* Zen free models (`--model free` ranks the catalog; Ox Alpha wins while
|
|
8
|
+
* listed). Not a lane default: mid stays claude-ds; easy stays OpenRouter
|
|
9
|
+
* 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
|
+
// Health stays offline: show a fresh cache hit, else the Ox Alpha fallback.
|
|
46
|
+
const pick = cachedZenFreePick() || OPENCODE_DEFAULT_MODEL;
|
|
47
|
+
return {
|
|
48
|
+
worker: "opencode",
|
|
49
|
+
ok: true,
|
|
50
|
+
binary,
|
|
51
|
+
detail: `ok (opencode run; --model free = live Zen pick, now ${pick}; auth at first start — opencode auth login if jobs fail; mid default remains claude-ds)`,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
buildLaunch({ promptFile, cwd, alwaysApprove, modelId }) {
|
|
55
|
+
// Missing binary is tolerated here so `--dry-run` can still print the
|
|
56
|
+
// command; real starts are gated by the health preflight.
|
|
57
|
+
const binary = findOpencode() || "opencode";
|
|
58
|
+
const id = openCodeModel(modelId);
|
|
59
|
+
const ask = process.env.CURSOR_ROUTE_ASK === "1";
|
|
60
|
+
const skip = alwaysApprove && !ask;
|
|
61
|
+
const parts = [
|
|
62
|
+
shellQuote(binary),
|
|
63
|
+
"run",
|
|
64
|
+
"--dir",
|
|
65
|
+
shellQuote(cwd),
|
|
66
|
+
"--model",
|
|
67
|
+
shellQuote(id),
|
|
68
|
+
];
|
|
69
|
+
if (skip)
|
|
70
|
+
parts.push("--auto");
|
|
71
|
+
parts.push(`"$(cat ${shellQuote(promptFile)})"`);
|
|
72
|
+
return {
|
|
73
|
+
worker: "opencode",
|
|
74
|
+
command: `cd ${shellQuote(cwd)} && ${parts.join(" ")}`,
|
|
75
|
+
alwaysApprove: skip,
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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
9
|
import { startJob, listJobs, readJob, killJob, cleanJobs, jobPaths, refreshStatus, jobEvidence, } from "./jobs.js";
|
|
10
10
|
import { capturePane, sendKeys, attachHint, listManagedSessions, sessionExists, } from "./tmux.js";
|
|
@@ -12,7 +12,7 @@ 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
|
|
@@ -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 (live Zen catalog pick) 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 pin (unset or free = live Zen free pick); --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/x-preview-f-free) 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
|
}
|
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,7 @@ export function openRouterModel() {
|
|
|
49
49
|
export function openRouterBaseUrl() {
|
|
50
50
|
return process.env.OPENROUTER_BASE_URL || "https://openrouter.ai/api/v1";
|
|
51
51
|
}
|
|
52
|
+
export { OPENCODE_DEFAULT_MODEL, assertOpenCodeModel, openCodeModel, cachedZenFreePick, } from "./zen-free.js";
|
|
52
53
|
function maxConcurrentJobsFromEnv() {
|
|
53
54
|
const raw = process.env.CURSOR_ROUTE_MAX_JOBS;
|
|
54
55
|
if (raw) {
|
|
@@ -65,7 +66,7 @@ function maxConcurrentJobsFromEnv() {
|
|
|
65
66
|
*/
|
|
66
67
|
export const config = {
|
|
67
68
|
product: "cursor-route",
|
|
68
|
-
version: "0.1.
|
|
69
|
+
version: "0.1.11",
|
|
69
70
|
get jobsDir() {
|
|
70
71
|
return defaultJobsDir();
|
|
71
72
|
},
|
|
@@ -73,7 +74,7 @@ export const config = {
|
|
|
73
74
|
defaultWorker: "grok",
|
|
74
75
|
/**
|
|
75
76
|
* Lane → default worker (Cemini /route public core).
|
|
76
|
-
* `deepseek`
|
|
77
|
+
* `deepseek` and `opencode` are opt-in only — mid stays on claude-ds.
|
|
77
78
|
*/
|
|
78
79
|
laneWorkers: {
|
|
79
80
|
easy: "openrouter",
|
package/dist/health.js
CHANGED
|
@@ -107,7 +107,7 @@ export function printHealth(report, asJson) {
|
|
|
107
107
|
if (!report.ok) {
|
|
108
108
|
console.log("");
|
|
109
109
|
console.log("Fix the ✗ items, then re-run: cursor-route health");
|
|
110
|
-
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
111
|
}
|
|
112
112
|
else if (report.checks.some((c) => c.name === "lane:mid" && !c.ok)) {
|
|
113
113
|
console.log("");
|
package/dist/jobs.js
CHANGED
|
@@ -2,7 +2,7 @@ import { mkdirSync, writeFileSync, readFileSync, readdirSync, existsSync, unlink
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
|
-
import { config, sessionName, defaultDsModelFromEnv, DS_MODEL_IDS, } from "./config.js";
|
|
5
|
+
import { config, sessionName, defaultDsModelFromEnv, DS_MODEL_IDS, openCodeModel, } from "./config.js";
|
|
6
6
|
import { getAdapter } from "./adapters/index.js";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
@@ -245,14 +245,17 @@ export function startJob(opts) {
|
|
|
245
245
|
writeSecure(paths.prompt, opts.prompt);
|
|
246
246
|
let model;
|
|
247
247
|
let modelId;
|
|
248
|
+
let dsAlias;
|
|
248
249
|
if (worker === "claude-ds" || worker === "deepseek") {
|
|
249
250
|
if (opts.model) {
|
|
251
|
+
dsAlias = opts.model;
|
|
250
252
|
model = opts.model;
|
|
251
253
|
modelId = opts.modelId ?? DS_MODEL_IDS[opts.model];
|
|
252
254
|
}
|
|
253
255
|
else {
|
|
254
256
|
try {
|
|
255
257
|
const choice = defaultDsModelFromEnv();
|
|
258
|
+
dsAlias = choice.alias;
|
|
256
259
|
model = choice.alias;
|
|
257
260
|
modelId = opts.modelId ?? choice.id;
|
|
258
261
|
}
|
|
@@ -267,13 +270,29 @@ export function startJob(opts) {
|
|
|
267
270
|
}
|
|
268
271
|
}
|
|
269
272
|
}
|
|
273
|
+
else if (worker === "opencode") {
|
|
274
|
+
try {
|
|
275
|
+
const ocModel = openCodeModel(opts.modelId);
|
|
276
|
+
model = ocModel;
|
|
277
|
+
modelId = ocModel;
|
|
278
|
+
}
|
|
279
|
+
catch (e) {
|
|
280
|
+
try {
|
|
281
|
+
unlinkSync(paths.prompt);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
/* ignore */
|
|
285
|
+
}
|
|
286
|
+
return { ok: false, error: e.message };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
270
289
|
let plan;
|
|
271
290
|
try {
|
|
272
291
|
plan = adapter.buildLaunch({
|
|
273
292
|
promptFile: paths.prompt,
|
|
274
293
|
cwd,
|
|
275
294
|
alwaysApprove,
|
|
276
|
-
model,
|
|
295
|
+
model: dsAlias,
|
|
277
296
|
modelId,
|
|
278
297
|
dryRun: Boolean(opts.dryRun),
|
|
279
298
|
});
|