cursor-route 0.1.7 → 0.1.8
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 +9 -0
- package/CONTRIBUTING.md +1 -0
- package/README.md +46 -5
- package/dist/adapters/deepseek.js +127 -8
- package/dist/cli.js +7 -4
- package/dist/config.js +5 -2
- package/dist/jobs.js +3 -2
- package/docs/DEMO_GIF.md +17 -1
- package/docs/briefs/WORKING.md +11 -6
- package/docs/demo-notes.md +5 -3
- package/docs/fixtures/generate-hero-demo.sh +109 -0
- package/docs/fixtures/hero-demo.log +52 -0
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/skills/route-orch/SKILL.md +15 -2
- package/src/adapters/deepseek.ts +145 -11
- package/src/adapters/types.ts +3 -1
- package/src/cli.test.ts +360 -25
- package/src/cli.ts +7 -4
- package/src/config.ts +9 -3
- package/src/jobs.ts +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.8 — 2026-08-14
|
|
4
|
+
|
|
5
|
+
- 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`.
|
|
6
|
+
- `--model flash|pro` (and `deepseek-v4-pro[1m]`) now applies to both `claude-ds` and `deepseek`; patch sets `agent-default-model` → `provider: deepseek-official` (never rewrites `~/.dsh/settings.yaml`, so parallel jobs don't race).
|
|
7
|
+
- Always-approve → `DSH_PERMISSION_MODE=danger-full-access`; `--ask` → `workspace-write`. `DEEPSEEK_API_KEY` travels via env only — never in the launch command or patch.
|
|
8
|
+
- Health: `worker:deepseek` ✓ only when `dsh` is on PATH (override `CURSOR_ROUTE_DSH_BIN`) **and** `DEEPSEEK_API_KEY` is set.
|
|
9
|
+
- Dry-run for `--worker deepseek` works without `dsh` installed (falls back to plain `dsh` in the printed command); real starts stay gated by health.
|
|
10
|
+
- `clean` also removes leftover `.dsh-patch.yml` files; patch path never overwrites a non-`.prompt` prompt file.
|
|
11
|
+
|
|
3
12
|
## 0.1.7 — 2026-08-12
|
|
4
13
|
|
|
5
14
|
- Fix: `CURSOR_ROUTE_DS_MODEL` / `ANTHROPIC_MODEL` now apply on the `start` product path (live default)
|
package/CONTRIBUTING.md
CHANGED
|
@@ -11,6 +11,7 @@ CURSOR_ROUTE_RELAXED=1 ./bin/cursor-route health # without tmux
|
|
|
11
11
|
./bin/cursor-route start --dry-run --worker grok "ping"
|
|
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
|
+
./bin/cursor-route start --dry-run --worker deepseek --model flash "ping" # needs dsh + DEEPSEEK_API_KEY for real starts
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
Keep `skills/route-orch/SKILL.md` and `.cursor/skills/route-orch/SKILL.md` identical when editing the skill.
|
package/README.md
CHANGED
|
@@ -102,6 +102,36 @@ Working notes for this repo (edit in place): [docs/briefs/WORKING.md](./docs/bri
|
|
|
102
102
|
| `sessions` | Managed tmux sessions |
|
|
103
103
|
| `clean [--days N]` | Drop old job files |
|
|
104
104
|
|
|
105
|
+
## Demo
|
|
106
|
+
|
|
107
|
+
A dry-run capture (no live workers, no secrets) is committed as a reproducible fixture:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Needs Bun or Node 20+ and a runnable ./bin/cursor-route (clone: bun install)
|
|
111
|
+
docs/fixtures/generate-hero-demo.sh # regenerate docs/fixtures/hero-demo.log
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
$ cursor-route --version
|
|
116
|
+
0.1.8
|
|
117
|
+
|
|
118
|
+
$ CURSOR_ROUTE_RELAXED=1 cursor-route health
|
|
119
|
+
cursor-route v0.1.8
|
|
120
|
+
health: OK
|
|
121
|
+
|
|
122
|
+
$ cursor-route start --lane mid --model flash --dry-run "Add a unit test for shellQuote"
|
|
123
|
+
dry-run job a1b2c3d4
|
|
124
|
+
worker: claude-ds
|
|
125
|
+
model: flash
|
|
126
|
+
command: cd '~/Projects/cursor-route' && '~/.local/bin/claude-ds' -PromptFile '~/.local/share/cursor-route/jobs/a1b2c3d4.prompt' -Model 'deepseek-v4-flash' --dangerously-skip-permissions
|
|
127
|
+
|
|
128
|
+
$ cursor-route jobs --json
|
|
129
|
+
[]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Full capture: [docs/fixtures/hero-demo.log](./docs/fixtures/hero-demo.log).
|
|
133
|
+
A real hero GIF is still pending — recording steps live in [docs/DEMO_GIF.md](./docs/DEMO_GIF.md).
|
|
134
|
+
|
|
105
135
|
## Lanes
|
|
106
136
|
|
|
107
137
|
| Lane | Worker | Intent |
|
|
@@ -155,7 +185,18 @@ Persist the same vars under `~/.claude/settings.json` → `"env": { … }` if yo
|
|
|
155
185
|
|
|
156
186
|
**Also accepted:** `claude-ds` or `deepseek-claude` on PATH (Cemini shims). The adapter passes `-Model deepseek-v4-flash|deepseek-v4-pro`.
|
|
157
187
|
|
|
158
|
-
**
|
|
188
|
+
**Experimental:** `--worker deepseek` runs the official DeepSeek Harness (`dsh`, npm `@deepseek-ai/dsh`) as an opt-in worker — **mid stays on `claude-ds`**; don't default lanes here.
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
npm i -g @deepseek-ai/dsh
|
|
192
|
+
export DEEPSEEK_API_KEY=YOUR_DEEPSEEK_API_KEY # from platform.deepseek.com
|
|
193
|
+
|
|
194
|
+
cursor-route health # worker:deepseek should be ✓
|
|
195
|
+
cursor-route start --worker deepseek "…" # Flash (default)
|
|
196
|
+
cursor-route start --worker deepseek --model pro "…" # Pro when you need it
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The adapter launches `dsh --profile headless` with a **per-job Cordis patch** (`jobs/<id>.dsh-patch.yml`, mode 0600) that pins `--model flash|pro` (`deepseek-v4-pro[1m]` preserved) — it never rewrites `~/.dsh/settings.yaml`, so parallel jobs don't race. Always-approve maps to `DSH_PERMISSION_MODE=danger-full-access`; `--ask` drops to `workspace-write`. Your `DEEPSEEK_API_KEY` travels via env only — never in the launch command or patch. Override the binary with `CURSOR_ROUTE_DSH_BIN`.
|
|
159
200
|
|
|
160
201
|
**Not the default:** bare `claude` still talking to Anthropic. Health refuses that so a misconfigured install cannot silently burn frontier $ rates. Escape hatch only: `CURSOR_ROUTE_ALLOW_ANTHROPIC=1`.
|
|
161
202
|
|
|
@@ -249,8 +290,8 @@ MIT © Cemini — see [LICENSE](LICENSE).
|
|
|
249
290
|
|
|
250
291
|
## Roadmap (explicitly later)
|
|
251
292
|
|
|
252
|
-
- Homebrew tap
|
|
253
|
-
-
|
|
254
|
-
- Codebase map injection (`--map`)
|
|
255
|
-
- Cursor CLI `agent` as alternate supervisor
|
|
293
|
+
- Homebrew tap
|
|
294
|
+
- Stabilize the DeepSeek harness adapter (experimental `--worker deepseek` since 0.1.8)
|
|
295
|
+
- Codebase map injection (`--map`)
|
|
296
|
+
- Cursor CLI `agent` as alternate supervisor
|
|
256
297
|
- Web UI / CAO-style MCP supervisor
|
|
@@ -1,19 +1,138 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync, writeFileSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { shellQuote } from "../util.js";
|
|
4
|
+
import { DS_MODEL_IDS, resolveDsModel, } from "../config.js";
|
|
1
5
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
6
|
+
* Experimental official DeepSeek Harness (`dsh`, npm @deepseek-ai/dsh) as a
|
|
7
|
+
* coding worker — `dsh --profile headless` with a per-job Cordis patch that
|
|
8
|
+
* pins the model. Mid lane stays on claude-ds; this is an opt-in worker only
|
|
9
|
+
* (`--worker deepseek`), not a mid replacement.
|
|
10
|
+
*
|
|
11
|
+
* We never write ~/.dsh/settings.yaml (parallel jobs would race) and never
|
|
12
|
+
* put DEEPSEEK_API_KEY in the command or patch — the key travels via plan.env.
|
|
4
13
|
*/
|
|
14
|
+
function findDsh() {
|
|
15
|
+
// Env override lets tests pin a fake dsh — but it must exist, so a stale
|
|
16
|
+
// override cannot pass health with a dangling path.
|
|
17
|
+
const override = process.env.CURSOR_ROUTE_DSH_BIN;
|
|
18
|
+
if (override)
|
|
19
|
+
return existsSync(override) ? override : null;
|
|
20
|
+
try {
|
|
21
|
+
return (execSync("command -v dsh", {
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
24
|
+
// Bun may ignore mutated process.env.PATH unless env is passed explicitly
|
|
25
|
+
env: { ...process.env },
|
|
26
|
+
}).trim() || null);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Same resolution as claude-ds: passed model/modelId wins, else env, else Flash. */
|
|
33
|
+
function pickModel(requested, modelId) {
|
|
34
|
+
if (modelId) {
|
|
35
|
+
const alias = requested ?? resolveDsModel(modelId).alias;
|
|
36
|
+
return { alias, id: modelId };
|
|
37
|
+
}
|
|
38
|
+
if (requested) {
|
|
39
|
+
return { alias: requested, id: DS_MODEL_IDS[requested] };
|
|
40
|
+
}
|
|
41
|
+
// Env default (startJob normally resolves this; kept for direct buildLaunch callers)
|
|
42
|
+
return resolveDsModel(process.env.CURSOR_ROUTE_DS_MODEL || process.env.ANTHROPIC_MODEL);
|
|
43
|
+
}
|
|
44
|
+
/** Whitelist model ids before interpolating into YAML (no newlines / injection). */
|
|
45
|
+
function assertPatchModelId(modelId) {
|
|
46
|
+
if (!/^[a-z0-9][a-z0-9.\-[\]]*$/i.test(modelId)) {
|
|
47
|
+
throw new Error(`Invalid DeepSeek model id for dsh patch: ${modelId}`);
|
|
48
|
+
}
|
|
49
|
+
return modelId;
|
|
50
|
+
}
|
|
51
|
+
/** Per-job Cordis patch path. Never reuse the prompt path (would overwrite it). */
|
|
52
|
+
export function patchPathForPrompt(promptFile) {
|
|
53
|
+
return promptFile.endsWith(".prompt")
|
|
54
|
+
? promptFile.replace(/\.prompt$/, ".dsh-patch.yml")
|
|
55
|
+
: `${promptFile}.dsh-patch.yml`;
|
|
56
|
+
}
|
|
57
|
+
/** Per-job Cordis patch (whole-row replace). `name` is required or dsh silently skips. */
|
|
58
|
+
function patchYaml(modelId) {
|
|
59
|
+
const id = assertPatchModelId(modelId);
|
|
60
|
+
return [
|
|
61
|
+
"- id: agent-default-model",
|
|
62
|
+
" name: '@deepseek-ai/dsh-agent-default-model'",
|
|
63
|
+
" config:",
|
|
64
|
+
" provider: deepseek-official",
|
|
65
|
+
` model: '${id}'`,
|
|
66
|
+
].join("\n") + "\n";
|
|
67
|
+
}
|
|
5
68
|
export const deepseekAdapter = {
|
|
6
69
|
kind: "deepseek",
|
|
7
|
-
label: "Official DeepSeek
|
|
70
|
+
label: "Official DeepSeek Harness (dsh)",
|
|
8
71
|
health() {
|
|
72
|
+
const binary = findDsh();
|
|
73
|
+
if (!binary) {
|
|
74
|
+
return {
|
|
75
|
+
worker: "deepseek",
|
|
76
|
+
ok: false,
|
|
77
|
+
binary: null,
|
|
78
|
+
detail: "dsh (@deepseek-ai/dsh) not found — install: npm i -g @deepseek-ai/dsh. Mid default remains claude-ds.",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (!process.env.DEEPSEEK_API_KEY) {
|
|
82
|
+
return {
|
|
83
|
+
worker: "deepseek",
|
|
84
|
+
ok: false,
|
|
85
|
+
binary,
|
|
86
|
+
detail: "DEEPSEEK_API_KEY not set — export your DeepSeek API key to use dsh (@deepseek-ai/dsh). Mid default remains claude-ds.",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
9
89
|
return {
|
|
10
90
|
worker: "deepseek",
|
|
11
|
-
ok:
|
|
12
|
-
binary
|
|
13
|
-
detail: "
|
|
91
|
+
ok: true,
|
|
92
|
+
binary,
|
|
93
|
+
detail: "ok (dsh @deepseek-ai/dsh headless; mid default remains claude-ds)",
|
|
14
94
|
};
|
|
15
95
|
},
|
|
16
|
-
buildLaunch() {
|
|
17
|
-
|
|
96
|
+
buildLaunch({ promptFile, cwd, alwaysApprove, model, modelId, dryRun }) {
|
|
97
|
+
// Missing dsh is tolerated here so `--dry-run` can still print the command;
|
|
98
|
+
// real starts are gated by the health preflight (binary + DEEPSEEK_API_KEY).
|
|
99
|
+
const binary = findDsh() || "dsh";
|
|
100
|
+
const choice = pickModel(model, modelId);
|
|
101
|
+
// Per-job patch next to the prompt file (never touch ~/.dsh/settings.yaml).
|
|
102
|
+
const patchFile = patchPathForPrompt(promptFile);
|
|
103
|
+
writeFileSync(patchFile, patchYaml(choice.id), { mode: 0o600 });
|
|
104
|
+
if (dryRun) {
|
|
105
|
+
// Dry-run keeps no durable artifacts (jobs.ts removes the prompt likewise).
|
|
106
|
+
try {
|
|
107
|
+
unlinkSync(patchFile);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* ignore */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Launcher flags before the task; prompt inlined via cat (never the key).
|
|
114
|
+
const parts = [
|
|
115
|
+
shellQuote(binary),
|
|
116
|
+
"--profile",
|
|
117
|
+
"headless",
|
|
118
|
+
"--patch",
|
|
119
|
+
shellQuote(patchFile),
|
|
120
|
+
`"$(cat ${shellQuote(promptFile)})"`,
|
|
121
|
+
];
|
|
122
|
+
const ask = process.env.CURSOR_ROUTE_ASK === "1";
|
|
123
|
+
const skip = alwaysApprove && !ask;
|
|
124
|
+
const env = {
|
|
125
|
+
DSH_PERMISSION_MODE: skip ? "danger-full-access" : "workspace-write",
|
|
126
|
+
};
|
|
127
|
+
// Key travels via env only — never interpolated into command or patch.
|
|
128
|
+
if (process.env.DEEPSEEK_API_KEY) {
|
|
129
|
+
env.DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
worker: "deepseek",
|
|
133
|
+
command: `cd ${shellQuote(cwd)} && ${parts.join(" ")}`,
|
|
134
|
+
alwaysApprove: skip,
|
|
135
|
+
env,
|
|
136
|
+
};
|
|
18
137
|
},
|
|
19
138
|
};
|
package/dist/cli.js
CHANGED
|
@@ -29,9 +29,9 @@ Usage:
|
|
|
29
29
|
cursor-route clean [--days N]
|
|
30
30
|
|
|
31
31
|
Start options:
|
|
32
|
-
--worker <grok|claude-ds|openrouter> Worker adapter (default: grok; deepseek =
|
|
32
|
+
--worker <grok|claude-ds|openrouter|deepseek> Worker adapter (default: grok; deepseek = experimental official dsh)
|
|
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
|
|
34
|
+
--model <flash|pro> Mid DeepSeek only (default: flash / CURSOR_ROUTE_DS_MODEL). Parsed for claude-ds + deepseek
|
|
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
|
|
@@ -47,6 +47,8 @@ Env:
|
|
|
47
47
|
CURSOR_ROUTE_DS_MODEL Default mid model flash|pro (or deepseek-v4-pro[1m]); overridden by --model
|
|
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
|
+
CURSOR_ROUTE_DSH_BIN Override the dsh binary path (tests / power users)
|
|
51
|
+
DEEPSEEK_API_KEY DeepSeek API key (required for --worker deepseek)
|
|
50
52
|
OPENROUTER_API_KEY OpenRouter key (required for --worker openrouter / --lane easy)
|
|
51
53
|
CURSOR_ROUTE_OPENROUTER_MODEL OpenRouter model (default: openrouter/free)
|
|
52
54
|
OPENROUTER_BASE_URL OpenRouter API base (default: https://openrouter.ai/api/v1)
|
|
@@ -221,8 +223,9 @@ async function main() {
|
|
|
221
223
|
const resolvedWorker = worker ?? (lane ? config.laneWorkers[lane] : config.defaultWorker);
|
|
222
224
|
let model;
|
|
223
225
|
let modelId;
|
|
224
|
-
// --model is DeepSeek
|
|
225
|
-
|
|
226
|
+
// --model is DeepSeek-only (claude-ds mid lane + experimental deepseek worker);
|
|
227
|
+
// ignore (do not validate) for other workers
|
|
228
|
+
if (f.model !== undefined && (resolvedWorker === "claude-ds" || resolvedWorker === "deepseek")) {
|
|
226
229
|
try {
|
|
227
230
|
const choice = asDsModelChoice(f.model);
|
|
228
231
|
if (choice) {
|
package/dist/config.js
CHANGED
|
@@ -65,13 +65,16 @@ function maxConcurrentJobsFromEnv() {
|
|
|
65
65
|
*/
|
|
66
66
|
export const config = {
|
|
67
67
|
product: "cursor-route",
|
|
68
|
-
version: "0.1.
|
|
68
|
+
version: "0.1.8",
|
|
69
69
|
get jobsDir() {
|
|
70
70
|
return defaultJobsDir();
|
|
71
71
|
},
|
|
72
72
|
tmuxPrefix: "cursor-route",
|
|
73
73
|
defaultWorker: "grok",
|
|
74
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Lane → default worker (Cemini /route public core).
|
|
76
|
+
* `deepseek` is experimental only — mid stays on claude-ds.
|
|
77
|
+
*/
|
|
75
78
|
laneWorkers: {
|
|
76
79
|
easy: "openrouter",
|
|
77
80
|
mid: "claude-ds",
|
package/dist/jobs.js
CHANGED
|
@@ -219,7 +219,7 @@ export function startJob(opts) {
|
|
|
219
219
|
writeSecure(paths.prompt, opts.prompt);
|
|
220
220
|
let model;
|
|
221
221
|
let modelId;
|
|
222
|
-
if (worker === "claude-ds") {
|
|
222
|
+
if (worker === "claude-ds" || worker === "deepseek") {
|
|
223
223
|
if (opts.model) {
|
|
224
224
|
model = opts.model;
|
|
225
225
|
modelId = opts.modelId ?? DS_MODEL_IDS[opts.model];
|
|
@@ -249,6 +249,7 @@ export function startJob(opts) {
|
|
|
249
249
|
alwaysApprove,
|
|
250
250
|
model,
|
|
251
251
|
modelId,
|
|
252
|
+
dryRun: Boolean(opts.dryRun),
|
|
252
253
|
});
|
|
253
254
|
}
|
|
254
255
|
catch (e) {
|
|
@@ -421,7 +422,7 @@ export function cleanJobs(olderThanDays = 7) {
|
|
|
421
422
|
if (Number.isFinite(t) &&
|
|
422
423
|
t < cutoff &&
|
|
423
424
|
(job.status === "completed" || job.status === "failed" || job.status === "killed")) {
|
|
424
|
-
for (const ext of [".json", ".prompt", ".log"]) {
|
|
425
|
+
for (const ext of [".json", ".prompt", ".log", ".dsh-patch.yml"]) {
|
|
425
426
|
const fp = underJobsDir(id, ext);
|
|
426
427
|
if (existsSync(fp))
|
|
427
428
|
unlinkSync(fp);
|
package/docs/DEMO_GIF.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Hero demo recording (GIF)
|
|
2
2
|
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
No real GIF yet — the checkbox stays open until a `docs/fixtures/hero.gif` exists.
|
|
6
|
+
|
|
7
|
+
Committed fixture for now: [docs/fixtures/hero-demo.log](./fixtures/hero-demo.log),
|
|
8
|
+
a reproducible dry-run capture (no live workers, no secrets). Regenerate it with:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
docs/fixtures/generate-hero-demo.sh
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Also see [docs/fixtures/claude-ds-smoke.log](./fixtures/claude-ds-smoke.log)
|
|
15
|
+
(headless `claude-ds` smoke proof). Both are machine-agnostic.
|
|
16
|
+
|
|
17
|
+
## Recording the real GIF (later)
|
|
18
|
+
|
|
3
19
|
tmux is required for the viral attach/send demo. On this laptop brew needs sudo — record after:
|
|
4
20
|
|
|
5
21
|
```bash
|
|
@@ -27,4 +43,4 @@ If you previously exported `$HOME/.cursor-route/bin`, that dir is stale — remo
|
|
|
27
43
|
|
|
28
44
|
Record with [asciinema](https://asciinema.org/) or CleanShot → export GIF → `docs/fixtures/hero.gif`.
|
|
29
45
|
|
|
30
|
-
|
|
46
|
+
When a real GIF lands, check off the **Hero GIF** item in `docs/briefs/WORKING.md` and reference it here.
|
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.8 (LIVE on npm latest)
|
|
5
5
|
created: 2026-08-12
|
|
6
|
-
updated: 2026-08-
|
|
6
|
+
updated: 2026-08-14
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# cursor-route — living brief
|
|
@@ -24,7 +24,7 @@ Cursor Agent plans. Workers run in tmux via `cursor-route`:
|
|
|
24
24
|
|
|
25
25
|
Always-approve on (`--ask` / `CURSOR_ROUTE_ASK=1` to opt out). Jobs live in `~/.local/share/cursor-route/jobs`, not in this clone.
|
|
26
26
|
|
|
27
|
-
Install: `npm i -g cursor-route` → **0.1.
|
|
27
|
+
Install: `npm i -g cursor-route` → **0.1.8**. Release notes: [CHANGELOG.md](../../CHANGELOG.md).
|
|
28
28
|
|
|
29
29
|
## Open (edit / check off)
|
|
30
30
|
|
|
@@ -33,9 +33,10 @@ Install: `npm i -g cursor-route` → **0.1.7**. Release notes: [CHANGELOG.md](..
|
|
|
33
33
|
- [x] add `--model flash|pro` on `start`
|
|
34
34
|
- [x] document Grok **auth** ≠ usage-out (`grok login`) vs quota → Pro stand-in
|
|
35
35
|
- [x] **Skill `route-orch`** — Flash/Pro table in `skills/` + `.cursor/skills/`
|
|
36
|
-
- [x] **Official DeepSeek Harness** — `deepseek` adapter slot present; mid
|
|
36
|
+
- [x] **Official DeepSeek Harness** — `deepseek` adapter slot present; `@deepseek-ai/dsh` 0.1.0-rc.6 (2026-08-13, github.com/deepseek-ai/deepseek-harness, MIT) is a developer-preview plugin kernel (web UI + `dsh --profile headless "job"`), not a Claude Code replacement; mid does not swap
|
|
37
37
|
- [x] **0.1.7 debug fixes** — env default wired through `startJob`; Anthropic hatch omits DS ids; preserve `[1m]`
|
|
38
|
-
- [
|
|
38
|
+
- [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
|
+
- [ ] **Hero GIF** — still outstanding; dry-run fixture ships as the substitute for now (`docs/fixtures/hero-demo.log` — see `docs/DEMO_GIF.md`)
|
|
39
40
|
- [x] **Do not** paste private `ROUTE_KIT`, SIP, prod paths, or hang-watchdog env into this public repo
|
|
40
41
|
|
|
41
42
|
## Repo map
|
|
@@ -44,7 +45,7 @@ Install: `npm i -g cursor-route` → **0.1.7**. Release notes: [CHANGELOG.md](..
|
|
|
44
45
|
|------|------|
|
|
45
46
|
| `src/cli.ts` | `health` / `start` / `jobs` / `capture` / `send` / `kill` |
|
|
46
47
|
| `src/adapters/claude-ds.ts` | Mid worker; DeepSeek URL required; `--model` → `-Model` |
|
|
47
|
-
| `src/adapters/deepseek.ts` |
|
|
48
|
+
| `src/adapters/deepseek.ts` | Experimental dsh worker (`--worker deepseek`; mid stays claude-ds) |
|
|
48
49
|
| `src/adapters/grok.ts` | Hard worker |
|
|
49
50
|
| `src/adapters/openrouter.ts` | Easy worker |
|
|
50
51
|
| `skills/route-orch/SKILL.md` | Cursor skill — spawn CLI, do not implement in-session |
|
|
@@ -59,3 +60,7 @@ Install: `npm i -g cursor-route` → **0.1.7**. Release notes: [CHANGELOG.md](..
|
|
|
59
60
|
| 2026-08-12 | Shipped Flash default + `--model pro`, deepseek slot, skill table → 0.1.6. |
|
|
60
61
|
| 2026-08-12 | npm `cursor-route@0.1.6` LIVE; CHANGELOG + README skill-install path fixed. |
|
|
61
62
|
| 2026-08-12 | Grok debug → 0.1.7: env DS default, Anthropic hatch, `[1m]` preserve. |
|
|
63
|
+
| 2026-08-13 | Hero demo dry-run fixture + docs: `docs/fixtures/generate-hero-demo.sh` → `hero-demo.log`; README Demo, DEMO_GIF.md, demo-notes pointer. Real GIF still open. |
|
|
64
|
+
| 2026-08-13 | GPTSOL fixes: scrub inherited env/paths, stable job ids, ignore `docs/briefs/handoffs/`, GIF checkbox wording. |
|
|
65
|
+
| 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). |
|
|
66
|
+
| 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. |
|
package/docs/demo-notes.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Demo fixture log (no secrets)
|
|
2
2
|
|
|
3
|
+
> **Current fixture:** [docs/fixtures/hero-demo.log](./fixtures/hero-demo.log) — generated, dry-run, machine-agnostic (regenerate: `docs/fixtures/generate-hero-demo.sh`). Real GIF still pending: [DEMO_GIF.md](./DEMO_GIF.md).
|
|
4
|
+
|
|
3
5
|
Simulated capture output for README / tweet assets — replace with a real GIF once tmux is available.
|
|
4
6
|
|
|
5
7
|
```
|
|
6
8
|
$ cursor-route --version
|
|
7
|
-
0.1.
|
|
9
|
+
0.1.8
|
|
8
10
|
|
|
9
11
|
$ cursor-route health
|
|
10
|
-
cursor-route v0.1.
|
|
12
|
+
cursor-route v0.1.8
|
|
11
13
|
health: OK
|
|
12
14
|
✓ tmux
|
|
13
15
|
✓ runtime bun ok
|
|
@@ -15,7 +17,7 @@ health: OK
|
|
|
15
17
|
✓ worker:grok
|
|
16
18
|
✓ worker:claude-ds
|
|
17
19
|
✓ worker:openrouter
|
|
18
|
-
✗ worker:deepseek
|
|
20
|
+
✗ worker:deepseek dsh not found — mid default remains claude-ds
|
|
19
21
|
✓ jobs_dir ~/.local/share/cursor-route/jobs
|
|
20
22
|
|
|
21
23
|
$ cursor-route start --lane mid "Add a failing test then make it pass"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# generate-hero-demo.sh — regenerate docs/fixtures/hero-demo.log
|
|
3
|
+
#
|
|
4
|
+
# Dry-run only: no live Grok, no worker spawned, no secrets. Jobs dir is
|
|
5
|
+
# sandboxed (trap-cleaned). Output is path-scrubbed + id-normalized so the
|
|
6
|
+
# committed fixture is machine-agnostic and regenerates stably.
|
|
7
|
+
#
|
|
8
|
+
# Prerequisites: Bun or Node 20+; runnable ./bin/cursor-route
|
|
9
|
+
# (git clone: bun install; npm global install ships dist/).
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
13
|
+
BIN="$ROOT/bin/cursor-route"
|
|
14
|
+
OUT="$ROOT/docs/fixtures/hero-demo.log"
|
|
15
|
+
|
|
16
|
+
if [[ ! -f "$BIN" ]]; then
|
|
17
|
+
echo "missing $BIN — run from a cursor-route checkout (need bun/node 20+)" >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Neutralize inherited overrides that would leak private paths into the log.
|
|
22
|
+
unset CURSOR_ROUTE_CLAUDE_DS_BIN CURSOR_ROUTE_GROK_BIN CURSOR_ROUTE_DSH_BIN \
|
|
23
|
+
CURSOR_ROUTE_ALLOW_ANTHROPIC \
|
|
24
|
+
CURSOR_ROUTE_ALLOW_STOCK_CLAUDE CURSOR_ROUTE_ANTHROPIC_BASE_URL CURSOR_ROUTE_DS_MODEL \
|
|
25
|
+
CURSOR_ROUTE_OPENROUTER_MODEL OPENROUTER_API_KEY OPENROUTER_BASE_URL \
|
|
26
|
+
ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN ANTHROPIC_API_KEY ANTHROPIC_MODEL \
|
|
27
|
+
DEEPSEEK_API_KEY XAI_API_KEY 2>/dev/null || true
|
|
28
|
+
|
|
29
|
+
TMP="$(mktemp -d)"
|
|
30
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
31
|
+
export CURSOR_ROUTE_JOBS_DIR="$TMP/.demo-jobs"
|
|
32
|
+
# Pin dsh to a missing path so worker:deepseek renders identically on machines
|
|
33
|
+
# that do have a real dsh installed (the adapter shows the install hint, no path).
|
|
34
|
+
export CURSOR_ROUTE_DSH_BIN="$TMP/no-such-dsh"
|
|
35
|
+
|
|
36
|
+
RAW="$TMP/hero-demo.raw"
|
|
37
|
+
NORM="$TMP/hero-demo.norm"
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
echo "\$ cursor-route --version"
|
|
41
|
+
"$BIN" --version
|
|
42
|
+
echo
|
|
43
|
+
|
|
44
|
+
echo "\$ CURSOR_ROUTE_RELAXED=1 cursor-route health"
|
|
45
|
+
CURSOR_ROUTE_RELAXED=1 "$BIN" health
|
|
46
|
+
echo
|
|
47
|
+
|
|
48
|
+
echo "\$ cursor-route start --lane mid --model flash --dry-run \"Add a unit test for shellQuote\""
|
|
49
|
+
"$BIN" start --lane mid --model flash --dry-run "Add a unit test for shellQuote"
|
|
50
|
+
echo
|
|
51
|
+
|
|
52
|
+
echo "\$ cursor-route start --lane easy --dry-run \"Rewrite this FAQ answer in 3 sentences\""
|
|
53
|
+
"$BIN" start --lane easy --dry-run "Rewrite this FAQ answer in 3 sentences"
|
|
54
|
+
echo
|
|
55
|
+
|
|
56
|
+
echo "\$ cursor-route start --lane hard --dry-run \"Refactor auth module; run tests; report verify evidence\""
|
|
57
|
+
"$BIN" start --lane hard --dry-run "Refactor auth module; run tests; report verify evidence"
|
|
58
|
+
echo
|
|
59
|
+
|
|
60
|
+
echo "\$ cursor-route start --lane mid --dry-run --json \"Add a failing test then make it pass\""
|
|
61
|
+
"$BIN" start --lane mid --dry-run --json "Add a failing test then make it pass"
|
|
62
|
+
echo
|
|
63
|
+
|
|
64
|
+
echo "\$ cursor-route jobs --json"
|
|
65
|
+
"$BIN" jobs --json
|
|
66
|
+
} > "$RAW"
|
|
67
|
+
|
|
68
|
+
jobs_display="$HOME/.local/share/cursor-route/jobs"
|
|
69
|
+
{
|
|
70
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
71
|
+
line="${line//$TMP\/.demo-jobs/$jobs_display}"
|
|
72
|
+
line="${line//$ROOT/~/Projects/cursor-route}"
|
|
73
|
+
line="${line//$HOME/~}"
|
|
74
|
+
line="$(printf '%s' "$line" | sed -E \
|
|
75
|
+
-e 's#/Users/[^/\"'\'' ]+#~#g' \
|
|
76
|
+
-e 's#/home/[^/\"'\'' ]+#~#g' \
|
|
77
|
+
-e 's#/opt/cemini[^\"'\'' ]*#~#g' \
|
|
78
|
+
-e 's#/var/folders/[^\"'\'' ]+#/tmp#g')"
|
|
79
|
+
printf '%s\n' "$line"
|
|
80
|
+
done
|
|
81
|
+
} < "$RAW" > "$NORM"
|
|
82
|
+
|
|
83
|
+
python3 - "$NORM" "$OUT" <<'PY'
|
|
84
|
+
import re, sys
|
|
85
|
+
src, dst = sys.argv[1], sys.argv[2]
|
|
86
|
+
text = open(src, encoding="utf-8").read()
|
|
87
|
+
seq = ["a1b2c3d4", "b2c3d4e5", "c3d4e5f6", "d4e5f6a7"]
|
|
88
|
+
seen: dict[str, str] = {}
|
|
89
|
+
|
|
90
|
+
def take(jid: str) -> str:
|
|
91
|
+
if jid not in seen:
|
|
92
|
+
seen[jid] = seq[len(seen)] if len(seen) < len(seq) else f"{len(seen):08x}"
|
|
93
|
+
return seen[jid]
|
|
94
|
+
|
|
95
|
+
out = []
|
|
96
|
+
for line in text.splitlines(True):
|
|
97
|
+
def sub(m: re.Match[str]) -> str:
|
|
98
|
+
return m.group(1) + take(m.group(2)) + m.group(3)
|
|
99
|
+
|
|
100
|
+
line = re.sub(r"(dry-run job )([0-9a-f]{8})(\b)", sub, line)
|
|
101
|
+
line = re.sub(r'("id": ")([0-9a-f]{8})(")', sub, line)
|
|
102
|
+
line = re.sub(r"(jobs/)([0-9a-f]{8})(\.prompt)", sub, line)
|
|
103
|
+
line = re.sub(r"(headless-|cursor-route-)([0-9a-f]{8})(\b)", sub, line)
|
|
104
|
+
line = re.sub(r'("createdAt": ")[^"]+(")', r"\g<1>2026-08-13T00:00:00.000Z\2", line)
|
|
105
|
+
out.append(line)
|
|
106
|
+
open(dst, "w", encoding="utf-8").write("".join(out))
|
|
107
|
+
PY
|
|
108
|
+
|
|
109
|
+
echo "wrote $OUT"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
$ cursor-route --version
|
|
2
|
+
0.1.8
|
|
3
|
+
|
|
4
|
+
$ CURSOR_ROUTE_RELAXED=1 cursor-route health
|
|
5
|
+
cursor-route v0.1.8
|
|
6
|
+
health: OK
|
|
7
|
+
|
|
8
|
+
✓ tmux ok
|
|
9
|
+
✓ runtime bun ok
|
|
10
|
+
✓ script(1) ok (tty log capture)
|
|
11
|
+
✓ worker:grok ok (auth checked at first start — run grok login if jobs fail) @ ~/.grok/bin/grok
|
|
12
|
+
✓ worker:claude-ds ok (claude-ds (DeepSeek shim); default model deepseek-v4-flash) @ ~/.local/bin/claude-ds
|
|
13
|
+
✗ worker:openrouter OPENROUTER_API_KEY not set — export your OpenRouter key (easy lane model defaults to openrouter/free) @ node '~/Projects/cursor-route/dist/openrouter-run.js'
|
|
14
|
+
✗ worker:deepseek dsh (@deepseek-ai/dsh) not found — install: npm i -g @deepseek-ai/dsh. Mid default remains claude-ds.
|
|
15
|
+
✓ cursor_cli optional ok (agent on PATH) — v0 supervisor is Cursor skill, not CLI
|
|
16
|
+
✓ relaxed CURSOR_ROUTE_RELAXED=1 — tmux optional (headless OK)
|
|
17
|
+
✓ jobs_dir ~/.local/share/cursor-route/jobs
|
|
18
|
+
|
|
19
|
+
$ cursor-route start --lane mid --model flash --dry-run "Add a unit test for shellQuote"
|
|
20
|
+
dry-run job a1b2c3d4
|
|
21
|
+
worker: claude-ds
|
|
22
|
+
model: flash
|
|
23
|
+
command: cd '~/Projects/cursor-route' && '~/.local/bin/claude-ds' -PromptFile '~/.local/share/cursor-route/jobs/a1b2c3d4.prompt' -Model 'deepseek-v4-flash' --dangerously-skip-permissions
|
|
24
|
+
|
|
25
|
+
$ cursor-route start --lane easy --dry-run "Rewrite this FAQ answer in 3 sentences"
|
|
26
|
+
dry-run job b2c3d4e5
|
|
27
|
+
worker: openrouter
|
|
28
|
+
command: node '~/Projects/cursor-route/dist/openrouter-run.js' --prompt-file '~/.local/share/cursor-route/jobs/b2c3d4e5.prompt'
|
|
29
|
+
|
|
30
|
+
$ cursor-route start --lane hard --dry-run "Refactor auth module; run tests; report verify evidence"
|
|
31
|
+
dry-run job c3d4e5f6
|
|
32
|
+
worker: grok
|
|
33
|
+
command: '~/.grok/bin/grok' -p "$(cat '~/.local/share/cursor-route/jobs/c3d4e5f6.prompt')" --cwd '~/Projects/cursor-route' --no-auto-update --output-format plain --always-approve
|
|
34
|
+
|
|
35
|
+
$ cursor-route start --lane mid --dry-run --json "Add a failing test then make it pass"
|
|
36
|
+
{
|
|
37
|
+
"id": "d4e5f6a7",
|
|
38
|
+
"schema": "cursor-route.job.v1",
|
|
39
|
+
"status": "pending",
|
|
40
|
+
"worker": "claude-ds",
|
|
41
|
+
"lane": "mid",
|
|
42
|
+
"model": "flash",
|
|
43
|
+
"prompt": "Add a failing test then make it pass",
|
|
44
|
+
"cwd": "~/Projects/cursor-route",
|
|
45
|
+
"alwaysApprove": true,
|
|
46
|
+
"tmuxSession": "cursor-route-d4e5f6a7",
|
|
47
|
+
"createdAt": "2026-08-13T00:00:00.000Z",
|
|
48
|
+
"command": "cd '~/Projects/cursor-route' && '~/.local/bin/claude-ds' -PromptFile '~/.local/share/cursor-route/jobs/d4e5f6a7.prompt' -Model 'deepseek-v4-flash' --dangerously-skip-permissions"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
$ cursor-route jobs --json
|
|
52
|
+
[]
|
package/llms.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Cursor stays the planner. DeepSeek (mid), Grok CLI (hard), and OpenRouter free models (easy) run parallel coding workers in tmux.
|
|
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.8**)
|
|
6
6
|
GitHub: https://github.com/cemini23/cursor-route
|
|
7
7
|
|
|
8
8
|
## FAQ
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-route",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,6 +46,19 @@ cursor-route start --lane mid --model pro --dir "$PWD" "…"
|
|
|
46
46
|
|
|
47
47
|
If `worker:grok` is ✗ on health, that is usually **auth** (`grok login` / `XAI_API_KEY`) — not the Pro stand-in case.
|
|
48
48
|
|
|
49
|
+
## Experimental: --worker deepseek (dsh)
|
|
50
|
+
|
|
51
|
+
Official DeepSeek Harness headless as an opt-in worker — **not the mid default** (mid stays `claude-ds`).
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm i -g @deepseek-ai/dsh
|
|
55
|
+
export DEEPSEEK_API_KEY=... # platform.deepseek.com
|
|
56
|
+
cursor-route start --worker deepseek --dir "$PWD" "…"
|
|
57
|
+
cursor-route start --worker deepseek --model pro --dir "$PWD" "…" # --model applies here too
|
|
58
|
+
```
|
|
59
|
+
|
|
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
|
+
|
|
49
62
|
## Workflow
|
|
50
63
|
|
|
51
64
|
1. Run `cursor-route health` (or `CURSOR_ROUTE_RELAXED=1` for headless). If the **target worker** is unhealthy, fix before spawning.
|
|
@@ -63,7 +76,7 @@ EOF
|
|
|
63
76
|
)"
|
|
64
77
|
```
|
|
65
78
|
|
|
66
|
-
Or `--worker grok` / `--worker claude-ds` / `--worker openrouter` (or `--lane easy`). Use `--no-tmux` only when tmux is unavailable.
|
|
79
|
+
Or `--worker grok` / `--worker claude-ds` / `--worker deepseek` (experimental) / `--worker openrouter` (or `--lane easy`). Use `--no-tmux` only when tmux is unavailable.
|
|
67
80
|
|
|
68
81
|
4. Monitor: `cursor-route jobs --json` · `cursor-route capture <id>` · `cursor-route send <id> "…"` (tmux only).
|
|
69
82
|
5. Summarize worker results with **verify evidence** — no status-only “done”. If verify fails, `send` a correction or spawn a follow-up — do not invent success.
|
|
@@ -75,6 +88,6 @@ Defaults on for workers. Opt out: `cursor-route start … --ask` or `CURSOR_ROUT
|
|
|
75
88
|
## Anti-patterns
|
|
76
89
|
|
|
77
90
|
- Do not paste API keys / private keys into prompts or `send`
|
|
78
|
-
- Do not claim DeepSeek
|
|
91
|
+
- Do not claim the official DeepSeek harness (`@deepseek-ai/dsh`) is the mid default — `--worker deepseek` is experimental only; mid stays **claude-ds**
|
|
79
92
|
- Do not open-source or dump private Cemini `agent-toolkit` paths into public handoffs
|
|
80
93
|
- Do not mark done without reading `capture` / exit status
|