Local-first, provider-agnostic error/fix memory with a semantic search TUI.
Project description
errnest
Local-first, provider-agnostic error/fix memory for the command line.
Capture the errors you hit and how you fixed them. errnest surfaces the past fix the next time a failure looks the same — matched semantically via embeddings, with an offline text-match fallback when no AI backend is reachable. Browse everything in an interactive terminal dashboard.
Contents
- Why errnest
- Install
- Quickstart
- Automatic error surfacing
- The dashboard
- AI explanations
- AI backends
- CLI reference
- Embedding in other tools
- Configuration
- How it works
- Contributing
- License
Why errnest
Every developer re-solves the same handful of errors over and over —
port already in use, a ModuleNotFoundError after switching branches, a
Docker permission error. errnest is a small, local database of those errors
and their fixes, plus a fast way to ask "have I seen this before?" — from a
shell one-liner, a TUI, or your own tooling.
- Local-first. SQLite on disk, no server, no account, no telemetry.
- Can capture automatically. A shell hook surfaces past fixes the moment a command fails, with no extra step — see Automatic error surfacing.
- Provider-agnostic AI. Ollama by default (fully offline); swap in
OpenAI, an OpenAI-compatible endpoint, Anthropic, or Gemini via one config
key. The rest of the codebase only ever calls
embed()/generate(). - Degrades gracefully. No AI backend reachable? Search falls back to text matching instead of failing.
- Redacts before it stores. Secrets and volatile tokens (paths, hex, emails) are stripped/normalized before anything hits disk.
- A real dashboard, not just a CLI — see below.
Install
pip install errnest # core (local Ollama backend)
pip install errnest[openai] # + OpenAI / OpenAI-compatible
pip install errnest[anthropic] # + Anthropic (generation)
pip install errnest[gemini] # + Google Gemini
pip install errnest[all-providers] # everything
Requires Python 3.9+. No provider is required to install or run errnest — with nothing configured it talks to a local Ollama daemon, and even without Ollama running, capture/search/dashboard all still work (offline text matching instead of semantic search).
Quickstart
some-failing-command 2>&1 | errnest capture -c "some-failing-command"
errnest resolve 1 "bumped the timeout in config.yaml"
errnest search "connection refused on startup"
errnest doctor # check provider + models + credentials
errnest dashboard # browse everything interactively
Or skip manual capture entirely — see below.
Automatic error surfacing
Two tiers, from fully hands-off to full detail:
Tier 1 — the shell hook. Add to your ~/.zshrc or ~/.bashrc:
eval "$(errnest shell-init zsh)" # or: bash
Every failed command is now captured automatically — no errnest capture
needed. It's deliberately cheap (no AI call, <200ms) so it never adds
noticeable latency to your prompt: it matches on the command string alone and
shows past resolved fixes for that exact command. Tune what gets captured
via [capture] in ~/.errnest/config.toml (see
Configuration) — skip low-severity exit codes, ignore
specific commands (cd, git status, …) or regex patterns, or turn it off
entirely.
Tier 2 — errnest run. For when you want the real thing:
errnest run docker compose up
Wraps the command, tees stderr (your terminal sees everything, unchanged),
and on failure stores the full redacted error with an embedding — real
semantic search against your history, not just an exact command-string match.
An AI explanation is generated in the background by default (--no-explain
to skip it).
The dashboard
errnest dashboard opens an interactive TUI (built with
Textual) over your error store —
search, filter by status or tag, resolve/dismiss/copy fixes, see
semantically-similar past errors, and spot recurring patterns via clustering.
Panes: a filterable, searchable error list on the left; a detail view on the right with metadata, error output, applied fix, an AI explanation (once resolved), and the top 3 semantically similar past errors. A Patterns tab clusters your error history (k-means over embeddings) once you've captured 20+ errors, so you can spot what's actually costing you the most time.
Keybindings:
| Key | Action |
|---|---|
j / k or ↑ / ↓ |
Navigate the error list |
r |
Mark selected error resolved |
d |
Dismiss selected error (won't-fix) |
u |
Reopen a resolved/won't-fix error back to open |
e |
Ask the AI backend to explain the selected error |
c |
Copy the fix text to your clipboard |
s |
Focus search |
/ |
Focus filter chips |
p or 1–4 |
Switch tabs (All / Unresolved / Resolved / Patterns) |
q or Ctrl-C |
Quit |
Search is semantic (embeddings + cosine similarity) when an embed backend is
reachable, and degrades to a live substring filter when it isn't — with no
error shown, just a quieter dashboard. Copy-fix uses a native clipboard tool
(pbcopy / xclip / wl-copy / clip) so it works even in terminals that
don't support OSC 52 clipboard sync.
AI explanations
errnest explain 7 # generate (or show a cached) explanation
errnest explain 7 --regenerate
Generation always runs detached — the command returns immediately, and the
result lands in the error's record whenever it finishes (check back with
errnest show 7 or the dashboard). Local models can genuinely take tens of
seconds; nothing here blocks your terminal waiting for it. errnest run and
the dashboard's e key generate the same way.
AI backends
The rest of errnest only calls embed() / generate() — it never references
a provider directly. The active provider comes from [ai] provider in
~/.errnest/config.toml:
[ai]
provider = "ollama" # ollama | openai | openai-compatible | anthropic | gemini
embed_fallback = "ollama" # used when the provider can't embed (e.g. anthropic)
[ai.ollama]
host = "http://localhost:11434"
embed_model = "nomic-embed-text"
gen_model = "llama3"
[ai.anthropic]
api_key_env = "ANTHROPIC_API_KEY"
gen_model = "claude-haiku-4-5"
Provider SDKs are imported lazily; a provider that's unreachable or missing
its SDK degrades to a clear AIUnavailableError, and every caller in errnest
(search, the dashboard, doctor) handles that by falling back rather than
crashing.
Switch providers without hand-editing TOML:
errnest config set ai.provider anthropic
errnest config show # effective config, secrets masked
See USAGE.md for the full config reference, every environment variable, and per-provider setup steps.
CLI reference
| Command | Description |
|---|---|
errnest capture -c "<cmd>" |
Capture a failing command + error output (stdin) as unresolved |
errnest run <cmd> [args...] |
Run + tee a command; capture full stderr + embedding on failure |
errnest resolve <id> "<fix>" |
Record the fix and mark an error resolved |
errnest edit <id> |
Edit an error's fix text in $EDITOR |
errnest dismiss <id> |
Mark an error won't-fix |
errnest reopen <id> |
Revert a resolved/won't-fix error back to open |
errnest explain <id> |
Generate (in the background) or show a cached AI explanation |
errnest search "<query>" |
Show the most similar past errors + fixes |
errnest show <id> |
Full detail for one error |
errnest shell-init zsh|bash |
Print the Tier 1 auto-capture shell hook (see below) |
errnest dashboard |
Open the interactive TUI |
errnest doctor |
Check provider/model reachability and credentials |
errnest config show |
Print effective config (secrets masked) |
errnest config set <key> <value> |
Set a config value, e.g. ai.provider anthropic |
Embedding in other tools
from errnest import ErnestClient
client = ErnestClient()
# Never raises — returns None if there's no confident, resolved match.
fix = client.surface(stderr_text)
# Browse recent errors, optionally scoped to a project directory.
recent = client.search("build failed", cwd="/path/to/project", top_k=3)
Wrap it behind a try/except ImportError in your own tool to make errnest an
optional dependency — never import it at module top level, and treat every
call as safe-to-fail (it already is, on errnest's side).
Configuration
Environment variables:
| Variable | Purpose | Default |
|---|---|---|
ERRNEST_CONFIG |
Path to the config file | ~/.errnest/config.toml |
ERRNEST_DB |
Path to the SQLite error store | ~/.errnest/errors.db |
ERRNEST_SIM_THRESHOLD |
Cosine-similarity cutoff for auto-surfacing | 0.7 |
OLLAMA_HOST |
Ollama daemon URL | http://localhost:11434 |
Tier 1 auto-capture is tuned via [capture] in ~/.errnest/config.toml
(auto_capture, min_exit_code, ignore_commands, ignore_patterns,
surface_threshold, quiet). Full config file reference, per-provider
setup, and troubleshooting: USAGE.md.
How it works
- Storage: SQLite (
~/.errnest/errors.db), one row per captured error, with an optional embedding vector attached at capture time. - Redaction: API keys, tokens, emails, and provider-specific credential patterns are stripped before storage; paths/hex/numbers are normalized so near-identical errors from different runs still match.
- Search: cosine similarity over embeddings when an embed backend is reachable; Jaccard token overlap over normalized text otherwise.
- Patterns: a small pure-Python k-means (no numpy) over stored embeddings, computed once per dashboard session.
See CHANGELOG.md for release history.
Contributing
Issues and PRs welcome. To run the test suite locally:
pip install -e ".[dev,all-providers]"
pytest
ruff check .
Provider tests mock the SDK/HTTP layer — no real API calls or a running Ollama instance are needed to run the suite.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file errnest-0.2.0.tar.gz.
File metadata
- Download URL: errnest-0.2.0.tar.gz
- Upload date:
- Size: 250.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d354129a57cc103120b4bea00b43e57e76833f9493724cc68f25895d01cf119c
|
|
| MD5 |
266c715a7284887d89a1d87661563fcb
|
|
| BLAKE2b-256 |
63dcc44fb91833c6c89468502a85f1e8b33e00ebb5fbb72df62c2dfa91eec99a
|
File details
Details for the file errnest-0.2.0-py3-none-any.whl.
File metadata
- Download URL: errnest-0.2.0-py3-none-any.whl
- Upload date:
- Size: 48.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7f77db22fe55f47bc10cec685e9a4d7ed5180923b56170fabb5c0c1dd05234f
|
|
| MD5 |
27ec8b8ff10686dc2680da95a65ce6f9
|
|
| BLAKE2b-256 |
4ee4ddd3f4b1e4d6fd0b2884aad1f56e8ae96ac6fed68eb7b76799be30f18c26
|