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
- The dashboard
- 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.
- 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
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) |
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.
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 resolve <id> "<fix>" |
Record the fix and mark an error resolved |
errnest dismiss <id> |
Mark an error won't-fix |
errnest search "<query>" |
Show the most similar past errors + fixes |
errnest show <id> |
Full detail for one error |
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 |
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.1.0.tar.gz.
File metadata
- Download URL: errnest-0.1.0.tar.gz
- Upload date:
- Size: 227.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6b5c14706290fccc0b5d5ae48b509212f0ca2b30df9083296b34490c052acc80
|
|
| MD5 |
21f1725b25d1933e4005c41ff8f8a082
|
|
| BLAKE2b-256 |
ee4c854d6cd99df7ce9756c256f3d686cce10507c2562cb00379c8418d569256
|
File details
Details for the file errnest-0.1.0-py3-none-any.whl.
File metadata
- Download URL: errnest-0.1.0-py3-none-any.whl
- Upload date:
- Size: 34.4 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 |
e5677e5f9362d0b75f8f30ed861ed6a898bd05b8ced83916d215c436db33dbe6
|
|
| MD5 |
15c88156f1d75c50bffc3024d0e4ccc5
|
|
| BLAKE2b-256 |
c0fb2f9bf1000ec434d83ba5f2287181df47fd32bf4fe6ed57e05952f1a09a92
|