A zero-dependency CLI and coding agent for Node.js (>= 20): an LLM driven always-self-aware agent with hands on your terminal. Ported from the architecture of the EVA project.
- Shell access — PowerShell on Windows, bash/sh on Linux/macOS, with timeouts and output capping. Every shell command is treated as arbitrary code execution and asks for approval by default.
- File work — the agent can inspect, create and edit files in your workspace. Proposed edits are shown before approval, and line endings are preserved so CRLF files do not turn into mixed-line-ending files.
- Code navigation — the agent can list project files and search source text
across the workspace, while skipping noisy directories such as
node_modulesand.git. - Project context — a user-authored
AGENTS.md(orCLAUDE.md/.cli-agent.md) in the workspace is loaded into the system prompt at startup, so the agent knows the project's build/test commands, conventions and constraints. The file stays human-owned; the agent never rewrites it. - Web access — the agent can fetch web pages as readable text and search the web via DuckDuckGo; no separate search API key is needed.
- Providers — Anthropic (Messages API) and OpenAI-compatible providers (OpenAI, Azure, Ollama, OpenRouter, …) with streaming, transient-error retries, Anthropic prompt caching and optional reasoning/thinking modes.
- Sessions — chats are stored under
~/.cli-agent/sessions/and can be resumed by id or prefix. The agent can recall details from the current session even after they have left terminal scrollback, without opening arbitrary files outside the workspace. - History compaction — old turns condense into a digest + memory block; the recent window and tail stay verbatim; cut points are quantized so prompt caching keeps working.
- TUI — streamed markdown-lite output, thinking indicator, spinner,
tool-call/observation formatting, approval prompts, input history,
multi-line input via trailing
\. - Approval system — in-workspace read tools run automatically, but file
writes and every shell command are gated by policy (
AUTO_YES,ALLOW_SHELL); declining with freeform text hands the model your feedback as the new instruction. Shell child processes are started with a minimal, sanitized environment so provider/API-key variables from the agent process are not inherited by default. - Workspace confinement — workspace reads are frictionless. Reads outside
the workspace require approval unless
ALLOW_OUTSIDE_WORKSPACE=1. The agent's own configuration, session logs and state remain protected even when outside-workspace reads are enabled. Private or loopback network requests require separate approval unlessALLOW_PRIVATE_NETWORK=1. - Self-model —
/selfexplains what the running agent is: available capabilities, security policy, model/provider details and runtime state. The report is derived from the live code and tests rather than from a hand-written static description. - Self-improvement — the agent keeps a small friction/requirements backlog
for things that got in the way or were requested during use.
/improvereviews that backlog and proposes changes; implementing one still follows the normal approval and test workflow.
Prerequisite: Node.js >= 20 must be installed.
Run the guided setup once:
node src/index.js setup
or, after package installation/linking:
cli-agent setup
The wizard writes the agent's trusted configuration to your user config directory
outside any repository (%APPDATA%\cli-agent\config.env on Windows,
~/Library/Application Support/cli-agent/config.env on macOS,
~/.config/cli-agent/config.env on Linux). The generated config.env is based
on .env.example, with your answers filled in. Repository .env files are
treated as application/repository config and are not loaded as cli-agent
configuration.
The wizard can also install a small cli-agent launcher into a user bin
directory. If that directory is not already on PATH, it asks before updating
your user PATH / shell profile automatically; if you decline, it prints the
manual PATH instruction instead.
Then start a session:
npm start
# or: cli-agent
cli-agent new session
cli-agent resume [id] resume latest (or given) session; id prefixes work
cli-agent --list recent sessions
Inside the chat: /help, /status, /self [section], /improve, /sessions, /resume [id], /new,
/image <path>, /paste-image, /pi [text], /compaction [preview|new], /delete <id>, /diff [n|path|last], /undo [n|path|last], /last [n], /full, /model [id], exit.
Command reference:
/status— model, session, history and cumulative token usage (↑input ↓output, prompt-cache share as⚡cached)./self [section]— the agent's live self-model:overview,anatomy,architecture,tools,capabilities,policy,friction,model,vitals./improve— review the friction/requirements backlog and propose improvements (read-only). To actually implement one, just say what you want; in the agent's own repo that is ordinary self-development./self frictionlists open items./compaction [preview|new]— raw vs. compacted history;previewprints the memory block;newstarts a fresh session seeded with the compacted history (the old append-only log is left intact)./diff [n|path|last]— files changed this session, independent of git;<index>/<path>shows one file's net change,lastonly the latest. Each diff has an action header (Create/Update/Delete(path)), a summary (Added N lines, removed M lines) and a left line-number gutter; long unchanged runs fold as... N unchanged lines omitted .... Baselines persist in the session folder and survive resume/restart./undo [n|path|last]— revert a file to its session baseline (restore the original, or remove a file the session created);lastundoes just the most recent change; no argument lists the revertable files./sessions,/resume [id],/new,/delete <id>— list sessions (with token totals), switch, start fresh, or remove a non-current session (by id, prefix or list index)./image <path>,/paste-image,/pi [text]— attach an image to your next message, import one from the clipboard, or send a clipboard image immediately with an optional question. Clipboard import uses built-in Windows support,xclip/wl-pasteon Linux, andpngpasteon macOS. Image bytes are stored as per-session blobs and sent only on the first model call for that message; later tool-loop calls use a compact reference to keep context small./last [n],/full— re-show recent tool output;/model [id]— show or switch the model;exit/quit— leave (the session stays resumable).
In approval mode, file-write approvals show the proposed diff before you answer; when a write is auto-approved (e.g. AUTO_YES), the applied diff is shown inline right after the tool runs, so changes are always visible. Token usage is reported by all three adapters (Anthropic, OpenAI Chat, OpenAI Responses) and persisted with each assistant event.
At an approval prompt (allow? [y/N/e/d/f]): y approves, e opens the proposed
file content in your editor when the pending write is editable, d opens the
proposed change as a side-by-side diff in your editor when REVEAL_EDITS=1 (the
terminal keeps focus unless you press d), f shows the
full command, empty/n declines, /last [n] shows recent tool output and asks
again, and any other text declines WITH that text as feedback for the agent. Set
COPILOT_EDITOR, EDITOR or VISUAL to choose the editor. The agent waits
for the editor process to close, then shows the updated diff and asks again;
for VS Code use code --wait so closing the edited tab resumes the approval.
Ctrl+C while a turn is running aborts the in-flight model request immediately
(a running tool is stopped at the next action boundary; press twice to force
quit); at the input prompt it exits the program. An unexpected crash during a
turn is caught, reported, and (opt-in) recorded as friction, so the session
survives it.
Large terminal pastes use bracketed paste when supported by the terminal: the CLI echoes a compact [pasted <n> lines, <n> chars] marker but sends the full pasted text to the model. Short pastes stay visible in the input so you can edit them normally.
| Variable | Default | Meaning |
|---|---|---|
PROVIDER |
anthropic |
anthropic | openai | openai_responses | fake |
MODEL |
— | model id |
API_KEY |
— | also accepts ANTHROPIC_API_KEY / OPENAI_API_KEY |
ENDPOINT |
provider default | custom API endpoint |
MAX_TOKENS |
4096 |
response cap |
PROMPT_CACHE |
1 |
Anthropic prompt caching |
THINKING |
off | Anthropic: enables extended thinking (1/adaptive or token budget). OpenAI (Responses API only): low/medium/high sets reasoning effort (other truthy = medium), truthy also requests visible summaries; unset = no reasoning on gpt-5.1+; ignored by Chat Completions |
STREAM |
1 |
stream responses |
LLM_TIMEOUT |
180 |
request timeout (s) |
LLM_RETRIES |
2 |
retries on transient model errors (never after streaming started) |
MAX_STEPS |
50 |
max tool-loop steps per turn before pausing |
TEMPERATURE |
provider default | sampling temperature (opt-in; some models reject a non-default value) |
HISTORY_BUDGET |
300000 |
chars before compaction kicks in |
HISTORY_KEEP |
50 |
events kept verbatim at the tail |
STATE_DIR |
~/.cli-agent |
where sessions, prompt history and generated state are stored |
AUTO_YES |
0 |
approve everything (no prompts) |
ALLOW_SHELL |
0 |
auto-approve shell commands. By default every shell command asks, even apparently read-only ones, because shell text is not a reliable safety boundary. Shell child processes receive a sanitized environment (no inherited provider/API-key variables by default), but shell access is still arbitrary code execution. Note: enabling this effectively bypasses file-write approval too — an auto-approved shell command can write/delete files (rm, Remove-Item, > file). Treat it as full trust. |
ALLOW_OUTSIDE_WORKSPACE |
0 |
auto-approve file reads outside the workspace (otherwise gated); does not affect network access and does not unlock protected cli-agent config/state paths |
ALLOW_PRIVATE_NETWORK |
0 |
auto-approve web requests to private, loopback or link-local addresses such as localhost or 169.254.x.x (otherwise gated) |
COPILOT_EDITOR |
notepad on Windows, vi elsewhere |
editor for editable file-write approvals (e); EDITOR and VISUAL are also accepted |
REVEAL_EDITS |
0 |
at a write approval, d opens the proposed change as a side-by-side diff in your editor (needs AUTO_YES=0) |
REVEAL_CMD |
code |
editor CLI for the reveal; must support --diff a b (e.g. code, cursor) |
FRICTION_NOTES |
0 |
offer, at the turn boundary, to draft a richer note for friction that fired (raw signals are always logged to the backlog regardless) |
PROMPT_SPOTLIGHT |
1 |
mark tool output as untrusted data in the model context (hidden from the terminal); set 0 to disable |
DEBUG_SSE |
off | path to a file; appends every SSE event type + payload gist (Responses API) — for diagnosing which reasoning/summary events a model emits. Note: reasoning summaries are sparse — short thinking bursts often produce none (model behavior, not a bug) |
src/
core.js provider-neutral agent loop
config.js trusted user configuration
adapters/ provider integrations and streaming/retry handling
tools/ shell, file, search, session and web capabilities
session.js resumable session storage
session_awareness.js session-memory prompt text
context.js history compaction
approval.js approval policy and editor-assisted approvals
diff.js session-scoped diffs and undo support
vision.js image attachments and clipboard image import
reveal.js optional editor diff reveal for proposed writes
project.js AGENTS.md / CLAUDE.md project context loader
self.js live self-model
friction.js friction/requirements backlog
tui.js terminal UI
index.js entry point / chat loop
test/ node --test suite
npm test