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 +21 -0
- package/README.md +257 -2
- package/bin/deepseek.js +263 -0
- package/docs/PARITY.md +108 -0
- package/index.js +13 -0
- package/package.json +49 -4
- package/runner/runner.js +1298 -0
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
|
-
#
|
|
1
|
+
# deepseek-cli-mode
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
package/bin/deepseek.js
ADDED
|
@@ -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;
|