weezcode
v0.1.0
Published
WeezCode — a terminal-based AI coding agent for any OpenAI-compatible chat-completions endpoint (Ollama, LM Studio, OpenRouter, etc.).
Maintainers
Readme
WeezCode
A terminal-based AI coding agent in the style of Claude Code / Codex / Cursor, backed by any OpenAI-compatible chat-completions endpoint — Ollama, LM Studio, OpenRouter, vLLM, llama.cpp, or your own self-hosted gateway. Run it locally, point it at a model, and it reads, edits, runs, and reviews code with a per-tool permission model that defaults to safe.
Why WeezCode
- Provider-agnostic. Works with any OpenAI-compatible endpoint. Tested against Ollama, OpenRouter, LM Studio, and llama.cpp.
- Permission-first safety model. Every mutating tool (
write_file,edit_file,bash) goes through an explicit permission prompt with{ once, always, session, deny }verdicts. Hard-rejected patterns (rm -rf /,sudo,curl|sh, heredocs) never run, even if the user approves. - Out-of-cwd is not auto-allowed.
cat /etc/passwdfrom the bash tool is downgraded to "ask" even thoughcatis in the allowlist. All file tools usefs.realpathso symlinks can't escape the sandbox. - Open source, no telemetry, no phone-home. Reads
~/.weezcode/config.jsonand your LLM endpoint, and nothing else. SeeSECURITY.mdfor the threat model. - Same tool surface as Claude Code, fewer surprises.
read_file,write_file,edit_file,list_directory,search,bash, plus vision (!img <path>), snippets (/snippet), MCP (weezcode mcp), and--jsonfor scripting.
Features
- Interactive REPL with streaming responses (Ink + React) and live thinking display
- 6 tools the model can call:
read_file,write_file,edit_file,list_directory,search,bash - Safety-first bash: read-only commands auto-run, anything else prompts; destructive patterns are hard-rejected; out-of-cwd reads are downgraded to "ask"
- Unified diff in the
write_file/edit_filepermission prompt — see exactly what changes before you approve - Permission prompts for
write_fileandedit_file— nothing on disk changes without your approval; "All writes (this session)" option to grant blanket write permission for the rest of the session - Headless mode (
-p) for scripts and CI, with--jsonNDJSON event stream - Slash command autocomplete — type
/to see matching commands, ↑/↓ to navigate, Enter to execute - Model picker —
/modelopens a live list of models from/v1/models - Token usage in the status row, with per-turn + cumulative counts
- Retry with backoff on transient API errors (network, 429, 5xx)
- MCP support (
weezcode mcp list|add|remove|test) — register stdio MCP servers and let their tools be called by the model - Vision —
!img <path>attaches an image to a user message - Snippets —
/snippet save <name> <body>for reusable prompts - Shell completions —
weezcode completion bash | zsh | fish
Requirements
- Node.js 20+ (the
bin/weezcodeshim refuses to run on older versions) ripgrep(rg) on$PATH— used by thesearchtool- An OpenAI-compatible chat-completions endpoint
Install
From npm (recommended for users)
npm install -g weezcode
weezcode # starts the REPLFrom source (recommended for contributors)
git clone https://github.com/akanda/weezcode.git
cd weezcode
npm install # builds dist/ via the `prepare` script
npm link # exposes `weezcode` on your PATHFrom Docker
docker build -t weezcode .
docker run -it --rm \
-v $PWD:/workspace \
-v ~/.weezcode:/home/node/.weezcode \
weezcodeThe Dockerfile is multi-stage (Node 20 slim, tini, ripgrep). See Dockerfile for details.
Shell completions
# bash — add to ~/.bashrc
eval "$(weezcode completion bash)"
# zsh — add to ~/.zshrc
eval "$(weezcode completion zsh)"
# Then: autoload -U compinit && compinit
# fish — add to ~/.config/fish/config.fish
weezcode completion fish | sourceConfiguration
Resolution order: CLI flag → env var → ~/.weezcode/config.json → built-in default.
| Setting | CLI flag | Env var | Default |
|----------------|--------------|----------------------|----------------------------------|
| API key | (none) | WEEZCODE_API_KEY | (empty) |
| Base URL | (none) | WEEZCODE_BASE_URL | http://localhost:11434/v1 |
| Model | -m <name> | WEEZCODE_MODEL | llama3.1:8b |
| Profile | (none) | WEEZCODE_PROFILE | (none — uses config.json) |
WEEZCODE_PROFILE=<name> swaps the config file to ~/.weezcode/config.<name>.json so you can keep multiple side-by-side (e.g. work, home, local-ollama). All other env vars and CLI flags still apply on top.
Option A: in-REPL setup (recommended for first run)
Just run weezcode and type /setup. The REPL walks you through base URL, API key, and model, tests the connection, and saves to ~/.weezcode/config.json. Changes take effect immediately — no restart needed.
Option B: non-interactive setup (for scripts / Docker)
weezcode --setup --setup-url https://your-tunnel.example.com/v1 \
--setup-key sk-your-key \
--setup-model qwen2.5:14bOr with plain readline prompts:
weezcode --setup
# Base URL: https://your-tunnel.example.com/v1
# API key: sk-your-key
# Model: qwen2.5:14b
# Saved to ~/.weezcode/config.json.Option C: write the config file directly
~/.weezcode/config.json example (mode 0600 recommended for the API key):
{
"apiKey": "sk-your-cloudflare-tunnel-key",
"baseUrl": "https://weez-llm.example.com/v1",
"model": "qwen2.5:14b"
}The API key is sent as both Authorization: Bearer <key> and a custom x-api-key: <key> header (the Cloudflare-tunnel-friendly form).
Option D: taste file (per-user preferences)
Drop a markdown file at ~/.weezcode/taste.md with key: value lines for non-secret preferences:
# My WeezCode preferences
show_thinking: off
quiet: on
system_prompt: |
Always respond in haiku. Be terse.Recognised keys: show_thinking (on/off), quiet (on/off), system_prompt (free-form block, appended to the built-in prompt). Lines starting with # and blank lines are ignored. Missing file → use defaults.
Usage
weezcode # interactive REPL
weezcode "what's in this directory?" # one-shot (deprecated — use -p)
weezcode -p "read package.json" # headless, prints result, exits
weezcode -m llama3.1:70b # override model for this run
weezcode --list-models # GET /v1/models and print IDs
weezcode -p "hi" --no-thinking # strip <think>…</think> from output
weezcode -p "hi" --show-thinking # print reasoning to stderr as it streams
weezcode -p "fix the bug" --diff # after running, print git diff to stderr
weezcode -p "summarize" --tool-choice none # pure chat, no tool calls
weezcode -p "..." --system-prompt-file p.md # custom system prompt
weezcode -p "..." --json # NDJSON event stream for piping
WEEZCODE_VERBOSE=1 weezcode -p "hi" # stream tool calls to stderr
weezcode -q # REPL: hide banner and help row
WEEZCODE_PROFILE=work weezcode -p "..." # use a separate config profile
weezcode install-hooks # write .git/hooks/pre-commit (typecheck + tests)
weezcode doctor # diagnose common setup problems (rg, config, model, taste)
weezcode completion bash # print a shell completion script (bash | zsh | fish)
weezcode mcp add fs npx -y mcp-filesystem /path # register an MCP server
weezcode mcp list # show registered MCP servers
weezcode mcp test fs # verify the MCP handshake + tool discovery
weezcode --model-smart big --model-fast small # multi-model routing (first/tool → smart, length → fast)REPL shortcuts
- Enter — send the message
- Tab — in the
/command menu, fill the input with the highlighted command - ↑/↓ — navigate the command menu (or scroll the model picker)
- Esc — close the command menu or model picker
- Ctrl+C — first press aborts the current stream; second press within 1500ms exits
- Ctrl+D — first press prints a hint, second press within 1500ms exits
Slash commands
Type / to open the autocomplete menu, then use ↑/↓ to navigate and Enter to execute. Some commands can also be typed directly.
| Command | Effect |
|--------------------|-----------------------------------------------------------------------------------------|
| /help or ? | Show the full command list in-REPL |
| /setup | Re-run the setup wizard (changes take effect immediately) |
| /model <name> | Switch the model for the current session (no name → open the model picker) |
| /model smart <n> | Set the smart model for the multi-model router (first turn, after tool turns) |
| /model fast <n> | Set the fast model for the multi-model router (length-continuation turns) |
| /thinking | Toggle the visibility of the model's <think> reasoning blocks |
| /reset (or /new)| Clear the conversation log and token usage. Config is preserved. Refused mid-stream. |
| /undo | Revert the most recent write_file or edit_file. Single-slot snapshot — second mutating tool replaces it. |
| /stats | Show session stats: turn count, tool call counts, total tool time, wall time, tokens |
| /snippet <name> | Show a saved snippet |
| /snippet save <name> <body> | Save a snippet to ~/.weezcode/snippets.json |
| /snippet list | List all saved snippets |
| /snippet delete <name> | Delete a saved snippet |
| /copy | Copy the most recent assistant message to the clipboard (falls back to a temp file) |
| /refresh | Explain how to ask the model to re-read files (no in-memory cache to clear) |
| /exit | Quit WeezCode |
/setup and /model are also accessible mid-stream (they don't queue). All commands appear in the conversation history like normal messages.
Token usage
The status row shows cumulative token usage and a per-turn delta:
WeezCode · model qwen3-coder:30b · cwd ~/proj · thinking on · 12.3k tok (in 8.0k / out 4.3k) · last +1.0kIf the model doesn't report usage (some local Ollama builds), the line is omitted.
Model thinking
Reasoning-capable models (Qwen, DeepSeek, etc.) emit their chain-of-thought inside <think>…</think> blocks. WeezCode detects these in the stream and renders them in a dimmed · thinking block above the visible answer, updated in real time. Toggle with /thinking or --no-thinking (headless). Three block formats are recognised: <think>…</think>, <antml:thinking>…</antml:thinking>, and OpenAI's <|channel|>analysis<|message|>…<|end|>.
By default the full content (with thinking) is kept in the message log so the model sees its own reasoning on the next turn. --no-thinking strips it from both the rendered output and the log. Unclosed think blocks are stripped from the log automatically (so the model doesn't see its own dangling <think> on the next turn).
Vision
Attach an image to your message by prefixing with !img <path> (or !image <path>). Multiple images are supported. The path must be absolute; local files are base64-encoded and sent as image_url data URLs. You can also pass an http(s):// URL directly. Supported file types: .jpg, .jpeg, .png, .gif, .webp, .bmp (max 10 MB each).
!img /tmp/screenshot.png what's wrong with this UI?
!img a.png !img b.png compare these two layouts
!img "~/Desktop/photo.png" describe this picture
!img https://example.com/diagram.png walk me through thisThe model can describe, transcribe, or reason about the image. The image is sent alongside your text as a vision message; the image isn't stored on disk by WeezCode.
Bash permission model
| Class of command | Behavior |
|-------------------------|-----------------------------------------------------------|
| ls, cat README.md, pwd, git status, … | Auto-runs (read-only) |
| cat /etc/passwd | Prompts (out-of-cwd reads can't auto-run) |
| Anything else | Prompts: once / always (session) / deny |
| rm -rf /, sudo, curl|sh, mkfs, dd of=/dev/, redirects, … | Hard-rejected, never runs |
Multi-command strings (a && b, a; b) are classified per-segment. If any segment is dangerous, the whole command is denied. After approval, only the first segment runs (the rest gets a [weezcode: only the first segment ran] warning so silent drops are visible). Out-of-cwd file reads (cat /etc/passwd) are downgraded to "ask" even when the command itself is allowlisted.
Permission model for write_file and edit_file
Both tools ask for explicit permission before touching disk. The "always" key is the absolute file path (narrower than bash, which keys on the first command token — see the table above). "Always allow write to foo.ts" only auto-allows that one file.
Development
npm run dev # tsx-watched run of src/cli.tsx
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm run format # prettier --write
npm test # node --test over test/*.test.ts
npm run build # tsc → dist/See CONTRIBUTING.md for project conventions and how to add a tool or slash command.
Limitations (v1)
- Sessions are in-memory only. Every REPL launch starts fresh — there's no
-c/--continue/--resumeyet (JSONL persistence is v2). Use themessageslog inside the REPL for the current session. - No sub-agents. MCP support is at the foundation level (
weezcode mcp list|add|remove|test); full tool-registry integration is a follow-up. - Vision: read-only. You can attach images to a message via
!img <path>(or!image <path>); the model can describe them. The tools themselves don't produce images, so model output can't include image generation. - Bash multi-segment runs only the first segment after a prompt approval (deliberate safety tradeoff — the model is expected to issue separate bash calls for follow-ups). A safety note is appended to the output.
- Single binary, no plugins.
Audit
A full production audit lives in AUDIT.md, with items graded 🔴 P0 / 🟠 P1 / 🟡 P2 / 🟢 P3. The P0 ship-blockers are all closed; the remaining items are polish and observability.
Changelog
See CHANGELOG.md for the full release history and the versioning policy. We follow semver.
For maintainers
The maintainer's runbook for cutting a release is in
docs/PUBLISHING.md. The short version:
- One-time setup: create an npm account with 2FA, claim the
weezcodename (a placeholder0.0.1is fine), enable Trusted Publishing on npmjs.com, create a GitHubnpm-publishenvironment with required reviewers. - Cut a release: merge to
mainwith a green CI, thengit tag -s v0.X.Y && git push --tags. The publish workflow runs the full check suite, requests your approval in thenpm-publishenvironment, and publishes with provenance. - Verify: from a clean shell,
npm install -g weezcodeand runweezcode --version+weezcode doctor.
For a deeper look at how releases flow (release-please,
Trusted Publishers, why we don't use NPM_TOKEN), see the
runbook.
License
MIT — see LICENSE.
