deepseek-cli-mode 0.0.0-stage → 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek Harness CLI contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,258 @@
1
- # Temporary Holding Version
1
+ # deepseek-cli-mode
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Terminal (CLI) mode for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)** — an interactive,
4
+ Claude Code-style coding agent that lives in your shell instead of a browser or an Electron window.
5
+
6
+ > **Community project.** This is an independent, unofficial project. It is **not** affiliated with, endorsed by, or
7
+ > sponsored by DeepSeek. "DeepSeek" and "DeepSeek Harness" are used only to describe what this tool connects to.
8
+ > Use it at your own risk and review the code before running it against your own API keys.
9
+
10
+ DeepSeek Harness ships two surfaces: the **Web** app and the **Desktop** app. This package adds the third one — a real
11
+ interactive **CLI** that boots the *same* Harness runtime (`dsh-base` + `dsh-headless`: agent loop, tools, sandbox,
12
+ session persistence, approvals) and keeps **one live Agent for the whole session**, so multi-turn context, tool state,
13
+ and the model's request prefix stay warm between messages.
14
+
15
+ ```
16
+ ❯ explain what changed in this repo and run the tests
17
+
18
+ ⌕ grep diff --stat
19
+ $ pwsh npm test
20
+ ◇ read package.json
21
+ The working tree has three modified files …
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Requirements
27
+
28
+ | | |
29
+ |---|---|
30
+ | Node.js | 20 or newer |
31
+ | DeepSeek Harness runtime | `@deepseek-ai/dsh` **0.2.1** or newer, installed locally |
32
+ | Credentials | an API key saved by the Desktop/Web app, or `DEEPSEEK_API_KEY` in the environment |
33
+
34
+ The CLI is a front-end for the Harness runtime; it does not bundle the runtime itself. Install both:
35
+
36
+ ```powershell
37
+ npm install -g @deepseek-ai/dsh
38
+ npm install -g deepseek-cli-mode
39
+ ```
40
+
41
+ The CLI reuses the key already stored in `$DSH_HOME/.credentials.yaml` by the GUI, so in most setups there is nothing
42
+ extra to configure.
43
+
44
+ ---
45
+
46
+ ## Installation
47
+
48
+ ### Global install (recommended)
49
+
50
+ ```powershell
51
+ npm install -g deepseek-cli-mode
52
+ deepseek --version
53
+ ```
54
+
55
+ This installs three equivalent command names: **`deepseek`** (primary), `deepseek-cli-mode`, and `dsh-cli`.
56
+
57
+ ### Run without installing
58
+
59
+ ```powershell
60
+ npx --yes deepseek-cli-mode
61
+ ```
62
+
63
+ ### From a local checkout
64
+
65
+ ```powershell
66
+ cd deepseek-cli-mode # the folder containing package.json
67
+ npm install
68
+ npm link # optional: exposes the `deepseek` command
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Usage
74
+
75
+ ```
76
+ deepseek start an interactive session
77
+ deepseek "task text..." answer one task and exit (scriptable)
78
+ deepseek -r, --resume [id] resume a session (newest in this directory by default)
79
+ deepseek -c, --continue continue the newest session without prompting
80
+ deepseek --list list recent sessions in this directory
81
+ deepseek -y, --yes auto-approve tool actions that would ask
82
+ deepseek --model <id> override the model for this run
83
+ deepseek --reasoning <effort> override the reasoning effort (off|high|max)
84
+ deepseek --help show help
85
+ deepseek --version show the version
86
+ ```
87
+
88
+ ### Examples
89
+
90
+ ```powershell
91
+ # interactive — the normal way to work
92
+ deepseek
93
+
94
+ # scriptable one-shot; stdout carries only the final answer
95
+ deepseek "summarize the uncommitted changes" > summary.md
96
+
97
+ # continue the previous conversation in this folder
98
+ deepseek -c
99
+
100
+ # let it edit files and run commands unattended (approvals auto-accepted)
101
+ deepseek -y "fix the failing test in tests/auth.spec.ts"
102
+
103
+ # pipe a task in
104
+ Get-Content .\error.log | Select-Object -First 50 | deepseek "what causes this error?"
105
+
106
+ # attach a file or an image to the next message
107
+ deepseek "review @src/app.ts and tell me what is risky"
108
+ ```
109
+
110
+ ### Inside a session
111
+
112
+ | Command | What it does |
113
+ |---|---|
114
+ | `/help` | command list, including the live Harness command registry |
115
+ | `/new` | start a fresh session (new id, keeps the process warm) |
116
+ | `/sessions` | recent sessions in this directory (with a ▶ running marker) |
117
+ | `/resume <id>` | switch to another session |
118
+ | `/model [id]` | show or switch the model for the next message |
119
+ | `/reasoning [off\|high\|max]` | show or set the reasoning effort |
120
+ | `/permission [preset]` | switch the sandbox + approval preset |
121
+ | `/skills` | list the skills discovered in this workspace |
122
+ | `/jobs` | list this session's background jobs |
123
+ | `/todos` | show the current todo list |
124
+ | `/image <path>` | attach an image to the next message (or mention `@photo.png`) |
125
+ | `/export [file]` | write this run's transcript as markdown |
126
+ | `/thinking` | print the last turn's reasoning (collapsed by default) |
127
+ | `/session` | print the current session id (for `--resume`) |
128
+ | `/clear` | clear the screen |
129
+ | `/quit`, `/exit` | leave (Ctrl+C at an empty prompt also exits) |
130
+
131
+ Harness commands are shared with the Web and Desktop apps and run through the same registry:
132
+
133
+ | Command | What it does |
134
+ |---|---|
135
+ | `/compact` | compact older conversation history |
136
+ | `/goal [objective\|clear\|edit\|pause\|resume]` | long-running goal mode |
137
+ | `/plan [off]` | plan mode (design first, approve, then execute) |
138
+ | `/feedback` | record feedback about the session |
139
+
140
+ Mention a file with `@path` to attach its text (up to 200 KB); mention an image (`@shot.png`) or use `/image` to attach
141
+ it durably through the same attachment store the GUI uses.
142
+
143
+ Ctrl+C **during a turn** cancels the turn; Ctrl+C **again** force-quits. Approval prompts (`Allow write …? [y/N]`) appear
144
+ when a tool wants to leave the workspace or act outside the sandbox policy.
145
+
146
+ ---
147
+
148
+ ## Configuration
149
+
150
+ | Environment variable | Default | Meaning |
151
+ |---|---|---|
152
+ | `DSH_HOME` | `~/.dsh` | Harness home (profiles, sessions, credentials) |
153
+ | `DEEPSEEK_API_KEY` | stored key | API key override |
154
+ | `DSH_CLI_DSH_BIN` | auto-detected | explicit path to `@deepseek-ai/dsh/lib/bin.js` |
155
+ | `DSH_PERMISSION_MODE` | `workspace-write` | `read-only` \| `workspace-write` \| `danger-full-access` |
156
+ | `DSH_CLI_REASONING` | unset | `1` streams reasoning text instead of collapsing it |
157
+ | `DSH_CLI_YES` | unset | `1` auto-approves tool actions (same as `--yes`) |
158
+ | `NO_COLOR` | unset | disable ANSI styling |
159
+
160
+ Model and reasoning effort come from the Harness settings (the same selection the Desktop app shows). `--model`,
161
+ `--reasoning`, `/model`, and `/reasoning` override them for the session.
162
+
163
+ ---
164
+
165
+ ## How it works
166
+
167
+ ```
168
+ deepseek (bin/deepseek.js)
169
+ ├─ finds the local @deepseek-ai/dsh installation
170
+ ├─ self-heals $DSH_HOME/profiles/dsh-cli (manifest + patch + runner.js)
171
+ └─ spawns: dsh --profile dsh-cli <your arguments>
172
+ └─ profile bundles: @deepseek-ai/dsh-base + @deepseek-ai/dsh-headless
173
+ └─ runner/runner.js (Cordis plugin, this package)
174
+ ├─ agents.create() / agents.resume()
175
+ ├─ agent/assistant-stream → token-level streaming
176
+ ├─ session/event → tool calls, todos, usage
177
+ ├─ user-questions/request → plan review, ask_user_question
178
+ └─ approval/request → y/N prompts
179
+ ```
180
+
181
+ Why a profile instead of a standalone program? The Harness is a Cordis plugin tree; running the CLI as a profile means
182
+ it gets the identical system prompt, tools, sandbox, permission policy, session store, and compaction behavior as the
183
+ GUI. This package only adds the terminal front-end.
184
+
185
+ Files this package writes (once, under `$DSH_HOME/profiles/dsh-cli/`):
186
+
187
+ ```
188
+ package.json profile manifest (bundles)
189
+ cordis.yml empty root
190
+ cordis.patch.yml disables the one-shot headless driver, re-enables the
191
+ credential store, mounts ./runner.js
192
+ pnpm-workspace.yaml
193
+ runner.js copy of runner/runner.js (kept in sync)
194
+ .cli-version version stamp for the files above
195
+ ```
196
+
197
+ Your Web/Desktop profiles are untouched.
198
+
199
+ ### Feature parity with the Web/Desktop apps
200
+
201
+ Roughly four fifths of the agent-facing product is shared by construction (tools, policies, sessions, plans, goals,
202
+ compaction, skills, subagents, workflows). A feature-by-feature comparison — including what stays GUI-only — is in
203
+ [docs/PARITY.md](docs/PARITY.md).
204
+
205
+ ---
206
+
207
+ ## Troubleshooting
208
+
209
+ **`deepseek: cannot find @deepseek-ai/dsh`** — install the runtime once: `npm i -g @deepseek-ai/dsh`, or set
210
+ `DSH_CLI_DSH_BIN` to the `lib/bin.js` path.
211
+
212
+ **`@deepseek-ai/dsh 0.1.x is too old for this CLI`** — upgrade with `npm i -g @deepseek-ai/dsh@latest`. This CLI needs
213
+ the `agent/assistant-stream` and `agentDefaultModel.saveSelection` APIs introduced in 0.2.1.
214
+
215
+ **`MISSING_CREDENTIAL` / no API key** — save a key in the Desktop/Web app (Models page), or
216
+ `$env:DEEPSEEK_API_KEY = "sk-…"` before starting `deepseek`.
217
+
218
+ **Approvals keep rejecting actions** — the default policy is `workspace-write`, so writes outside the working directory
219
+ ask first. Use `-y` for unattended runs, or start with `$env:DSH_PERMISSION_MODE = "danger-full-access"`.
220
+
221
+ **Command not found after `npm i -g`** — the npm global prefix may not be on `PATH`. Check `npm prefix -g` and add its
222
+ `bin` folder, or run through `npx deepseek-cli-mode`.
223
+
224
+ **`npx` with an absolute Windows tarball path does nothing** — pass a relative path or a `file:` spec:
225
+ `npx --yes .\deepseek-cli-mode-1.0.0.tgz`.
226
+
227
+ **Input looks garbled in Git Bash / MSYS** — use Windows Terminal, PowerShell 7, or `cmd.exe`; the runner uses Node's
228
+ readline and expects a normal Windows console.
229
+
230
+ **Startup shows `[llm-deepseek-api-key] model listing …`** — the adapter is discovering models; it is written to stderr,
231
+ so piped stdout stays clean.
232
+
233
+ ---
234
+
235
+ ## Development
236
+
237
+ ```powershell
238
+ npm install
239
+ npm test # reports the CLI version
240
+ npm start # same as `deepseek`
241
+ node bin/deepseek.js --list # boot the profile without a model call
242
+ npm run pack:check # inspect the publish archive
243
+ ```
244
+
245
+ The runner is plain ESM with no build step. It is copied to the profile on every launch when its content changed, so
246
+ editing `runner/runner.js` and re-running `deepseek` picks the change up.
247
+
248
+ Compatibility note: the runner targets the 0.2.1 `@deepseek-ai/dsh` API surface (Cordis plugin entry, `agents.create`,
249
+ `agent/assistant-stream`, `session/event`, `user-questions/request`, `approval/request`,
250
+ `agentDefaultModel.saveSelection`). A future dsh release that changes those internals needs a matching runner update.
251
+
252
+ ---
253
+
254
+ ## License
255
+
256
+ [MIT](LICENSE) © 2026 DeepSeek Harness CLI contributors.
257
+
258
+ This project is independent and community-maintained; see the disclaimer at the top.
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env node
2
+ // deepseek — the DeepSeek Harness terminal CLI launcher.
3
+ //
4
+ // DeepSeek Harness (dsh) ships Web and Desktop surfaces. This launcher adds the
5
+ // missing third surface: a Claude-Code-style terminal session. It resolves the
6
+ // locally installed `@deepseek-ai/dsh`, self-heals a dedicated `dsh-cli` profile
7
+ // under $DSH_HOME/profiles, and boots that profile with this package's runner.
8
+ //
9
+ // The profile keeps ONE live Agent for the whole session, so tools, approvals,
10
+ // session persistence, and the model's request prefix stay warm between turns.
11
+ import { spawn, spawnSync } from "node:child_process";
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
+ import { createRequire } from "node:module";
14
+ import { homedir } from "node:os";
15
+ import { dirname, join, resolve } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+
18
+ const here = dirname(fileURLToPath(import.meta.url));
19
+ const root = resolve(here, "..");
20
+ const requireFromHere = createRequire(import.meta.url);
21
+
22
+ const PROFILE = "dsh-cli";
23
+ const VERSION = readVersion(join(root, "package.json"));
24
+ const DSH_MIN = "0.2.1";
25
+
26
+ function readVersion(file) {
27
+ try {
28
+ return JSON.parse(readFileSync(file, "utf8")).version ?? "0.0.0";
29
+ } catch {
30
+ return "0.0.0";
31
+ }
32
+ }
33
+
34
+ const args = process.argv.slice(2);
35
+
36
+ // Help/version are answered here so they never pay for a profile boot.
37
+ if (args.includes("--version") || args.includes("-v")) {
38
+ console.log(VERSION);
39
+ process.exit(0);
40
+ }
41
+ if (args.length === 0 ? false : args[0] === "--help" || args[0] === "-h") {
42
+ printHelp();
43
+ process.exit(0);
44
+ }
45
+
46
+ function printHelp() {
47
+ console.log(`deepseek ${VERSION} — DeepSeek Harness in your terminal.
48
+
49
+ Usage:
50
+ deepseek start an interactive session
51
+ deepseek "task text..." answer one task and exit (scriptable)
52
+ deepseek -r, --resume resume the newest session in this directory
53
+ deepseek -r <id> resume a specific session
54
+ deepseek -c, --continue continue the newest session without prompting
55
+ deepseek --list list recent sessions in this directory
56
+ deepseek -y, --yes auto-approve tool actions that ask
57
+ deepseek --model <id> override the model for this run
58
+ deepseek --reasoning <effort> override the reasoning effort (off|high|max)
59
+ deepseek --help this help
60
+ deepseek --version print the version
61
+
62
+ Inside a session:
63
+ /help commands /new fresh session
64
+ /sessions recent sessions /resume <id> switch session
65
+ /model [id] show/switch model /reasoning [e] show/set effort
66
+ /thinking last reasoning /session current session id
67
+ /clear clear the screen /quit, /exit leave
68
+
69
+ Environment:
70
+ DSH_HOME Harness home (default: ~/.dsh)
71
+ DSH_CLI_DSH_BIN explicit path to @deepseek-ai/dsh/lib/bin.js
72
+ DSH_PERMISSION_MODE workspace-write (default) | read-only | danger-full-access
73
+ DEEPSEEK_API_KEY API key; otherwise the key saved by the Desktop/Web app is used
74
+
75
+ Requires a local install of @deepseek-ai/dsh (npm i -g @deepseek-ai/dsh).`);
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // 1. Locate the dsh installation.
80
+ // ---------------------------------------------------------------------------
81
+ function resolveDshBin() {
82
+ const candidates = [
83
+ process.env.DSH_CLI_DSH_BIN,
84
+ // Same global prefix as this node binary (npm default layout).
85
+ join(dirname(process.execPath), "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"),
86
+ // Common global prefixes.
87
+ process.env.APPDATA ? join(process.env.APPDATA, "npm", "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js") : undefined,
88
+ process.env.NPM_CONFIG_PREFIX ? join(process.env.NPM_CONFIG_PREFIX, "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js") : undefined,
89
+ process.env.npm_config_prefix ? join(process.env.npm_config_prefix, "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js") : undefined,
90
+ join(homedir(), "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"),
91
+ join(homedir(), ".npm-global", "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"),
92
+ join(process.cwd(), "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"),
93
+ ].filter(Boolean);
94
+ for (const candidate of candidates) if (existsSync(candidate)) return candidate;
95
+ // Resolve like a normal import from this package, then from the cwd.
96
+ for (const req of [requireFromHere, createRequire(join(process.cwd(), "resolve-dsh.js"))]) {
97
+ try {
98
+ return req.resolve("@deepseek-ai/dsh/lib/bin.js");
99
+ } catch {}
100
+ }
101
+ // Last resort: ask npm where its global root is (covers custom prefixes).
102
+ try {
103
+ const npmCli = join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
104
+ if (existsSync(npmCli)) {
105
+ const result = spawnSync(process.execPath, [npmCli, "root", "-g"], { encoding: "utf8", timeout: 20000 });
106
+ const dir = result.stdout?.trim();
107
+ if (dir) {
108
+ const candidate = join(dir, "@deepseek-ai", "dsh", "lib", "bin.js");
109
+ if (existsSync(candidate)) return candidate;
110
+ }
111
+ }
112
+ } catch {}
113
+ return undefined;
114
+ }
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // 2. Self-heal the dedicated profile.
118
+ // ---------------------------------------------------------------------------
119
+ const PROFILE_MANIFEST = JSON.stringify(
120
+ {
121
+ name: "dsh-profile-dsh-cli",
122
+ private: true,
123
+ type: "module",
124
+ dependencies: {},
125
+ dsh: { profile: { bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-headless"] } },
126
+ },
127
+ null,
128
+ 2
129
+ ) + "\n";
130
+
131
+ const PROFILE_ROOT =
132
+ "# dsh profile root — an empty entry list. The tree is composed as patches:\n" +
133
+ "# each bundle in package.json's dsh.profile.bundles, then cordis.patch.yml.\n[]\n";
134
+
135
+ const PROFILE_PATCH = `# dsh-cli — interactive terminal profile for the DeepSeek Harness.
136
+ # Bundles: dsh-base (core machinery) + dsh-headless (coding persona, tools,
137
+ # sandbox policy). This layer disables the one-shot headless driver, restores
138
+ # the managed credential store the headless bundle disables, and mounts the
139
+ # interactive runner from ./runner.js.
140
+ - id: headless-startup
141
+ disabled: true
142
+
143
+ - id: headless-runner
144
+ disabled: true
145
+
146
+ # The CLI is interactive: use the API key saved by the Desktop/Web app.
147
+ - id: credentials
148
+ disabled: false
149
+ - id: authorization
150
+ disabled: false
151
+ - id: deepseek-account
152
+ disabled: false
153
+
154
+ - insert:
155
+ - id: cli-runner
156
+ name: './runner.js'
157
+ - id: tool-ask-user
158
+ name: '@deepseek-ai/dsh-tool-ask-user'
159
+ `;
160
+
161
+ const PNPM_WORKSPACE = "packages:\n - .\n\nnodeLinker: hoisted\nautoInstallPeers: false\n";
162
+
163
+ function ensureProfile() {
164
+ const dshHome =
165
+ process.env.DSH_HOME && process.env.DSH_HOME.trim() !== ""
166
+ ? process.env.DSH_HOME
167
+ : join(homedir(), ".dsh");
168
+ const profileDir = join(dshHome, "profiles", PROFILE);
169
+ mkdirSync(profileDir, { recursive: true });
170
+
171
+ const stamp = join(profileDir, ".cli-version");
172
+ let stampVersion = "";
173
+ try {
174
+ stampVersion = readFileSync(stamp, "utf8").trim();
175
+ } catch {}
176
+
177
+ const staticFiles = {
178
+ "package.json": PROFILE_MANIFEST,
179
+ "cordis.yml": PROFILE_ROOT,
180
+ "cordis.patch.yml": PROFILE_PATCH,
181
+ "pnpm-workspace.yaml": PNPM_WORKSPACE,
182
+ };
183
+ for (const [name, content] of Object.entries(staticFiles)) {
184
+ const target = join(profileDir, name);
185
+ if (!existsSync(target) || stampVersion !== VERSION) writeFileSync(target, content);
186
+ }
187
+
188
+ // Keep the runner exactly in sync with this package.
189
+ const runnerSrc = join(root, "runner", "runner.js");
190
+ const runnerDst = join(profileDir, "runner.js");
191
+ const src = readFileSync(runnerSrc, "utf8");
192
+ if (!existsSync(runnerDst) || readFileSync(runnerDst, "utf8") !== src) writeFileSync(runnerDst, src);
193
+
194
+ writeFileSync(stamp, VERSION + "\n");
195
+ return profileDir;
196
+ }
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // 3. Boot.
200
+ // ---------------------------------------------------------------------------
201
+ const dshBin = resolveDshBin();
202
+ if (dshBin === undefined) {
203
+ console.error(
204
+ "deepseek: cannot find @deepseek-ai/dsh.\n" +
205
+ " Install it once: npm i -g @deepseek-ai/dsh\n" +
206
+ " Or point DSH_CLI_DSH_BIN at .../@deepseek-ai/dsh/lib/bin.js"
207
+ );
208
+ process.exit(1);
209
+ }
210
+
211
+ const dshVersion = readVersion(join(dshBin, "..", "..", "package.json"));
212
+ if (compareVersions(dshVersion, DSH_MIN) < 0) {
213
+ console.error(
214
+ `deepseek: @deepseek-ai/dsh ${dshVersion} is too old for this CLI (needs >= ${DSH_MIN}).\n` +
215
+ " Upgrade with: npm i -g @deepseek-ai/dsh@latest"
216
+ );
217
+ process.exit(1);
218
+ }
219
+
220
+ function compareVersions(a, b) {
221
+ const parse = (v) => String(v).split(/[.+-]/).slice(0, 3).map((n) => Number.parseInt(n, 10) || 0);
222
+ const [x, y, z] = parse(a);
223
+ const [p, q, r] = parse(b);
224
+ if (x !== p) return x < p ? -1 : 1;
225
+ if (y !== q) return y < q ? -1 : 1;
226
+ if (z !== r) return z < r ? -1 : 1;
227
+ return 0;
228
+ }
229
+
230
+ ensureProfile();
231
+
232
+ const child = spawn(process.execPath, [dshBin, "--profile", PROFILE, ...args], {
233
+ stdio: "inherit",
234
+ env: { ...process.env, DSH_CLI_VERSION: VERSION },
235
+ windowsHide: false,
236
+ });
237
+
238
+ // The child shares this console, so it receives the user's Ctrl+C directly and
239
+ // tears down gracefully (flush + dispose + exit). This process must not die
240
+ // first, or the child's session flush is lost: only hard-kill on a second
241
+ // Ctrl+C or after a grace period.
242
+ let sigints = 0;
243
+ process.on("SIGINT", () => {
244
+ sigints += 1;
245
+ if (sigints >= 2) {
246
+ if (child.exitCode === null && !child.killed) child.kill();
247
+ return;
248
+ }
249
+ setTimeout(() => {
250
+ if (child.exitCode === null && !child.killed) child.kill();
251
+ }, 15000).unref();
252
+ });
253
+ process.on("SIGTERM", () => {
254
+ if (child.exitCode === null && !child.killed) child.kill();
255
+ });
256
+
257
+ child.on("error", (error) => {
258
+ console.error(`deepseek: failed to start dsh: ${error.message}`);
259
+ process.exit(1);
260
+ });
261
+ child.on("exit", (code, signal) => {
262
+ process.exit(code ?? (signal ? 1 : 0));
263
+ });
package/docs/PARITY.md ADDED
@@ -0,0 +1,108 @@
1
+ # Feature parity: Web/Desktop vs. the CLI
2
+
3
+ This page is the honest audit of how much of the DeepSeek Harness Web/Desktop product the terminal CLI reproduces.
4
+ Two facts drive it:
5
+
6
+ 1. The CLI is **not a re-implementation**. It boots the same `dsh-base` + `dsh-headless` bundles the GUI runs, so the
7
+ agent loop, tools, sandbox, approvals, session store, compaction, goals, plans, skills, subagents, workflows and
8
+ MCP resource plumbing are literally the same code.
9
+ 2. The **surface** differs. A terminal has no sidebar, no drag-and-drop, no browser panel, and no pixel cards. Some
10
+ GUI features map to a command; some are meaningless; a few are genuinely missing.
11
+
12
+ Legend: ✅ works · 🟡 terminal adaptation · ❌ not available in the CLI
13
+
14
+ ## Chat and conversation
15
+
16
+ | Web / Desktop | CLI | Notes |
17
+ |---|---|---|
18
+ | ✅ Streaming assistant text | ✅ | Token-level via `agent/assistant-stream` |
19
+ | ✅ Markdown rendering | 🟡 | Headings, bold, inline code, fenced code; no tables/images layout |
20
+ | ✅ Stop a running turn | ✅ | Ctrl+C → `agent.cancel()` |
21
+ | ✅ Multi-turn context | ✅ | One live Agent per process; `/new` starts a fresh session |
22
+ | ✅ Resume a conversation | ✅ | `deepseek -r [id]`, `-c`, `/resume <id>`, `/sessions` |
23
+ | ✅ Session titles/list | 🟡 | `/sessions` lists ids + age; titles are generated in the log but not shown |
24
+ | ✅ Rename / delete / pin sessions | ❌ | Not exposed by any CLI surface |
25
+ | ✅ Message edit & retry | ❌ | No rewind/retry command |
26
+ | ✅ Message feedback | 🟡 | `/feedback` (native Harness command) writes the record |
27
+ | ✅ Attach images (paste/drag) | 🟡 | `/image <path>` or `@photo.png`; stored durably, model sees them |
28
+ | ✅ Attach files | 🟡 | `@path` inlines text up to 200 KB; no binary upload |
29
+ | ✅ @-file references | ✅ | `@path` resolves against the working directory |
30
+ | ✅ Token usage meter | ✅ | Per-turn `· 12.3k in / 456 out` line |
31
+
32
+ ## Tools and agent control
33
+
34
+ | Web / Desktop | CLI | Notes |
35
+ |---|---|---|
36
+ | ✅ Tool cards (read/write/edit/grep/glob/pwsh/web/…) | 🟡 | One-line icons + path preview; results summarized |
37
+ | ✅ File diffs in cards | ❌ | No diff renderer (use `/diff`-style tooling or git in another shell) |
38
+ | ✅ Approval prompts | ✅ | `Allow write …? [y/N]`; `-y/--yes` auto-approves |
39
+ | ✅ Permission presets | ✅ | Native `/permission`, plus `/permissions` alias |
40
+ | ✅ Todo checklist | ✅ | Live `☑ 2/3` checklist with incremental updates |
41
+ | ✅ Subagent cards | 🟡 | `↳ subagent … started/finished` progress lines |
42
+ | ✅ Jobs panel | ✅ | `/jobs` lists id, status, label, progress |
43
+ | ✅ Workflow run view | 🟡 | Tool calls stream; no dedicated tree view |
44
+ | ✅ Deliverables / present cards | ❌ | No card surface in a terminal |
45
+ | ✅ Plan mode | ✅ | Native `/plan`, `/plan off`; plan review uses the terminal question provider |
46
+ | ✅ Goal mode | ✅ | Native `/goal set/view/pause/resume` + goal tools |
47
+ | ✅ Compaction | ✅ | Automatic, plus native `/compact` |
48
+ | ✅ Ask-user questions | ✅ | Rendered with numbered options in the terminal |
49
+ | ✅ Scheduled reminders | ❌ | Needs the Host Web session controller; not mountable in a CLI-only profile |
50
+ | ✅ Skills | 🟡 | `/skills` lists them; the model invokes them through the skill tool |
51
+
52
+ ## Models, auth, settings
53
+
54
+ | Web / Desktop | CLI | Notes |
55
+ |---|---|---|
56
+ | ✅ Pick model / reasoning effort | ✅ | `/model`, `/reasoning`, `--model`, `--reasoning` |
57
+ | ✅ API key management UI | 🟡 | Reuses the key saved by the GUI; `DEEPSEEK_API_KEY` override |
58
+ | ✅ Account / balance panel | ❌ | No account surface |
59
+ | ✅ Provider list / model discovery | 🟡 | Adapter discovery runs on boot; no picker UI |
60
+ | ✅ Permission, theme, font, language settings | 🟡 | Permission via `/permission`; the rest via env vars/`$DSH_HOME` config |
61
+ | ✅ Plugin manager / marketplace | ❌ | Use `dsh plugin --profile dsh-cli add <pkg>` from a shell |
62
+ | ✅ MCP server management | ❌ | The CLI profile does not mount `mcp-client` |
63
+
64
+ ## Workspace and host surface
65
+
66
+ | Web / Desktop | CLI | Notes |
67
+ |---|---|---|
68
+ | ✅ File tree / file preview | ❌ | The terminal is the workspace |
69
+ | ✅ Git changes panel | ❌ | Use git in the shell (tools can run it) |
70
+ | ✅ Integrated terminal panel | ❌ | The CLI *is* a terminal |
71
+ | ✅ Browser panel | ❌ | Desktop-only |
72
+ | ✅ Document preview / Office conversion | ❌ | Office skills still work through tools |
73
+ | ✅ Voice input | ❌ | Desktop/Web only |
74
+ | ✅ Session trajectory / outline / stats / log download | 🟡 | `/export` writes the current run as markdown; no trajectory viewer |
75
+ | ✅ Multiple tabs / concurrent sessions | 🟡 | Run several `deepseek` processes (each owns one session) |
76
+ | ✅ Notifications, tray, auto-update | ❌ | Desktop shell features |
77
+
78
+ ## Commands and workflows that are identical
79
+
80
+ Because the CLI executes the shared `ctx.commands` registry, these behave exactly like the GUI:
81
+
82
+ - `/compact` — compact older history
83
+ - `/goal …` — set/view/pause/resume a long-running goal
84
+ - `/plan …` — enter/leave plan mode
85
+ - `/permission …` — switch the sandbox + approval preset
86
+ - `/feedback` — record session feedback
87
+ - Any command added by a third-party plugin in the profile
88
+
89
+ Run `/help` inside a session to see the live list.
90
+
91
+ ## What is deliberately different
92
+
93
+ - **No GUI chrome.** No sidebar, cards, drag-and-drop, browser, or settings screens.
94
+ - **One session per process.** The Web app multiplexes many sessions in one host; the CLI keeps one live Agent (spawn
95
+ another terminal for another conversation).
96
+ - **stdout is scriptable.** One-shot mode prints only the final answer on stdout, all progress on stderr.
97
+ - **Local paths only.** Attachments come from the filesystem (`@path`, `/image`), never from a browser upload.
98
+
99
+ ## Bottom line
100
+
101
+ Roughly **four fifths of the agent-facing product is shared by construction** (tools, policies, sessions, plans, goals,
102
+ compaction, skills, subagents, workflows). The CLI adds terminal equivalents for most user-facing controls
103
+ (`/plan`, `/goal`, `/permission`, `/compact`, `/jobs`, `/todos`, `/skills`, `/image`, `/export`). What remains
104
+ Web/Desktop-only is the **visual host**: sidebar and cards, file/browser/terminal panels, diff viewer, drag-and-drop,
105
+ voice, account and plugin-management screens, and scheduled reminders (which require the Host session controller).
106
+
107
+ If you need one of those in the terminal, the practical path is a thin client over the same Host API rather than a
108
+ second implementation — that would be a separate project.
package/index.js ADDED
@@ -0,0 +1,13 @@
1
+ // Programmatic entry point. This package is a CLI, so importing it never starts
2
+ // a session — it only exposes the package metadata and the executable path for
3
+ // tooling that discovers CLIs programmatically.
4
+ import { readFileSync } from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const manifest = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8"));
8
+
9
+ export const name = manifest.name;
10
+ export const version = manifest.version;
11
+ /** Absolute path to the executable that `bin` maps to. */
12
+ export const binPath = fileURLToPath(new URL("./bin/deepseek.js", import.meta.url));
13
+ export default manifest;