Skip to content

Repository files navigation

sup

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.

Setup

Requires Node.js 24+ (the CLI runs its TypeScript sources directly, no build step).

  1. Run a backend: Ollama (ollama pull <model>) or llama.cpp (llama-server --jinja).
  2. Copy .env.example to .env, set PROVIDER and the matching host (OLLAMA_HOST or LLAMACPP_HOST)/MODEL.
  3. npm link, then run sup in any directory.

Usage

  • Type / to see commands (Tab completes): switch model, toggle plan / auto / thinking / verbose output, list saved plans, clear history; Shift+Tab cycles 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 .env variable with a sane default — see .env.example.
  • Defaults to Ollama; sup --llama (or sup --provider <name>) switches for a single run without touching .env.
  • AGENTS.md in the working directory is appended to the system prompt, and sup --no-system-prompt drops 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/skills and callable as /<skill-name>; see Skills.
  • Paste an image from the clipboard.
  • Talk to it instead of typing — see Voice.

Skills

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/skills in the directory you run sup in, so every project brings its own; USE_CLAUDE_SKILLS=true also picks up .claude/skills there, with .sup/skills winning a name collision.
  • Tools go by sup's names: Readread_file, Bashrun_shell, WebFetchfetch_url, plus write_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: /skills lists 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.

Steps and Verification

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 ## Steps becomes a ledger step the model ticks off with ledger_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.
  • ## Verification commands 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_COMMAND in src/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.

Voice

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+G records, then Enter sends what you said, Ctrl+G inserts it without sending, Esc drops it — /stt mid-session or USE_STT=false at startup turns it off.
  • Audio is captured with parecord and transcribed by a local faster-whisper server; how it is wired is in src/stt/README.md (in Russian).
  • Text to speech is off by default (/tts mid-session, or USE_TTS=true at 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=true keeps them as short as they are with speech off.
  • Esc cuts 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_FILE points at an optional pronunciation lexicon — written = spoken lines applied on the way to the synthesizer only, for names it stresses or transliterates wrong.

Run journal

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.

Babysitter mode

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.

Security

  • 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=false starts 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=false to disable).
  • sup --read-only (or USE_READ_ONLY_MODE=true) removes write_file / edit_file / run_shell from the toolset for the whole session.
  • sup --dangerously-skip-permissions bypasses every approval prompt for the whole session — only use it when you trust the model and the working directory.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages