A small terminal coding agent for DeepSeek models: it reads your repository, runs commands after asking, proposes edits as patches you apply, and checkpoints the workspace around every turn so any turn can be undone.
It is a TypeScript port of the design of
CodeWhale (formerly deepseek-tui,
MIT), a much larger and more mature Rust agent. The snapshot scheme, session
fork/backtrack, cost footer, auto-routing and prompt layout all come from CodeWhale;
see THIRD_PARTY_NOTICES.md. Not affiliated with DeepSeek.
$ npm run demo # build output and startup banner left out
session: demo
path: ~/.weiping-whale/sessions/demo.json
━━ test.mjs fails, find the bug and fix it
thinking...
tool execute_bash {"command":"node test.mjs"}
execute_bash failed in 3471ms
thinking round 2...
tool read_file {"path":"src/slugify.mjs"}
read_file done in 12ms
thinking round 3...
tool edit_file {"path":"src/slugify.mjs","old_string":" .replace(/[^a-z0-9]+/g, \"-\");","new_string":" .replace(/[^a-z0-9]+/g...
edit_file done in 174ms
thinking round 4...
WEIPING_WHALE
`node test.mjs` fails because slugify("Hello, World!") returns "hello-world-".
The regex turns every run of non-alphanumerics into a dash, including the
trailing "!", and nothing trims dashes at the ends.
I prepared patch 6f52149d, which strips leading and trailing dashes.
Review it with /patches, then /apply 6f52149d.
└ $0.0004 · 6.5k tok · cache 71%
━━ /apply 6f52149d
Applied patch 6f52149d to ~/slugify-demo/src/slugify.mjs
━━ run the tests again
thinking...
tool execute_bash {"command":"node test.mjs"}
execute_bash done in 2452ms
thinking round 2...
WEIPING_WHALE
Ran `node test.mjs`: 2 tests passed
└ $0.0005 · 10.4k tok · cache 80%
━━ /snapshots
4e7b557da997 2026-10-22 04:59:01 post-turn:2
ecaf8c885847 2026-10-22 04:58:55 pre-turn:2
7f520501f32a 2026-10-22 04:58:51 post-turn:1
507aabc12156 2026-10-22 04:58:42 pre-turn:1
That session is real except for the model. npm run demo starts
scripts/mock-deepseek.mjs, a fake DeepSeek endpoint
whose replies are scripted, and points the built CLI at it. The shell command, the
file read, the patch preview, the snapshots and the session file are produced by the
actual tool code; the token counts are the mock's rough estimate (4 characters per
token) priced at the default flash rates. The same session as an image is in
docs/demo.png.
I wanted to understand how tools like Claude Code, Codex and CodeWhale work, not just use them. Porting CodeWhale to a language I read every day, at a size I can hold in my head, was the most direct way. If you just want a coding agent, use CodeWhale itself (OS sandboxing, more language servers, more providers), Aider, or one of the vendor CLIs. This repo is for reading and changing the loop.
Requires Node.js 22.18 or newer and git. The package is not published on npm.
git clone https://github.com/appleweiping/WEIPING_WHALE.git
cd WEIPING_WHALE
npm ci
npm run demo # builds, then replays the session above; no API key needed
npm link # puts `wwhale` and `weiping-whale` on your PATHWith a DeepSeek key:
export DEEPSEEK_API_KEY=sk-...
wwhale --doctor # checks config, key source and paths
wwhale # REPL in the current directory
wwhale -t "summarize the architecture of this repo"Inside the REPL, /patches and /apply <id> review edits, /approve <id> runs a held
shell command, /undo rolls the workspace back one turn, and /cost shows spend.
The full list of commands, config keys and safety profiles is in
docs/usage.md.
src/ is about 8,500 lines of TypeScript (wc -l src/*.ts src/*/*.ts). One turn
looks like this:
flowchart TD
P["System prompt<br/>prompts/base.md + AGENTS.md/CLAUDE.md<br/>+ repo map + skills<br/>src/prompts/assemble.ts"] --> L
U["You: REPL, -t task or HTTP API<br/>src/index.ts"] --> S1["pre-turn snapshot<br/>src/snapshot/"]
S1 --> R{"--model auto?<br/>src/router.ts"}
R --> L["Agent loop<br/>src/agent.ts"]
L -->|"messages + tool schemas"| M["DeepSeek chat API<br/>src/llm/deepseek.ts"]
M -->|"usage"| C["Cost and cache-hit tracker<br/>src/cost.ts"]
M -->|"tool_calls"| T["Tool registry<br/>src/tools/registry.ts"]
T --> B["Built-in tools<br/>read/write/edit, glob/grep, bash,<br/>todo, revert_turn, sub-agents"]
T --> X["MCP servers<br/>src/mcp/"]
B --> G["Approval queue and patch previews<br/>src/safety/"]
G -->|"tool results"| L
X -->|"tool results"| L
M -->|"plain answer"| S2["post-turn snapshot<br/>save session JSON<br/>src/session.ts"]
- Prompt.
src/prompts/assemble.tsbuilds one system message, most static part first so DeepSeek's prefix cache keeps hitting:prompts/base.md, the project'sAGENTS.md/CLAUDE.md, a size-limited map of tracked files and their top-level symbols (src/context/repo-map.ts), and the list of skills. - Checkpoint. Before the turn,
src/snapshot/repo.tscommits the whole workspace into a separate git repository under~/.weiping-whale/snapshots/, passing--git-dirand--work-treeexplicitly so your own.gitis never touched./undochecks that tree back out. - Routing. With
--model auto,src/router.tspicks flash or pro and the thinking level from keywords in your message. No extra API call. - The loop.
Agent.complete()sends the messages plus every tool schema, executes each returned tool call, appends the results and asks again, until the model answers without tool calls ormax_iterationsis reached. The loop and its tool dispatcher are about 80 lines; most of the code is the tools and bookkeeping around it. - Tools and safety. Built-in tools live in
src/tools/. Shell commands are matched against two regex lists insrc/safety/approval.ts: broad recursive deletes, disk and shutdown commands are always blocked; deletes, moves, redirections, package installs, force-pushes andsshwait for/approve; everything else runs. In the default modeedit_fileandwrite_fileonly create a preview (src/safety/patches.ts) until you/applyit. MCP tools come from the official SDK client insrc/mcp/client.tsand are namespaced per server. - Cost. Every response's
usagegoes tosrc/cost.ts, which prices cache-hit and cache-miss input tokens separately; the footer turns yellow or red when the cache-hit ratio drops, which usually means the context was reshuffled. - Sessions. After the turn the message list is saved as JSON
(
src/session.ts)./forkcopies it into a new session,/backtrack ncuts it just before the n-th last user message, and/compact(src/compaction.ts) folds old messages into a summary while keeping tool calls paired with their results. - Sub-agents.
agent_openstarts anotherAgentwith a copy of the config (src/tools/subagent.ts); the pool and nesting depth are capped.
The terminal UI (src/ui/terminal.ts), LSP diagnostics after
edits (src/lsp/) and the optional --serve HTTP/SSE API
(src/server/runtime-api.ts) sit around this core.
npm run typecheck
npm testnpm test builds the CLI and runs 18 end-to-end scripts in scripts/
(snapshots, sessions, cost, compaction, routing, MCP against a fake server, the HTTP
API, the full agent loop against the mock model, and more), then packs the tarball,
installs it in a temp directory and runs both binaries. Nothing calls the real
DeepSeek API. CI runs the same on Ubuntu and Windows.
- Shell safety is approval prompts plus pattern matching, not an OS sandbox. A command you approve runs with your permissions.
- The client targets DeepSeek's OpenAI-compatible chat API; other endpoints set via
base_urlare untested. The default prices insrc/cost.tswill drift; override them with[pricing]in the config. - LSP diagnostics cover TypeScript/JavaScript and Python, and only if the language servers are installed.
- The demo and tests prove the plumbing, not the quality of the model's edits.
MIT, see LICENSE. CodeWhale's MIT notice is in THIRD_PARTY_NOTICES.md.