Metadata-Version: 2.4
Name: errnest
Version: 0.2.0
Summary: Local-first, provider-agnostic error/fix memory with a semantic search TUI.
Project-URL: Homepage, https://github.com/ucmani/errnest
Project-URL: Repository, https://github.com/ucmani/errnest
Project-URL: Issues, https://github.com/ucmani/errnest/issues
Project-URL: Changelog, https://github.com/ucmani/errnest/blob/master/CHANGELOG.md
Author: ucmani
License: MIT
License-File: LICENSE
Keywords: cli,developer-tools,embeddings,error-tracking,llm,ollama,tui
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Debuggers
Classifier: Topic :: Utilities
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24
Requires-Dist: rich>=13.0
Requires-Dist: textual>=0.60
Requires-Dist: tomli>=2.0; python_version < '3.11'
Requires-Dist: typer>=0.12
Provides-Extra: all-providers
Requires-Dist: anthropic>=0.20.0; extra == 'all-providers'
Requires-Dist: google-generativeai>=0.5.0; extra == 'all-providers'
Requires-Dist: openai>=1.0.0; extra == 'all-providers'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.20.0; extra == 'anthropic'
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: gemini
Requires-Dist: google-generativeai>=0.5.0; extra == 'gemini'
Provides-Extra: ollama
Provides-Extra: openai
Requires-Dist: openai>=1.0.0; extra == 'openai'
Description-Content-Type: text/markdown

<div align="center">

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

[![tests](https://github.com/ucmani/errnest/actions/workflows/tests.yml/badge.svg)](https://github.com/ucmani/errnest/actions/workflows/tests.yml)
[![PyPI](https://img.shields.io/pypi/v/errnest.svg)](https://pypi.org/project/errnest/)
[![Python](https://img.shields.io/badge/python-3.9%2B-blue)](https://pypi.org/project/errnest/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

</div>

---

## Contents

- [Why errnest](#why-errnest)
- [Install](#install)
- [Quickstart](#quickstart)
- [Automatic error surfacing](#automatic-error-surfacing)
- [The dashboard](#the-dashboard)
- [AI explanations](#ai-explanations)
- [AI backends](#ai-backends)
- [CLI reference](#cli-reference)
- [Embedding in other tools](#embedding-in-other-tools)
- [Configuration](#configuration)
- [How it works](#how-it-works)
- [Contributing](#contributing)
- [License](#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](#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

```bash
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

```bash
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`:

```bash
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](#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:

```bash
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](https://github.com/Textualize/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.

![errnest dashboard](docs/dashboard.png)

**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

```bash
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`:

```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:

```bash
errnest config set ai.provider anthropic
errnest config show     # effective config, secrets masked
```

See [USAGE.md](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

```python
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](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](CHANGELOG.md) for release history.

## Contributing

Issues and PRs welcome. To run the test suite locally:

```bash
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

[MIT](LICENSE)
