emp-code
v0.5.26
Published
EMP CLI - coding agent with full-screen TUI (ink, clickable tool output, mouse, plan mode, markdown, vision) and 18 providers. Runs great on any light PC / weak CPU / old motherboard. Sessions, context, effort, web search, background tasks, revert.
Maintainers
Readme
EMP CLI — coding agent for ANY PC
Built with Ink + React for a rich, interactive TUI. Made to run on any light PC — weak CPUs, old motherboards, low-RAM laptops — while still matching heavy agents feature-for-feature: full-screen TUI (clickable tool output, mouse, tables, markdown), vision (clipboard / image paste / website + active-tab capture), sessions, revert, web search, background tasks, and 18 LLM providers.
EMP CLI speaks the OpenAI-compatible /v1/chat/completions + /v1/models API, so every provider works the same: paste an API key, EMP CLI auto-fetches the model list from GET /models and caches it locally. Free models are sorted first and tagged (free) in the picker.
Free providers (no credit card)
All of these have a genuinely free tier and do not require a credit card. Add a key with /connect <provider> (or emp config), then pick a model.
| Provider | id | Free key | Free models |
|---|---|---|---|
| OpenCode Zen | zen | opencode.ai/auth | *-free (hy3-free, nemotron-3-ultra-free, laguna-s-2.1-free…) |
| TokenRouter | tokenrouter | tokenrouter.com | Qwen 3.8 Max free right now |
| OpenRouter | openrouter | openrouter.ai/keys | any model ending in :free (380+ models, lists w/o key) |
| Groq | groq | console.groq.com/keys | fast Llama/Qwen, free tier |
| Cerebras | cerebras | inference.cerebras.ai | fast Qwen/Llama, free tier |
| Google Gemini | gemini | aistudio.google.com/apikey | generous free tier |
| Hugging Face | huggingface | huggingface.co/settings/tokens | free inference (lists w/o key) |
| Z.AI (GLM) | zai | z.ai | GLM flash/air free |
| SiliconFlow | siliconflow | siliconflow.com | free open models |
| NVIDIA NIM | nim | build.nvidia.com | free Nemotron + open models |
| TrueSOTA | truesota | true-sota.com | AI API gateway (Bearer key) |
| B.AI | bai | b.ai | AI API gateway (Bearer key) |
Connect examples:
/connect openrouter :: then paste key, pick a :free model
/connect groq
/connect gemini
emp use openrouter/nvidia/nemotron-3-ultra-550b-a55b:free
emp --groq ask "hello" :: force a provider for one turnNote: a few free models are occasionally rate-limited (429) or down on the provider's side (500). EMP CLI auto-retries transient errors and tells you honestly which model/provider is at fault — it never silently swaps your model.
Why Ink + React (no native build)
EMP CLI uses Ink + React for its full-screen TUI — both are pure-JS packages (no compiled binaries, no native module). So it:
- works on old CPUs without AVX1 / AVX2 and without Bun
- runs on any Node.js 16+ (
--no-warningsnot required) - needs only a
npm install(pulls pure-JS packages — no native build, no AVX)
Requirements
- Node.js 16+.
npm installonce (pure JS, no native build — still no AVX/Bun). - The
install.ps1/install.cmdrunsnpm installfor you.
Install
npm (recommended — Windows, macOS, Linux):
npm install -g emp-codeWindows one-liner (PowerShell):
irm https://raw.githubusercontent.com/saintdevzz/emp-cli/main/install.ps1 | iexmacOS / Linux one-liner:
curl -fsSL https://raw.githubusercontent.com/saintdevzz/emp-cli/main/install.sh | shNo API key needed to start — just run emp and ask; add keys with /connect for other providers.
Quick start
emp setup :: wizard asks for NIM + Zen keys, fetches models
emp ask "create a hello.py and run it" :: agentic one-shot (uses tools)
emp --effort high ask "refactor this" :: high effort
emp :: agentic REPL (saves session, auto-names)
emp --zen ask "refactor this file" :: force Zen provider for this turn
emp --nim --no-tools ask "plain chat, no tools" :: plain chat
emp sessions :: list saved sessions
emp effort max :: set effort globallyThat's a coding CLI — not just chat
Like opencode, EMP CLI gives the model tools and lets it act as a coding agent. Every ask / chat turn is an agentic loop (up to 10 rounds):
- Model streams text or emits
tool_calls(native OpenAItoolsor fallback<emp_tool>JSON) - CLI executes the tool locally and shows
[tool]/[tool_result] - Tool output is fed back as a
toolmessage; the model continues - Destructive actions and
delete_file/git_commitask for interactive confirmation (y/N) unlessEMP_AUTO_APPROVE=1 ask_questionlets the model ask you questions mid-loop (single or multi-choice)
Tools (opencode-style toolkit + subagents + browser)
All tools use the same schemas the model sees (tools.getSchemas()) and run via lib/agent.js with permission checks. Inside the workspace everything is unrestricted; paths outside the workspace ask for permission (y/N) — set EMP_UNRESTRICTED=1 for the old unrestricted behavior.
| Tool | What it does | Outside dir? | Approval |
|---|---|---|---|
| list_files | List dir (default workspace, ignore node_modules/.git etc) | ask | — |
| search_files | Search by filename or file content (walk) | ask | — |
| grep | Regex grep — pattern (JS RegExp), include glob, case_insensitive | ask | — |
| glob | Find files by glob **/*.js | ask | — |
| read_file | Read file with offset/limit pagination | ask | — |
| write_file | Create/replace file, mkdir -p | ask | if >16 KB overwrite |
| edit_file | old_string→new_string patch | ask | — |
| delete_file | Delete file | ask | always (y/N) |
| run_command | spawn shell, captures stdout/stderr/exitCode, timeout | ask (cwd) | if destructive regex |
| run_background | Long command in background, taskId, auto-notified | ask (cwd) | if destructive regex |
| bg_status | List background tasks, or one's status + output | — | — |
| git_status git_diff git_log git_commit | git operations | ask (cwd) | commit always |
| ask_question | Model asks you questions (choices or open) | — | — |
| todo_write | todos array (pending/in_progress/completed/cancelled) | — | — |
| task_complete | Signals task fully done (hidden from the UI; the summary IS the final answer) | — | — |
| wait | Sleep N seconds | — | — |
| agent | Launch a background subagent with its own tools + prompt. Shows Agent(task) in the UI; parallel agents run concurrently | — | — |
| agent_wait | Sleep until all (or given) subagents finish — results returned | — | — |
| agent_status | List subagents / peek at one's output | — | — |
| browser_open | Built-in zero-dep browser: open URL, get text/links/inputs/buttons | — | — |
| browser_click | Click link/button by index (navigates) | — | — |
| browser_type + browser_submit | Fill form fields and submit (GET/POST) | — | — |
| browser_scroll browser_back | Scroll page text / go back in history | — | — |
| web_search | DuckDuckGo search — no API key | — | — |
| web_fetch | Fetch URL, redirects, HTML → text | — | — |
| capture_website | Screenshot any URL (vision) | — | — |
| capture_active_tab | Screenshot the user's open browser tab | — | — |
| chrome_open | Open URL in real Chrome/Edge | — | — |
Plan mode (/plan or Tab): the system prompt switches to plan-first AND mutating tools (write_file, edit_file, delete_file, run_command, run_background, git_commit) are hard-blocked until the user approves and toggles plan off. Reads stay available so the AI can inspect before planning.
Destructive command regex covers rm -rf, git reset --hard, curl | sh, reg delete, etc.
Sessions, Context & Effort
- Sessions
lib/session.js:1— everyemp chat/asksaves to~/.emp/sessions/<id>.jsonwithname,provider,model,effort,messages. Auto-names from first user message (Hello world...→Hello world this is a test...) after 2 messages. List withemp sessionsor/sessionsin chat, new with/new, clear with/clear. - Context window
lib/context.js:1— estimates tokens aschars/4, limit128k(200k for GPT-5/Claude/Gemini/Codex). Counts every message including assistanttool_callsandtoolresults, so the number reflects real usage. Shows right-aligned above eachyou>with a bar:ctx [####......] 42% 53.2k/128k. Auto-compacts at 80% by keeping first 2 + last 6 messages and inserting a[context compacted ...]summary (system prompt is regenerated, never stored). - Effort
lib/effort.js:1—none/low/medium/high/max(defaultmedium). Controls system addendum + temperature. Set globallyemp effort highoremp --effort high ask "..."or in-chat/effort high. Stored in~/.emp/config.jsonsettings.effortand per-session.
Interrupt the model (ESC ESC / Ctrl+C)
While the model is streaming, press ESC twice (within ~0.6s) or Ctrl+C to abort the in-flight request immediately. The socket is destroyed, [interrupted] is printed, any partial text is kept in the session, and you're back at the you> prompt. Wired via an abort controller (lib/http.js:makeCtl) threaded through providers.chat → streamRequest.
Reliability — auto-retry, honest errors, no forced fallback
- Auto-retry: transient failures (429 rate limit, 500/502/503, timeouts, connection resets) are retried up to 3 times with exponential backoff. You'll see
[retry 1/3] <reason> — waiting 1.6s. If a flaky provider recovers mid-retry, the turn just succeeds. - No forced fallback: EMP never silently switches your model. If
muse-spark-1.2-contributor-free(or any model) keeps failing, you get an honest message naming the model and cause — e.g.... returned 500 Internal server error (auto-retried). Their backend is failing this model right now — not an emp-cli bug.— and your chosen model stays chosen. - Partial-stream safety: if text already reached you, EMP does not retry (that would duplicate output); it only retries failures that produced no content yet.
- Free-tier models on Zen (
big-pickle,mimo-v2.5-free, etc.) are subject to OpenCode's own rate limits; a 429 means wait a bit or pick another model with/model <name>.
Web search & fetch (no key)
web_search hits DuckDuckGo's HTML endpoint — free, no API key, no credit card — and returns clean titles/URLs/snippets (ads filtered out). web_fetch follows redirects and converts HTML to readable text. The agent is told to search before guessing URLs.
Background tasks
run_background starts a long command (build/test/install/download) and returns a taskId instantly, so the agent keeps using other tools while it runs. You can run several at once. When one finishes it is injected back into the conversation automatically and the agent continues with the result — no polling needed. bg_status checks tasks on demand, and /bg lists them. If the agent is about to finish a turn while tasks are still running, it waits for them and reports their output.
Plan-first mode
/plan toggles plan mode: the agent produces a step-by-step implementation plan and waits for your approval before touching files. /plan on / /plan off set it explicitly, and /plan <task> plans that specific task right away. Plan mode is stored per-session and shown in the header as [plan mode].
Full-screen TUI with clickable tool output
emp chat opens a full-screen TUI (like opencode / Claude Code) built on Ink + React — no Bun, no AVX, so it runs on old/slow machines.
- Click any tool chip (bash / edit / write / thinking) to expand or collapse its output — bash stdout, a
+/−diff for edits/writes, or the model's reasoning. Clicking is handled by the TUI's own mouse parser, so no escape-sequence garbage ever leaks into the screen. - Scroll with the mouse wheel or
PgUp/PgDn. Up/Down recall your previous prompts (shell-style history); long input scrolls horizontally so the cursor stays visible. - Queue messages while the AI is busy — type and hit Enter and it's queued; a
queued:line shows the latest plus(+N other messages), and they're sent in order when the reply finishes. - Select / copy text — hold Shift and drag (this bypasses mouse capture in Windows Terminal and most terminals). Or set
EMP_NO_MOUSE=1to turn off mouse capture entirely (you keep native selection but lose click-to-expand and wheel-scroll). - Tab toggles plan / build mode (indicator in the header).
- Enter sends, Backspace edits, Left/Right move the cursor, Ctrl+L redraws.
- ESC (or Ctrl+C) interrupts the agent while it works; Ctrl+C on an empty input quits, Ctrl+C twice force-quits, Ctrl+D quits.
- A status bar shows provider/model, plan mode, context %, todo progress, and running background tasks.
/showstill prints the last turn's hidden output;/todoshows the agent's todo list.
Set EMP_NO_TUI=1 to force the classic inline UI (useful over SSH or in basic terminals). In the classic UI, mouse expansion is opt-in via EMP_MOUSE=1 and /show is the universal fallback. Everything degrades gracefully on terminals without mouse support.
Commands
emp Agentic chat (tools on, streaming, saves session)
emp ask "your prompt" One-shot (also saved as session)
emp chat --effort high High effort (none|low|medium|high|max)
emp models [nim|zen] List models (auto-fetches + caches)
emp refresh [nim|zen] Re-fetch models
emp config Manage both providers' keys + default models
emp setup First-run wizard (both providers)
emp use <nim|zen>[/model] Set active provider/model
emp workspace [path] Show or set workspace dir
emp sessions List saved sessions (~/.emp/sessions)
emp effort [level] Show/set effort (none|low|medium|high|max)
emp info Show config + tools + workspace + context
emp doctor Node/arch + tool count + probe endpoints
emp helpFlags: --nim / --zen force provider; --effort <level>; --no-tools disables tools.
In-chat REPL:
/exit, /clear, /new, /sessions, /models, /model <name>, /effort [level], /compact
/info, /tools, /workspace [path], /help, /connect [nvidia|opencode]
Context bar above input (e.g. "ctx [####......] 42% 53.2k/128k"), auto-compact at 80%
While the AI is generating: press ESC twice (or Ctrl+C) to interrupt itAPI keys & auto-fetch
Stored in plain text at ~/.emp/config.json (lib/config.js:15). Prompts are not masked (portable plain Node). Or use env vars (take precedence):
NVIDIA_API_KEY=... NIM (nim)
OPENCODE_ZEN_API_KEY=... OpenCode Zen (zen)
TOKENROUTER_API_KEY=... TokenRouter
OPENROUTER_API_KEY=... OpenRouter
GROQ_API_KEY=... Groq
CEREBRAS_API_KEY=... Cerebras
GEMINI_API_KEY=... Google Gemini
HF_API_TOKEN=... Hugging Face
ZAI_API_KEY=... Z.AI (GLM)
SILICONFLOW_API_KEY=... SiliconFlowOn setup/config/refresh (or first ask/chat when a key is present), EMP CLI does GET <provider>/models with Authorization: Bearer <key> (OpenRouter + Hugging Face also list with no key), sorts IDs with free models first, and saves providers[<id>].models + modelsFetchedAt to ~/.emp/config.json. Use emp refresh <provider> to force update. All providers go through the same lib/providers.js OpenAI-compatible client.
Notes
- Default provider is
nimuntilemp use zen. - Streaming + tool calls handled in
lib/providers.js:132(accumulatesdelta.tool_callsbyindexand falls back to<emp_tool>parsing viaextractToolRequest()lib/providers.js:41). - Agent loop is in
bin/emp.js:141(runAgent) — up to 10 rounds, feedstoolmessages back to the model. - Auto-approve for CI:
EMP_AUTO_APPROVE=1 emp ask "..." - No lock-in: you can still use any NIM/Zen model as plain chat with
--no-tools.
