<p align="center">
  <a href="https://www.unerr.dev/"><img src="https://unerr.dev/icon-wordmark.svg" alt="unerr — local intelligence layer for AI coding agents" width="320" /></a>
</p>

<p align="center">
  <strong>Lands your AI agent at the right code in fewer turns, tokens, & breakages.</strong>
</p>

<p align="center">
  A local intelligence layer that sits between your AI agent and your codebase —<br/>
  indexes every call, remembers every decision, and gets sharper the longer you use it.
</p>

<p align="center">
  <a href="https://www.unerr.dev/"><img src="https://img.shields.io/badge/website-unerr.dev-8B5CF6?style=flat-square&logo=icloud&logoColor=white" alt="Website" /></a>
  <a href="https://www.npmjs.com/package/@unerr-ai/unerr"><img src="https://img.shields.io/badge/install-npm_i_@unerr--ai/unerr-8B5CF6?style=flat-square&logo=npm" alt="Install" /></a>
  <a href="https://discord.gg/JfZ4pYgb"><img src="https://img.shields.io/badge/community-Discord-5865F2?style=flat-square&logo=discord&logoColor=white" alt="Discord" /></a>
  <a href="https://x.com/unerr_ai"><img src="https://img.shields.io/badge/follow-@unerr__ai-000000?style=flat-square&logo=x&logoColor=white" alt="X / Twitter" /></a>
  <a href="https://www.linkedin.com/company/unerr"><img src="https://img.shields.io/badge/linkedin-unerr-0A66C2?style=flat-square&logo=linkedin&logoColor=white" alt="LinkedIn" /></a>
  <img src="https://img.shields.io/badge/runtime-Node.js_≥20-339933?style=flat-square&logo=node.js&logoColor=white" alt="Node.js" />
  <img src="https://img.shields.io/badge/protocol-MCP-7C3AED?style=flat-square" alt="MCP" />
  <img src="https://img.shields.io/badge/local--first-no_cloud-22D3EE?style=flat-square" alt="Local-first" />
  <img src="https://img.shields.io/badge/license-ELv2-A1A1AA?style=flat-square" alt="License" />
</p>

<p align="center">
  <code>npm install -g @unerr-ai/unerr</code>
</p>

---

## The agent isn't stupid. It's flying blind.

Watch any AI coding session for ten minutes and you'll see the same loop:

- It **reads 30 files** to find one function — burning the context window before it writes a line.
- It **edits something with 40 callers** and never knows it just broke three services.
- It **re-derives the same conventions** you taught it yesterday, this morning, and an hour ago.
- It **forgets the entire session** the moment the window closes.

Every one of these is the same root cause: the agent has **no persistent memory of your code, your team's style, or its own past mistakes**. unerr is that memory. One process, fully local, indexed in seconds — and your agent picks it up automatically through MCP.

---

## What you actually see

Run `unerr` and open the dashboard. Four panes, all live:

| Pane | Answers the question | Powered by |
|---|---|---|
| **Token Optimization** | *How much context did unerr save my agent this session?* — saved vs. delivered, compounding multiplier, breakdown by mechanism (compression, graph hits, skipped re-reads, web fetches). | Per-turn ledger of every tool call |
| **Reasoning Quality** | *Did the agent actually use what it remembered?* — 4-pillar score across exploration, planning, execution, persistent memory. | 5-turn outcome window per fact/convention |
| **Codebase Map + Code Intelligence** | *What's the call graph and where are the blast-radius landmines?* — entities, edges, fan-in/out chokepoints, cross-module surprise links. | CozoDB graph (in-process, <5ms) |
| **Project Memory + Activity** | *What did we already learn, and what was I doing last time?* — facts the agent recorded, sessions stitched into intents, open blockers. | Append-only fact store + timeline.db |

The agent reads from the same store through MCP — every claim on the dashboard is also a tool call it just made.

### See it in action

<p align="center">
  <img src="https://unerr.dev/open-cli/video/unerr_short.gif" alt="unerr in action" width="720" />
</p>

<table align="center">
  <tr>
    <td align="center" width="240">
      <img src="https://unerr.dev/open-cli/screenshots/dashboard.png" alt="unerr dashboard — live overview" width="240" height="150" />
      <br/><sub><strong>Dashboard</strong><br/>Live overview — active sessions, recent tool calls, tokens saved this turn.</sub>
    </td>
    <td align="center" width="240">
      <img src="https://unerr.dev/open-cli/screenshots/token-trace-main.png" alt="unerr token trace — global" width="240" height="150" />
      <br/><sub><strong>Token Trace · global</strong><br/>Aggregate savings across every session, broken down by mechanism (graph, file_read, fetch_url, shell, dedup, format).</sub>
    </td>
    <td align="center" width="240">
      <img src="https://unerr.dev/open-cli/screenshots/token-session.png" alt="unerr token trace — session" width="240" height="150" />
      <br/><sub><strong>Token Trace · session</strong><br/>Single session: per-turn savings, mechanism mix, and the compounding multiplier.</sub>
    </td>
    <td align="center" width="240">
      <img src="https://unerr.dev/open-cli/screenshots/token-turn.png" alt="unerr token trace — turn" width="240" height="150" />
      <br/><sub><strong>Token Trace · turn</strong><br/>Single turn: which tool calls fired, tokens each would have cost without unerr vs what was delivered.</sub>
    </td>
  </tr>
  <tr>
    <td align="center" width="240">
      <img src="https://unerr.dev/open-cli/screenshots/reasoning-quality.png" alt="unerr reasoning quality — global" width="240" height="150" />
      <br/><sub><strong>Reasoning Quality · global</strong><br/>Four-pillar score across cleaner context, fewer wasted turns, fewer breakages, persistent memory.</sub>
    </td>
    <td align="center" width="240">
      <img src="https://unerr.dev/open-cli/screenshots/reasoning-session.png" alt="unerr reasoning quality — session" width="240" height="150" />
      <br/><sub><strong>Reasoning Quality · session</strong><br/>Per-session: which facts and conventions were reinforced, acted on, ignored, or corrected.</sub>
    </td>
    <td align="center" width="240">
      <img src="https://unerr.dev/open-cli/screenshots/code-base-intelligence.png" alt="unerr code intelligence" width="240" height="150" />
      <br/><sub><strong>Code Intelligence</strong><br/>Call graph, fan-in/out chokepoints, cross-module surprise links, and a risk grade per file.</sub>
    </td>
    <td align="center" width="240">
      <img src="https://unerr.dev/open-cli/screenshots/project-memory.png" alt="unerr project memory — facts" width="240" height="150" />
      <br/><sub><strong>Project Memory</strong><br/>Conventions, anti-patterns, decisions — with decay-adjusted confidence and reinforcement counts.</sub>
    </td>
  </tr>
  <tr>
    <td align="center" width="240" colspan="4">
      <img src="https://unerr.dev/open-cli/screenshots/activity.png" alt="unerr activity — timeline + heatmap" width="240" height="150" />
      <br/><sub><strong>Activity</strong><br/>Turn-grouped timeline with a 30-day heatmap — each row is one burst of agent work (intent → tools → outcome).</sub>
    </td>
  </tr>
</table>

---

## Quick Start

Three steps. Step 1 is once per machine; steps 2–3 are per repo.

### 1. Install the CLI

```bash
npm install -g @unerr-ai/unerr
```

Puts the `unerr` binary on your PATH. If the global `npm` directory isn't already in your shell's PATH (common with nvm, fnm, volta, pnpm), run `unerr doctor` once — it patches your shell config and won't need to run again.

### 2. Install for your agent (per repo)

```bash
cd ~/your-project
unerr install cursor
```

Writes the MCP config, skills, hooks, and instructions for that agent in the current repo. Swap `cursor` for any of the [supported agents](#supported-agents): `claude-code`, `windsurf`, `gemini-cli`, `antigravity`, `github-copilot-cli`.

### 3. Restart your IDE

Close and reopen your IDE (or start a new chat session). Your agent picks up unerr through MCP — graph-backed tools, persistent memory, shell compression all available immediately.

> **Dashboard:** <http://localhost:9847> — open any time to watch token savings, reasoning quality, and the codebase map update as your agent works.

### Supported agents

```bash
unerr install claude-code        # → .mcp.json + CLAUDE.md + .claude/skills/ + hooks
unerr install cursor             # → .cursor/mcp.json + .cursor/rules/ + hooks
unerr install antigravity        # → .antigravity/mcp_config.json + .agents/rules/ + .agents/skills/
unerr install windsurf           # → ~/.codeium/windsurf/mcp_config.json + .windsurf/rules/ + .windsurf/skills/
unerr install gemini-cli         # → .gemini/settings.json + GEMINI.md + .gemini/skills/
unerr install github-copilot-cli # → .copilot/mcp-config.json + .github/copilot-instructions.md + .github/skills/
```

Install multiple agents in the same repo — each writes its own config:

```bash
unerr install cursor
unerr install claude-code
```

> Need manual setup? `unerr install --show-instructions <agent>` prints copy-pasteable steps.

<details>
<summary>Manual MCP config (any MCP-compatible client)</summary>

```json
{
  "mcpServers": {
    "unerr": {
      "command": "npx",
      "args": ["@unerr-ai/unerr", "--mcp"]
    }
  }
}
```

</details>

<details>
<summary>What <code>unerr install</code> writes</summary>

| Item | File(s) |
|------|------|
| MCP config pointing to `unerr --mcp` | `.mcp.json`, `.cursor/mcp.json`, … |
| Skills — 12 definitions teaching the agent when to use each tool | `.claude/skills/`, `.cursor/rules/`, … |
| Instructions — tool-routing table injected into the agent's instruction file | `CLAUDE.md`, `.cursor/rules/unerr-instructions.mdc` |
| Hooks — shell compression + tool-adoption nudging | `.claude/settings.json`, `.cursor/hooks.json` |
| Gitignore — keeps `.unerr/` out of commits | `.gitignore` |
| Force tools (Claude Code only) — denies built-in Read/Grep/Glob so the agent uses graph tools. Opt out with `--no-force-tools`. | `.claude/settings.json` |

Idempotent — re-running updates if content changed, skips if identical. Remove with `unerr uninstall`.

</details>

---

## What changes the moment you connect

### First session — instant value

- **Graph navigation in <5ms** — `get_entity`, `get_references`, `get_imports`, `search_code`. The agent stops reading 30 files to find one function.
- **Blast radius before edits** — `get_references` returns every caller. No more confident wrong changes that ripple across services.
- **Targeted file reads** — `file_read({entity: "fnName"})` returns just that function + relevant conventions/facts, not 2000 lines.
- **Shell compression** — 11 strategies, 645+ command classifiers. Diffs, errors, logs, test runs, YAML — each compressed differently. **93% average compression** across real-world benchmarks (2 MB → 138 KB). Raw output is kept on disk; the agent can recover it on demand.
- **Web fetches** — `fetch_url` strips page chrome via Defuddle/Readability, converts to markdown, splits into heading-bounded passages, optionally re-ranks with BM25 when a `prompt` is supplied, and caches by content hash. Replaces built-in WebFetch — **5–10× fewer tokens** per page.
- **Convention awareness** — naming, structure, import patterns auto-detected and injected into the agent's context.
- **Tool adoption nudging** — five reinforcement layers (exec nudges, hook interception, instruction injection, skill reminders, default-deny of built-ins on Claude Code) push the agent to use the graph instead of grep.

### Session 2+ — it starts compounding

- **Session persistence** — what the agent learned today is available tomorrow. No more starting from zero.
- **Fact memory** — `record_fact` persists conventions, decisions, and anti-patterns; `recall_facts` retrieves them with decay-adjusted confidence. Facts also auto-detect from coding sessions.
- **Episodic narratives** — when you reopen a file, the agent sees what was modified there, when, and why.
- **Loop prevention** — a circuit breaker fires after repeated failed attempts on the same entity, surfacing the failure mode instead of letting the agent thrash.
- **Memory-effectiveness scoring** — every fact and convention opens a 5-turn observation window and resolves to a verdict (reinforced / acted_on / caught / ignored / corrected). The Reasoning Quality pane shows the **load-bearing rate** — not just how much the agent remembered, but how much of it actually mattered.

### Background behaviors

While unerr is running these activate automatically — no extra commands:

- **Architecture guard** — flags structural violations before they ship.
- **Cascade guard** — warns when an edit has wide blast radius.
- **Convention drift** — detects when new code diverges from established patterns.
- **Auto-doc** — generates docs for undocumented entities.
- **Change narrative** — tracks the story behind multi-step refactors.
- **Loop breaker** — intervenes when the agent is stuck retrying.
- **Session continuity** — preserves state across restarts.

### Shell compression benchmarks

| Strategy | What it compresses | Avg compression |
|----------|-------------------|:-:|
| **diff** | `git diff`, patch output | **99%** |
| **structured** | JSON APIs, `docker inspect` | **97%** |
| **progress** | `npm install`, `pip install` | **95%** |
| **log_text** | Build logs, server logs, `make`, `cargo build` | **89%** |
| **test_results** | `vitest`, `pytest`, `cargo test`, `playwright` | **80%** |
| **tabular** | `ps aux`, `docker ps`, `kubectl get` | **77%** |
| **error_diagnostic** | `tsc`, `eslint`, `rustc`, `shellcheck` | **72%** |
| **key_value** | `env`, `kubectl describe`, `systemctl status` | **48%** |
| **tree_paths** | `find`, `tree`, `ls -R` | **42%** |
| **yaml** | YAML configs, `kubectl get -o yaml`, Helm output | adaptive |
| **omni** | Fallback for unrecognized output | adaptive |

**Overall: 93% compression** (2 MB → 138 KB across 40 real-world test cases).

### Language support

| Language | Tier | Entities | Edges | Tree-sitter | SCIP |
|----------|:---:|:-:|:-:|:-:|:-:|
| TypeScript / JavaScript / Python / Go / Java / Kotlin / Scala / Rust / Ruby / C / C++ / C# | 1 | ✓ | ✓ | ✓ | ✓ |
| PHP / Swift / Lua / Dart / Elixir / Zig | 2 | ✓ | ✓ | ✓ | — |

**Tier 1** (12 languages): full tree-sitter AST + dedicated extraction + SCIP compiler-verified call graphs where the toolchain is on PATH.
**Tier 2** (6 languages): tree-sitter AST + generic extraction. Regex fallback for the rest.

**Tier 3 (search-discoverable):** Markdown, IaC (`.tf`, `.yaml`, `.toml`), schemas (`.proto`, `.graphql`, `.prisma`), SQL, shell, templates, build files, CI configs — indexed for `search_code` only, no entity extraction.

---

## How unerr compares

Adjacent tools each own one layer — graph navigation, persistent memory, or output compression. unerr integrates all three, plus the drift prevention that keeps the graph tools in active rotation. Peer strengths are real; the table credits them where they win.

| Capability | unerr | Graphify (~47K) | Serena (~23K) | claude-mem (~75K) | RTK (~40K) |
|---|:---:|:---:|:---:|:---:|:---:|
| **Code intelligence** | | | | | |
| Pre-hoc file-read intercept — resolves entity via graph, returns ~200 lines + conventions + blast radius instead of a 3,000-line file | ✓ | ✗ | Partial | ✗ | ✗ |
| Convention auto-detection — naming, structure, import direction from ≥70% adherence; no manual rules file | ✓ | ✗ | ✗ | ✗ | ✗ |
| Drift / staleness signals — `ur\|dft` fires when code moves under stored memory | ✓ | ✗ | ✗ | ✗ | ✗ |
| **Memory & continuity** | | | | | |
| Persistent across sessions — typed facts with per-type decay, contradiction handling | ✓ | ✗ | ✗ | ✓ | ✗ |
| Per-repo isolation — all state in `.unerr/` inside the repo, no cross-project leakage | ✓ | ✓ | ✓ | ✗ | — |
| **Runtime** | | | | | |
| Zero LLM in core — no API keys, no per-turn inference cost | ✓ | ✓ | ✓ | ✗ | ✓ |
| Keeps MCP tools in active rotation — without enforcement, agents revert to built-in Read/Grep/Glob within 3–5 turns | ✓ | ✗ | ✗ | ✗ | ✗ |

Three numbers behind the table:

- **~84%** of an AI coding agent's tokens are tool output, mostly file reads (JetBrains, NeurIPS 2025 DL4Code Workshop) — unerr intercepts before the read.
- **0** LLM calls per query in the Free tier — facts, conventions, and drift signals are algorithmic.
- **3–5** turns is how long agents take to revert to built-in Read/Grep/Glob without drift prevention.

Honest acknowledgements: unerr is the new entrant with fewer stars than every peer; the install is heavier than `brew install` (Node + index step); TypeScript is deepest, other languages run on tree-sitter; no semantic vector retrieval and no narrative session resume in the Free tier.

---

## How it works

```
AI Agent (Claude Code / Cursor / Windsurf / any MCP client)
    │
    ├── stdio MCP ──→ unerr --mcp (bridge, per IDE session)
    │                       │
    │                       └── UDS ──→ unerrd (lightweight Node process,
    │                                           one per machine, auto-spawned)
    │                                       │
    │                                       └── per-repo unerr process(es)
    │                                              │
    │                                              ├── CozoDB graph     (in-process, <5ms)
    │                                              ├── Fact store       (cross-session memory)
    │                                              ├── Timeline + ledger (every tool call)
    │                                              ├── File watcher     (incremental reindex)
    │                                              ├── Convention engine
    │                                              ├── Compression engine (11 strategies, 645+ classifiers)
    │                                              └── Behavior modules (cascade-guard, loop-breaker, auto-doc…)
    │
    └── Dashboard ──→ http://localhost:9847 (SSE-streamed live)
```

One local DB per repo. Zero network calls. No API keys. No cloud. Your code never leaves the machine.

---

## MCP Tools (20)

### Graph Intelligence (8)

| Tool | What the agent gets |
|------|-----|
| `get_entity` | Any code entity — signature, body, callers, callees, risk |
| `get_file` | All entities in a file with risk summary |
| `get_references` | Callers (blast radius) or callees (dependencies) |
| `get_imports` | Import graph for a file |
| `search_code` | Graph-ranked full-text search across all entities |
| `get_conventions` | Detected naming/structure/import patterns + adherence rates |
| `get_critical_nodes` | High fan-in/fan-out chokepoints |
| `get_cross_boundary_links` | Unexpected cross-module dependencies, scored by surprise |

### Structural Analysis (3)

| Tool | What the agent gets |
|------|-----|
| `get_project_stats` | Entity counts, risk distribution, health grade |
| `file_connections` | Imports + co-change correlations for a file |
| `get_test_coverage` | Direct + transitive tests for any entity |

### File Protocol (2)

| Tool | What the agent gets |
|------|-----|
| `file_read` | Context-aware read — auto-injects conventions and facts |
| `file_outline` | File structure (entities, exports) without reading the body |

### Persistent Memory (2)

| Tool | What the agent gets |
|------|-----|
| `record_fact` | Persist a convention, decision, or anti-pattern |
| `recall_facts` | Retrieve facts with hierarchical scope + decay-adjusted confidence |

### Session Narrative — Markers (4)

Inline markers the agent emits as it works. Persisted to the shadow ledger and `.unerr/timeline.db` — powers turn titles, cross-session intent stitching, the resume strip, and loop/blocker miners.

| Tool | What it does |
|------|-----|
| `mark_intent` | One-sentence task start (≤80 chars). Becomes the turn title |
| `mark_decision` | Records a chosen approach + up to 5 alternatives (≤140 chars) |
| `mark_blocker` | Flags an unresolved obstacle. Carries into the next session's resume strip |
| `mark_resolution` | Resolves a prior blocker by `marker_id` |

### Web Fetch (1)

| Tool | What the agent gets |
|------|-----|
| `fetch_url` | DOM-extracted markdown of a web page (Defuddle/Readability), split into heading-bounded passages, optionally re-ranked by BM25 against a `prompt`, cached by content hash. Replaces built-in WebFetch — 5–10× fewer tokens. Optional Playwright SPA fallback. |

Every response includes `_meta` (latency, risk level, drift status) and inline `ur|<tag>` signals for high-priority guidance (drift, blast-radius warnings, circuit-breaker halts).

---

<details>
<summary><strong>CLI commands</strong></summary>

```bash
unerr install <agent> # MCP config + skills + hooks + instructions for one agent
unerr uninstall       # Remove unerr integration from this repo
unerr doctor          # Check PATH + environment, auto-fix if unerr isn't on all shells
unerr status          # Proxy health, entity count, graph age
unerr stats           # Session statistics (tokens, tool calls, compression)

unerr --mcp           # Stdio bridge — what your IDE invokes via .mcp.json
unerr                 # Start a standalone per-repo proxy (rare — IDE invocation covers this)
```

`unerr pm …` manages the cross-repo `unerrd` process — see the [reference](#process-manager-command-reference) below.

</details>

<details>
<summary><strong>Architecture</strong></summary>

```
src/
  entrypoints/   CLI entry + boot state machine
  proxy/         Per-repo MCP server, stdio↔UDS bridge, session stats, shell compression
  daemon/        Process manager (unerrd) — registry, supervisor, spawn lock, HTTP API
  intelligence/  CozoDB graph, AST extraction, conventions, rules, search, semantic
  tracking/      Prompt ledger, drift detection, git attribution
  behaviors/     Cascade guard, loop breaker, auto-doc, change narrative…
  commands/      CLI commands (install, status, stats, pm, debug, …)
  tools/         MCP tool implementations (intelligence + coding)
  hooks/         Claude Code hook system integration
  skills/        12 bundled skill definitions
  server/ + ui/  HTTP API + React (Vite) dashboard
  config/        Agent registry, MCP config writer, instruction injector
  schemas/       Zod schemas
```

**Design principles**

- Zero network calls — fully local, no API keys.
- stdout is sacred — MCP JSON-RPC only; everything else to stderr.
- <5 ms query responses — CozoDB runs in-process (Rust via NAPI).
- First useful output <5 s — shallow index first, deep enrichment in background.
- Graceful degradation — the agent still works if unerr is down, you just lose the intelligence layer.

**Tech stack** TypeScript (ESM) · CozoDB (Rust/NAPI) · web-tree-sitter (WASM) · MCP SDK · Ink (React CLI) · React + Vite (dashboard) · tsup · Vitest

</details>

<details>
<summary><strong>Development</strong></summary>

```bash
pnpm install
pnpm run build          # tsup → dist/ (ESM, node20)
pnpm run dev            # tsx watch
pnpm run test:run       # full suite
pnpm run lint           # biome check
pnpm run typecheck      # tsc --noEmit

pnpm link --global      # make local `unerr` available globally
```

</details>

<details>
<summary><strong>Contributing</strong></summary>

Contributions welcome — please open an issue first.

**Before submitting a PR:**
- `pnpm run typecheck && pnpm run lint && pnpm run test:run`
- All output to stderr — never stdout (MCP JSON-RPC channel)
- All CozoDB interactions are async — always `await`
- Use `.js` extensions in imports (NodeNext resolution)

See [CLAUDE.md](./CLAUDE.md) for full conventions.

</details>

---

## Process Manager Command Reference

`unerrd` is a lightweight Node process that supervises every registered repo. Your IDE invocation auto-spawns it; it exits cleanly after 30 minutes of no MCP activity. You rarely run these commands directly, but they're here when you want a look under the hood.

```bash
unerr pm status                       # PID, uptime, repos, memory, idle countdown
unerr pm start                        # Start manually (auto-spawn usually covers this)
unerr pm stop                         # Graceful shutdown — stops children, flushes state

unerr pm add <path>                   # Register a repo (auto-registered on first MCP call)
unerr pm remove <path>                # Unregister a repo
unerr pm config <path> <key>=<value>  # Per-repo settings (idleTimeout, javaBuildTool, …)

unerr pm logs                         # Tail ~/.unerr/logs/unerrd.log
unerr pm logs --repo <path>           # Tail a specific repo's log
unerr pm logs --bridge --follow       # Stream bridge session logs continuously
unerr pm logs --boot                  # Most recent spawn sequence only

unerr pm dashboard                    # Open http://localhost:9847 in your browser
```

**Dashboard** shows the global overview (registered repos, health, active sessions), a repo switcher into each repo's full intelligence dashboard, and process-manager info (uptime, memory, idle countdown).

**Updates** — `npm i -g @unerr-ai/unerr` and restart the IDE. The next bridge invocation re-spawns the manager on the new version.

---

## License

[Elastic License 2.0 (ELv2)](./LICENSE) — free to use, modify, and distribute. Cannot be offered as a hosted service.

---

<p align="center">
  <code>npm install -g @unerr-ai/unerr</code>
  <br /><br />
  <a href="https://www.unerr.dev/"><sub>unerr.dev</sub></a> · <a href="https://www.npmjs.com/package/@unerr-ai/unerr"><sub>npm registry</sub></a> · <a href="https://discord.gg/2BjRftz8kG"><sub>Discord</sub></a> · <a href="https://x.com/unerr_ai"><sub>X</sub></a> · <a href="https://www.linkedin.com/company/unerr"><sub>LinkedIn</sub></a> · <sub>Fully local. No account. No cloud. Free.</sub>
</p>
