deepseek-cli-mode 1.0.1 → 1.1.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/CHANGELOG.md +20 -0
- package/README.md +26 -0
- package/bin/deepseek.js +16 -3
- package/package.json +1 -1
- package/runner/runner.js +132 -45
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0 — Claude-Code-style terminal UI
|
|
4
|
+
|
|
5
|
+
- New welcome panel: rounded box with version, model, cwd, and first-run hints.
|
|
6
|
+
- Prompt is now `> `; each turn ends with a dim status line (`model · tokens · seconds`).
|
|
7
|
+
- Tool activity renders as `⏺ Tool(args)` followed by a `⎿ result` summary line.
|
|
8
|
+
- Animated `✻ Thinking… (Ns · ctrl+c to interrupt)` spinner while the model works.
|
|
9
|
+
- Permission prompts use the numbered `Do you want to allow …? 1. Yes / 2. No` dialog.
|
|
10
|
+
- Todo list uses `☒`/`☐` checkboxes and prints only changed items after the first render.
|
|
11
|
+
- New `/status` command (session, model, cwd, mode, token pressure); `/session` kept.
|
|
12
|
+
- Security: all new rendering paths pass model/repository text through the existing
|
|
13
|
+
control-character sanitizer; no new process, network, or filesystem surfaces were added.
|
|
14
|
+
|
|
15
|
+
## 1.0.2 — runtime discovery fixes
|
|
16
|
+
|
|
17
|
+
- `resolveDshBin()` now also looks for `@deepseek-ai/dsh` next to this package (same `node_modules`) and in the
|
|
18
|
+
`node_modules` of every directory on `PATH`. This fixes "cannot find @deepseek-ai/dsh" when dsh is installed under a
|
|
19
|
+
custom npm prefix (including Windows drive-relative prefixes such as `E:\root\.npm-global`).
|
|
20
|
+
- The not-found message now explains the Windows drive-relative `prefix=/root/.npm-global` pitfall and points at
|
|
21
|
+
`npm root -g`.
|
|
22
|
+
|
|
3
23
|
## 1.0.1 — security fixes
|
|
4
24
|
|
|
5
25
|
Fixes from an independent security audit (Cloudflare `security-audit` skill, quick profile).
|
package/README.md
CHANGED
|
@@ -12,6 +12,31 @@ interactive **CLI** that boots the *same* Harness runtime (`dsh-base` + `dsh-hea
|
|
|
12
12
|
session persistence, approvals) and keeps **one live Agent for the whole session**, so multi-turn context, tool state,
|
|
13
13
|
and the model's request prefix stay warm between messages.
|
|
14
14
|
|
|
15
|
+
The terminal UI follows the Claude Code layout:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
╭──────────────────────────────────────────────────────╮
|
|
19
|
+
│ ✻ DeepSeek CLI 1.1.0 │
|
|
20
|
+
│ │
|
|
21
|
+
│ /help for help · /status for your current setup │
|
|
22
|
+
│ model: deepseek-official/deepseek-chat │
|
|
23
|
+
│ cwd: C:\work\my-project │
|
|
24
|
+
╰──────────────────────────────────────────────────────╯
|
|
25
|
+
|
|
26
|
+
> explain what changed and run the tests
|
|
27
|
+
|
|
28
|
+
⏺ glob(**/*.ts)
|
|
29
|
+
⎿ src/app.ts src/auth.ts tests/auth.spec.ts
|
|
30
|
+
✻ Thinking… (2s · ctrl+c to interrupt)
|
|
31
|
+
|
|
32
|
+
⏺ pwsh(npm test)
|
|
33
|
+
⎿ Tests: 42 passed, 42 total
|
|
34
|
+
|
|
35
|
+
The auth change is safe; all tests pass.
|
|
36
|
+
|
|
37
|
+
✻ deepseek-official/deepseek-chat · 8.4k tokens · 12.1s
|
|
38
|
+
```
|
|
39
|
+
|
|
15
40
|
```
|
|
16
41
|
❯ explain what changed in this repo and run the tests
|
|
17
42
|
|
|
@@ -125,6 +150,7 @@ deepseek "review @src/app.ts and tell me what is risky"
|
|
|
125
150
|
| `/export [file]` | write this run's transcript as markdown |
|
|
126
151
|
| `/thinking` | print the last turn's reasoning (collapsed by default) |
|
|
127
152
|
| `/session` | print the current session id (for `--resume`) |
|
|
153
|
+
| `/status` | session, model, cwd, mode, and token pressure |
|
|
128
154
|
| `/clear` | clear the screen |
|
|
129
155
|
| `/quit`, `/exit` | leave (Ctrl+C at an empty prompt also exits) |
|
|
130
156
|
|
package/bin/deepseek.js
CHANGED
|
@@ -12,7 +12,7 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
13
|
import { createRequire } from "node:module";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
|
-
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import { dirname, delimiter, join, resolve } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
|
|
18
18
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -81,6 +81,9 @@ Requires a local install of @deepseek-ai/dsh (npm i -g @deepseek-ai/dsh).`);
|
|
|
81
81
|
function resolveDshBin() {
|
|
82
82
|
const candidates = [
|
|
83
83
|
process.env.DSH_CLI_DSH_BIN,
|
|
84
|
+
// Sibling install: dsh next to this package in the same node_modules.
|
|
85
|
+
// This is the normal layout when both were installed with one npm prefix.
|
|
86
|
+
join(root, "..", "@deepseek-ai", "dsh", "lib", "bin.js"),
|
|
84
87
|
// Same global prefix as this node binary (npm default layout).
|
|
85
88
|
join(dirname(process.execPath), "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"),
|
|
86
89
|
// Common global prefixes.
|
|
@@ -96,6 +99,14 @@ function resolveDshBin() {
|
|
|
96
99
|
candidates.push(join(process.cwd(), "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"));
|
|
97
100
|
}
|
|
98
101
|
for (const candidate of candidates) if (existsSync(candidate)) return candidate;
|
|
102
|
+
// npm's own bin directories are on PATH; check their sibling node_modules.
|
|
103
|
+
// This covers custom prefixes (and Windows drive-relative prefixes) that the
|
|
104
|
+
// fixed candidates above cannot know about.
|
|
105
|
+
for (const dir of String(process.env.PATH ?? "").split(delimiter)) {
|
|
106
|
+
if (dir === "") continue;
|
|
107
|
+
const candidate = join(dir, "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js");
|
|
108
|
+
if (existsSync(candidate)) return candidate;
|
|
109
|
+
}
|
|
99
110
|
// Resolve only from this package's own module graph (never from cwd).
|
|
100
111
|
try {
|
|
101
112
|
return requireFromHere.resolve("@deepseek-ai/dsh/lib/bin.js");
|
|
@@ -204,8 +215,10 @@ const dshBin = resolveDshBin();
|
|
|
204
215
|
if (dshBin === undefined) {
|
|
205
216
|
console.error(
|
|
206
217
|
"deepseek: cannot find @deepseek-ai/dsh.\n" +
|
|
207
|
-
" Install it once: npm i -g @deepseek-ai/dsh\n" +
|
|
208
|
-
" Or point DSH_CLI_DSH_BIN at .../@deepseek-ai/dsh/lib/bin.js"
|
|
218
|
+
" Install it once in the same npm prefix as this CLI: npm i -g @deepseek-ai/dsh\n" +
|
|
219
|
+
" Or point DSH_CLI_DSH_BIN at .../@deepseek-ai/dsh/lib/bin.js\n" +
|
|
220
|
+
" If it is installed but not found, check `npm root -g` (a POSIX-style npm prefix such as\n" +
|
|
221
|
+
" /root/.npm-global resolves per drive on Windows; use C:\\... instead)."
|
|
209
222
|
);
|
|
210
223
|
process.exit(1);
|
|
211
224
|
}
|
package/package.json
CHANGED
package/runner/runner.js
CHANGED
|
@@ -210,6 +210,9 @@ function createRenderer({ write, capture }) {
|
|
|
210
210
|
get lastTodos() {
|
|
211
211
|
return lastTodos;
|
|
212
212
|
},
|
|
213
|
+
get turnUsage() {
|
|
214
|
+
return { ...turnUsage };
|
|
215
|
+
},
|
|
213
216
|
resetTurn() {
|
|
214
217
|
streamedThisTurn = false;
|
|
215
218
|
reasoning = "";
|
|
@@ -246,15 +249,24 @@ function createRenderer({ write, capture }) {
|
|
|
246
249
|
case "tool/call": {
|
|
247
250
|
if (data.name === "todo_write") break; // the checklist below is the presentation
|
|
248
251
|
endLine();
|
|
249
|
-
const icon = TOOL_ICONS[data.name] ?? "⏺";
|
|
250
252
|
const preview = previewToolCall(data.name, data.arguments);
|
|
251
|
-
write(
|
|
253
|
+
write(
|
|
254
|
+
paint(ansi.cyan, "⏺ ") +
|
|
255
|
+
paint(ansi.bold, sanitize(data.name)) +
|
|
256
|
+
paint(ansi.dim, `(${preview})`) +
|
|
257
|
+
"\n"
|
|
258
|
+
);
|
|
252
259
|
break;
|
|
253
260
|
}
|
|
254
261
|
case "tool/result": {
|
|
255
|
-
|
|
256
|
-
|
|
262
|
+
let summary;
|
|
263
|
+
if (data.error) summary = `Error: ${sanitize(data.error.name)}${data.error.code ? ` (${sanitize(data.error.code)})` : ""}`;
|
|
264
|
+
else {
|
|
265
|
+
const content = data.message?.content ?? [];
|
|
266
|
+
const text = content.filter((b) => b.type === "text").map((b) => b.text).join(" ").replace(/\s+/g, " ").trim();
|
|
267
|
+
summary = text === "" ? "Done" : truncate(text, 76);
|
|
257
268
|
}
|
|
269
|
+
write(paint(ansi.dim, ` ⎿ ${sanitize(summary)}`) + "\n");
|
|
258
270
|
break;
|
|
259
271
|
}
|
|
260
272
|
case "assistant/message": {
|
|
@@ -280,15 +292,15 @@ function createRenderer({ write, capture }) {
|
|
|
280
292
|
endLine();
|
|
281
293
|
flushText();
|
|
282
294
|
const done = todos.filter((todo) => todo.status === "completed").length;
|
|
283
|
-
write(paint(ansi.dim, `
|
|
295
|
+
write(paint(ansi.dim, ` ☒ todos ${done}/${todos.length}`) + "\n");
|
|
284
296
|
// Print the whole list once; later writes show only the items that moved.
|
|
285
297
|
const shown = firstRender ? todos : todos.filter((todo) => previous.get(todo.content) !== todo.status);
|
|
286
298
|
for (const todo of shown) {
|
|
287
299
|
const mark =
|
|
288
300
|
todo.status === "completed"
|
|
289
|
-
? paint(ansi.green, "
|
|
301
|
+
? paint(ansi.green, "☒")
|
|
290
302
|
: todo.status === "in_progress"
|
|
291
|
-
? paint(ansi.yellow, "
|
|
303
|
+
? paint(ansi.yellow, "☐")
|
|
292
304
|
: paint(ansi.dim, "☐");
|
|
293
305
|
const content = todo.status === "completed" ? paint(ansi.dim, sanitize(todo.content)) : sanitize(todo.content);
|
|
294
306
|
write(` ${mark} ${content}\n`);
|
|
@@ -442,32 +454,43 @@ function printSessionList(sessions, write = out) {
|
|
|
442
454
|
write(paint(ansi.dim, " no sessions in this directory yet\n"));
|
|
443
455
|
return;
|
|
444
456
|
}
|
|
445
|
-
write(` Recent sessions in ${process.cwd()}:\n`);
|
|
457
|
+
write(` Recent sessions in ${sanitize(process.cwd())}:\n`);
|
|
446
458
|
sessions.slice(0, 10).forEach((session, index) => {
|
|
447
459
|
const running = session.openTurn ? paint(ansi.yellow, " ▶ running") : "";
|
|
448
|
-
write(` ${paint(ansi.cyan, String(index + 1))}) ${paint(ansi.dim, session.id)} (${paint(ansi.dim, timeAgo(session.mtime))})${running}\n`);
|
|
460
|
+
write(` ${paint(ansi.cyan, String(index + 1))}) ${paint(ansi.dim, sanitize(session.id))} (${paint(ansi.dim, timeAgo(session.mtime))})${running}\n`);
|
|
449
461
|
});
|
|
450
462
|
}
|
|
451
463
|
|
|
452
464
|
// ---------------------------------------------------------------------------
|
|
453
|
-
// Help /
|
|
465
|
+
// Help / welcome
|
|
454
466
|
// ---------------------------------------------------------------------------
|
|
467
|
+
/** Draw a rounded box, truncating content to the terminal width. */
|
|
468
|
+
function box(lines, minWidth = 56) {
|
|
469
|
+
const cols = Math.max(40, Math.min(process.stdout.columns || 88, 100));
|
|
470
|
+
const width = Math.min(cols - 2, Math.max(minWidth, ...lines.map((line) => line.length + 4)));
|
|
471
|
+
const inner = width - 4;
|
|
472
|
+
const outLines = [];
|
|
473
|
+
outLines.push(paint(ansi.dim, "╭" + "─".repeat(width - 2) + "╮"));
|
|
474
|
+
for (const line of lines) {
|
|
475
|
+
let text = line;
|
|
476
|
+
if (text.length > inner) text = text.slice(0, inner - 1) + "…";
|
|
477
|
+
outLines.push(paint(ansi.dim, "│ ") + text.padEnd(inner) + paint(ansi.dim, " │"));
|
|
478
|
+
}
|
|
479
|
+
outLines.push(paint(ansi.dim, "╰" + "─".repeat(width - 2) + "╯"));
|
|
480
|
+
for (const line of outLines) out(line + "\n");
|
|
481
|
+
}
|
|
482
|
+
|
|
455
483
|
function printBanner(model) {
|
|
456
|
-
const
|
|
457
|
-
" ooo",
|
|
458
|
-
' " """',
|
|
459
|
-
' " "',
|
|
460
|
-
' ___:____ " "|',
|
|
461
|
-
" ,' \\. \\//",
|
|
462
|
-
" | O \\___/ |",
|
|
463
|
-
"~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~",
|
|
464
|
-
];
|
|
484
|
+
const modelLine = sanitize(`${model.provider}/${model.model}${model.reasoningEffort ? ` · ${model.reasoningEffort}` : ""}`);
|
|
465
485
|
out("\n");
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
486
|
+
box([
|
|
487
|
+
`${paint(ansi.cyan, "✻")} ${paint(ansi.bold, "DeepSeek CLI")} ${paint(ansi.dim, VERSION)}`,
|
|
488
|
+
"",
|
|
489
|
+
paint(ansi.dim, " /help for help · /status for your current setup"),
|
|
490
|
+
paint(ansi.dim, ` model: ${modelLine}`),
|
|
491
|
+
paint(ansi.dim, ` cwd: ${sanitize(process.cwd())}`),
|
|
492
|
+
]);
|
|
493
|
+
out(paint(ansi.dim, " Type a task, or /help. Ctrl+C interrupts; twice exits.") + "\n\n");
|
|
471
494
|
}
|
|
472
495
|
|
|
473
496
|
function printHelp(write = out) {
|
|
@@ -498,6 +521,7 @@ Interactive commands:
|
|
|
498
521
|
/export [file] write this run's transcript as markdown
|
|
499
522
|
/thinking show the last turn's reasoning
|
|
500
523
|
/session print the current session id (for --resume)
|
|
524
|
+
/status session, model, cwd, token pressure
|
|
501
525
|
/clear clear the screen
|
|
502
526
|
/quit, /exit leave (also Ctrl+C at an empty prompt)
|
|
503
527
|
|
|
@@ -576,6 +600,7 @@ async function run(ctx, options, exit) {
|
|
|
576
600
|
const planMode = ctx.get("planMode");
|
|
577
601
|
const permissionPresets = ctx.get("permissionPresets");
|
|
578
602
|
const attachments = ctx.get("attachments");
|
|
603
|
+
const tokenMeter = ctx.get("tokenMeter");
|
|
579
604
|
|
|
580
605
|
if (options.list) {
|
|
581
606
|
printSessionList(listSessions());
|
|
@@ -783,24 +808,63 @@ async function run(ctx, options, exit) {
|
|
|
783
808
|
// ---- agent lifecycle ----------------------------------------------------
|
|
784
809
|
const makeAgentOptions = () => ({ ...selectionRef.current });
|
|
785
810
|
|
|
811
|
+
// ---- Claude-style activity indicator ------------------------------------
|
|
812
|
+
let spinnerTimer = null;
|
|
813
|
+
let spinnerIndex = 0;
|
|
814
|
+
let spinnerActive = false;
|
|
815
|
+
const SPINNER = ["✻", "✳", "✶", "✳"];
|
|
816
|
+
|
|
817
|
+
function startSpinner(label = "Thinking") {
|
|
818
|
+
if (!tty || options.mode === "oneshot" || spinnerActive) return;
|
|
819
|
+
spinnerActive = true;
|
|
820
|
+
spinnerIndex = 0;
|
|
821
|
+
const started = Date.now();
|
|
822
|
+
out("\x1b[?25l");
|
|
823
|
+
const tick = () => {
|
|
824
|
+
const glyph = SPINNER[spinnerIndex++ % SPINNER.length];
|
|
825
|
+
const seconds = Math.floor((Date.now() - started) / 1000);
|
|
826
|
+
out(`\r\x1b[2K${paint(ansi.dim, `${glyph} ${label}… (${seconds}s · ctrl+c to interrupt)`)}`);
|
|
827
|
+
};
|
|
828
|
+
tick();
|
|
829
|
+
spinnerTimer = setInterval(tick, 120);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function stopSpinner() {
|
|
833
|
+
if (!spinnerActive) return;
|
|
834
|
+
spinnerActive = false;
|
|
835
|
+
if (spinnerTimer !== null) {
|
|
836
|
+
clearInterval(spinnerTimer);
|
|
837
|
+
spinnerTimer = null;
|
|
838
|
+
}
|
|
839
|
+
out("\r\x1b[2K\x1b[?25h");
|
|
840
|
+
}
|
|
841
|
+
|
|
786
842
|
const wire = (agent) => {
|
|
787
843
|
agent.ctx.on("session/event", (session, event) => {
|
|
788
|
-
if (session === agent.session)
|
|
844
|
+
if (session === agent.session) {
|
|
845
|
+
stopSpinner();
|
|
846
|
+
renderer.event(event);
|
|
847
|
+
}
|
|
789
848
|
});
|
|
790
849
|
agent.ctx.on("agent/assistant-stream", (payload) => {
|
|
791
|
-
if (payload.agent === agent)
|
|
850
|
+
if (payload.agent === agent) {
|
|
851
|
+
stopSpinner();
|
|
852
|
+
renderer.frame(payload.frame);
|
|
853
|
+
}
|
|
792
854
|
});
|
|
793
855
|
agent.ctx.on("user-questions/request", async (request) => answerQuestions(request));
|
|
794
856
|
agent.ctx.on("approval/request", async (request) => {
|
|
795
|
-
const toolName = request.toolName ?? "tool";
|
|
857
|
+
const toolName = sanitize(request.toolName ?? "tool");
|
|
796
858
|
if (autoYes) return "allowed-once";
|
|
797
|
-
|
|
798
|
-
out(paint(ansi.dim, paint(ansi.italic, ` ⚠ ${toolName} needs approval: ${request.reason}`)) + "\n");
|
|
799
|
-
}
|
|
859
|
+
stopSpinner();
|
|
800
860
|
if (!interactive || !process.stdin.isTTY) return "rejected";
|
|
801
|
-
|
|
802
|
-
if (
|
|
803
|
-
|
|
861
|
+
out("\n");
|
|
862
|
+
if (request.reason) out(paint(ansi.dim, ` ${sanitize(request.reason)}`) + "\n");
|
|
863
|
+
out(` ${paint(ansi.bold, "Do you want to allow")} ${paint(ansi.cyan, toolName)}${paint(ansi.bold, "?")}\n`);
|
|
864
|
+
out(` ${paint(ansi.cyan, "1")}. Yes\n ${paint(ansi.cyan, "2")}. No\n`);
|
|
865
|
+
const answer = (await ask(paint(ansi.green, " ❯ "))) ?? "";
|
|
866
|
+
const choice = answer.trim().toLowerCase();
|
|
867
|
+
return choice === "1" || choice === "y" || choice === "yes" ? "allowed-once" : "rejected";
|
|
804
868
|
});
|
|
805
869
|
};
|
|
806
870
|
|
|
@@ -836,17 +900,26 @@ async function run(ctx, options, exit) {
|
|
|
836
900
|
busy = true;
|
|
837
901
|
cancelSent = false;
|
|
838
902
|
renderer.resetTurn();
|
|
903
|
+
const startedAt = Date.now();
|
|
839
904
|
setPaused(true);
|
|
905
|
+
startSpinner("Thinking");
|
|
840
906
|
try {
|
|
841
907
|
agent.followup(createUserMessage({ content, source: { kind: "user" } }));
|
|
842
908
|
await agent.whenIdle();
|
|
843
909
|
await persistence.flush();
|
|
844
910
|
} finally {
|
|
911
|
+
stopSpinner();
|
|
845
912
|
busy = false;
|
|
846
913
|
setPaused(false);
|
|
847
914
|
if (renderer.lastAssistantText && renderer.lastAssistantText !== previousAnswer) {
|
|
848
915
|
transcript.push({ role: "assistant", text: renderer.lastAssistantText });
|
|
849
916
|
}
|
|
917
|
+
if (interactive && tty) {
|
|
918
|
+
const usage = renderer.turnUsage;
|
|
919
|
+
const seconds = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
920
|
+
const tokens = usage.input + usage.output;
|
|
921
|
+
out(paint(ansi.dim, ` ✻ ${sanitize(modelDesc())}${tokens > 0 ? ` · ${formatTokens(tokens)} tokens` : ""} · ${seconds}s`) + "\n\n");
|
|
922
|
+
}
|
|
850
923
|
}
|
|
851
924
|
};
|
|
852
925
|
|
|
@@ -1057,7 +1130,7 @@ async function run(ctx, options, exit) {
|
|
|
1057
1130
|
try {
|
|
1058
1131
|
agent = resumeTarget ? await startAgent(undefined, resumeTarget) : await startAgent(SessionId(`session-${randomUUID()}`), undefined);
|
|
1059
1132
|
} catch (error) {
|
|
1060
|
-
err(`deepseek: cannot start a session: ${
|
|
1133
|
+
err(`deepseek: cannot start a session: ${sanitize(describeError(error))}\n`);
|
|
1061
1134
|
await shutdown(1);
|
|
1062
1135
|
return;
|
|
1063
1136
|
}
|
|
@@ -1070,14 +1143,14 @@ async function run(ctx, options, exit) {
|
|
|
1070
1143
|
|
|
1071
1144
|
printBanner(selectionRef.current);
|
|
1072
1145
|
if (resumeTarget) {
|
|
1073
|
-
out(paint(ansi.green, `▶ resumed `) + paint(ansi.dim, resumeTarget) + "\n");
|
|
1146
|
+
out(paint(ansi.green, `▶ resumed `) + paint(ansi.dim, sanitize(resumeTarget)) + "\n");
|
|
1074
1147
|
out(paint(ansi.dim, " (history is in the session log; continue the conversation below)") + "\n\n");
|
|
1075
1148
|
} else {
|
|
1076
|
-
out(paint(ansi.dim, `session: ${agent.session.id} (resume with: deepseek --resume ${agent.session.id})`) + "\n\n");
|
|
1149
|
+
out(paint(ansi.dim, `session: ${sanitize(agent.session.id)} (resume with: deepseek --resume ${sanitize(agent.session.id)})`) + "\n\n");
|
|
1077
1150
|
}
|
|
1078
1151
|
|
|
1079
1152
|
while (!quitting) {
|
|
1080
|
-
const line = await ask(paint(ansi.
|
|
1153
|
+
const line = await ask(paint(ansi.bold, "> "));
|
|
1081
1154
|
if (line === null) break;
|
|
1082
1155
|
const text = line.trim();
|
|
1083
1156
|
if (text === "") continue;
|
|
@@ -1098,9 +1171,9 @@ async function run(ctx, options, exit) {
|
|
|
1098
1171
|
out(paint(ansi.dim, " ↻ new session\n"));
|
|
1099
1172
|
try {
|
|
1100
1173
|
agent = await startAgent(SessionId(`session-${randomUUID()}`), undefined);
|
|
1101
|
-
out(paint(ansi.dim, ` session: ${agent.session.id}\n`));
|
|
1174
|
+
out(paint(ansi.dim, ` session: ${sanitize(agent.session.id)}\n`));
|
|
1102
1175
|
} catch (error) {
|
|
1103
|
-
out(paint(ansi.red, ` ✘ ${
|
|
1176
|
+
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1104
1177
|
}
|
|
1105
1178
|
continue;
|
|
1106
1179
|
}
|
|
@@ -1116,13 +1189,27 @@ async function run(ctx, options, exit) {
|
|
|
1116
1189
|
out(paint(ansi.dim, ` ↻ resuming ${argument}\n`));
|
|
1117
1190
|
agent = await startAgent(undefined, argument);
|
|
1118
1191
|
} catch (error) {
|
|
1119
|
-
out(paint(ansi.red, ` ✘ ${
|
|
1192
|
+
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1120
1193
|
}
|
|
1121
1194
|
continue;
|
|
1122
1195
|
}
|
|
1123
1196
|
case "/session":
|
|
1124
|
-
out(paint(ansi.dim, ` ${agent.session.id}\n`));
|
|
1197
|
+
out(paint(ansi.dim, ` ${sanitize(agent.session.id)}\n`));
|
|
1125
1198
|
continue;
|
|
1199
|
+
case "/status": {
|
|
1200
|
+
out(paint(ansi.bold, " Status") + "\n");
|
|
1201
|
+
out(` session: ${sanitize(agent.session.id)}\n`);
|
|
1202
|
+
out(` model: ${sanitize(modelDesc())}\n`);
|
|
1203
|
+
out(` cwd: ${sanitize(process.cwd())}\n`);
|
|
1204
|
+
out(` mode: ${interactive ? "interactive" : "one-shot"}${autoYes ? " · auto-approve" : ""}\n`);
|
|
1205
|
+
try {
|
|
1206
|
+
if (tokenMeter !== undefined) {
|
|
1207
|
+
const measurement = tokenMeter.measure(agent.session);
|
|
1208
|
+
out(` tokens: ${measurement.totalTokens} (surface ${measurement.surfaceTokens})\n`);
|
|
1209
|
+
}
|
|
1210
|
+
} catch {}
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
1126
1213
|
case "/model": {
|
|
1127
1214
|
if (argument === "") {
|
|
1128
1215
|
out(paint(ansi.dim, ` model: ${modelDesc()}\n`));
|
|
@@ -1134,7 +1221,7 @@ async function run(ctx, options, exit) {
|
|
|
1134
1221
|
selectionRef.current = next;
|
|
1135
1222
|
out(paint(ansi.green, ` ✓ model set: ${next.provider}/${next.model} (next message)\n`));
|
|
1136
1223
|
} catch (error) {
|
|
1137
|
-
out(paint(ansi.red, ` ✘ ${
|
|
1224
|
+
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1138
1225
|
}
|
|
1139
1226
|
continue;
|
|
1140
1227
|
}
|
|
@@ -1149,7 +1236,7 @@ async function run(ctx, options, exit) {
|
|
|
1149
1236
|
selectionRef.current = next;
|
|
1150
1237
|
out(paint(ansi.green, ` ✓ reasoning set: ${argument} (next message)\n`));
|
|
1151
1238
|
} catch (error) {
|
|
1152
|
-
out(paint(ansi.red, ` ✘ ${
|
|
1239
|
+
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1153
1240
|
}
|
|
1154
1241
|
continue;
|
|
1155
1242
|
}
|
|
@@ -1281,7 +1368,7 @@ async function run(ctx, options, exit) {
|
|
|
1281
1368
|
}
|
|
1282
1369
|
}
|
|
1283
1370
|
|
|
1284
|
-
out("\n" + paint(ansi.
|
|
1371
|
+
out("\n" + paint(ansi.bold, "> ") + sanitize(text) + "\n\n");
|
|
1285
1372
|
await runTurn(agent, text);
|
|
1286
1373
|
}
|
|
1287
1374
|
|
|
@@ -1310,7 +1397,7 @@ export function apply(ctx) {
|
|
|
1310
1397
|
}
|
|
1311
1398
|
run(ctx, options, exit).catch((error) => {
|
|
1312
1399
|
const detail = process.env.DSH_CLI_DEBUG === "1" && error instanceof Error ? `\n${error.stack}` : "";
|
|
1313
|
-
process.stderr.write(`deepseek: ${
|
|
1400
|
+
process.stderr.write(`deepseek: ${sanitize(describeError(error))}${detail}\n`);
|
|
1314
1401
|
try {
|
|
1315
1402
|
exit(1);
|
|
1316
1403
|
} catch {}
|