A minimalist terminal AI coding agent. No IDE, no electron, no noise — just your terminal, an OpenAI-compatible model, and a sharp set of tools.
██╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗
██║ ██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔════╝
██║ █████╗ ██║ ██║ ██║██║ ██║█████╗
██║ ██╔══╝ ██║ ██║ ██║██║ ██║██╔══╝
███████╗███████╗╚██████╗╚██████╔╝██████╔╝███████╗
╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
v0.2.0 · by Kalvad — now with 100% more purple
╭────────────────────────── lecode — fix-auth ───────────────────────────╮
│ ✓ session fix-auth — new session │
│ ✓ config ~/.config/lecode/config.toml + .lecode/config.toml │
│ ✓ provider openrouter · https://openrouter.ai/api/v1 │
│ model deepseek/deepseek-v4-flash · key from env │
│ ✓ models 327 fetched live from the provider │
│ ✓ prompt minimal │
│ ✓ context AGENTS.md · docs/AGENTS.md │
│ – skills none │
│ ✓ agents primaries: build, plan · custom: researcher │
│ ✓ memory long-term 2.4 KB injected │
│ ✓ tools 19 tools │
│ ✓ permissions mode yolo · custom rules │
│ – hooks none configured │
│ – pierre off │
│ ✓ lsp enabled │
│ – mcp no servers │
╰─────────────────── ~/github.com/you/your-project ─────────────────────╯
Every interactive start shows you exactly what was loaded — config files, provider, prompt, project context, skills, agents, memory, permissions, hooks, LSP, MCP — before the chat opens.
- Tiny harness, big ecosystem. The default system prompt is under 300
tokens; project context comes from your
AGENTS.mdfiles, skills, and memory — not from a bloated prompt. - OpenRouter, or anything OpenRouter-compatible. OpenRouter is the
first-class preset (live catalog, pricing, caching); other gateways —
Ollama, LM Studio, llama.cpp, corporate proxies — plug in with one
--base-urlflag or a[custom_providers]entry. Keyless local endpoints supported. The model catalog is fetched live from the provider at startup (when the fetch fails the catalog is simply empty — models lose their pricing/modality annotations; nothing cached on disk). - Real permissions, not vibes. Two modes (
yoloby default,readonlyvia--safewhen you want a look-but-don't-touch agent), glob + regex rules, last-match-wins, unbypassable denies, doom-loop detection, and lifecycle hooks that can narrow — never widen — any decision. - Money is a metric. Live token + cost totals in the statusline, and a
tokens in/out · costsummary every time a chat ends. - Inspectable by design. Sessions are append-only JSONL you can grep; sessions are always named; every session is resumable, undoable, rewindable, exportable.
-
Python 3.12+
-
Three external binaries on
PATH(verified at startup; no auto-download, no fallback):binary powers install fdfind_filesbrew install fd·apt install fd-find·cargo install fd-findrggrepbrew install ripgrep·apt install ripgrep·cargo install ripgreprtkbash output compaction see https://github.com/rtk-ai/rtk
Targets macOS and Linux; Windows is best-effort.
uv tool install --force git+https://github.com/KalvadTech/lecodelecode is distributed from this repository only — it is not on PyPI.
lecode --setup # import from pi/opencode or answer 4 questions
export OPENROUTER_API_KEY=sk-or-... # or keep the key in config.toml
cd your-project
lecode # name the session, then ask for somethingdir: lecode · commit: 974015a · branch: main · diff: ±1 +4 -4
model: deepseek/deepseek-v4-flash · cost: $0.0062 · ctx: ▓▓░░░ 36.0k/200.0k 18%
session: fix-auth · agent: build · in: 4.1k · out: 0.9k · ⠼
One fixed statusline, every element labelled: directory · commit · branch ·
diff / model · cost · context meter / session · agent · tokens · state
(a reasoning-level override shows on the model line when /thinking is set).
No configuration needed.
Useful things to type:
/help all slash commands, grouped
/welcome key bindings cheat-sheet
/tutor permissions explain one feature
/tasks background tasks, live
/doctor health-check the install
!make test run a shell command, see the output
!!pytest -x run it AND feed the output to the model
@src/auth.py attach a file (images/PDF/audio too)
.plan refactor this run with a persona
Tab cycle agents: build ⇄ plan
Alt-Enter steer the agent mid-turn
lecode -p "write a haiku about this repo" # one prompt, then exit
lecode -p "review this repo" --max-cost 1 --timeout 120
git diff | lecode -p "review this diff" # a bare -p reads stdin
lecode --loop plan.md --loop-cmd "make test" # iterate until the plan is done
lecode --chain "redesign the parser" # brainstorm→plan→code→review
lecode -p "review this repo" --output-format jsonThe final answer goes to stdout; a tokens: <in> in / <out> out · cost: $X.XXXX summary goes to stderr, so scripts can pipe the answer cleanly.
Incomplete accounting uses cost: known $X.XXXX; this is only the known
portion of spending, not a complete total.
Headless, loop, and chain modes use these exit codes:
| Code | Meaning |
|---|---|
0 |
Done |
1 |
Generic error, including other HTTP failures |
2 |
Startup error: missing dependencies or required credentials, bad flags, non-tty --setup |
3 |
Max turns or max loop iterations |
4 |
Provider authentication failure |
5 |
Provider budget or credit exhaustion |
6 |
Model/resource not found, including HTTP 404 (which can also mean an incorrect endpoint) |
7 |
Provider rate limit, after retries are exhausted |
8 |
Upstream, timeout, conflict, or transport failure |
9 |
Unknown or malformed in-stream error |
10 |
Context overflow |
11 |
Cost limit or unknown spend (single prompt only) |
12 |
Execution timeout (single prompt only) |
HTTP and in-stream errors share classification. Recognized
error.metadata.error_type values take precedence over symbolic error.code
and error.type, followed by HTTP status (or a numeric in-stream code).
In-stream codes can be integers or three-digit ASCII strings. Recognized
symbolic codes are authentication, invalid_api_key, payment_required,
insufficient_quota, credit_balance_exhausted, model_not_found, not_found,
rate_limit_exceeded, provider_overloaded, provider_unavailable, server,
server_error, and timeout. Classification never guesses from message text.
Budget failures are not retried, including quota errors reported as HTTP 429. Other retries use the numeric HTTP/in-stream status when supplied, retaining the existing set: 408, 409, 429, 500, 502, 503, 504 and transport failures. Symbolic-only in-stream errors use their corresponding status. Unknown or malformed in-stream errors are not retried. Interactive mode continues to display provider failures and stay open.
For single-prompt headless runs, --max-cost accepts a positive USD threshold.
All model calls share it, including workers, subagents, compaction, learning,
reviews, and retries. Once accounted spending reaches it, no further model
requests start. Requests already in flight can exceed it. Unknown or invalid
model prices reject the request before execution, and missing or invalid usage
stops further requests. Explicit zero prices are valid for free models.
--timeout accepts positive seconds and covers active execution from MCP
connection through model calls, tools, and review. Synchronous startup and
resource cleanup are outside this deadline, so process exit can occur later.
Neither flag applies to interactive, loop, or chain mode.
--output-format json works with --prompt, --loop, and --chain.
It prints one JSON object on stdout with final_text, stop_reason, turns,
input_tokens, output_tokens, cost_usd, model, and usage_incomplete.
Progress and diagnostics go to stderr. Text output remains the default.
For loops and chains, final_text is the last iteration or phase's answer,
and turns counts completed main model calls across the run. Token and cost
totals include recorded worker and subagent usage, compaction, memory learning,
and review. model identifies the main configured model. Cost is in USD,
using provider-reported cost when available and catalog pricing otherwise.
Failures also emit JSON. Known partial usage is retained as a lower bound
with usage_incomplete: true; unavailable metrics are null. Stop reasons
include startup_error, error, interrupted, blocked, and the runner or
loop's existing reasons (done, empty, max_turns, context_overflow,
max_iterations, cost_limit, timeout). Help, version, and argument-parsing errors use normal CLI
output; JSON is unavailable for interactive, setup, and hooks-test modes.
- Permissions — two modes (
yoloallows everything,readonlyallows read-class tools only; defaultyolo), per-tool glob/regex rules, session allow-always grants, per-agent overlays./permissions,/mode,/toggle. - Sessions — always named, append-only JSONL, scoped to the folder they
were created in (resume never crosses directories),
/new/resume/undo/redo/rewind/retry/compact/handoff/rename, searchable picker with delete, HTML export (/export), secret-gist sharing (/share), re-import (/import). Approaching the context window, the runner compacts automatically (summary + recent tail;[compaction]tunes the trigger, buffer and overflow policy). - Custom agents — markdown files in
~/.config/lecode/agents/or.lecode/agents/with their own model, temperature, prompt and permission overlay. Tab cycles primaries;@agentor thetasktool runs subagents. - Skills —
SKILL.mdpacks in.agents/skills/(project) or~/.agents/skills/(global), discoverable by the model, optionally registered as slash commands. - Memory — persistent per-project markdown: long-term
MEMORY.md(auto-injected, 32 KB cap), daily logs, scratchpad, named notes./memoryto inspect; the agent hasmemory_*tools. - Hooks — shell commands on 15 lifecycle events (
PreToolUse,PostToolUse,UserPromptSubmit,Stop, …) that return verdicts; they can only narrow permissions.--hooks-testdry-runs the pipeline. - Pierre mode — when enabled, a second model reviews every finished
task: it compares your request with the agent's result and tells you
plainly whether it delivered.
/pierre on|off|model. - Structured questions — the
ask_usertool lets the agent ask 1-4 multiple-choice questions mid-turn instead of guessing; you pick with the arrow-key picker (↑/↓to move,enterto select/confirm,spaceto toggle a multi-select,escto dismiss). The picker's last row, "Type your own answer…", switches to a free-text answer that you type and submit withenter. Headless, loop, chain, and subagent runs get a "use your best judgment" result instead of a prompt. - Notifications — sound (afplay/paplay/aplay, terminal bell fallback) and
desktop notifications (osascript / notify-send) on turn finish, error, and
approval-needed.
/notifications on|off; channels and per-event toggles in[notifications]. - MCP — stdio, streamable-HTTP, and SSE servers, with optional OAuth 2.1
(
auth = "oauth", browser flow, tokens under<config_dir>/mcp-auth/;/mcp authto authorize,/mcp login|logoutto manage). - LSP — diagnostics from real language servers appended to
write/editresults; fail-open, never blocks. - Worktrees —
--worktree <name>or/worktreefor isolated branches,/wt-merge//wt-exitwith conflict detection. - Multimodal —
/add image.pngor@file.pdf; capability-checked against the model. - Background tasks —
bashandtaskacceptrun_in_backgroundand return a task id immediately; the agent inspects them with thetasks_list/tasks_output/tasks_wait/tasks_stoptools, you watch them with/tasks, and completions land in the feed and in the next turn. Remaining tasks are stopped (SIGTERM, then SIGKILL) when the session exits. - Prompts and personas — the default system prompt is minimal (<300
tokens);
style = "rich"opts into a detailed prompt. 16 named personas (.review …,.plan …) overlay one turn,/promptswitches style,/editsysopens the system prompt in$EDITOR. - Reasoning levels —
/thinkingsets the reasoning effort; collapsible thinking blocks in the feed; the statusline shows the active override. - Pickers and polish —
/opens a slash-command dropdown,@and.open fuzzy file/agent/persona pickers — all in themed panels; Tab path completion; queued prompts while the agent runs, Alt-Enter to steer mid-turn; OSC 8 hyperlinks;/copy(OSC 52 / pbcopy / xclip). - Status signals — start/stop/git-conflict events over a Unix socket, for external status bars and scripts.
- Herdr — inside a Herdr pane, lecode reports its idle, working, and blocked state automatically; elsewhere it is a no-op.
- Doctor —
/doctorhealth-checks the install: external binaries, config, provider connectivity, MCP servers, memory, hooks, telemetry. - Telemetry (opt-in) — Sentry/GlitchTip error reports and OpenTelemetry
metrics (turns, tokens, cost, tool calls) via
[telemetry]; needs thetelemetryextra, fail-open by design.
Global config: ~/.config/lecode/config.toml (override the directory with
LECODE_CONFIG_DIR); a project-local .lecode/config.toml deep-merges over
it. TOML is the only accepted format. The setup wizard writes the
file with 0600 permissions because it can hold an API key — prefer the
OPENROUTER_API_KEY / OPENAI_API_KEY env vars to keep secrets out of it.
Full reference: docs/configuration.md.
- docs/configuration.md — every config section and default
- docs/hooks.md — hook events, envelope, verdict protocol
- docs/memory.md — the memory store
- docs/agents-and-skills.md — custom agents and skills
- docs/mcp.md — MCP servers
- docs/build-plan.md — the full product definition
uv sync
uv run python -m pytest # 1200+ tests
uv run ruff check && uv run ruff format --check
prek install # git hooks: ruff on commit, pytest on pushReleases are automated with release-please
and follow semver, driven by conventional commits: fix: → patch, feat: →
minor, feat!: (or any !) → major. Every push to main updates a release
PR that bumps pyproject.toml and CHANGELOG.md; merging that PR creates the
vX.Y.Z tag and the GitHub Release, with the built dists (wheel + sdist)
attached to the release. Nothing is published to PyPI.
MIT