A minimal CLI agent for local models, via Ollama or llama.cpp. Inspired by Claude Code and a5c-ai/babysitter.
I've only tested it with models in the 27–35B range — nothing larger.
Requires Node.js 24+ (the CLI runs its TypeScript sources directly, no build step).
- Run a backend: Ollama (
ollama pull <model>) or llama.cpp (llama-server --jinja). - Copy
.env.exampleto.env, setPROVIDERand the matching host (OLLAMA_HOSTorLLAMACPP_HOST)/MODEL. npm link, then runsupin any directory.
- Type
/to see commands (Tabcompletes): switch model, toggle plan / auto / thinking / verbose output, list saved plans, clear history;Shift+Tabcycles the mode in place. - Tuned for small local models: low default temperature, tool arguments validated against the schema with precise repair errors, the approved plan re-injected near the end of the context every turn, old tool outputs collapsed to keep the context short.
- Models without native tool calling fall back to prompt-engineered tools (
USE_NATIVE_TOOLS=false). - Every setting is an
.envvariable with a sane default — see.env.example. - Defaults to Ollama;
sup --llama(orsup --provider <name>) switches for a single run without touching.env. AGENTS.mdin the working directory is appended to the system prompt, andsup --no-system-promptdrops the system prompt entirely (role, workflow, skills,AGENTS.md) for probing a research model's raw behavior.- Optional persona (
PERSONA_FILE): a markdown character that replaces the assistant voice — role line, style rules, and a per-request reminder all hand over to it, while tools and permissions stay untouched. - Skills: Claude Code ones load as-is, from
.sup/skillsand callable as/<skill-name>; see Skills. - Paste an image from the clipboard.
- Talk to it instead of typing — see Voice.
Claude Code skills load as-is — same SKILL.md convention, multi-line description: >- parsed, unknown frontmatter fields ignored. What differs:
- They are read from
.sup/skillsin the directory you run sup in, so every project brings its own;USE_CLAUDE_SKILLS=truealso picks up.claude/skillsthere, with.sup/skillswinning a name collision. - Tools go by sup's names:
Read→read_file,Bash→run_shell,WebFetch→fetch_url, pluswrite_file,edit_file,grep,glob,web_search. $ARGUMENTS,allowed-tools, hooks, and subagents don't exist here and are ignored, so strip them.- Bundled files, subfolders included, are listed to the model automatically, so the body doesn't have to point at them.
- A small model rarely notices "this task matches a skill", so you can force one:
/skillslists them,/skills <number|name> [task]loads one, and every skill is also its own command (/aif-commit only staged files) unless a built-in command has the same name. - Shorten aggressively — one procedure, imperative steps, concrete commands, no branching like "if X, see references/y.md"; what a frontier model follows is usually too long and too branchy for a <30B one.
Two headings mean more than prose to the babysitter (needs USE_BABYSITTER=true; BABYSITTER_GATED_SKILLS is on by default), which turns them into an enforced checklist and a finish gate:
## Steps
1. [x] Bump the version in package.json.
2. Commit, tag `v<version>`, and run `npm publish`.
## Verification
- `npm run lint`- Every list item under
## Stepsbecomes a ledger step the model ticks off withledger_update, bullets included — keep notes and rules under their own heading. - [x]marks a step as already done, so an interrupted run resumes where it stopped; tick them all and the skill only runs its verification.## Verificationcommands have to pass before the model may finish, and are read from inline code,$-prefixed lines, or fenced blocks.- Such a command must start with a known runner (
npm,git,pytest,cargo, … —RUNNER_COMMANDinsrc/babysitter/parse-sections.ts) and carry no shell metacharacters, so&&and pipes are skipped. - Russian headings (
## Шаги,## Проверка) work, as does a bold**Steps**line instead of a real heading.
Speech in and out, both through local servers, so nothing leaves the machine. The servers themselves live outside this app — their scripts, systemd units, and a from-scratch setup guide (in Russian) are in voice-servers/.
- Dictation is on by default:
Ctrl+Grecords, thenEntersends what you said,Ctrl+Ginserts it without sending,Escdrops it —/sttmid-session orUSE_STT=falseat startup turns it off. - Audio is captured with
parecordand transcribed by a local faster-whisper server; how it is wired is insrc/stt/README.md(in Russian). - Text to speech is off by default (
/ttsmid-session, orUSE_TTS=trueat startup) and speaks the final answer through Silero v5, with Chatterbox and Qwen3-TTS as cloned-voice alternatives. - Thinking, tool output, proposed plans, and the report after an interrupted turn stay silent — they are written for the screen, paths and identifiers included — as do code blocks and tables.
- While speech is on, each request carries a spoken-style reminder (conversational sentences, no markdown, no dictated paths) that also makes answers a little roomier;
VOICE_MODE_SHORT=truekeeps them as short as they are with speech off. Esccuts the voice, during the request or at the prompt afterwards, as does sending the next one; a TTS server that stops answering turns speech off by itself.TTS_LEXICON_FILEpoints at an optional pronunciation lexicon —written = spokenlines applied on the way to the synthesizer only, for names it stresses or transliterates wrong.
On by default (USE_JOURNAL=true; independent of the babysitter). Every run is journaled as an event log at .sup/runs/<id>/journal.jsonl — the user requests, the model's replies, and every tool call and result. A failed write never interrupts the work.
This is what powers sup --resume (or sup --resume=<run-id> for a specific run): it loads the journal from the current directory, restores the dialog, and continues where you left off. If the babysitter was active, the step ledger is restored too.
Deterministic harness-side control that keeps the model on its checklist and verifies "all done" before accepting it. Off by default (USE_BABYSITTER=true) — see src/babysitter/README.md.
- All file tools are confined to the working directory; sensitive files (
.env, keys, credentials) are refused for both reading and writing. - Starts in auto mode, so edits (
write_file/edit_file) run unattended while non-allowlisted shell commands still ask[y / n / type feedback];USE_AUTO_MODE=falsestarts in normal mode, where edits ask too. - Network tools (
fetch_url/web_search) run without confirmation. - The shell tool is on by default (
USE_SHELL_TOOL=falseto disable). sup --read-only(orUSE_READ_ONLY_MODE=true) removeswrite_file/edit_file/run_shellfrom the toolset for the whole session.sup --dangerously-skip-permissionsbypasses every approval prompt for the whole session — only use it when you trust the model and the working directory.