termita
v2.10.0
Published
Local AI first copilot for your console — chat with a model that operates your shell, one approved command at a time. Local (LM Studio/Ollama), or OpenAI/Anthropic.
Maintainers
Readme
🏴☠️ termita 🇦🇷
Local AI first copilot for your console.
🏴☠️ TERMITA 🇦🇷
████████╗███████╗██████╗ ███╗ ███╗██╗████████╗ █████╗
╚══██╔══╝██╔════╝██╔══██╗████╗ ████║██║╚══██╔══╝██╔══██╗
██║ █████╗ ██████╔╝██╔████╔██║██║ ██║ ███████║
██║ ██╔══╝ ██╔══██╗██║╚██╔╝██║██║ ██║ ██╔══██║
██║ ███████╗██║ ██║██║ ╚═╝ ██║██║ ██║ ██║ ██║
╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝
Local AI first copilot for your consoletermita is a chat with a model that runs in your terminal and can operate the shell. It proposes one command at a time, explains what it does and why, and waits for you to approve it. The command runs, its real output streams into the chat, the model reads the result and responds. Then it's your turn again. One Node process, runnable over SSH on a headless box.
It talks to a local model by default — LM Studio, Ollama, or anything speaking the OpenAI-compatible API — and can also point at OpenAI or Anthropic.
termita replies in the language you write to it — English or Spanish.
- Every command that changes something is approved by you, or by an allowlist you
build.
rm -rf,dd,mkfs, fork bombs and the like always prompt, even in auto-approve mode. - Real stdout/stderr streams in line by line — you read what actually happened, not a summary of it.
- Runs against any OpenAI-compatible endpoint (LM Studio, Ollama, vLLM…), or OpenAI / Anthropic directly.
- Pure Node, no native build.
npm i -gand run it.
Requirements
- Node.js ≥ 20 (uses native
fetch,ReadableStream,AbortController) - A model endpoint: a local server (LM Studio / Ollama), or an OpenAI / Anthropic API key.
Check Node:
node --version # should be v20 or higherInstall
macOS / Linux (recommended)
npm install -g termita
termitaThat's it. (If termita isn't found, make sure your npm global bin is on $PATH —
npm bin -g shows the dir.)
Windows
termita's shell tool targets a POSIX shell (sh). On Windows, run it inside WSL2
(Ubuntu) or Git Bash, where sh is available:
# In WSL2 / Git Bash:
npm install -g termita
termitaNative PowerShell/cmd is not supported yet (the shell tool assumes sh -c).
Over SSH (the main use case)
Install on the box, then just run it — termita is a TUI that works fine over SSH:
ssh you@your-vps
npm install -g termita
termitaFrom source
git clone https://github.com/andycufari/termita2.git
cd termita2
npm install
npm run build
npm link # makes `termita` available globally
termitaFirst run & config
Just run termita. On first launch an interactive setup wizard walks you
through it — pick your provider (Local / OpenAI / Anthropic), enter the endpoint
or API key, and choose a model (fetched live from the server when possible). No
JSON editing required.
Re-run the wizard anytime with /setup inside termita.
Settings are saved to:
- Config:
~/.config/termita/config.json(respects$XDG_CONFIG_HOME) - System facts:
~/.config/termita/system.json(re-probe withtermita init)
termita doctor # check provider, endpoint, model, node version
termita init # re-probe system factsYou can still edit config.json by hand if you prefer — here's the shape:
config.json
{
"llm": {
"provider": "openai-compatible", // "openai-compatible" | "openai" | "anthropic"
"endpoint": "http://localhost:1234/v1", // base URL (for openai-compatible)
"model": "your-model-id",
"apiKey": "lm-studio", // ignored by LM Studio; required by OpenAI/Anthropic
"maxTokens": 4096,
"reasoning": false // thinking trace on/off (Qwen etc.)
},
"ui": { "theme": "neon" },
"policy": { "allowlist": [], "autoRunReadOnly": true, "autoApprove": false }
}Provider presets
LM Studio (default — local, no key needed):
"llm": {
"provider": "openai-compatible",
"endpoint": "http://localhost:1234/v1",
"model": "qwen3.6-27b",
"apiKey": "lm-studio"
}Serving LM Studio on your LAN? Enable "Serve on Local Network" (binds
0.0.0.0) and open the port, then pointendpointathttp://<host>:1234/v1.
Ollama (local):
"llm": {
"provider": "openai-compatible",
"endpoint": "http://localhost:11434/v1",
"model": "llama3.1",
"apiKey": "ollama"
}OpenAI:
"llm": {
"provider": "openai",
"model": "gpt-4o",
"apiKey": "sk-..." // or set OPENAI_API_KEY in your env
}Anthropic (Claude):
"llm": {
"provider": "anthropic",
"model": "claude-sonnet-4-6",
"apiKey": "sk-ant-..." // or set ANTHROPIC_API_KEY in your env
}API keys can come from the config or environment variables (
OPENAI_API_KEY,ANTHROPIC_API_KEY) — env wins if both are set.
Using it
You chat. When something needs the system, termita proposes a command and waits.
Approval
| Action | Key | What it does |
|------------|-------|----------------------------------------------------|
| Run | R | run this step once |
| Edit | E | tweak the command inline, then re-gate it |
| Always | A | allowlist this kind of command, then run it |
| No | N | decline; the model is told and reacts |
Navigate with ↑ / ↓ and Enter, or hit the letter directly.
Auto-approve mode (TAB or /auto): commands run without asking — except
dangerous patterns, which always prompt with a red warning. File writes auto-run too.
Esc interrupts anything: a streaming reply, a running command, or a prompt.
Run a command yourself — !
Type a line starting with ! to run a command yourself — no model round-trip,
no approval prompt. What happens depends on the command:
Plain commands (!ls, !git status, !grep …) run inside termita. Their
output streams straight into the transcript — it stays there, scrollable, like any
command output. When it finishes, a small menu lets you pick what the model hears:
─────────────────────────────────────────────
!ls -la finished
▸ tell termita you ran it (command only)
share command + output
silent — tell termita nothing
↑↓ select · enter · f share+output · e silent
─────────────────────────────────────────────Pick enter/f/e, then you're back at the input with that choice staged —
add a note ("the config isn't here") or just press enter to send it as-is. So a
throwaway lookup stays private (e), and a result worth acting on gets shared,
optionally with your own take.
Full-screen programs (!vim, !htop, !tail -f, !python, …) need the whole
terminal. termita suspends, hands it off (fully interactive — vim keys, prompts,
live TUIs, exactly as in your shell), and redraws when you exit (:wq, quit, or
Ctrl-C):
you › !vim notes.txt # your editor, full-screen
you › !git commit # opens $EDITOR, prompts work
you › !tail -f app.log # live stream — Ctrl-C to stop, back to termita
you › !cd ../other-repo # sticks — termita follows you thereThese own the screen, so there's no captured output to share — the model just
learns you ran them. termita picks which path automatically; !!command forces
the full-terminal mode for an interactive program it didn't recognize. A !cd
always sticks, so termita follows you. (No native PTY — stays pure Node.)
Attaching files — @
Reference a file with @path in your message (or drag-and-drop it onto
the terminal — that pastes the path, which termita picks up the same way). What
happens depends on the file type, decided at send time:
you › summarize @notes.md and @src/config.py
you › what's wrong in this screenshot? @~/Desktop/error.png- Text files (
.md,.txt, source code,.json, …) are read and inlined into your message, so any model can see the actual contents — including a text-only local model. Big files are trimmed to a head+tail (the model canreadthe rest). - Images (
.png,.jpg,.webp,.gif) are attached using the standard OpenAI multimodal format (a base64image_urlpart). This needs a vision model — with a text-only model loaded (most local Qwens), termita surfaces a clear "this model has no vision — load a vision model" message rather than a raw error. Point termita at a vision model (a Qwen-VL / Llava in LM Studio, or OpenAI/Anthropic) and images just work.
Paths may be quoted (@"my file.md"), use ~, or be relative to the current
directory. An @mention that isn't a real file is left in your message untouched.
Slash commands
| Command | Action |
|----------------------|-------------------------------------------------|
| /help | show commands + keys |
| /setup | re-run the provider / model setup wizard |
| /model [id] | list models / switch the active model |
| /reasoning on\|off | toggle the thinking trace |
| /auto | toggle auto-approve (same as TAB) |
| /clean | wipe transcript + history (fresh context) |
| /compact | summarize history into a note, free context |
| /context [n] | show / set the model context window (gauge) |
| /mouse [on\|off] | wheel-scroll vs native drag-select / copy |
| /memory […] | show / manage what termita remembers |
| /cognito [on\|off] | incognito — no memory saved or recalled |
| /allow | list allowlist rules |
| /websearch [key] | set/show Brave web-search key (off to clear) |
| /credits | who made this |
| /quit | exit (also Ctrl-C twice) |
Memory
termita can remember durable facts you can't discover by running a command — a preference, a project detail, a constraint. Just tell it:
you › remember I use pnpm here and deploy with fly
term › noted. → memory: "Uses pnpm; deploys via fly.io"The model distills what you said into one line and saves it (via a memory
tool, no approval needed). Notes are project-scoped by default (tied to the
current directory) or global (machine-wide prefs). Saved notes are shown to
the model at the top of every turn, so it stops re-asking.
Manage them by hand too:
/memory list active notes (global + this project)
/memory add <note> save a project note (add -g for a global one)
/memory forget <n> drop note #n
/memory clear wipe everythingStored at ~/.config/termita/memory.json. termita does not auto-save things
it can probe (whether a tool is installed, a service is running) — it runs a
command for those.
Incognito: /cognito on is a session blackout — nothing is saved and
nothing is recalled, so the model runs as if memory were empty. /cognito off
brings it back. Resets to off each launch. The footer shows 🕶️ while it's on.
Web search
Give the model live web access with a Brave Search API
key: /websearch <key> (or set BRAVE_API_KEY). Once a key is set, the model
calls a read-only websearch tool on its own whenever an answer needs current
info — latest versions, release dates, recent events, unfamiliar errors — and
cites the URLs it used. Without a key the tool stays hidden from the model.
Scrolling output
termita runs in the alternate screen (no resize ghosting), so it draws its own
scrollable transcript. Scroll with the mouse wheel, PageUp/PageDown,
Ctrl+↑/Ctrl+↓, Home (oldest) / End (latest) — it scrolls by rows, so the
wheel moves evenly and Home lands on real history instead of a blank top. New
output auto-follows the bottom until you scroll up.
To select/copy text with the mouse while wheel-scroll is on, hold a modifier
while dragging — Option on macOS (iTerm2/Terminal.app), Shift on most
Linux terminals. Prefer plain drag-to-select? Run /mouse off to release
mouse capture (native selection + copy-paste work everywhere; the wheel then uses
your terminal's own scrollback). /mouse on brings wheel-scroll back. The
setting persists.
Safety model
shellandwriteask before running;readandgrepauto-run (read-only).- Allowlist is persisted per normalized command shape. Broad/slow commands
(
find,du,tar,rsync…) key on command + path, so blessingfind .does not also blessfind /. - A built-in danger list (
rm -rf,mkfs,dd of=/dev/…, fork bombs,curl | sh, destructive git, …) can never be silently auto-allowed — it always prompts with a loud warning, even in auto-approve. - Remote endpoints: command output is fed back to the model. If your endpoint is non-local, that output leaves your machine. Keep it local (LM Studio/Ollama) for fully offline use.
Architecture
Three clean layers, each swappable and testable:
UI (Ink/React) renders the transcript, captures input + approval keys
Engine the copilot loop — owns history, drives the LLM, runs ONE
tool per round, awaits a human decision, feeds result back
Providers + Tools streaming clients (OpenAI-compatible / OpenAI / Anthropic),
the shell/read/write/grep tools, the approval gateThe engine emits events and awaits decisions; the UI renders events and supplies decisions. They talk through an async interface, never direct calls.
src/
cli.js entry: init | doctor | TUI
app.jsx Ink root
engine/ copilot loop + event bus
providers/ openai.js, anthropic.js + a factory
tools/ shell (live stream + cwd), fs (read/write/grep), schemas
policy/gate.js allowlist + danger list + auto-approve
config/ config.json + system probe
prompt/system.js persona + machine facts
ui/ theme, components, banner
build.js esbuild bundle → dist/cli.jsDevelopment
npm run build # bundle src → dist/cli.js
npm run dev # build + run
npm start # run the built bundleLicense
MIT
