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 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
- **Reserved:** `--worker deepseek` is a slot for an official DeepSeek coding harness not wired yet. Mid stays on `claude-ds`.
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
- - Native DeepSeek harness adapter
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
- * Reserved slot for the official DeepSeek coding harness when it ships.
3
- * Mid lane stays on claude-ds until then do not route jobs here.
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 harness (unreleased)",
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: false,
12
- binary: null,
13
- detail: "unreleased mid lane uses claude-ds (DeepSeek behind Claude Code). See README.",
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
- throw new Error("Official DeepSeek harness is not available yet use --lane mid / --worker claude-ds");
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 = reserved slot)
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 only for claude-ds
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/claude-ds only; ignore (do not validate) for other workers
225
- if (f.model !== undefined && resolvedWorker === "claude-ds") {
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.7",
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
- /** Lane → default worker (Cemini /route public core). */
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
- Until then, use `docs/fixtures/claude-ds-smoke.log` as the committed proof fixture.
46
+ When a real GIF lands, check off the **Hero GIF** item in `docs/briefs/WORKING.md` and reference it here.
@@ -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.7 (LIVE on npm latest)
4
+ npm: cursor-route@0.1.8 (LIVE on npm latest)
5
5
  created: 2026-08-12
6
- updated: 2026-08-12
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.7**. Release notes: [CHANGELOG.md](../../CHANGELOG.md).
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 stays on claude-ds
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
- - [ ] **Hero GIF**still outstanding (`docs/DEMO_GIF.md`)
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` | Reserved unreleased harness slot |
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. |
@@ -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.6
9
+ 0.1.8
8
10
 
9
11
  $ cursor-route health
10
- cursor-route v0.1.6
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 unreleased — mid lane uses claude-ds
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.7**)
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.7",
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-native harness until that adapter shipstoday is **claude-ds**
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