CLI & Startup
Official counterpart: the
dshCLI,--profile headlessone-shot mode
Framework and plugins are in place. Now we give mini-dsh a "mouth": three run modes, mirroring the official's three usages.
Mode Overview
| Command | What it does | Official counterpart |
|---|---|---|
pnpm chat | Interactive REPL, prints as it generates | dsh interactive mode |
pnpm run run "task" | One-shot task execution, prints the result | dsh --profile headless "task" |
pnpm web | Starts the browser UI | dsh web |
Implementation
ts
/**
* dsh CLI:chat(交互 REPL)与 run(一次性执行)两种形态。
* 官方:`dsh --profile headless "task"` 是单次任务,`dsh web` 是浏览器应用。
*/
import { createInterface } from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";
import { buildAgent } from "../boot.ts";
import { web } from "./web.ts";
async function chat(): Promise<void> {
const { ctx, agent } = await buildAgent();
// 订阅流式增量,边生成边打印(演示事件驱动 UI)
const off = ctx.on("assistant/chunk")(({ delta }: any) => {
if (typeof delta.content === "string") process.stdout.write(delta.content);
});
const rl = createInterface({ input, output });
console.log("mini-dsh chat — 输入 exit 退出\n");
for (;;) {
const line = await rl.question("你> ");
if (line.trim() === "exit" || line.trim() === "") break;
const reply = await agent.turn(line);
console.log(`\n\nmini-dsh> ${reply.content}\n`);
}
off();
await ctx.stop();
rl.close();
}
async function run(task: string): Promise<void> {
const { ctx, agent } = await buildAgent();
const reply = await agent.turn(task);
console.log(reply.content);
await ctx.stop();
}
async function main(): Promise<void> {
const [cmd, ...rest] = process.argv.slice(2);
if (cmd === "chat") return chat();
if (cmd === "run") return run(rest.join(" "));
if (cmd === "web") return web(3080);
console.error(`用法: dsh <chat|run "任务"|web>`);
process.exit(1);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});chat: event-driven typewriter effect
ts
async function chat(): Promise<void> {
const { ctx, agent } = await buildAgent();
// subscribe to streaming deltas and print as they arrive (event-driven UI)
const off = ctx.on("assistant/chunk")(({ delta }: any) => {
if (typeof delta.content === "string") process.stdout.write(delta.content);
});
const rl = createInterface({ input, output });
console.log("mini-dsh chat — type exit to quit\n");
for (;;) {
const line = await rl.question("you> ");
if (line.trim() === "exit" || line.trim() === "") break;
const reply = await agent.turn(line);
console.log(`\n\nmini-dsh> ${reply.content}\n`);
}
off();
await ctx.stop();
rl.close();
}Note: ctx.on("assistant/chunk") subscribes to exactly the event broadcast by the Agent loop via ctx.emit("assistant/chunk"). The UI never touches the core loop — it only subscribes to events. That's the direct payoff of the event-driven architecture.
run: headless one-shot
ts
async function run(task: string): Promise<void> {
const { ctx, agent } = await buildAgent();
const reply = await agent.turn(task);
console.log(reply.content);
await ctx.stop();
}Two lines of core logic. Perfect for scripts, CI, and automation pipelines — exactly what the official headless profile does.
Real Run Output
You can run it without an API key — use the scripted demo:
bash
npx tsx demo.tsOutput (real execution — run_bash really invoked echo 1+1 | bc):
text
让我先算一下。计算完成:1+1=2。
==== 最终回复: 计算完成:1+1=2。 ====
==== 会话日志(append-only SessionEvent)====
turn/start {"seq":0,"ts":...,"agent":"5a1b896f-beb"}
user/message {"seq":1,"content":"1+1 等于多少?用工具算一下"}
step/start {"seq":2,"step":0}
assistant/message {"seq":5,"content":"让我先算一下。","tool_calls":[...run_bash...]}
tool/result {"seq":6,"tool_call_id":"call_1","content":"2"}
step/end {"seq":7,"step":0,"tool_calls":1}
step/start {"seq":8,"step":1}
assistant/message {"seq":12,"content":"计算完成:1+1=2。","tool_calls":[]}
step/end {"seq":13,"step":1,"tool_calls":0}
turn/end {"seq":14}Hooking Up the Real DeepSeek
bash
export DEEPSEEK_API_KEY=sk-your-key
pnpm run run "write a bubble sort and save it to sort.py"
# or point at any OpenAI-compatible endpoint (local vLLM, gateway...)
export DEEPSEEK_BASE_URL=https://your-endpoint
# default model is deepseek-chat; switch via env (e.g. opencode endpoints)
export DEEPSEEK_MODEL=deepseek-v4-flash
pnpm chatTroubleshooting
缺少 DEEPSEEK_API_KEY— you forgot to export, or the key is emptyDeepSeek API 401— invalid key- Network issues — check your proxy/firewall; is
DEEPSEEK_BASE_URLreachable?
Next: Testing & Verification →