@zhongqian97-code/ecode
v0.5.111
Published
A minimal Claude Code clone with REPL interface and bash tool calling
Maintainers
Readme
ecode
A minimal Claude Code clone — fullscreen TUI with streaming LLM responses, bash tool calling, Emacs keybindings, and a skill system.
Install
npm install -g @zhongqian97-code/ecodeQuick start
export ECODE_API_KEY=sk-...
ecodeConfiguration
Priority: env vars > config file > defaults
Environment variables
| Variable | Default | Description |
|---|---|---|
| ECODE_API_KEY | (required) | API key |
| ECODE_BASE_URL | https://api.openai.com/v1 | Base URL (any OpenAI-compatible endpoint) |
| ECODE_MODEL | gpt-4o | Model name |
| ECODE_LOG_DIR | (disabled) | Directory for session logs (JSONL) |
| ECODE_SYSTEM_PROMPT | (built-in default) | Override the built-in system prompt; empty string "" disables it |
| ECODE_MAX_RETRIES | 10 | Max provider request retries (respects retry-after / x-should-retry) |
| ECODE_WEB_TOKEN | (random per launch) | Fixed access token for ecode web — set this to keep the same URL across restarts |
Config file
~/.ecode/config.json — all fields are optional, only set what you need.
{
"apiKey": "sk-...",
"baseUrl": "https://api.openai.com/v1",
"model": "gpt-4o",
"logDir": "~/.ecode/logs",
"webToken": "my-fixed-token",
"contextLimit": 128000,
"dangerousPatterns": [
"rm -rf", "sudo", "chmod", "chown",
"mkfs", "dd", "fdisk",
"kill", "pkill", "killall",
"reboot", "shutdown", "halt",
"curl -X DELETE", "wget --delete-after"
]
}contextLimit — override automatic context window detection (tokens). Useful for unlisted or self-hosted models.
dangerousPatterns — list of command prefixes that require double confirmation. Setting this field replaces the entire default list.
webToken — fixed access token for the ecode web admin server. When unset, a fresh random token is generated on every launch (so the access URL changes each time). Set this (or ECODE_WEB_TOKEN) to a stable value to keep the same http://host:port?token=... URL across restarts. Priority: ECODE_WEB_TOKEN env > config file > random.
Multi-provider config
Configure multiple providers and switch between them:
{
"defaultProvider": "anthropic",
"providers": {
"anthropic": {
"baseUrl": "https://api.anthropic.com",
"apiKey": "sk-ant-...",
"model": "claude-sonnet-4-6",
"apiFormat": "anthropic"
},
"deepseek": {
"baseUrl": "https://api.deepseek.com/v1",
"apiKey": "sk-...",
"model": "deepseek-chat"
}
}
}apiFormat — set to "anthropic" to use the native Anthropic Messages API. Auto-detected when baseUrl contains "anthropic"; required for Anthropic-compatible endpoints with custom URLs.
Using with other providers
# DeepSeek
export ECODE_BASE_URL=https://api.deepseek.com/v1
export ECODE_API_KEY=sk-...
export ECODE_MODEL=deepseek-chat
ecode
# Anthropic Claude (native API — no proxy needed)
export ECODE_BASE_URL=https://api.anthropic.com
export ECODE_API_KEY=sk-ant-...
export ECODE_MODEL=claude-sonnet-4-6
ecode
# MiniMax M2.5
export ECODE_BASE_URL=https://api.minimax.chat/v1
export ECODE_API_KEY=sk-...
export ECODE_MODEL=MiniMax-M2.5
ecode
# Local Ollama
export ECODE_BASE_URL=http://localhost:11434/v1
export ECODE_API_KEY=ollama
export ECODE_MODEL=llama3
ecodeContext window sizes are pre-configured for common models (GPT-4o, Claude, DeepSeek, o1/o3). Unknown models default to 128K.
Keyboard shortcuts
Submitting and navigation
| Key | Action |
|---|---|
| Enter | Submit message |
| Shift+Enter | Insert newline (multi-line input) |
| Tab | Toggle tool call / thinking expansion |
| PageUp / PageDown | Scroll conversation history |
| Ctrl+V | Scroll down (same as PageDown) |
| Alt+V | Scroll up (same as PageUp) |
| Ctrl+P | Previous command (input history) |
| Ctrl+N | Next command (input history) |
Emacs cursor movement
| Key | Action |
|---|---|
| Ctrl+A | Jump to start of line |
| Ctrl+E | Jump to end of line |
| Ctrl+B / ← | Move left one character |
| Ctrl+F / → | Move right one character |
| Alt+B | Move left one word |
| Alt+F | Move right one word |
Emacs editing
| Key | Action |
|---|---|
| Backspace | Delete character before cursor |
| Ctrl+D | Delete character at cursor |
| Ctrl+K | Kill from cursor to end of line |
| Ctrl+U | Kill from start of line to cursor |
| Ctrl+W | Kill word backward |
Bash tool calling
ecode gives the LLM access to a bash tool. Commands are classified into three tiers:
| Tier | Examples | Behavior |
|---|---|---|
| Allow | ls, cat, pwd, echo, head, tail, wc, date, whoami, which, env | Auto-execute, no prompt |
| Normal | git status, npm install, grep | Single confirmation |
| Danger | rm -rf, sudo, chmod, kill, reboot | Double confirmation |
The danger list is fully customizable via dangerousPatterns in the config file.
Skills (slash commands)
Type / to see available skills with Tab autocomplete. Skills inject structured instructions into the LLM context.
| Skill | Description |
|---|---|
| /plan | Restate requirements and create a step-by-step implementation plan |
| /tdd | Test-driven development with red-green-refactor loop |
| /diagnose | Disciplined debug loop: reproduce → minimise → hypothesise → instrument → fix |
| /grill-me | Relentless interview to stress-test a plan or design |
| /grill-with-docs | Grilling session that challenges plans against the project's domain model |
| /improve-codebase-architecture | Find deepening opportunities and architectural friction |
| /security-review | Security vulnerability scan of pending changes |
| /search-first | Research-before-coding workflow |
| /zoom-out | Get a higher-level map of the relevant modules and callers |
| /to-prd | Turn current context into a PRD |
| /to-issues | Break a plan into independently-grabbable issues |
| /triage | Move issues through a triage state machine |
| /write-a-skill | Create new agent skills |
| /caveman | Ultra-compressed mode (~75% token reduction) |
Run /setup-matt-pocock-skills once to configure the issue tracker and triage vocabulary for your repo.
Automation: /loop and /goal
Run LLM tasks automatically — on a schedule or until a condition is met.
/loop — recurring tasks
/loop [interval] <prompt>Runs the prompt on a fixed interval. Interval formats: 30s, 5m, 1h, 1h30m. Defaults to 10 minutes if omitted (DEFAULT_INTERVAL_MS = 600_000, src/automation/loop/parse.ts:5). The minimum allowed interval is 30 seconds (MIN_INTERVAL_MS = 30_000, src/automation/loop/parse.ts:8); anything below it is rejected and the job is not created.
/loop 10m check git status and summarize uncommitted changes
/loop 1h summarize today's work and update CHANGELOG/goal — condition-driven tasks
/goal <condition>Runs the condition as both the prompt and the success criterion. The LLM executes the prompt, then a separate evaluator LLM judges whether the condition has been met. Repeats until done or blocked.
When the evaluator returns not_done, the runner waits 5 seconds before the next turn (src/automation/index.ts:262); the wait can be interrupted early by an abort (e.g. /ungoal).
/goal all TypeScript errors are fixed
/goal the test suite passes with no failuresManaging jobs
/jobs — list all active jobs
/unloop <id> — cancel a loop job (use first 8 chars of id)
/ungoal <id> — cancel a goal jobTool policy
Automation jobs run under a restricted default tool policy (DEFAULT_AUTOMATION_POLICY, src/automation/policy.ts:17):
bashallowed (allowBash: true) — read-only shell and command execution are permitted.write/editdenied (allowWrite: false,allowEdit: false) — jobs cannot create or modify files by default.- No nested automation (
denyAutomationCreation: true) — a job cannot create another/loopor/goalfrom inside itself, preventing runaway recursion. - All other tools (
read,glob,grep, etc.) default to allowed.
Loop job lifecycle fields
A LoopJob (src/automation/types.ts:66) carries two scheduling controls beyond the interval:
ttlMs(optional) — a time-to-live. The scheduler stops dispatching the job oncecreatedAt + ttlMs <= now, so the loop self-expires (src/automation/loop/scheduler.ts:151).autoRunNow— whether the job fires once immediately on creation. The/loopcommand currently creates jobs withautoRunNow: false(src/automation/loop/command.ts:98), i.e. the first run waits a full interval.
Budget limits
AutomationBudget (src/automation/types.ts:17) declares four fields, but only one is enforced today:
maxTurns— ENFORCED. WhenrunCount >= maxTurns, the runtime skips execution and returns without running or logging (src/automation/runtime.ts:34).maxRuntimeMs— declared but NOT enforced. No code reads this field yet.maxIdleMs— declared but NOT enforced. No code reads this field yet.maxTokens— declared but NOT enforced. No code reads this field yet.
Treat maxRuntimeMs, maxIdleMs, and maxTokens as reserved/future fields — setting them has no effect on the current runtime.
Goal rollback on blocked
When the evaluator judges a goal blocked, the result message appends a one-line rollback command pointing at the session snapshot taken when the goal was created (src/automation/goal/rollback.ts:15):
ecode --fork <snapshotLogFile>:<snapshotTurn>This forks the session back to the last known-good turn before the goal started. It is only emitted when both snapshotLogFile and snapshotTurn are present on the job — which in turn requires a configured log dir (logDir); without it the rollback hint is silently omitted.
Data storage
Job state and run logs are stored in <ECODE_LOG_DIR>/automation/ (or ~/.config/ecode/automation/ if no log dir is set). Jobs survive restarts — they resume automatically on next launch.
Web admin (ecode web)
Run ecode as a browser-based admin server instead of the TUI — manage sessions, chat, skills, config, and automation jobs from a web UI.
ecode web [options]| Option | Default | Description |
|---|---|---|
| --host <host> | 127.0.0.1 | Bind host. Localhost-only by default; pass 0.0.0.0 to expose on your network |
| --port <port> | 4310 | Bind port |
| --auto | (off) | Auto-approve tool calls (skip confirmation prompts) |
| -h, --help | | Show command help |
On start it prints the access URL with the token baked in:
ecode web admin started
Bind: 127.0.0.1:4310
URL: http://127.0.0.1:4310?token=...
Token: fixed (from config)Just open the URL in a browser — the ?token= query param authenticates you, no header setup needed.
Access token
The server is token-authenticated. By default a fresh random token is generated on every launch, so the URL changes each time. Set a fixed token to keep a stable URL across restarts:
# via env var
export ECODE_WEB_TOKEN=my-fixed-token
# or in ~/.ecode/config.json
# "webToken": "my-fixed-token"Priority: ECODE_WEB_TOKEN env > config file webToken > random. See Configuration for details.
Security: binds to
127.0.0.1by default. If you pass--host 0.0.0.0to expose it, always set a strong fixed token — anyone who can reach the port and guess the token gets full session/shell access.
Requires Node.js 18.7+ (Fastify 5). On Node 16 use the TUI or
--pipemode instead.
Headless pipe mode (--pipe)
Run ecode non-interactively — read a prompt from stdin and stream the response as JSON Lines to stdout. Ideal for scripts, CI, and shell pipelines.
echo "用一句话解释 async/await" | ecode --pipe
# extract plain text with jq
echo "list HTTP status codes" | ecode --pipe \
| jq -r 'select(.type=="chunk") | .text' | tr -d '\n'Each output line is a JSON object with a type field: chunk (text), tool_call, tool_result, error, done.
See docs/pipe-mode.md for the full output format reference, available tools, and usage examples (code review, auto commit messages, CI integration).
Session logging
Enable JSONL session logs to replay or analyze conversations:
# via env var
export ECODE_LOG_DIR=~/.ecode/logs
ecode
# via config file
# "logDir": "~/.ecode/logs"Each session writes a timestamped .jsonl log file and a companion -session.json metadata file (id, title, model, token count, turn count).
ecode sessions subcommands
# list all sessions, sorted by most recent activity
ecode sessions list
# show full metadata for a session (use 8-char prefix or full UUID)
ecode sessions inspect a1b2c3d4
# get a shell command to replay a session
ecode sessions replay a1b2c3d4
# → ecode --replay ~/.ecode/logs/2026-05-13T08-47-00.jsonl
# get a shell command to fork a session at a specific turn (default: last turn)
ecode sessions fork a1b2c3d4 5
# → ecode --fork ~/.ecode/logs/2026-05-13T08-47-00.jsonl:5The fork command is the basis for /goal rollback: when the evaluator detects context drift, it surfaces a sessions fork command pointing to the last known-good turn.
Requirements
- Node.js >= 18
License
MIT
