Configuration¶
letscode resolves settings from four sources in this precedence order:
Config files¶
~/.letscode/config.toml
.letscode/config.toml in your project root (or any ancestor of cwd).
Project values override user values key-by-key. Use this to pin a specific model or disable footers per-project without touching your global config.
API keys are not loaded from TOML
OPENAI_API_KEY and other secrets are read from the environment only, never from a config file. Don't put keys in config.toml.
Environment variables¶
| Variable | Purpose |
|---|---|
OPENAI_API_KEY |
Required. The provider's API key. |
OPENAI_BASE_URL |
The provider's endpoint. Defaults to OpenAI. |
LETSCODE_MODEL |
Equivalent to --model. |
LETSCODE_THINKING |
Equivalent to --thinking. Since v0.7. |
LETSCODE_SESSIONS_DIR |
Override ~/.letscode/sessions/ (testing seam). |
LETSCODE_NO_UPDATE_CHECK |
Disable the daily PyPI update check. Since v0.7. |
CI |
If set, also disables the update check. Since v0.7. |
LETSCODE_E2E |
Enable the e2e test suite (developer-only). |
HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY |
Standard httpx-honoured proxy env. The generic socks://… scheme is rewritten to socks5://… on startup so it just works. Since v0.7. |
Subcommands¶
| Subcommand | Effect |
|---|---|
letscode install <spec> |
Install a plugin into this letscode env. Accepts a PyPI name, local path, or git+https://…#subdirectory=… URL. Since v0.7. |
CLI flags¶
| Flag | Effect |
|---|---|
-m, --model NAME |
Model name. |
--base-url URL |
OpenAI-compatible endpoint. |
--api-key KEY |
API key (prefer env). |
--system TEXT |
Override the default system prompt. |
--tools LIST |
Comma-separated whitelist (e.g. read,bash). |
--no-tools |
Disable all tools. |
--frontend NAME |
Pick a registered frontend (default basic). |
-c, --continue |
Continue the most recent session in cwd. |
--no-session |
Don't persist this run. |
--no-context-files |
Skip AGENTS.md / CLAUDE.md discovery. |
-p, --print |
Print mode: prompt → final text → exit. |
--timeout FLOAT |
LLM request timeout in seconds (default 600). |
--thinking LEVEL |
Reasoning depth: off | minimal | low | medium | high | xhigh. Sends reasoning_effort to OpenAI-compatible providers; rounded down if the model's catalog ceiling is lower. Since v0.7. |
--debug |
Verbose logging (openai SDK wire, httpx, plugin discovery). |
Resolution example¶
You have a user config setting model = "gpt-4o-mini", a project config overriding to model = "claude-sonnet-4-5", and you run:
The effective model is deepseek-chat. CLI wins over env, which wins over project, which wins over user.
Context files¶
letscode walks up from cwd looking for these files and prepends their contents to the system prompt:
AGENTS.md(the cross-tool standard)CLAUDE.md(Claude Code convention)
Plus the user-global ~/.letscode/AGENTS.md if it exists.
Disabling context files
Pass --no-context-files to skip the walk. Useful for short one-off prompts where the surrounding repo's context is noise.
Sections (since v0.4)¶
Flat core keys remain valid and permanent. Since v0.4 the loader also passes through any [table] it doesn't model, so plugins read their own namespace without the core knowing about them. For example, letscode-memory:
model = "..." # flat core keys, unchanged
[memory] # plugin-owned, passed through verbatim
relevance = false
relevance_top_n = 5
Run letscode --init to write a commented starter config at ~/.letscode/config.toml (it never overwrites an existing file). A malformed config degrades to defaults with a one-line warning and never crashes.
Model catalog (since v0.7)¶
Per-model metadata (pricing, context window, supported thinking levels, provider routing) lives in a data file rather than Python source. The built-in catalog ships inside the wheel; users overlay it by dropping a file at ~/.letscode/catalog.toml.
Models¶
[[models]]
id = "my-custom-model"
provider = "openai" # optional; see Providers below
input_per_million = 5.0
output_per_million = 20.0
context_window = 128000
thinking_levels = ["off", "low", "medium", "high"] # optional; omit = permissive
Overlay entries win on id conflict, so you can also override built-in models (fix stale pricing, cap the context window, restrict thinking levels). The values feed the per-turn footer, auto-compaction, /model's listing, and thinking-level down-rounding. Missing models render as $— in the footer and skip auto-compaction. /reload re-reads the overlay.
Providers (since v0.7)¶
The catalog also carries a [[providers]] table naming endpoints. When a model has provider = "<name>", letscode routes to that provider automatically; no need to set OPENAI_BASE_URL per session:
[[providers]]
name = "cerebras"
base_url = "https://api.cerebras.ai/v1"
api_key_env = ["CEREBRAS_API_KEY"] # one or more; first-set wins
[[models]]
id = "llama-4-scout"
provider = "cerebras"
context_window = 128000
Then letscode --model llama-4-scout picks up CEREBRAS_API_KEY and hits Cerebras.
Built-in providers: openai, fireworks, deepseek, zai, openrouter, gemini. The corresponding env vars: OPENAI_API_KEY, FIREWORKS_API_KEY, DEEPSEEK_API_KEY, ZAI_API_KEY (or GLM_API_KEY), OPENROUTER_API_KEY, GEMINI_API_KEY (or GOOGLE_API_KEY).
Precedence: explicit --api-key beats everything. Otherwise the model's provider (if any) wins over OPENAI_API_KEY. So letscode --model deepseek-chat with both OPENAI_API_KEY and DEEPSEEK_API_KEY set uses DEEPSEEK_API_KEY, not OPENAI_API_KEY (which would 401). --base-url / OPENAI_BASE_URL / config base_url beat the catalog's base_url.
OpenRouter: the catalog ships no OpenRouter models by default (there are hundreds). Add the routes you use:
Then letscode --model anthropic/claude-sonnet-4-5 routes through OpenRouter with OPENROUTER_API_KEY.
Update check (since v0.7)¶
At interactive startup letscode makes one PyPI request per day (cached under ~/.letscode/cache/) to see if a newer version has shipped. When it has, a one-line notice goes to stderr. Failures are silent. Suppress with LETSCODE_NO_UPDATE_CHECK=1 or by running in CI (CI=1 is auto-detected).