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/dist/zen-free.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
/**
|
|
6
|
+
* Live OpenCode Zen free-model pick — same idea as agent-toolkit
|
|
7
|
+
* `select-openrouter-free-model.ps1`: probe the catalog, keep $0 text/coding
|
|
8
|
+
* models, rank, cache ~15 min. Hardcoded ids are fallback only.
|
|
9
|
+
*
|
|
10
|
+
* Catalog: GET https://opencode.ai/zen/v1/models (no key). Zen ids are
|
|
11
|
+
* `big-pickle` / `x-preview-f-free`; the CLI wants `opencode/<id>`.
|
|
12
|
+
*/
|
|
13
|
+
export const ZEN_MODELS_URL_DEFAULT = "https://opencode.ai/zen/v1/models";
|
|
14
|
+
/** Offline / fetch-fail fallback — Ox Alpha (zero-retention, currently top-tier free). */
|
|
15
|
+
export const OPENCODE_DEFAULT_MODEL = "opencode/x-preview-f-free";
|
|
16
|
+
const EXCLUDE_RE = /lyria|whisper|tts|embed|embedding|image|vision-only|audio|diffusion|flux|stable-diffusion|moderation/i;
|
|
17
|
+
const CODING_RE = /coder|instruct|chat|laguna|nemotron|glm|qwen|kimi|deepseek|mimo|hy3|gpt-oss|north|gemma/i;
|
|
18
|
+
const OX_ALPHA_RE = /x-preview|ox-alpha|oxalpha/i;
|
|
19
|
+
const CONTRIBUTOR_RE = /contributor-free/i;
|
|
20
|
+
function zenModelsUrl() {
|
|
21
|
+
return (process.env.CURSOR_ROUTE_ZEN_MODELS_URL || ZEN_MODELS_URL_DEFAULT).trim();
|
|
22
|
+
}
|
|
23
|
+
function cacheMinutes() {
|
|
24
|
+
const n = Number(process.env.CURSOR_ROUTE_ZEN_CACHE_MINUTES);
|
|
25
|
+
return Number.isFinite(n) && n > 0 ? n : 15;
|
|
26
|
+
}
|
|
27
|
+
function cachePath() {
|
|
28
|
+
return process.env.CURSOR_ROUTE_ZEN_CACHE_PATH?.trim() ||
|
|
29
|
+
join(tmpdir(), "cursor-route-zen-best-free.json");
|
|
30
|
+
}
|
|
31
|
+
export function assertOpenCodeModel(id) {
|
|
32
|
+
if (!/^[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9][a-z0-9._\-:]+)+$/i.test(id)) {
|
|
33
|
+
throw new Error(`Invalid OpenCode model ${id}; expected provider/model (e.g. opencode/x-preview-f-free) or free`);
|
|
34
|
+
}
|
|
35
|
+
return id;
|
|
36
|
+
}
|
|
37
|
+
/** Prefix a Zen catalog id (`x-preview-f-free`) for `opencode run --model`. */
|
|
38
|
+
export function asOpenCodeZenId(raw) {
|
|
39
|
+
const id = raw.trim();
|
|
40
|
+
if (!id)
|
|
41
|
+
throw new Error("empty Zen model id");
|
|
42
|
+
return id.includes("/") ? assertOpenCodeModel(id) : assertOpenCodeModel(`opencode/${id}`);
|
|
43
|
+
}
|
|
44
|
+
function num(v) {
|
|
45
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
46
|
+
return v;
|
|
47
|
+
if (typeof v === "string" && v.trim()) {
|
|
48
|
+
const n = Number(v);
|
|
49
|
+
if (Number.isFinite(n))
|
|
50
|
+
return n;
|
|
51
|
+
}
|
|
52
|
+
return NaN;
|
|
53
|
+
}
|
|
54
|
+
export function isZenFreeModel(m) {
|
|
55
|
+
const id = (m.id || "").trim();
|
|
56
|
+
if (!id)
|
|
57
|
+
return false;
|
|
58
|
+
if (EXCLUDE_RE.test(id) || EXCLUDE_RE.test(m.name || ""))
|
|
59
|
+
return false;
|
|
60
|
+
const prompt = num(m.pricing?.prompt);
|
|
61
|
+
const completion = num(m.pricing?.completion);
|
|
62
|
+
const pricedFree = prompt === 0 && completion === 0;
|
|
63
|
+
const tagged = id.endsWith("-free") ||
|
|
64
|
+
id.endsWith(":free") ||
|
|
65
|
+
/(^|\/)big-pickle$/i.test(id);
|
|
66
|
+
return tagged || pricedFree;
|
|
67
|
+
}
|
|
68
|
+
/** Higher boost wins. Ox Alpha first while it is in the free catalog. */
|
|
69
|
+
export function zenFreeBoost(id) {
|
|
70
|
+
const s = id.toLowerCase();
|
|
71
|
+
if (OX_ALPHA_RE.test(s))
|
|
72
|
+
return 40;
|
|
73
|
+
if (CONTRIBUTOR_RE.test(s))
|
|
74
|
+
return 1;
|
|
75
|
+
if (CODING_RE.test(s))
|
|
76
|
+
return 20;
|
|
77
|
+
if (s.endsWith("-free") || s.endsWith(":free"))
|
|
78
|
+
return 10;
|
|
79
|
+
if (/(^|\/)big-pickle$/.test(s))
|
|
80
|
+
return 5;
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
export function rankZenFreeModels(models) {
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const m of models) {
|
|
86
|
+
if (!isZenFreeModel(m))
|
|
87
|
+
continue;
|
|
88
|
+
const raw = (m.id || "").trim();
|
|
89
|
+
let id;
|
|
90
|
+
try {
|
|
91
|
+
id = asOpenCodeZenId(raw);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const ctx = Number.isFinite(Number(m.context_length)) ? Number(m.context_length) : 0;
|
|
97
|
+
out.push({
|
|
98
|
+
id,
|
|
99
|
+
name: (m.name || raw).trim(),
|
|
100
|
+
context_length: ctx,
|
|
101
|
+
boost: zenFreeBoost(id),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
out.sort((a, b) => {
|
|
105
|
+
if (b.boost !== a.boost)
|
|
106
|
+
return b.boost - a.boost;
|
|
107
|
+
if (b.context_length !== a.context_length)
|
|
108
|
+
return b.context_length - a.context_length;
|
|
109
|
+
return a.id.localeCompare(b.id);
|
|
110
|
+
});
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
function readCache() {
|
|
114
|
+
const p = cachePath();
|
|
115
|
+
if (!existsSync(p))
|
|
116
|
+
return null;
|
|
117
|
+
try {
|
|
118
|
+
const j = JSON.parse(readFileSync(p, "utf8"));
|
|
119
|
+
if (!j?.id)
|
|
120
|
+
return null;
|
|
121
|
+
const ageMs = Date.now() - Date.parse(j.picked_at);
|
|
122
|
+
if (!Number.isFinite(ageMs) || ageMs < 0)
|
|
123
|
+
return null;
|
|
124
|
+
if (ageMs > cacheMinutes() * 60_000)
|
|
125
|
+
return null;
|
|
126
|
+
assertOpenCodeModel(j.id);
|
|
127
|
+
return j;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function writeCache(pick) {
|
|
134
|
+
try {
|
|
135
|
+
writeFileSync(cachePath(), JSON.stringify(pick), { mode: 0o600 });
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
/* cache is optional */
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function httpGetSync(url, timeoutMs = 4000) {
|
|
142
|
+
const sec = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
143
|
+
const curl = spawnSync("curl", ["-fsS", "--max-time", String(sec), "-H", "Accept: application/json", url], { encoding: "utf8", timeout: timeoutMs + 500, env: { ...process.env } });
|
|
144
|
+
if (curl.status === 0 && curl.stdout?.trim())
|
|
145
|
+
return curl.stdout;
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
function parseCatalog(raw) {
|
|
149
|
+
const j = JSON.parse(raw);
|
|
150
|
+
if (Array.isArray(j))
|
|
151
|
+
return j;
|
|
152
|
+
if (Array.isArray(j.data))
|
|
153
|
+
return j.data;
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
export function loadZenCatalog() {
|
|
157
|
+
const inline = process.env.CURSOR_ROUTE_ZEN_CATALOG_JSON?.trim();
|
|
158
|
+
if (inline)
|
|
159
|
+
return parseCatalog(inline);
|
|
160
|
+
if (process.env.CURSOR_ROUTE_ZEN_OFFLINE === "1")
|
|
161
|
+
return [];
|
|
162
|
+
const body = httpGetSync(zenModelsUrl());
|
|
163
|
+
if (!body)
|
|
164
|
+
return [];
|
|
165
|
+
try {
|
|
166
|
+
return parseCatalog(body);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** Last live pick if the cache is still fresh — health-safe (no network). */
|
|
173
|
+
export function cachedZenFreePick() {
|
|
174
|
+
try {
|
|
175
|
+
return readCache()?.id ?? null;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Best live Zen free model, or OPENCODE_DEFAULT_MODEL if the catalog is empty/offline.
|
|
183
|
+
* `CURSOR_ROUTE_ZEN_REFRESH=1` skips cache. Tests: `CURSOR_ROUTE_ZEN_OFFLINE=1` or
|
|
184
|
+
* `CURSOR_ROUTE_ZEN_CATALOG_JSON`.
|
|
185
|
+
*/
|
|
186
|
+
export function pickZenFreeModel(opts) {
|
|
187
|
+
const refresh = opts?.refresh ||
|
|
188
|
+
process.env.CURSOR_ROUTE_ZEN_REFRESH === "1";
|
|
189
|
+
if (!refresh && opts?.catalog === undefined) {
|
|
190
|
+
const cached = readCache();
|
|
191
|
+
if (cached)
|
|
192
|
+
return cached.id;
|
|
193
|
+
}
|
|
194
|
+
const models = opts?.catalog ?? loadZenCatalog();
|
|
195
|
+
const ranked = rankZenFreeModels(models);
|
|
196
|
+
const winner = ranked[0];
|
|
197
|
+
if (!winner) {
|
|
198
|
+
const fallback = {
|
|
199
|
+
id: OPENCODE_DEFAULT_MODEL,
|
|
200
|
+
name: "Ox Alpha Free (fallback)",
|
|
201
|
+
context_length: 0,
|
|
202
|
+
boost: 0,
|
|
203
|
+
picked_at: new Date().toISOString(),
|
|
204
|
+
candidates: 0,
|
|
205
|
+
source: "fallback",
|
|
206
|
+
};
|
|
207
|
+
if (opts?.catalog === undefined)
|
|
208
|
+
writeCache(fallback);
|
|
209
|
+
return fallback.id;
|
|
210
|
+
}
|
|
211
|
+
const pick = {
|
|
212
|
+
id: winner.id,
|
|
213
|
+
name: winner.name,
|
|
214
|
+
context_length: winner.context_length,
|
|
215
|
+
boost: winner.boost,
|
|
216
|
+
picked_at: new Date().toISOString(),
|
|
217
|
+
candidates: ranked.length,
|
|
218
|
+
source: "ranked-free",
|
|
219
|
+
};
|
|
220
|
+
if (opts?.catalog === undefined)
|
|
221
|
+
writeCache(pick);
|
|
222
|
+
return pick.id;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Resolve OpenCode `--model` / env to a concrete `provider/model` id.
|
|
226
|
+
* Empty or `free` → CURSOR_ROUTE_OPENCODE_MODEL pin, else live Zen free pick.
|
|
227
|
+
*/
|
|
228
|
+
export function openCodeModel(raw) {
|
|
229
|
+
const v = (raw ?? "").trim();
|
|
230
|
+
if (!v || v.toLowerCase() === "free") {
|
|
231
|
+
const env = (process.env.CURSOR_ROUTE_OPENCODE_MODEL ?? "").trim();
|
|
232
|
+
if (env && env.toLowerCase() !== "free")
|
|
233
|
+
return assertOpenCodeModel(env);
|
|
234
|
+
return pickZenFreeModel();
|
|
235
|
+
}
|
|
236
|
+
return assertOpenCodeModel(v);
|
|
237
|
+
}
|
package/docs/briefs/WORKING.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: cursor-route workspace — working brief (edit in place)
|
|
3
3
|
repo: ~/Projects/cursor-route
|
|
4
|
-
npm: cursor-route@0.1.
|
|
4
|
+
npm: cursor-route@0.1.11 (LIVE on npm latest)
|
|
5
5
|
created: 2026-08-12
|
|
6
|
-
updated: 2026-08-
|
|
6
|
+
updated: 2026-08-21
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# cursor-route — living brief
|
|
@@ -21,10 +21,11 @@ Cursor Agent plans. Workers run in tmux via `cursor-route`:
|
|
|
21
21
|
| `easy` | OpenRouter free | Wording / drafts — non-secret prompts only |
|
|
22
22
|
| `mid` | claude-ds (DeepSeek behind Claude Code) | Default implement (**Flash**; `--model pro` when needed) |
|
|
23
23
|
| `hard` | Grok CLI | Hard implement |
|
|
24
|
+
| opt-in | OpenCode | `--worker opencode` coding agent; `--model free` ranks live Zen catalog (Ox Alpha first while listed) |
|
|
24
25
|
|
|
25
26
|
Always-approve on for coding worktrees (`--ask` / `CURSOR_ROUTE_ASK=1` to opt out) — not LIVE Discord/trading. Jobs live in `~/.local/share/cursor-route/jobs`, not in this clone.
|
|
26
27
|
|
|
27
|
-
Install: `npm i -g cursor-route` → **0.1.
|
|
28
|
+
Install: `npm i -g cursor-route` → **0.1.11**. Release notes: [CHANGELOG.md](../../CHANGELOG.md).
|
|
28
29
|
|
|
29
30
|
## Open (edit / check off)
|
|
30
31
|
|
|
@@ -38,6 +39,8 @@ Install: `npm i -g cursor-route` → **0.1.9** once published. Release notes: [C
|
|
|
38
39
|
- [x] **Experimental `--worker deepseek`** — 0.1.8: real dsh adapter (`dsh --profile headless` + per-job Cordis patch pins the model; never writes `~/.dsh/settings.yaml`). Always-approve → `DSH_PERMISSION_MODE=danger-full-access`, `--ask` → `workspace-write`; key via env only. Health ✓ needs `dsh` + `DEEPSEEK_API_KEY` (override `CURSOR_ROUTE_DSH_BIN`). Mid stays **claude-ds**.
|
|
39
40
|
- [x] **route-orch brief steals (2026-08-14)** — AutoDesign / misevolution / Vero habits into the public skill: **Verify / claim closeout** (external eval contract; activity ≠ verification), **Eval & skill hygiene** (mid-run Verify-rewrite ban; skill misevolution HITL — no auto-promotion of worker-trajectory variants; verify-fail → reconsider plan/definition + stage attribution spawn/execute/verify); handoff shape Success criteria + Verify + NEVER; skills synced; mid stays claude-ds.
|
|
40
41
|
- [x] **Health proves mid DeepSeek + evidence tree** — 0.1.9: `lane:mid` ✓ only when DeepSeek is proven (shim or DeepSeek `ANTHROPIC_BASE_URL`; Anthropic hatch is not proof); health JSON `lanes.mid`; `status --json` evidence tree (`spawn` / `execute` / `verify.claim=unverified`); skill health-before-mid + evidence-tree closeout. Mid stays **claude-ds**.
|
|
42
|
+
- [x] **Opt-in `--worker opencode`** — 0.1.10: `opencode run --dir` + `--model`; always-approve → `--auto`; `--ask` omits it; never rewrites `~/.config/opencode/opencode.json`. Health ✓ needs `opencode` on PATH (`CURSOR_ROUTE_OPENCODE_BIN`). Mid stays **claude-ds**; easy stays OpenRouter chat. Free Zen may log/train — non-secret prompts.
|
|
43
|
+
- [x] **Live Zen free pick** — 0.1.11: `--model free` ranks `GET https://opencode.ai/zen/v1/models` (Ox Alpha first while listed; ~15 min cache; Ox Alpha fallback if fetch fails). Pin with `CURSOR_ROUTE_OPENCODE_MODEL`. Health stays offline (cache/fallback only).
|
|
41
44
|
- [ ] **Hero GIF** — still outstanding; dry-run fixture ships as the substitute for now (`docs/fixtures/hero-demo.log` — see `docs/DEMO_GIF.md`)
|
|
42
45
|
- [x] **Do not** paste private `ROUTE_KIT`, SIP, prod paths, or hang-watchdog env into this public repo
|
|
43
46
|
|
|
@@ -50,6 +53,8 @@ Install: `npm i -g cursor-route` → **0.1.9** once published. Release notes: [C
|
|
|
50
53
|
| `src/adapters/deepseek.ts` | Experimental dsh worker (`--worker deepseek`; mid stays claude-ds) |
|
|
51
54
|
| `src/adapters/grok.ts` | Hard worker |
|
|
52
55
|
| `src/adapters/openrouter.ts` | Easy worker |
|
|
56
|
+
| `src/adapters/opencode.ts` | Opt-in OpenCode worker (`--worker opencode`; mid stays claude-ds) |
|
|
57
|
+
| `src/zen-free.ts` | Live Zen catalog rank for `--model free` |
|
|
53
58
|
| `skills/route-orch/SKILL.md` | Cursor skill — spawn CLI, do not implement in-session |
|
|
54
59
|
| `CHANGELOG.md` | Release notes |
|
|
55
60
|
| `SECURITY.md` | Secret refuse gate |
|
|
@@ -67,4 +72,6 @@ Install: `npm i -g cursor-route` → **0.1.9** once published. Release notes: [C
|
|
|
67
72
|
| 2026-08-14 | DeepSeek Harness eval: `@deepseek-ai/dsh` 0.1.0-rc.6 is a developer-preview plugin kernel, not a mid replacement; `--worker deepseek` stays unhealthy; mid remains claude-ds (docs-only, no version bump). |
|
|
68
73
|
| 2026-08-14 | Experimental `--worker deepseek` wired to official dsh (headless + per-job patch + `DSH_PERMISSION_MODE` + key-via-env); `--model` applies to claude-ds + deepseek; mid stays claude-ds → 0.1.8 LIVE. |
|
|
69
74
|
| 2026-08-14 | route-orch brief steals (AutoDesign / misevolution / Vero): **Verify / claim closeout** + **Eval & skill hygiene**; handoff Success criteria + Verify + NEVER; both skill copies synced; mid stays claude-ds. Docs-only, no version bump. |
|
|
70
|
-
| 2026-08-18 | Health `lane:mid` + `lanes.mid` prove DeepSeek; status evidence tree (`verify.claim` stays unverified); skill health-before-mid + closeout tree; mid stays claude-ds → 0.1.9
|
|
75
|
+
| 2026-08-18 | Health `lane:mid` + `lanes.mid` prove DeepSeek; status evidence tree (`verify.claim` stays unverified); skill health-before-mid + closeout tree; mid stays claude-ds → 0.1.9 LIVE. |
|
|
76
|
+
| 2026-08-21 | Opt-in `--worker opencode` (Zen free `opencode/big-pickle`, `--auto`, no config rewrite); mid stays claude-ds → 0.1.10 LIVE. |
|
|
77
|
+
| 2026-08-21 | Live Zen free pick (Ox Alpha first while listed; OpenRouter-style catalog rank) → 0.1.11 LIVE. |
|
package/llms.txt
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# cursor-route
|
|
2
2
|
|
|
3
|
-
> Cursor stays the planner. DeepSeek (mid), Grok CLI (hard), and OpenRouter free models (easy) run parallel coding workers in tmux.
|
|
3
|
+
> Cursor stays the planner. DeepSeek (mid), Grok CLI (hard), and OpenRouter free models (easy) run parallel coding workers in tmux. `--worker opencode` is an opt-in coding agent on OpenCode Zen free models.
|
|
4
4
|
|
|
5
|
-
MIT CLI + Cursor skill. npm: https://www.npmjs.com/package/cursor-route (latest **0.1.
|
|
5
|
+
MIT CLI + Cursor skill. npm: https://www.npmjs.com/package/cursor-route (latest **0.1.11**)
|
|
6
6
|
GitHub: https://github.com/cemini23/cursor-route
|
|
7
7
|
|
|
8
8
|
## FAQ
|
|
9
9
|
|
|
10
10
|
### What is cursor-route?
|
|
11
|
-
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 (wording/drafts, non-secret prompts only).
|
|
11
|
+
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 (wording/drafts, non-secret prompts only). `--worker opencode` is an opt-in coding agent (`--model free` ranks the live Zen catalog; Ox Alpha wins while listed) — not a lane default.
|
|
12
12
|
|
|
13
13
|
### How is this different from Codex orchestrator?
|
|
14
14
|
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.
|
|
@@ -22,15 +22,19 @@ No. The mid worker is DeepSeek. Claude Code is the harness, configured with `ANT
|
|
|
22
22
|
| `--model flash` (default) | `deepseek-v4-flash` | Cheap mid execute |
|
|
23
23
|
| `--model pro` | `deepseek-v4-pro` | Harder mid / Grok usage stand-in |
|
|
24
24
|
|
|
25
|
+
### How do I use OpenCode free models?
|
|
26
|
+
Install OpenCode (`npm i -g opencode-ai`), run `opencode auth login`, then `cursor-route start --worker opencode "…"`. `--model free` ranks live Zen free models (Ox Alpha first while listed). Mid stays `claude-ds`.
|
|
27
|
+
|
|
25
28
|
### How do I install?
|
|
26
29
|
Run `npm i -g cursor-route`, install tmux if needed, then run `cursor-route health`. Copy the Cursor skill from `$(npm root -g)/cursor-route/skills/route-orch`. Source: https://github.com/cemini23/cursor-route
|
|
27
30
|
|
|
28
31
|
### Is it free?
|
|
29
|
-
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`).
|
|
32
|
+
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 use OpenCode Zen free models.
|
|
30
33
|
|
|
31
34
|
## Install
|
|
32
35
|
|
|
33
36
|
- npm: `npm i -g cursor-route`
|
|
34
37
|
- Health: `cursor-route health`
|
|
35
38
|
- Mid: `cursor-route start --lane mid "…"` (Flash) · `--model pro` when needed
|
|
39
|
+
- OpenCode (opt-in): `cursor-route start --worker opencode "…"`
|
|
36
40
|
- Skill: `/route-orch` (Cursor)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-route",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Cursor stays the brain. Grok CLI + DeepSeek (claude-ds) + OpenRouter easy lane are the parallel army \u2014 lane-aware /route orchestration in tmux.",
|
|
3
|
+
"version": "0.1.11",
|
|
4
|
+
"description": "Cursor stays the brain. Grok CLI + DeepSeek (claude-ds) + OpenRouter easy lane + OpenCode (opt-in free) are the parallel army \u2014 lane-aware /route orchestration in tmux.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"deepseek",
|
|
42
42
|
"claude-ds",
|
|
43
43
|
"openrouter",
|
|
44
|
+
"opencode",
|
|
44
45
|
"tmux",
|
|
45
46
|
"orchestrator",
|
|
46
47
|
"agents",
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
name: route-orch
|
|
3
3
|
description: >-
|
|
4
4
|
Delegate coding work from Cursor to parallel Grok CLI / claude-ds (DeepSeek) /
|
|
5
|
-
OpenRouter (easy lane) workers via cursor-route. Use
|
|
6
|
-
/route-orch, spawn workers, parallel agents with
|
|
7
|
-
asks to outsource implementation to
|
|
8
|
-
Cemini /route.
|
|
5
|
+
OpenRouter (easy lane) / OpenCode (opt-in free) workers via cursor-route. Use
|
|
6
|
+
when the user says /route-orch, spawn workers, parallel agents with
|
|
7
|
+
cursor-route, or explicitly asks to outsource implementation to
|
|
8
|
+
Grok/DeepSeek/OpenCode panes — not for private Cemini /route.
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# route-orch (cursor-route)
|
|
@@ -14,8 +14,8 @@ You are the **orchestrator**. Do **not** implement bulk code in this Cursor sess
|
|
|
14
14
|
|
|
15
15
|
## When to activate
|
|
16
16
|
|
|
17
|
-
- User says `/route-orch`, `spawn workers`, or asks for parallel Grok/DeepSeek via **cursor-route**
|
|
18
|
-
- Mid/hard implementation that should run on a subscription worker (Grok CLI / claude-ds)
|
|
17
|
+
- User says `/route-orch`, `spawn workers`, or asks for parallel Grok/DeepSeek/OpenCode via **cursor-route**
|
|
18
|
+
- Mid/hard implementation that should run on a subscription worker (Grok CLI / claude-ds) or opt-in OpenCode free models
|
|
19
19
|
- Multi-file investigation that benefits from parallel panes
|
|
20
20
|
|
|
21
21
|
**Do not steal federation `/route`.** Private Cemini `/route` (route-task → verify → Grok/claude-ds chain) is a different skill. This public skill only drives the `cursor-route` CLI.
|
|
@@ -59,6 +59,20 @@ cursor-route start --worker deepseek --model pro --dir "$PWD" "…" # --model
|
|
|
59
59
|
|
|
60
60
|
Health ✓ needs `dsh` on PATH and `DEEPSEEK_API_KEY` set. The adapter pins `--model` via a per-job `--patch` (never touches `~/.dsh/settings.yaml`); always-approve → `DSH_PERMISSION_MODE=danger-full-access`, `--ask` → `workspace-write`. The key never enters the command or patch.
|
|
61
61
|
|
|
62
|
+
## Experimental: --worker opencode (free Zen)
|
|
63
|
+
|
|
64
|
+
OpenCode as an opt-in **coding agent** on OpenCode Zen free models — **not a lane default** (mid stays `claude-ds`; easy stays OpenRouter chat). Use this to cut Grok / DeepSeek usage on implement work.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm i -g opencode-ai
|
|
68
|
+
opencode auth login
|
|
69
|
+
cursor-route start --worker opencode --dir "$PWD" "…"
|
|
70
|
+
cursor-route start --worker opencode --model free --dir "$PWD" "…"
|
|
71
|
+
cursor-route start --worker opencode --model opencode/hy3-free --dir "$PWD" "…"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Health ✓ needs `opencode` on PATH (override `CURSOR_ROUTE_OPENCODE_BIN`). `--model free` / unset ranks the live OpenCode Zen catalog (Ox Alpha first while listed; pin with `CURSOR_ROUTE_OPENCODE_MODEL` or `--model provider/model`). Always-approve → `opencode run --auto`; `--ask` omits `--auto`. Never rewrites `~/.config/opencode/opencode.json`. Free Zen models may log/train — keep secrets off this worker (same refuse gate as easy). Ox Alpha (`opencode/x-preview-f-free`) is the zero-retention free option.
|
|
75
|
+
|
|
62
76
|
## Workflow
|
|
63
77
|
|
|
64
78
|
1. Run `cursor-route health` (or `CURSOR_ROUTE_RELAXED=1` for headless). If the **target worker** is unhealthy, fix before spawning. If targeting **mid**, require `lane:mid` ✓ (or health JSON `lanes.mid.deepseek`) before spawn — `CURSOR_ROUTE_ALLOW_ANTHROPIC=1` is not DeepSeek proof.
|
|
@@ -82,7 +96,7 @@ EOF
|
|
|
82
96
|
)"
|
|
83
97
|
```
|
|
84
98
|
|
|
85
|
-
Or `--worker grok` / `--worker claude-ds` / `--worker deepseek` (experimental) / `--worker openrouter` (or `--lane easy`). Use `--no-tmux` only when tmux is unavailable.
|
|
99
|
+
Or `--worker grok` / `--worker claude-ds` / `--worker deepseek` (experimental) / `--worker opencode` (opt-in free) / `--worker openrouter` (or `--lane easy`). Use `--no-tmux` only when tmux is unavailable.
|
|
86
100
|
|
|
87
101
|
4. Monitor: `cursor-route jobs --json` · `cursor-route capture <id>` · `cursor-route send <id> "…"` (tmux only).
|
|
88
102
|
5. Summarize worker results with **verify evidence** — no status-only “done” (see Verify / claim closeout). If verify fails, reconsider the plan/definition (not only retry) — `send` a correction or spawn a follow-up; do not invent success.
|
|
@@ -111,6 +125,7 @@ Defaults on for workers. Opt out: `cursor-route start … --ask` or `CURSOR_ROUT
|
|
|
111
125
|
|
|
112
126
|
- Do not paste API keys / private keys into prompts or `send`
|
|
113
127
|
- Do not claim the official DeepSeek harness (`@deepseek-ai/dsh`) is the mid default — `--worker deepseek` is an opt-in experiment (cheap to abandon), not a product fork; mid stays **claude-ds**
|
|
128
|
+
- Do not claim OpenCode is the mid default — `--worker opencode` is opt-in for free Zen (or other) models; mid stays **claude-ds**
|
|
114
129
|
- Do not fork a second mid harness
|
|
115
130
|
- Do not open-source or dump private cemini `agent-toolkit` paths into public handoffs
|
|
116
131
|
- Do not mark done without reading `capture` / exit status
|
package/src/adapters/index.ts
CHANGED
|
@@ -4,12 +4,14 @@ import { grokAdapter } from "./grok.ts";
|
|
|
4
4
|
import { claudeDsAdapter } from "./claude-ds.ts";
|
|
5
5
|
import { openRouterAdapter } from "./openrouter.ts";
|
|
6
6
|
import { deepseekAdapter } from "./deepseek.ts";
|
|
7
|
+
import { opencodeAdapter } from "./opencode.ts";
|
|
7
8
|
|
|
8
9
|
const registry: Record<WorkerKind, Adapter> = {
|
|
9
10
|
grok: grokAdapter,
|
|
10
11
|
"claude-ds": claudeDsAdapter,
|
|
11
12
|
openrouter: openRouterAdapter,
|
|
12
13
|
deepseek: deepseekAdapter,
|
|
14
|
+
opencode: opencodeAdapter,
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
export function getAdapter(worker: WorkerKind): Adapter {
|