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 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, and OpenRouter free models handle the easy lane for wording/drafts.
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. **Always-approve is powerful.** Default headless workers skip interactive permission prompts (`--always-approve` / `--dangerously-skip-permissions`). Opt out with `--ask` / `CURSOR_ROUTE_ASK=1` when you need HITL.
19
- 4. **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.
20
- 5. **Workers inherit your auth.** Grok CLI and Claude Code / claude-ds use *your* local login. Treat worker panes like you would treat your own shell.
21
- 6. **No LIVE Discord / trading egress demos** from this tool. Orchestration is local.
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
 
@@ -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 = experimental official dsh)
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> Mid DeepSeek only (default: flash / CURSOR_ROUTE_DS_MODEL). Parsed for claude-ds + deepseek
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 is DeepSeek-only (claude-ds mid lane + experimental deepseek worker);
227
- // ignore (do not validate) for other workers
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(6) : "".padEnd(6);
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.9",
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` is experimental only — mid stays on claude-ds.
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
  });