Skip to content

Repository files navigation

lecode

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.

Why lecode

  • Tiny harness, big ecosystem. The default system prompt is under 300 tokens; project context comes from your AGENTS.md files, 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-url flag 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 (yolo by default, readonly via --safe when 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 · cost summary 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.

Requirements

  • Python 3.12+

  • Three external binaries on PATH (verified at startup; no auto-download, no fallback):

    binary powers install
    fd find_files brew install fd · apt install fd-find · cargo install fd-find
    rg grep brew install ripgrep · apt install ripgrep · cargo install ripgrep
    rtk bash output compaction see https://github.com/rtk-ai/rtk

Targets macOS and Linux; Windows is best-effort.

Install

uv tool install --force git+https://github.com/KalvadTech/lecode

lecode is distributed from this repository only — it is not on PyPI.

Quickstart

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 something
dir: 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

Headless

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 json

The 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.

A tour of the power features

  • Permissions — two modes (yolo allows everything, readonly allows read-class tools only; default yolo), 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; @agent or the task tool runs subagents.
  • Skills — SKILL.md packs 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. /memory to inspect; the agent has memory_* tools.
  • Hooks — shell commands on 15 lifecycle events (PreToolUse, PostToolUse, UserPromptSubmit, Stop, …) that return verdicts; they can only narrow permissions. --hooks-test dry-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_user tool lets the agent ask 1-4 multiple-choice questions mid-turn instead of guessing; you pick with the arrow-key picker (↑/↓ to move, enter to select/confirm, space to toggle a multi-select, esc to dismiss). The picker's last row, "Type your own answer…", switches to a free-text answer that you type and submit with enter. 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 auth to authorize, /mcp login|logout to manage).
  • LSP — diagnostics from real language servers appended to write/edit results; fail-open, never blocks.
  • Worktrees — --worktree <name> or /worktree for isolated branches, /wt-merge / /wt-exit with conflict detection.
  • Multimodal — /add image.png or @file.pdf; capability-checked against the model.
  • Background tasks — bash and task accept run_in_background and return a task id immediately; the agent inspects them with the tasks_list / tasks_output / tasks_wait / tasks_stop tools, 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, /prompt switches style, /editsys opens the system prompt in $EDITOR.
  • Reasoning levels — /thinking sets 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 — /doctor health-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 the telemetry extra, fail-open by design.

Configuration

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

Development

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 push

Releasing

Releases 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.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages