cursor-route 0.1.1 → 0.1.4
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/README.md +53 -8
- package/SECURITY.md +3 -1
- package/SUPPORT.md +24 -0
- package/bin/cursor-route +6 -8
- package/bin/cursor-route.js +11 -8
- package/dist/adapters/claude-ds.js +189 -0
- package/dist/adapters/grok.js +57 -0
- package/dist/adapters/index.js +15 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli.js +431 -0
- package/dist/config.js +45 -0
- package/dist/health.js +100 -0
- package/dist/jobs.js +410 -0
- package/dist/mark-complete.js +38 -0
- package/dist/runtime.js +27 -0
- package/dist/secrets.js +39 -0
- package/dist/tmux.js +117 -0
- package/dist/util.js +21 -0
- package/docs/DEMO_GIF.md +9 -2
- package/docs/demo-notes.md +8 -1
- package/docs/fixtures/claude-ds-smoke.log +1 -1
- package/llms.txt +29 -0
- package/package.json +12 -6
- package/src/adapters/claude-ds.ts +68 -22
- package/src/adapters/grok.ts +10 -5
- package/src/adapters/types.ts +2 -0
- package/src/cli.test.ts +24 -9
- package/src/cli.ts +105 -15
- package/src/config.ts +22 -2
- package/src/health.ts +8 -6
- package/src/integration.test.ts +167 -0
- package/src/jobs.ts +85 -11
- package/src/mark-complete.ts +2 -6
- package/src/runtime.ts +9 -4
- package/src/secrets.ts +21 -4
- package/src/tmux.ts +20 -8
- package/src/util.ts +3 -1
- package/docs/audit-2026-08-10-sol-grok-kimi.md +0 -64
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Exact dollars vary — the point is **reuse subscriptions you already have**.
|
|
|
46
46
|
```bash
|
|
47
47
|
# Prerequisites
|
|
48
48
|
brew install tmux # or build tmux into ~/.local
|
|
49
|
-
# Bun
|
|
49
|
+
# Bun or Node 20+ — npm package ships compiled dist/ (Node runs it directly)
|
|
50
50
|
|
|
51
51
|
# Preferred install
|
|
52
52
|
npm i -g cursor-route
|
|
@@ -56,7 +56,7 @@ grok login # if using Grok
|
|
|
56
56
|
# configure claude-ds — see DeepSeek setup below
|
|
57
57
|
|
|
58
58
|
cursor-route health
|
|
59
|
-
# without tmux (CI / headless):
|
|
59
|
+
# without tmux / workers (CI / headless infra smoke):
|
|
60
60
|
CURSOR_ROUTE_RELAXED=1 cursor-route health
|
|
61
61
|
```
|
|
62
62
|
|
|
@@ -141,19 +141,64 @@ This is **not** inside a git clone of this repo.
|
|
|
141
141
|
- See [SECURITY.md](./SECURITY.md)
|
|
142
142
|
- Local orchestration only — no LIVE Discord / trading egress demos
|
|
143
143
|
|
|
144
|
-
##
|
|
144
|
+
## FAQ
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
|
|
146
|
+
**What is cursor-route?**
|
|
147
|
+
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, and Grok CLI handles the hard lane.
|
|
148
|
+
|
|
149
|
+
**How is this different from Codex orchestrator?**
|
|
150
|
+
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.
|
|
151
|
+
|
|
152
|
+
**Does mid lane use Anthropic Claude?**
|
|
153
|
+
No. The mid worker is DeepSeek. Claude Code is the harness, configured with `ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic` and a DeepSeek key in `ANTHROPIC_AUTH_TOKEN`.
|
|
154
|
+
|
|
155
|
+
**How do I install?**
|
|
156
|
+
Run `npm i -g cursor-route`, install tmux if needed, then run `cursor-route health`. The package is available at https://www.npmjs.com/package/cursor-route, and the source is at https://github.com/cemini23/cursor-route.
|
|
157
|
+
|
|
158
|
+
**Is it free?**
|
|
159
|
+
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.
|
|
160
|
+
|
|
161
|
+
## Related
|
|
162
|
+
|
|
163
|
+
- Newsletter: [Outlier Weekly](https://outlierweekly.substack.com)
|
|
164
|
+
- YouTube: [@Cemini23](https://www.youtube.com/@Cemini23)
|
|
165
|
+
- Agent meta-wiki: [cemini-claude-code-CCC](https://github.com/cemini23/cemini-claude-code-CCC)
|
|
166
|
+
- Products: [Atto](https://youratto.com) · [GuruWatcher](https://guruwatcher.com)
|
|
167
|
+
- Agent toolkit: [vet](https://github.com/cemini23/vet) · [wikilint](https://github.com/cemini23/wikilint) · [phase0](https://github.com/cemini23/phase0) · [agent-toolkit-demo](https://github.com/cemini23/agent-toolkit-demo) · [ara-schema](https://github.com/cemini23/ara-schema) · [cursor-audit](https://github.com/cemini23/agent-toolkit-demo/tree/main/skills/cursor-audit) · [super-audit](https://github.com/cemini23/agent-toolkit-demo/tree/main/skills/super-audit)
|
|
168
|
+
- Public wikis: [Gambling](https://github.com/cemini23/Gambling-wiki) · [Game Dev](https://github.com/cemini23/Game-Dev-wiki) · [SEO/GEO](https://github.com/cemini23/SEO-GEO-B-M-Wiki) · [Cybersecurity](https://github.com/cemini23/Cybersecurity-wiki) · [3D Printing](https://github.com/cemini23/3D-Printing-Wiki) · [Image Gen](https://github.com/cemini23/uncensored-image-gen-wiki)
|
|
169
|
+
- Trading: [world-cup-bot](https://github.com/cemini23/world-cup-bot)
|
|
170
|
+
- Donation wallets (canonical): [SUPPORT.md](SUPPORT.md)
|
|
171
|
+
|
|
172
|
+
## Support
|
|
173
|
+
|
|
174
|
+
Thank you for your support — stars, issues, shares, and tips all help keep this CLI and the broader Cemini open-research stack alive.
|
|
175
|
+
|
|
176
|
+
If you’d like to tip, use the **donation-only** addresses below (not trading or production wallets). Prefer following the work? These are the best places to start:
|
|
177
|
+
|
|
178
|
+
| Project | Link |
|
|
179
|
+
|---------|------|
|
|
180
|
+
| **Outlier Weekly** (methodology newsletter) | [outlierweekly.substack.com](https://outlierweekly.substack.com) |
|
|
181
|
+
| **Atto** — organize Italian family documents on your computer | [youratto.com](https://youratto.com) |
|
|
182
|
+
| **GuruWatcher** — Discord alerts for your newsletter’s price levels | [guruwatcher.com](https://guruwatcher.com) |
|
|
183
|
+
| **YouTube** | [@Cemini23](https://www.youtube.com/@Cemini23) |
|
|
184
|
+
|
|
185
|
+
| Chain family | Address |
|
|
186
|
+
|--------------|---------|
|
|
187
|
+
| **EVM** (Ethereum, Polygon, Base, Arbitrum, …) | `0x444C5C2eC439E0382aa5a17F70313c536BcC5D58` |
|
|
188
|
+
| **Solana / SVM** | `J4zNn4hK9jTrKBFY8sbAGJHLoZvXvQf4B9pQSbSrocZE` |
|
|
189
|
+
| **Polymarket** (referral) | [polymarket.com/?r=Cemini23](https://polymarket.com/?r=Cemini23) |
|
|
190
|
+
|
|
191
|
+
Full wallet note: [SUPPORT.md](SUPPORT.md) · canon also in [CCC SUPPORT.md](https://github.com/cemini23/cemini-claude-code-CCC/blob/main/SUPPORT.md).
|
|
192
|
+
|
|
193
|
+
We’re grateful you’re here. Thank you for your support.
|
|
149
194
|
|
|
150
195
|
## License
|
|
151
196
|
|
|
152
|
-
MIT © Cemini
|
|
197
|
+
MIT © Cemini — see [LICENSE](LICENSE).
|
|
153
198
|
|
|
154
199
|
## Roadmap (explicitly later)
|
|
155
200
|
|
|
156
|
-
-
|
|
201
|
+
- Homebrew tap
|
|
157
202
|
- Native DeepSeek harness adapter
|
|
158
203
|
- Codebase map injection (`--map`)
|
|
159
204
|
- Cursor CLI `agent` as alternate supervisor
|
package/SECURITY.md
CHANGED
|
@@ -21,6 +21,8 @@ Do **not** open a public issue for credential leaks or RCE-class bugs.
|
|
|
21
21
|
|
|
22
22
|
## Supply chain
|
|
23
23
|
|
|
24
|
-
-
|
|
24
|
+
- Declared runtime npm dependencies: **none**. System needs: Node ≥20 or Bun, `tmux`, and worker CLIs (Grok / claude-ds).
|
|
25
|
+
- The npm package ships a compiled `dist/` (plain JS, no loader). The bin shim and the completion hook run the compiled JS via `node`; there is **no network fetch of `tsx` on the happy path**.
|
|
26
|
+
- Running from a git clone without a build uses Bun to execute `src/` directly (Bun runs TypeScript natively) — also offline-friendly.
|
|
25
27
|
- Review `package.json` before upgrading.
|
|
26
28
|
- Prefer pinned git tags for production installs.
|
package/SUPPORT.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Support Cemini open research
|
|
2
|
+
|
|
3
|
+
Thank you for your interest — and for any support, large or small. Tips and kind words both help keep Cemini research and tooling open.
|
|
4
|
+
|
|
5
|
+
Voluntary tips fund wikis, OSS tooling, and public research. **Donation-only addresses** — not trading or production wallets.
|
|
6
|
+
|
|
7
|
+
| Chain family | Address |
|
|
8
|
+
|--------------|---------|
|
|
9
|
+
| **EVM** (Ethereum, Polygon, Base, Arbitrum, …) | `0x444C5C2eC439E0382aa5a17F70313c536BcC5D58` |
|
|
10
|
+
| **Solana / SVM** | `J4zNn4hK9jTrKBFY8sbAGJHLoZvXvQf4B9pQSbSrocZE` |
|
|
11
|
+
| **Polymarket** (referral) | [polymarket.com/?r=Cemini23](https://polymarket.com/?r=Cemini23) |
|
|
12
|
+
|
|
13
|
+
If you’d rather follow along or try something we ship:
|
|
14
|
+
|
|
15
|
+
- Newsletter — [Outlier Weekly](https://outlierweekly.substack.com) (Substack)
|
|
16
|
+
- Genealogy kit — [youratto.com](https://youratto.com)
|
|
17
|
+
- Newsletter parameter alerts — [guruwatcher.com](https://guruwatcher.com)
|
|
18
|
+
- YouTube — [@Cemini23](https://www.youtube.com/@Cemini23)
|
|
19
|
+
- Agent meta-wiki — [cemini-claude-code-CCC](https://github.com/cemini23/cemini-claude-code-CCC)
|
|
20
|
+
- This CLI — [cursor-route](https://github.com/cemini23/cursor-route)
|
|
21
|
+
|
|
22
|
+
Canonical federation copy: [CCC SUPPORT.md](https://github.com/cemini23/cemini-claude-code-CCC/blob/main/SUPPORT.md).
|
|
23
|
+
|
|
24
|
+
We’re grateful you’re here. Thank you for your support.
|
package/bin/cursor-route
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# cursor-route — thin launcher (
|
|
2
|
+
# cursor-route — thin launcher (compiled dist via node, else Bun on src)
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
SOURCE="${BASH_SOURCE[0]}"
|
|
5
5
|
while [ -L "$SOURCE" ]; do
|
|
@@ -9,16 +9,14 @@ while [ -L "$SOURCE" ]; do
|
|
|
9
9
|
done
|
|
10
10
|
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
11
11
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
|
-
CLI="$ROOT/src/cli.ts"
|
|
13
12
|
|
|
14
|
-
if command -v
|
|
15
|
-
exec
|
|
13
|
+
if [ -f "$ROOT/dist/cli.js" ] && command -v node >/dev/null 2>&1; then
|
|
14
|
+
exec node "$ROOT/dist/cli.js" "$@"
|
|
16
15
|
fi
|
|
17
16
|
|
|
18
|
-
if command -v
|
|
19
|
-
|
|
20
|
-
exec npx --yes tsx@4.19.4 "$CLI" "$@"
|
|
17
|
+
if command -v bun >/dev/null 2>&1; then
|
|
18
|
+
exec bun "$ROOT/src/cli.ts" "$@"
|
|
21
19
|
fi
|
|
22
20
|
|
|
23
|
-
echo "cursor-route:
|
|
21
|
+
echo "cursor-route: no compiled build (run 'bun run build') and no Bun — need Node 20+ or Bun (https://bun.sh)" >&2
|
|
24
22
|
exit 127
|
package/bin/cursor-route.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* npm bin
|
|
3
|
+
* npm bin redirect — prefers compiled dist (node), else Bun on src.
|
|
4
|
+
* The shell launcher (bin/cursor-route) is the primary npm bin entry;
|
|
5
|
+
* this file keeps direct `node bin/cursor-route.js` working the same way.
|
|
4
6
|
*/
|
|
5
7
|
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
6
9
|
import { fileURLToPath } from "node:url";
|
|
7
10
|
import { dirname, join } from "node:path";
|
|
8
11
|
|
|
9
12
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
-
const cli = join(root, "src", "cli.ts");
|
|
11
13
|
const args = process.argv.slice(2);
|
|
12
14
|
|
|
13
15
|
function run(cmd, cmdArgs) {
|
|
@@ -16,11 +18,12 @@ function run(cmd, cmdArgs) {
|
|
|
16
18
|
process.exit(r.status ?? 1);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
run("
|
|
21
|
+
function main() {
|
|
22
|
+
const distCli = join(root, "dist", "cli.js");
|
|
23
|
+
if (existsSync(distCli) && run("node", [distCli, ...args]) !== null) return;
|
|
24
|
+
if (run("bun", [join(root, "src", "cli.ts"), ...args]) !== null) return;
|
|
25
|
+
console.error("cursor-route: no compiled build (run 'bun run build') and no Bun — need Node 20+ or Bun (https://bun.sh)");
|
|
26
|
+
process.exit(127);
|
|
22
27
|
}
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
console.error("cursor-route: need Bun (https://bun.sh) or Node 20+ with npx");
|
|
26
|
-
process.exit(127);
|
|
29
|
+
main();
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { shellQuote } from "../util.js";
|
|
6
|
+
/**
|
|
7
|
+
* Mid-lane = DeepSeek (cheap). The Claude Code binary is only a harness —
|
|
8
|
+
* requests must hit DeepSeek, not Anthropic.
|
|
9
|
+
*
|
|
10
|
+
* Resolution order:
|
|
11
|
+
* 1. claude-ds / deepseek-claude shims
|
|
12
|
+
* 2. stock `claude` when ANTHROPIC_BASE_URL (env or ~/.claude/settings.json)
|
|
13
|
+
* points at api.deepseek.com — auto-ok, no opt-in
|
|
14
|
+
* 3. stock `claude` on Anthropic only if CURSOR_ROUTE_ALLOW_ANTHROPIC=1
|
|
15
|
+
* (explicit expensive escape hatch — not the product default)
|
|
16
|
+
*/
|
|
17
|
+
function which(cmd) {
|
|
18
|
+
try {
|
|
19
|
+
return (execSync(`command -v ${shellQuote(cmd)}`, {
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
22
|
+
}).trim() || null);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** True when URL hostname is deepseek.com (or a subdomain). */
|
|
29
|
+
export function isDeepSeekBaseUrl(url) {
|
|
30
|
+
try {
|
|
31
|
+
const u = new URL(url);
|
|
32
|
+
const host = u.hostname.toLowerCase();
|
|
33
|
+
return host === "deepseek.com" || host.endsWith(".deepseek.com");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function deepseekBaseFromSettings() {
|
|
40
|
+
// Home settings only — do not trust cwd/.claude/settings.json (spoof / exfil risk)
|
|
41
|
+
const p = join(homedir(), ".claude", "settings.json");
|
|
42
|
+
if (!existsSync(p))
|
|
43
|
+
return null;
|
|
44
|
+
try {
|
|
45
|
+
const j = JSON.parse(readFileSync(p, "utf8"));
|
|
46
|
+
const url = j.env?.ANTHROPIC_BASE_URL;
|
|
47
|
+
if (url && isDeepSeekBaseUrl(url))
|
|
48
|
+
return url;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* ignore */
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/** Resolved DeepSeek base URL for the mid-lane harness, or null. */
|
|
56
|
+
export function resolvedDeepSeekBaseUrl() {
|
|
57
|
+
for (const candidate of [
|
|
58
|
+
process.env.ANTHROPIC_BASE_URL,
|
|
59
|
+
process.env.CURSOR_ROUTE_ANTHROPIC_BASE_URL,
|
|
60
|
+
deepseekBaseFromSettings(),
|
|
61
|
+
]) {
|
|
62
|
+
if (candidate && isDeepSeekBaseUrl(candidate))
|
|
63
|
+
return candidate;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
/** True when Claude Code harness is routed to DeepSeek (cheap path). */
|
|
68
|
+
export function isDeepSeekRouted() {
|
|
69
|
+
return Boolean(resolvedDeepSeekBaseUrl());
|
|
70
|
+
}
|
|
71
|
+
function resolveClaudeDs() {
|
|
72
|
+
// Env override lets tests pin a fake claude-ds (and power users pick a specific binary).
|
|
73
|
+
if (process.env.CURSOR_ROUTE_CLAUDE_DS_BIN) {
|
|
74
|
+
return {
|
|
75
|
+
binary: process.env.CURSOR_ROUTE_CLAUDE_DS_BIN,
|
|
76
|
+
mode: "claude-ds (CURSOR_ROUTE_CLAUDE_DS_BIN override)",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
for (const c of [
|
|
80
|
+
{ cmd: "claude-ds", mode: "claude-ds (DeepSeek shim)" },
|
|
81
|
+
{ cmd: "deepseek-claude", mode: "deepseek-claude" },
|
|
82
|
+
]) {
|
|
83
|
+
const path = which(c.cmd);
|
|
84
|
+
if (path)
|
|
85
|
+
return { binary: path, mode: c.mode };
|
|
86
|
+
}
|
|
87
|
+
const claude = which("claude");
|
|
88
|
+
if (!claude)
|
|
89
|
+
return null;
|
|
90
|
+
if (isDeepSeekRouted()) {
|
|
91
|
+
return {
|
|
92
|
+
binary: claude,
|
|
93
|
+
mode: "claude → DeepSeek (ANTHROPIC_BASE_URL)",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// Expensive Anthropic path — opt-in only so we never silently bill frontier rates
|
|
97
|
+
if (process.env.CURSOR_ROUTE_ALLOW_ANTHROPIC === "1" ||
|
|
98
|
+
process.env.CURSOR_ROUTE_ALLOW_STOCK_CLAUDE === "1" // legacy alias
|
|
99
|
+
) {
|
|
100
|
+
return {
|
|
101
|
+
binary: claude,
|
|
102
|
+
mode: "claude → Anthropic (CURSOR_ROUTE_ALLOW_ANTHROPIC=1)",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Env that must reach stock `claude` for DeepSeek routing.
|
|
109
|
+
* Passed via process/tmux env — never interpolated into the printed command.
|
|
110
|
+
*/
|
|
111
|
+
function deepSeekWorkerEnv() {
|
|
112
|
+
const base = resolvedDeepSeekBaseUrl();
|
|
113
|
+
if (!base)
|
|
114
|
+
return undefined;
|
|
115
|
+
const env = { ANTHROPIC_BASE_URL: base };
|
|
116
|
+
const token = process.env.ANTHROPIC_AUTH_TOKEN ||
|
|
117
|
+
process.env.ANTHROPIC_API_KEY ||
|
|
118
|
+
process.env.DEEPSEEK_API_KEY ||
|
|
119
|
+
"";
|
|
120
|
+
if (token)
|
|
121
|
+
env.ANTHROPIC_AUTH_TOKEN = token;
|
|
122
|
+
const model = process.env.ANTHROPIC_MODEL;
|
|
123
|
+
if (model)
|
|
124
|
+
env.ANTHROPIC_MODEL = model;
|
|
125
|
+
return env;
|
|
126
|
+
}
|
|
127
|
+
export const claudeDsAdapter = {
|
|
128
|
+
kind: "claude-ds",
|
|
129
|
+
label: "DeepSeek (via Claude Code harness)",
|
|
130
|
+
health() {
|
|
131
|
+
const resolved = resolveClaudeDs();
|
|
132
|
+
if (!resolved) {
|
|
133
|
+
const hasClaude = Boolean(which("claude"));
|
|
134
|
+
return {
|
|
135
|
+
worker: "claude-ds",
|
|
136
|
+
ok: false,
|
|
137
|
+
binary: null,
|
|
138
|
+
detail: hasClaude
|
|
139
|
+
? "claude on PATH but ANTHROPIC_BASE_URL is not DeepSeek — set https://api.deepseek.com/anthropic (see README). Refusing Anthropic default so mid-lane stays cheap."
|
|
140
|
+
: "need claude-ds, deepseek-claude, or claude + DeepSeek ANTHROPIC_BASE_URL — see README DeepSeek setup",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
worker: "claude-ds",
|
|
145
|
+
ok: true,
|
|
146
|
+
binary: resolved.binary,
|
|
147
|
+
detail: `ok (${resolved.mode})`,
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
buildLaunch({ promptFile, cwd, alwaysApprove }) {
|
|
151
|
+
const resolved = resolveClaudeDs();
|
|
152
|
+
if (!resolved) {
|
|
153
|
+
throw new Error("DeepSeek worker not available — run: cursor-route health");
|
|
154
|
+
}
|
|
155
|
+
const ask = process.env.CURSOR_ROUTE_ASK === "1" || process.env.CLAUDE_DS_ASK === "1";
|
|
156
|
+
const skip = alwaysApprove && !ask;
|
|
157
|
+
// Stock `claude` needs DeepSeek env injected into the worker process
|
|
158
|
+
// (tmux panes may not inherit client env from a long-lived server).
|
|
159
|
+
const env = resolved.mode.startsWith("claude → DeepSeek") ? deepSeekWorkerEnv() : undefined;
|
|
160
|
+
if (resolved.mode.startsWith("claude-ds")) {
|
|
161
|
+
const parts = [
|
|
162
|
+
shellQuote(resolved.binary),
|
|
163
|
+
"-PromptFile",
|
|
164
|
+
shellQuote(promptFile),
|
|
165
|
+
];
|
|
166
|
+
if (skip)
|
|
167
|
+
parts.push("--dangerously-skip-permissions");
|
|
168
|
+
return {
|
|
169
|
+
worker: "claude-ds",
|
|
170
|
+
command: `cd ${shellQuote(cwd)} && ${parts.join(" ")}`,
|
|
171
|
+
alwaysApprove: skip,
|
|
172
|
+
env,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const parts = [
|
|
176
|
+
shellQuote(resolved.binary),
|
|
177
|
+
"-p",
|
|
178
|
+
`"$(cat ${shellQuote(promptFile)})"`,
|
|
179
|
+
];
|
|
180
|
+
if (skip)
|
|
181
|
+
parts.push("--dangerously-skip-permissions");
|
|
182
|
+
return {
|
|
183
|
+
worker: "claude-ds",
|
|
184
|
+
command: `cd ${shellQuote(cwd)} && ${parts.join(" ")}`,
|
|
185
|
+
alwaysApprove: skip,
|
|
186
|
+
env,
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { shellQuote } from "../util.js";
|
|
3
|
+
function findGrok() {
|
|
4
|
+
// Env override lets tests pin a fake grok (and power users pick a specific binary).
|
|
5
|
+
if (process.env.CURSOR_ROUTE_GROK_BIN)
|
|
6
|
+
return process.env.CURSOR_ROUTE_GROK_BIN;
|
|
7
|
+
try {
|
|
8
|
+
return (execSync("command -v grok", {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
11
|
+
}).trim() || null);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export const grokAdapter = {
|
|
18
|
+
kind: "grok",
|
|
19
|
+
label: "Grok CLI (xAI)",
|
|
20
|
+
health() {
|
|
21
|
+
const binary = findGrok();
|
|
22
|
+
if (!binary) {
|
|
23
|
+
return {
|
|
24
|
+
worker: "grok",
|
|
25
|
+
ok: false,
|
|
26
|
+
binary: null,
|
|
27
|
+
detail: "grok not on PATH — install from https://x.ai/cli then run: grok login",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
worker: "grok",
|
|
32
|
+
ok: true,
|
|
33
|
+
binary,
|
|
34
|
+
detail: "ok (auth checked at first start — run grok login if jobs fail)",
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
buildLaunch({ promptFile, cwd, alwaysApprove }) {
|
|
38
|
+
const binary = findGrok() || "grok";
|
|
39
|
+
const parts = [
|
|
40
|
+
shellQuote(binary),
|
|
41
|
+
"-p",
|
|
42
|
+
`"$(cat ${shellQuote(promptFile)})"`,
|
|
43
|
+
"--cwd",
|
|
44
|
+
shellQuote(cwd),
|
|
45
|
+
"--no-auto-update",
|
|
46
|
+
"--output-format",
|
|
47
|
+
"plain",
|
|
48
|
+
];
|
|
49
|
+
if (alwaysApprove)
|
|
50
|
+
parts.push("--always-approve");
|
|
51
|
+
return {
|
|
52
|
+
worker: "grok",
|
|
53
|
+
command: parts.join(" "),
|
|
54
|
+
alwaysApprove,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { grokAdapter } from "./grok.js";
|
|
2
|
+
import { claudeDsAdapter } from "./claude-ds.js";
|
|
3
|
+
const registry = {
|
|
4
|
+
grok: grokAdapter,
|
|
5
|
+
"claude-ds": claudeDsAdapter,
|
|
6
|
+
};
|
|
7
|
+
export function getAdapter(worker) {
|
|
8
|
+
const a = registry[worker];
|
|
9
|
+
if (!a)
|
|
10
|
+
throw new Error(`Unknown worker: ${worker}`);
|
|
11
|
+
return a;
|
|
12
|
+
}
|
|
13
|
+
export function allAdapters() {
|
|
14
|
+
return Object.values(registry);
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|