Skip to content

Commit 999e0fc

Browse files
ralyodioclaude
andauthored
feat(engines): add qwen (Qwen Code) and deepseek (DeepSeek Code) (#287)
`/install qwen` and `/install deepseek` now resolve, alongside the kimi entry added in #286. qwen is a Gemini CLI fork, so it reuses gemini's `--approval-mode=yolo` for agent mode — `--yolo`/`-y` names the same mode, and passing both is a hard error, so only one form is wired. deepseek has no first-party CLI; `@serjm/deepseek-code` is the live community one. The two names that get suggested alongside it are dead ends: `deepseek-tui` is now a stub whose own description says it was renamed to `codewhale`, and `deepseek-cli` has not shipped since January 2025. Its package installs `dsc` and `deepseek-code`; we launch the long name, because `dsc` collides with Microsoft's Desired State Configuration binary and the short name would silently start the wrong program on a machine that has both. The agents/start test loop now writes its stub under the engine's *bin* rather than its key. Those were the same string for every engine until now; deepseek is the first where they differ, and a stub named after the key would leave nothing on PATH to find. MCP and skills fan-out stay opt-in per engine, so both new engines report "no support" there until their native `mcp add` surfaces are wired. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 4f0026a commit 999e0fc

3 files changed

Lines changed: 50 additions & 3 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ moshcode install privacycode # curl -fsSL https://getprivacycode.com/install | s
6262
moshcode install claude # npm i -g @anthropic-ai/claude-code
6363
moshcode install codex # npm i -g @openai/codex
6464
moshcode install kimi # curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash
65+
moshcode install qwen # npm i -g @qwen-code/qwen-code
66+
moshcode install deepseek # npm i -g @serjm/deepseek-code
6567
```
6668

6769
### Autonomous agents versus raw starts
@@ -78,6 +80,8 @@ moshcode agents privacycode # privacycode agent list (agen
7880
moshcode agents codex # codex --dangerously-bypass-approvals-and-sandbox (autonomous)
7981
moshcode agents gemini # gemini --approval-mode=yolo (autonomous)
8082
moshcode agents kimi # kimi --yolo (autonomous)
83+
moshcode agents qwen # qwen --approval-mode=yolo (autonomous)
84+
moshcode agents deepseek # deepseek-code --turbo (autonomous)
8185
moshcode agents aider # aider --yes-always (autonomous)
8286
```
8387

src/engines.mjs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,34 @@ export const ENGINES = {
9595
// lands in the rc the same way; only the default needs bridging here.)
9696
binDirs: [path.join(homedir(), ".kimi-code", "bin")],
9797
},
98+
qwen: {
99+
desc: "Qwen Code — Alibaba's agentic coding CLI",
100+
bin: "qwen",
101+
// A Gemini CLI fork, so it takes gemini's approval-mode flag. `--yolo`/`-y`
102+
// selects the same mode by another name, and passing both is a hard error
103+
// ("Cannot use --yolo (-y) and --approval-mode together"), so this stays on
104+
// the one form — the gemini-shaped one, matching the entry above.
105+
agentArgs: ["--approval-mode=yolo"],
106+
install: { cmd: "npm", args: ["install", "-g", "@qwen-code/qwen-code"] },
107+
},
108+
deepseek: {
109+
desc: "DeepSeek Code — terminal coding agent on DeepSeek models",
110+
// Community-built (SerjMihashin), not a DeepSeek-published CLI — DeepSeek
111+
// ships no first-party agentic CLI. The two other names that get suggested
112+
// are dead ends: `deepseek-tui` is now a stub whose own description says it
113+
// was renamed to `codewhale`, and `deepseek-cli` has not been touched since
114+
// January 2025.
115+
//
116+
// The package installs two identical bins, `dsc` and `deepseek-code`. Take
117+
// the long one: `dsc` is also Microsoft's Desired State Configuration
118+
// binary, so on a machine that has both, the short name would silently
119+
// launch the wrong program.
120+
bin: "deepseek-code",
121+
// `--turbo` is its "auto-approve all actions" mode (equivalently
122+
// `--approval-mode turbo`, alongside plan/default/auto-edit).
123+
agentArgs: ["--turbo"],
124+
install: { cmd: "npm", args: ["install", "-g", "@serjm/deepseek-code"] },
125+
},
98126
aider: {
99127
desc: "Aider — pair-programming in your terminal",
100128
bin: "aider",
@@ -118,6 +146,10 @@ export const ENGINE_ALIASES = {
118146
cc: "claude", "claude-code": "claude", openai: "codex", gpt: "codex", google: "gemini",
119147
pc: "privacycode", getprivacycode: "privacycode", privacy: "privacycode",
120148
"kimi-cli": "kimi", "kimi-code": "kimi", moonshot: "kimi",
149+
"qwen-code": "qwen", qwencode: "qwen", alibaba: "qwen",
150+
// `dsc` and `deepseek-code` are the binary names the package installs; accept
151+
// both as engine names so whichever one someone has seen resolves.
152+
ds: "deepseek", dsc: "deepseek", "deepseek-code": "deepseek",
121153
};
122154

123155
/** Resolve a name/alias to `[key, engine]`, or null. */
@@ -199,6 +231,8 @@ const AI_EXEC = {
199231
privacycode: (p) => ["run", p], // privacycode one-shot (opencode-derived)
200232
aider: (p) => ["--message", p, "--yes", "--no-auto-commits"], // aider single message
201233
kimi: (p) => ["-p", p], // kimi prompt mode (prints the response, text by default)
234+
qwen: (p) => ["-p", p], // qwen prompt mode (gemini-derived)
235+
deepseek: (p) => ["--headless", "-p", p], // deepseek: -p runs one prompt and exits; --headless drops the TUI so stdout is pipe-clean
202236
};
203237

204238
/** argv that runs `prompt` headlessly on `engine` (throws if it has no headless mode). */
@@ -220,7 +254,7 @@ export function aiExecArgs(engine, prompt) {
220254
*/
221255
export function pickAiEngine(preferred) {
222256
const wanted = preferred ? resolveEngine(preferred)?.[0] : null;
223-
const order = preferred ? (wanted ? [wanted] : []) : ["claude", "codex", "opencode", "privacycode", "gemini", "kimi", "aider"];
257+
const order = preferred ? (wanted ? [wanted] : []) : ["claude", "codex", "opencode", "privacycode", "gemini", "kimi", "qwen", "deepseek", "aider"];
224258
for (const key of order) {
225259
if (Object.hasOwn(ENGINES, key) && Object.hasOwn(AI_EXEC, key) && isInstalled(ENGINES[key].bin, ENGINES[key].binDirs)) return key;
226260
}

test/engines.test.mjs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const EXPECTED_AGENT_ARGS = {
2525
codex: ["--dangerously-bypass-approvals-and-sandbox"],
2626
gemini: ["--approval-mode=yolo"],
2727
kimi: ["--yolo"],
28+
qwen: ["--approval-mode=yolo"],
29+
deepseek: ["--turbo"],
2830
aider: ["--yes-always"],
2931
};
3032

@@ -37,6 +39,8 @@ const EXPECTED_LAUNCH_ARGS = {
3739
codex: ["--dangerously-bypass-approvals-and-sandbox"],
3840
gemini: ["--approval-mode=yolo"],
3941
kimi: ["--yolo"], // no agents view — kimi has no agent list to land on
42+
qwen: ["--approval-mode=yolo"],
43+
deepseek: ["--turbo"],
4044
aider: ["--yes-always"],
4145
};
4246

@@ -98,10 +102,15 @@ test("every engine declares its reviewed autonomous-mode arguments", () => {
98102
});
99103

100104
for (const [key, expected] of Object.entries(EXPECTED_LAUNCH_ARGS)) {
105+
// The stub has to be named after the engine's *binary*, which is not always
106+
// the engine's name — deepseek launches `deepseek-code`. Writing a stub called
107+
// `deepseek` would leave nothing on PATH for moshcode to find.
108+
const stub = ENGINES[key].bin;
109+
101110
test(`agents ${key} injects its agent-launch args before user arguments`, async () => {
102111
const nativeBin = tempDir();
103112
mkdirSync(nativeBin, { recursive: true });
104-
writeEngine(nativeBin, key);
113+
writeEngine(nativeBin, stub);
105114

106115
const result = await run(["agents", key, "--user-arg", "two words"], nativeBin);
107116

@@ -113,7 +122,7 @@ for (const [key, expected] of Object.entries(EXPECTED_LAUNCH_ARGS)) {
113122
test(`start ${key} injects no arguments`, async () => {
114123
const nativeBin = tempDir();
115124
mkdirSync(nativeBin, { recursive: true });
116-
writeEngine(nativeBin, key);
125+
writeEngine(nativeBin, stub);
117126

118127
const result = await run(["start", key, "--user-arg", "two words"], nativeBin);
119128

0 commit comments

Comments
 (0)