← All tools

dieharders/obrew-cli

AI Agent
Popularity 65 Updated AI & Agents

Run local GGUF models as a headless agent with sandboxed tools and MCP, streaming JSON lines from one Bun binary.

githubauto-collected

Installation

A directly usable install command is not verified yet. Check the project documentation or releases.

Obrew CLI

Local AI models as a headless agent CLI. obrew-cli runs GGUF models on your machine through llama.cpp's llama-server, gives the model a fenced set of tools plus any MCP servers you point it at, and streams what happens as JSON lines.

The Python obrew-engine is being ported here in phases. No Python, no webview, no REPL: one Bun binary.

Quickstart

bun install
bun run dev login                      # installs llama-server + pulls the default model
bun run dev exec "say hi"              # human output
bun run dev exec --json "say hi"       # one JSON event per line
bun run dev exec resume <id> "again"   # continue a session

Commands

obrew exec [resume <sessionId>] [--json] [--model <id>] [--effort low|medium|high]
           [-c key=value ...] [--cwd <dir>] [--system-prompt <t> | --system-prompt-file <p>]
           [--mcp-server name=<url> ...] [--tools Read,Grep,Glob|none]
           [--image <path> ...] [--vision] ("<prompt>" | --input-format json)
obrew auth status [--json]
obrew login [--model <repo[:file]>] [--mmproj | --no-mmproj] [--json]
obrew models list|pull <repo>[:file] [--mmproj]|rm <id>|use <id>
obrew engine install [--variant cuda|cpu|vulkan|metal] | status | stop
obrew sessions list|show <id>|rm <id>

Every run-time knob is also a -c key=value pair (thinking, maxtokens, temperature, ctxsize, ngpulayers, toolmode, ...), so exec and exec resume accept the identical flag set.

A host driving exec should pass the turn's text on stdin rather than in argv: with --input-format json, exec reads one JSON object, {"prompt": "...", "systemPrompt": "...", "outputSchema": {...}} (systemPrompt and outputSchema optional), and then EOF. That keeps a long prompt, or a large schema, clear of the ~32 KB Windows allows for a whole command line, and nothing has to be written to a temp file.

Which model runs

exec with no --model runs the default: the one chosen with obrew models use <id, else the built-in Gemma 4 E2B when the machine has it, else whatever else it has — so a machine set up with obrew models pull alone still runs. A plain pull never takes the default away from a model that already holds it, and removing the default falls back to a model that is left rather than to one that was never downloaded. obrew models list marks the default with , and obrew auth status reports it and whether it is installed.

obrew login installs that same default, so a choice survives the next login instead of being replaced by the built-in model; login --model <repo[:file] names one and makes it the default.

Tools are constrained, always

The model never emits free-form tool JSON. With a chat template that knows about tools (Qwen 2.5/3, Llama 3.x, Hermes, Mistral, DeepSeek), obrew sends the tool schemas and llama.cpp decodes the call under a grammar built from them. With any other template, or -c toolmode=universal, choosing a tool and filling its arguments are two separate requests, each decoded under a JSON schema. Either way the arguments are validated against the tool's schema before it runs, and an invalid call is repaired once under that schema. A failing tool is reported back to the model as a result; it never ends the run.

--output-schema '{...}' (or @file.json, or outputSchema on stdin) decodes the final answer under a schema and puts the parsed value on turn.completed.output; --grammar @file.gbnf does the same with GBNF. With tools available the model may use them first and only the answer is constrained; with --tools none the constrained request is the whole turn.

MCP servers

--mcp-server name=http://127.0.0.1:1234/mcp dials a streamable-HTTP MCP server for this run only; --mcp-server name=stdio:<command … launches one as a child process. Its tools appear to the model as mcp<name<tool, get the same schema validation as built-ins, and a result the server marks isError is fed back to the model rather than ending the run.

The warm engine