IDE-grade autocomplete for your terminal.
Fig-style popup completions for zsh on macOS.
No login. No AI. No telemetry. No Electron. Just one small binary and your zsh.
Nerv (nerv-sh/nerv) is an open-source, IDE-style inline autocomplete
for the macOS terminal: a Rust daemon plus a zsh widget that shows the next
token — subcommands, flags, git branches, npm scripts, kubectl resources — as
you type, with descriptions, inside the terminal you already use. It is a successor to
Fig's autocomplete: it runs the Fig completion engine that AWS open-sourced and
the 715 community specs from withfig/autocomplete, without Fig's cloud, login,
AI, or Electron app. Apache-2.0, installed with Homebrew.
| Category | Terminal / shell autocomplete (Fig replacement) |
| Platform | macOS on Apple Silicon; zsh ≥ 5.8 default, bash and fish via opt-in PTY mode |
| Install | brew install nerv-sh/tap/nerv && eval "$(nerv init zsh)" |
| Language | Rust (nerv CLI 2.8 MB + nervd daemon 3.8 MB, static) + one zsh script |
| Specs | 715 commands converted from withfig/autocomplete, 15 MB gzipped, shipped in the package |
| Latency | 0.055 ms engine p95 against a 25 ms per-keystroke budget |
| Network / accounts / telemetry | None — documented non-goals |
| License | Apache-2.0 (engine), MIT (spec corpus) |
Fig gave the terminal IDE-grade autocomplete: type git ch and the next token
is just there. Then Fig was acquired, folded into Amazon Q, and the
experience got buried under a mandatory Builder ID login, AI chat, and a
multi-hundred-megabyte bundle.
Nerv digs it back out. The engine is the Rust code AWS preserved and
open-sourced; the specs are withfig/autocomplete,
compiled to static JSON at build time. What shipped as a desktop app with a
cloud attached is now a local daemon and a zsh widget.
Press a key, see the next token. That's it.
- 715 CLI specs —
git,docker,kubectl,aws,npm,cargo,gh,brew,terraform, … converted fromwithfig/autocompleteat build time. - Live values, not just flags — git branches, npm scripts, kubectl namespaces and resources, AWS profiles and resource IDs, SSH hosts, make targets, man pages, zoxide history. Recovered natively in Rust; no Node, no JS runtime on the hot path.
- Inline ghost text — the history command you most likely mean, ranked
by how often and how recently you ran it, in which folder, and what you
ran just before (
git add .→git commit …). It falls back to the top spec suggestion. On an empty prompt it predicts the command you usually run next (NERV_PREDICT=0turns that off). Right-arrow accepts. The ranking follows deja. - Readable popup — a purple bar on the highlighted row and the letters you typed marked in the same purple; no background of its own, names in your terminal's text color, chrome in its grey, so dark and light themes both read right. History rows sit below the spec's, and the row the grey ghost text points at comes first.
- Frecency ranking — rows you pick, or type by hand, float to the top, more so in the folder where you use them.
- Understands your shell — completes through
alias g=git, behindsudo/env/watch, and on the last segment of compound lines (git pull && git ch<Tab>). Flags you already typed aren't offered twice. - Never stalls your typing — zsh talks to the daemon over its socket
directly (no process per keystroke, about 0.25 ms round trip), the engine answers
in well under a millisecond against a 25 ms per-keystroke budget, and a slow live completion
(a
brewordockershell-out) runs in the background instead of freezing the prompt — its result lands on the next keystroke. Lazy-loaded specs, bounded memory. - Commands without a spec still complete — if nothing in the bundle or
your overlay covers a command, the zsh popup asks the completion function
the tool installed for itself (
_uv,_rg, …) — branches, PIDs and per-subcommand options included. Without one, Nerv derives a spec from its--helpoutput once, in the background, and caches it. Typos in the command word (zpeh→zeph) get a one-line did you mean correction. - Leaves no trace —
nerv uninstallremoves every file it ever wrote. We treat trace zero as a release-blocking acceptance criterion.
Requires macOS on Apple Silicon and zsh ≥ 5.8.
brew install nerv-sh/tap/nerv
eval "$(nerv init zsh)"That's the whole install. The eval line writes an idempotent, marker-fenced
block into your ~/.zshrc and activates completion in the current session;
the daemon starts itself on demand, and the 715 completion specs ship inside
the package. Type git — the popup should appear. If it doesn't, run
nerv doctor: it checks the shell hook, the daemon (including a stale daemon
left behind by an upgrade — its version mismatching the CLI is reported), the
spec cache, and the schema version, and tells you exactly what's wrong.
Functions and aliases you already have (g, dockr, …) complete at the first
token too; their names live in the daemon's memory only, never on disk.
Homebrew installs don't trip Gatekeeper:
brewdoesn't quarantine its downloads, and the ad-hoc signature from the Rust toolchain is sufficient on Apple Silicon. If you download a release tarball in a browser instead, clear the flag once withxattr -dr com.apple.quarantine <path>.
The popup opens as you type. Its first row, Immediately execute, is
highlighted by default, so Enter still runs what you typed; the list only
takes a key once you move into it. The row always names what Enter will do
from the current highlight — Enter: run on itself, Enter: insert on an
item.
| Key | What it does |
|---|---|
| ↓ / ↑ | Move the highlight; it wraps at either end |
| PageDown / PageUp | Move a page at a time |
| Tab | Insert the highlighted item and open the next level (a subcommand's flags, a flag's values). On Immediately execute it moves to the first item |
| Shift+Tab | Move the highlight up |
| Enter | On Immediately execute, run the line as typed. On an item, insert it and stay on the line — folders included, so a mis-highlight never cds away; a second Enter runs the line |
| → | Accept the grey ghost text |
| Esc / Ctrl+G | Close the popup and its ghost text |
zsh ──────────────────────────────┐ ┌─ nervd (daemon) ────────────────┐
│ ZLE widget (_nerv.zsh) │ UDS │ SpecRegistry — lazy, LRU-bound │
│ every keystroke: ├───────►│ specs ship in the package │
│ nerv _complete "git ch" 6 │ │ (715 specs, 15 MB gzipped) │
│ │◄───────┤ generators — git branch, │
│ renders ghost + popup │ 5-field│ npm scripts, … (cached, 800ms │
│ (raw ANSI, no alternate screen) │ lines │ hard cap, off keystroke path) │
└─────────────────────────────────┘ │ frecency ranking │
└─────────────────────────────────┘
- Specs are compiled, not interpreted. At build time a converter runs the
TypeScript specs from
withfig/autocompleteand emits plain JSON. At runtime there is no Node and no network — the daemon reads gzipped JSON off disk, lazily, with an LRU bound on both entry count and bytes. - Dynamic completions run off the keystroke path. A subprocess
(
git branch --list,cargo metadata, …) runs on a background thread with a hard 800 ms cap and a 5-second cache (60 s for generators measured slower than 50 ms), so a slow one never freezes the prompt — its result lands on the next keystroke. Well-known patterns (package.json, SSH config, AWS INI files) are read directly in Rust and never fork; a sandboxed QuickJS interpreter (~1 MB) covers the long tail of spec-defined JavaScript generators. - The widget is plain ZLE. No alternate screen, no 24-bit color, no PTY
interposition — just cursor save/restore, line clearing and the 16 ANSI
colors, so it stays inside what real terminals reliably support and takes
on your color theme. An opt-in PTY mode
(
NERV_PTY=1) exists for bash and fish.
Measured on Apple Silicon (release build, v0.1.15, 2026-09):
| Path | Latency |
|---|---|
| Engine completion, warm (p95) | 0.055 ms |
| IPC round-trip (p95) | 0.052 ms |
| CLI bridge cold start (p95) | 4.07 ms |
| Per-keystroke budget | 25 ms |
Largest spec (aws, 117 MB as JSON) — first keystroke, cold |
3.9 ms (split into 343 lazily loaded subtrees at build time) |
Numbers come from cargo test --release -p nerv-daemon --test bench_latency
and crates/nerv-engine/tests/bench_spec_load.rs; they are re-measured
before each release.
Scope is a feature. Nerv has no AI, no account or login, no
telemetry or analytics, no runtime network calls, no auto-updater,
and no webview. The CLI surface is frozen at init, start, stop,
doctor, spec list, uninstall — there is deliberately no nerv config.
These are documented non-goals, not a backlog.
One optional file, ~/.config/nerv/nerv.toml:
[matching]
mode = "fuzzy" # default: "prefix"
[derived]
enabled = false # default: truePrefix matching is the default and the contract: git co matches commit,
not checkout (checkout starts with c-h-e). Fuzzy matching is a deliberate
opt-in and only kicks in from 3 typed characters. It lets at most 4 characters
fall between two typed letters, so chk finds checkout but a query does not
match letters scattered across a long name.
[derived] controls the --help fallback: when a command has no spec in
any layer, Nerv runs <command> --help once (no shell, 1 s timeout, output
capped, cached under ~/Library/Caches/nerv/derived/) and builds a spec from
it. Set enabled = false and nothing is ever spawned. Either setting is read
once at daemon start, so restart after editing (nerv stop && nerv start).
In zsh, a command with no spec in the bundle or your overlay first gets the
candidates of its own zsh completion function, the ones Tab would show. The
function runs once per word; a command whose completion takes over 300 ms
(1 s for its first run) is not asked again in that shell. export NERV_COMPSYS=0 turns this off.
Drop a spec JSON into ~/.config/nerv/specs/ and it is layered over the
bundled set — one file per command, no rebuild. A file with the same name as
a bundled spec replaces it wholesale (no merge), so this is also how you
patch a bundled spec. examples/specs/claude.json
is a complete example (the claude CLI, which upstream never covered):
mkdir -p ~/.config/nerv/specs
cp examples/specs/claude.json ~/.config/nerv/specs/
nerv stop && nerv start # once — the dir is watched from then on
nerv doctor # → "user specs 1 in ~/.config/nerv/specs"
nerv spec list | grep '\*' # overlay rows are marked *The format is the engine's plain JSON (name / description / subcommands
/ options[].names / args), the same as
crates/nerv-engine/tests/fixtures/specs/. Edits are picked up on the next
keystroke; a broken file disables only that one command and shows up red in
nerv doctor. nerv uninstall removes the dir with the rest of
~/.config/nerv/ unless you pass --keep-config.
| Status | |
|---|---|
| zsh ≥ 5.8 | Default path — native ZLE widget |
| bash, fish | Opt-in PTY shim: NERV_PTY=1 before nerv init bash / fish |
| iTerm2, Terminal.app (incl. tmux inside them) | Guaranteed — regressions block release |
| WezTerm, Alacritty, kitty | Best-effort |
| Linux, Windows | Not yet — see roadmap |
Details and the exact ANSI contract: docs/terminal-compat.md.
Nerv occupies a specific spot: Fig's completion UX and spec corpus, with no runtime beyond a native binary. The closest projects, and how they differ:
| Project | What it is | How Nerv differs |
|---|---|---|
| Fig (discontinued 2024) | Electron desktop app with IDE-style autocomplete; the origin of the spec format | Nerv runs Fig's own engine and specs as a local daemon + zsh widget; no desktop app, no account |
Amazon Q Developer CLI (aws/amazon-q-developer-cli) |
Fig's successor: autocomplete plus agentic AI chat; requires an AWS Builder ID login | Nerv keeps only the autocomplete half — no login, no AI, no telemetry, a few MB instead of hundreds |
inshellisense (microsoft/inshellisense) |
Node.js/TypeScript tool that also consumes Fig specs; cross-platform, runs the shell inside a PTY | Nerv is Rust with no Node on the hot path, and the default zsh path is a plain ZLE widget, not a PTY wrapper |
carapace (carapace-sh/carapace-bin) |
Go multi-shell completion binary with its own spec format, hooked into each shell's native completion system | Nerv is an inline popup with descriptions and live values on every keystroke, not a Tab-triggered completer |
| zsh-autosuggestions | History-based grey ghost text | Nerv ranks its history ghost by folder and the previous command, and adds the spec popup; with the plugin loaded, the plugin draws nerv's ranked ghost as its first strategy |
| fzf-tab | Fuzzy picker over zsh's native compsys completions on Tab |
Nerv completes as you type from the Fig spec corpus, and falls back to compsys only for commands no spec covers, shown in the same popup |
Nerv is a good fit if you want Fig back on macOS + zsh with zero cloud. It is not the right tool if you need Linux or Windows today (see roadmap) or want a completer for a shell it does not support.
Is Nerv a Fig replacement?
Yes, for autocomplete on macOS with zsh. It runs the Fig completion engine
(preserved and open-sourced by AWS) and the withfig/autocomplete spec
corpus. It does not reproduce Fig's dashboard, dotfile sync, or team features.
Does Nerv send any data anywhere?
No. There are no network calls at runtime, no account, no telemetry, and no
auto-updater. Specs ship inside the Homebrew package. The only files Nerv
writes are under ~/.config/nerv/, ~/Library/Caches/nerv/, and
~/Library/Logs/nerv/, and nerv uninstall removes all of them.
Does Nerv use AI?
No. Suggestions come from static completion specs and the output of the
commands themselves (git branch --list, package.json, kubectl get).
This is a documented non-goal, not a missing feature.
Which commands does Nerv complete?
715 commands as of v0.1.15 — git, docker, kubectl, helm, aws, gcloud, npm,
yarn, pnpm, cargo, gh, brew, terraform, make, ssh and the rest of the
withfig/autocomplete corpus. nerv spec list prints the installed set.
Commands outside it use their own zsh completion function (in zsh) or a spec
derived from --help, and you can add or
override any spec with one JSON file in ~/.config/nerv/specs/.
Does Nerv work on Linux or Windows? Not yet. v1.0 is macOS on Apple Silicon only; Linux is planned for v1.x, Windows later.
Does Nerv work with oh-my-zsh, powerlevel10k, tmux, and zsh-autosuggestions?
Yes. The widget rebinds its keys after other frameworks load, aligns the
popup under a full-width powerlevel10k prompt, and is tested inside tmux.
With zsh-autosuggestions loaded, the plugin keeps drawing the inline ghost
but takes it from Nerv's ranking first (the nerv strategy), and Nerv adds
its popup; NERV_AUTOSUGGEST=0 turns the strategy off
(docs/history-suggestions.md §7).
How do I pick a suggestion from the popup? Arrow down to it and press Tab to insert it, or Enter to insert it (a folder also runs). Right-arrow accepts the grey ghost text. Every key is in Using the popup.
Can I change the popup's colors?
The border, icons and hints use your terminal theme's "bright black", so they
follow the theme. The bar and the matched letters are a fixed purple (256-color
index 134). For a popup made only of your theme's 16 colors (a "bright
magenta" bar), set NERV_POPUP_THEME=native before the nerv init line in
your ~/.zshrc.
What does Nerv record about the commands I run?
Each command you run, with its folder, exit status and the command before it,
goes into ~/Library/Caches/nerv/history.tsv (readable only by you). That file
ranks the ghost text. Nothing leaves your machine. Nerv skips what zsh itself
would not keep: a command with a leading space, or one matching
HISTORY_IGNORE. To clear it, delete the file and run nerv stop && nerv start.
Details: docs/history-suggestions.md.
Does it slow down opening a shell?
Barely. nerv init zsh --shell-script also writes the widget script it prints to
~/Library/Caches/nerv/init.zsh, and the .zshrc block sources that file rather
than starting nerv. The file carries the size, mtime and inode of the binary
that wrote it. After an upgrade it refuses to load, and the block falls back to
running nerv once, which rewrites it. An rc block written before this change
keeps the old eval; run nerv init zsh once to get the new block.
How fast is it?
The engine answers in about 0.05 ms at p95 and the whole keystroke path is
held under a 25 ms budget. Slow live completions (a brew or docker
shell-out) run on a background thread and land on the next keystroke, so
typing is never blocked.
Does Nerv need Node.js, Python, or a JavaScript runtime? No. Specs are converted from TypeScript to JSON at build time. A ~1 MB sandboxed QuickJS interpreter inside the binary handles the minority of spec-defined JavaScript generators; nothing external is required.
- v1.0 — macOS + zsh, currently in internal dogfooding. Blockers are written acceptance criteria, not vibes: latency budget, terminal matrix, error UX, trace-zero uninstall.
- v1.x — Linux; Windows later. Deeper recovery of the remaining JavaScript-closure generators.
Issues and discussions welcome at
github.com/nerv-sh/nerv/issues.
Commits require a DCO sign-off (git commit -s). Completion behavior bugs
are the most valuable reports during the alpha — a one-liner with the exact
input and what you expected is enough.
Apache-2.0 — see LICENSE.
Nerv stands on two upstream projects, gratefully:
withfig/autocomplete (the spec
corpus, MIT) and
aws/amazon-q-developer-cli-autocomplete
(the preserved Fig engine, Apache-2.0 + MIT). See NOTICE.
