e034a93815 perf(tui): bounded file read, LRU rendering cache, and size caps for full-file view (#1203)
* perf(tui): bounded file read, LRU rendering cache, and size caps for full-file view

- Bound full-file view reads (4000 lines, 1 MiB total, 4 KiB per line) with async loading and an LRU render cache capped at 64 entries and 8 MiB.
- Refresh both BTW surfaces when a tool result (such as write_stdin) carries both exact changed paths and an unknown-scope or aggregated directory summary outside Git.
- Gate Run details FILES mouse hit-testing on Run details being the visible foreground overlay so higher-priority modals (speech API-key prompt, leader help) block clicks behind them.

(cherry picked from commit 100d847aaf)

* fix(tui): gate full-view read errors on snapshot validity

renderFileViewFull returned renderedContent whenever hasError was set,
before the snapshot-validity check that governs every other completed
snapshot. A /btw fork of a full view that had settled on a read error
invalidates the inherited snapshot and clears renderedContent, but the
error latch survived, so the side pane rendered an empty body instead of
the loading placeholder until its own load landed. Reloads of an errored
view (resize, refresh, theme) likewise kept showing the stale error.

Serve the error snapshot through the same validity predicate as content
snapshots, so a pending or invalidated load falls through to "Loading…".
Error displays still do not count as cache hits.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit a1887f4b03)

* fix(tui): keep file-view revisions monotonic and revalidate on git sweeps

Shared path revisions reset to zero on every cache purge, while each surface
kept its own required revision. Three sites could also raise that revision
past the shared one by incrementing it locally. After a purge, an exact-path
edit made on the other BTW surface could produce a shared revision no higher
than this surface's own. recoverInvalidatedFileView then saw nothing new, and
the view stayed stale. Revisions now come from one cache-wide clock that
survives purges. A purge raises the epoch floor to that clock instead of
resetting it, and each surface adopts the revision the cache returns.

Every git sweep invalidated each swept path and forced a full refresh. The
swept list is cumulative since the baseline, and each live sweep is launched
beside the tool-result, turn-end or BTW-return handling that already reloads
or rechecks the open view. The sweep that followed therefore reread and
re-highlighted an unchanged file and flashed "Loading…". A sweep now
revalidates the open view instead. It waits for an in-flight load to land,
then rereads the bounded source in the background and compares it with the
displayed snapshot's identity (hash and truncation flags). The view reloads
only if the bytes changed. A mutation the swept list omits is still caught.

Regression tests (all fail on 311290c0 and pass here):
- TestFileViewCrossSurfaceExactEditAfterPurgeReloadsOtherSurface
- TestFileViewGitSweepRevalidatesWithoutDuplicateReload
- TestFileViewGitSweepDuringLoadDefersToThatLoad
- TestFileViewStaleRevalidationDoesNotReloadNewerSnapshot
- TestFileViewCachePurgeResetsPathRevisions (now asserts monotonicity)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* refactor(tui): return source identity from loadAndRender directly

1ce4f96e added loadAndRenderSource and left loadAndRender as a two-value
wrapper. After that change only tests called the wrapper, so make deadcode
newly reported it. Fold the wrapper away: loadAndRender now returns the
rendered text, the source identity, and the error, and its callers take the
value they need. Behavior is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(tui): close three file-view staleness gaps from review

- fileViewSourceHash joined lines with separators, so an empty file (no
  lines) and a file holding one newline (one empty line) hashed alike.
  Sweep revalidation compares identities without the file size, so adding
  or removing that blank line went undetected. Every line is now
  terminated, which keeps the encoding injective.
- A load cancelled inside the bounded read returned errFileViewSuperseded
  with no lines. loadAndRender then treated it as a read error and evicted
  the path, discarding an entry a newer load had already committed. The
  sentinel now returns before the eviction branch.
- A cancelled run keeps going until it finishes, and agentRowMsg ignores its
  late tool results. Its completion only flushed session events, so a write
  that finished during cancellation left an open full-file view stale. The
  flush completion now runs the unknown-scope refresh.

Regression tests (each fails on ca1cdaf3 and passes here):
- TestFileViewGitSweepDetectsBlankLineAddedToEmptyFile
- TestFileViewSupersededReadKeepsNewerCacheEntry
- TestFileViewCancelledRunCompletionRefreshesOpenView

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(tui): gate Run details Enter on the visible overlay

The Run details Enter drill-in keyed off the raw runDetailsOpen flag,
while mouse FILES picks use runDetailsVisible(). The flag outlives the
overlay: clicking a specialist card beside Run details enters the
subchat, which hides the overlay but leaves runDetailsOpen set, and
Enter then opened the parent's file view from inside the subchat.

Use runDetailsVisible() and the shared runDetailsInnerWidth() geometry
on the keyboard path, matching the mouse path. The branch's existing
Esc/Ctrl+B and key-swallow behavior is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: hazyhaar <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-07 10:28:29 -08:00

Zero

A terminal coding agent you own.

Twigpine/zero | Trendshift Twigpine/zero | Trendshift Twigpine/zero | Trendshift

license Go 1.26.10+ 25+ providers Discord Vercel OSS Program
English | 中文

Zero is an AI coding agent for your local terminal. It can inspect a repository, edit files, run commands, use browser/terminal helpers, and keep durable local sessions while you choose the model and the permission level.

zero
zero exec "fix the failing test in ./pkg"
zero exec --output-format stream-json < turns.jsonl

Partners

Twigpine logo Xiaomi MiMo logo Novita AI logo Concentrate logo Exa logo
Twigpine Xiaomi MiMo Novita AI Concentrate Exa
GMI Cloud logo Vercel logo
GMI Cloud Vercel

Why Zero

  • Use the model you want. Bring OpenAI, Anthropic, Gemini, Groq, OpenRouter, DeepSeek, Mistral, xAI, Qwen, Kimi, GitHub Models, Ollama, LM Studio, Atomic Chat, or any OpenAI-/Anthropic-compatible endpoint.
  • Stay in control. File writes, shell commands, network access, and out-of-workspace writes go through Zero's permission and sandbox policy.
  • Works in the terminal. The TUI has model/provider pickers, image input, slash commands, live plan/tool rendering, scrollback, themes, and resume/fork support.
  • Works without the TUI. zero exec is scriptable, supports text/JSON/ stream-JSON I/O, isolated worktrees, spec-first runs, and meaningful exit codes for CI.
  • Keeps context local. Sessions are stored on disk, searchable, resumable, and never uploaded as telemetry by Zero.
  • Extensible when you need it. Use MCP servers, skills, plugins, hooks, and specialist subagents from the same CLI.

Install

npm

npm install -g @twigpine/zero
zero

The npm package is a small wrapper whose platform build (Linux and macOS on x64/arm64, Windows on x64 — including the browser/terminal control helpers) installs as an optional dependency straight from the npm registry — no install scripts, no downloads outside npm. Bun, pnpm, and yarn work the same way with no trust or approval steps. Installs that skip optional dependencies (--omit=optional) still work: the wrapper fetches the binary from the matching GitHub Release whenever it is missing. Windows on ARM runs the x64 build under emulation. See docs/NPM_PACKAGING.md for how the package is put together.

Installed Zero as @gitlawb/zero? The package moved to @twigpine/zero in 0.10.0 and the old name gets no further releases. Both packages provide the zero command, so quit any running zero, then remove the old package first:

npm uninstall -g @gitlawb/zero
npm install -g @twigpine/zero

Your config, sessions, and logins are kept; they live outside the package. For Bun, pnpm, and yarn, see docs/INSTALL.md.

Install scripts

Linux/macOS:

curl -fsSL https://raw.githubusercontent.com/Twigpine/zero/main/scripts/install.sh | bash

Windows PowerShell:

irm https://raw.githubusercontent.com/Twigpine/zero/main/scripts/install.ps1 | iex

From source

Source builds require Go 1.26.10+.

git clone https://github.com/Twigpine/zero.git
cd zero
go run ./cmd/zero

Release installers and the npm wrapper require published GitHub Release assets. If you are testing before the first public release, build from source:

go build -o zero ./cmd/zero

On Linux, native sandboxing needs Bubblewrap. The main zero binary re-executes itself as the helper when zero-linux-sandbox is absent. A separate helper next to zero or on PATH still takes precedence:

go build -o zero-linux-sandbox ./cmd/zero-linux-sandbox   # optional
go build -o zero-seccomp ./cmd/zero-seccomp               # optional compatibility wrapper

Put zero on PATH (~/.local/bin is a good default). macOS does not need an extra helper binary. Windows source builds can use the main zero.exe as their sandbox helper; release archives still ship standalone Windows helper executables.

More install details: docs/INSTALL.md.

First Run

Start the TUI:

zero

The setup wizard helps you pick a provider and model. You can also configure providers from the command line:

zero setup
zero providers list
zero models list
zero doctor

For API providers, set the matching environment variable before setup or enter the key in the wizard:

export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=...
export GEMINI_API_KEY=...
export AIMLAPI_API_KEY=...
export LONGCAT_API_KEY=...
export FIREWORKS_API_KEY=...
export GMI_API_KEY=...
export CONCENTRATE_API_KEY=...
export MINIMAX_API_KEY=...
export MINIMAXI_API_KEY=...

To configure AI/ML API directly, run:

zero providers setup aimlapi --set-active

To configure Meituan LongCat (LongCat-2.0) directly, run:

zero providers setup longcat --set-active

To configure Fireworks AI directly, run:

zero providers setup fireworks --set-active

To configure GMI Cloud directly, run:

zero providers setup gmicloud --set-active

To configure Concentrate directly, run:

zero providers setup concentrate --set-active

MiniMax presets use the Anthropic-compatible endpoints for the global and China regions:

zero providers add minimax --set-active
zero providers add minimaxi-cn --set-active

To use the OpenAI-compatible endpoints instead, add a custom compatible profile for the required region:

zero providers add custom-openai-compatible \
  --name minimax-openai \
  --model MiniMax-M3 \
  --base-url https://api.minimax.io/v1 \
  --api-key-env MINIMAX_API_KEY \
  --set-active

zero providers add custom-openai-compatible \
  --name minimax-cn-openai \
  --model MiniMax-M3 \
  --base-url https://api.minimaxi.com/v1 \
  --api-key-env MINIMAXI_API_KEY \
  --set-active

For local models, run Ollama, LM Studio, or the Atomic Chat desktop app, then use zero setup or zero providers detect. For Atomic Chat, load a model and enable its local OpenAI-compatible API (default http://127.0.0.1:1337/v1). Choose atomic-chat-local; detection includes the loaded model ID in the add command. If no usable ID is discovered, load a model and retry. Model IDs requiring shell-specific quoting use interactive setup.

Daily Use

Interactive TUI

zero

Useful controls:

Control Action
Enter send the prompt
/ open slash-command suggestions
Ctrl+X then letter common slash commands (e.g. m → /model; Ctrl+X ? for full list)
Ctrl+P / Ctrl+N previous / next item in menus (arrows still work)
Shift+Tab cycle permission mode
Ctrl+B show/hide the sidebar
Ctrl+C cancel, exit, or return from a /btw conversation

Common slash commands:

Command Purpose
/model, /provider switch the active model/provider
/spec, /plan draft and review a plan before building
/image attach an image for vision-capable models
/resume, /rewind continue or roll back local sessions
/new start a fresh session in place (previous session stays on disk)
/btw [question] ask in an isolated fork without adding the side conversation to the main session
/loop repeat a prompt or custom /command on an interval (/loop 5m /babysit-prs) or self-paced
/compact, /context manage context usage
/permissions, /tools inspect available tools and policy
/add-dir allow an extra write directory for this session
/theme, /doctor, /config adjust appearance and inspect setup

Headless exec

zero exec "explain internal/agent/loop.go"
zero exec --model claude-sonnet-4.5 "refactor the config loader"
zero exec --use-spec "add rate limiting to the API client"
zero exec --worktree "try the migration in an isolated worktree"
zero exec --resume
zero exec --fork <session-id> "try the other approach"

Programmatic use:

zero exec --input-format stream-json --output-format stream-json < turns.jsonl

The stream-JSON contract is documented in docs/STREAM_JSON_PROTOCOL.md.

Safety Model

Zero is designed to make side effects visible.

  • Workspace reads are allowed by default.
  • File writes are limited to the workspace unless you grant another directory.
  • Shell commands, network access, destructive commands, and elevated actions are permission-gated.
  • --add-dir <path> and /add-dir <path> grant additional write roots without giving the agent the whole filesystem.
  • Unsafe/autonomous modes are explicit opt-ins.
  • Secrets are redacted from tool output and logs where Zero controls the surface.

Example:

zero --add-dir ../docs-site
zero exec --add-dir ../shared "update both repos"

Sandbox behavior can be inspected with:

zero sandbox policy
zero sandbox grants list

Web And Local Control

Zero includes local file/search/edit/shell tools, web_fetch for public URLs, and MCP support for additional tools.

web_fetch refuses loopback, private and other special-use addresses: it checks the URL before asking permission, resolves the host, and dials the address it validated so a name cannot resolve to something else in between.

If HTTP_PROXY/HTTPS_PROXY is set, web_fetch and the provider connectivity probe use it, and that last step changes: the proxy is dialed and the target hostname is sent to it, so the proxy decides which address the request actually reaches. The URL is still checked and resolved locally first, but a proxy that answers differently can reach a private service. A forward proxy already sees and can rewrite every request through it, so this is the trust you accept by configuring one. Leave the variables unset for the checks to be enforced end to end.

The built-in web_search tool is registered only when a search backend is configured: ZERO_WEBSEARCH_BASE_URL (with an optional ZERO_WEBSEARCH_API_KEY) for a generic JSON endpoint, ZERO_WEBSEARCH_PROVIDER=searxng with a SearXNG base URL, or the AI/ML API Toolbox:

export ZERO_WEBSEARCH_PROVIDER=aimlapi

The AI/ML API backend runs the Toolbox tool web/search, and every search is billed to your AI/ML API balance: about $0.0013 per result, with a documented minimum of $0.0091 per search. Each search is sent with a $0.025 spending cap, which the Toolbox enforces. It is used only when ZERO_WEBSEARCH_PROVIDER is set to aimlapi; an AI/ML API key alone never enables it. The key is read from your user configuration only; provider profiles in a project .zero config are not used. It is the aimlapi profile's key (the active profile when it is aimlapi, otherwise the first aimlapi profile), including a key in the credential store, or AIMLAPI_API_KEY when no aimlapi profile exists. It is resolved again on every search; ZERO_WEBSEARCH_API_KEY is not used. When any aimlapi profile, or any profile that reads AIMLAPI_API_KEY, points at a base URL other than https://api.aimlapi.com/v1, or AIMLAPI_INFERENCE_URL is overridden, the key may belong to a proxy, so it is not sent and the search fails without a request. Requests go only to https://tools.aimlapi.com (ZERO_WEBSEARCH_BASE_URL is ignored and redirects are refused) and carry X-AIMLAPI-Source: agent/zero and an X-AIMLAPI-Partner-ID header. The domains argument is sent to the Toolbox as include_domains, and results are still filtered locally.

AI/ML API Toolbox backend for web_fetch

web_fetch can fetch pages through the AI/ML API Toolbox tool search1api/crawl instead of a direct HTTP GET. It is off unless you choose it:

export ZERO_WEBFETCH_PROVIDER=aimlapi

Having an AI/ML API key never switches it on by itself, and any other value (or none) keeps the direct fetch. The variable is read on every fetch.

  • Billing: Toolbox runs are billed to your AI/ML API balance, about $0.0013 per run. Each run is sent with max_cost_usd 0.005, so a run priced above that is refused before it starts and not charged. Rate-limited or gateway-failed requests are retried with the same idempotency key, so a retry is never billed twice.
  • Key: the key comes from your aimlapi provider profile (including a key in the credential store), or from AIMLAPI_API_KEY when there is no such profile. It is withheld, and web_fetch returns an error, when any aimlapi profile or any profile using AIMLAPI_API_KEY points at a base URL other than https://api.aimlapi.com/v1, or when AIMLAPI_INFERENCE_URL is overridden: the key may then belong to a proxy.
  • Destination: requests go only to the fixed https://tools.aimlapi.com endpoint, with Authorization: Bearer <key> and the attribution headers X-AIMLAPI-Source: agent/zero and X-AIMLAPI-Partner-ID. Redirects are refused. HTTP_PROXY/HTTPS_PROXY apply to these requests as to Zero's other aimlapi.com calls.
  • No fallback: without a usable key, or when the run fails, web_fetch returns an error and does not fetch the page directly. Unset the variable to go back to direct fetches.
  • Remote fetch: the page is fetched by a remote service, which may serve a cached copy, and the full URL (query string included) is sent to it. The URL checks above still run first, so loopback, private and metadata URLs are never handed to it. The result is Markdown with a Backend: aimlapi (search1api/crawl) line; max_bytes cuts the Markdown, and format: "raw" is refused because the original response is not available. The complete Toolbox run response is limited to 8 MiB before JSON decoding; larger responses return an error even if max_bytes is smaller. The remote run may already have been billed when this limit is detected.

For local dev servers, use shell commands such as curl through exec_command so the normal sandbox and permission policy applies. Long-running commands stay attached to a background terminal session and can be listed or stopped from the TUI.

The npm package also includes browser and terminal helper packages used by local browser/terminal tools. Source builds can use the same helpers when they are on PATH or configured in Zero's local-control settings.

Common Commands

zero                  interactive TUI
zero exec             one-shot or scripted agent run
zero setup            first-run provider setup
zero auth             OAuth/login helpers for supported providers
zero models           model registry and capabilities
zero providers        provider profiles and detection
zero doctor           setup, key, and connectivity checks
zero context          context-budget report
zero repo-map         deterministic repository map
zero repo-info        local repository summary
zero search | find    search local session history
zero sessions         inspect, resume, fork, rewind, and prune sessions
zero spec             manage spec-mode drafts
zero specialist       manage specialist subagents
zero skills           manage markdown instruction skills
zero plugins          manage plugins
zero hooks            manage lifecycle hooks
zero mcp              manage MCP servers and tools
zero serve --mcp      expose Zero tools over MCP stdio
zero sandbox          inspect sandbox policy and grants
zero worktrees        prepare isolated git worktrees
zero verify           detect and run local verification checks
zero changes          inspect and commit local git changes
zero usage            token usage and estimated cost
zero cron             scheduled agent jobs
zero update --check   check for newer releases
zero upgrade          download, verify, and install the latest release

Cron keeps the newest 1,000 run outcomes per job in runs.jsonl. Each new run atomically replaces the history with the retained tail; existing larger histories are trimmed on their next run. Archive the file before that run if you need older outcomes. This retention does not delete session directories or reset fire counts. Malformed records and legacy lines of 1 MiB or more are skipped when reading or compacting history; new records of that size are rejected.

Extending Zero

Project and personal instructions

Zero appends project-specific guidance to the system prompt from the first AGENTS.md, ZERO.md, or .zero/AGENTS.md file found in each directory from the git root down to your current working directory (checked in that order per directory). Files are injected general-to-specific, capped at 8 KiB per file and 32 KiB total.

A personal ZERO.md under config.UserConfigDir()/zero/ZERO.md ($XDG_CONFIG_HOME/zero/ZERO.md or ~/.config/zero/ZERO.md on Linux/macOS, %AppData%\Roaming\zero\ZERO.md on Windows) applies across every workspace, ahead of any project guidelines.

Plugins

Plugins are discovered from ~/.config/zero/plugins/<name>/plugin.json (user scope — $XDG_CONFIG_HOME or ~/.config on every OS, independent of the config.UserConfigDir() path used above) and <cwd>/.zero/plugins/<name>/plugin.json (project scope — resolved from the current working directory, not the repo root), and managed with zero plugins. A manifest can declare:

  • tools — custom tools (command, args, inputSchema, and a permission of prompt or deny; allow is honored only when manifest tool auto-approval is enabled)
  • hooks — commands run on beforeTool, afterTool, sessionStart, or sessionEnd
  • prompts and skills — additional prompt/skill files

MCP servers (zero mcp) and standalone markdown skills (zero skills) use the same extension points and can also be wired up outside of a plugin manifest.

Appearance And Accessibility

Control Effect
NO_COLOR=<anything> disables color output
ZERO_THEME=<name> selects the startup theme (auto, dark, light, or a color theme like dracula, nord, gruvbox, tokyo-night, catppuccin, one-dark, solarized-dark, rose-pine, everforest, neon, solarized-light, dune)
--theme <name> selects the TUI theme from the CLI (same names)
/theme opens the theme picker inside the TUI (live preview; /theme <name> switches directly)
ZERO_NO_FADE=1 disables streaming fade animation

Meaning does not rely on color alone; diffs, permissions, and statuses also use text or glyph markers.

Development

go test ./...
go run ./cmd/zero-release build
go run ./cmd/zero-release smoke
go run ./cmd/zero-perf-bench

Experimental: ZERO_OPENAI_TURN_SESSION=1 enables the optimized OpenAI turn session (background connection prewarm + request-prefix telemetry) for headless zero exec runs against official OpenAI profiles. Off by default; 0, false, or off disable it. A/B-benchmark it by running the same zero-perf-bench suite with the variable unset and set.

Native ChatGPT Responses sessions are enabled by default. Set ZERO_CHATGPT_TURN_SESSION=0, false, or off to restore stateless HTTP/SSE transport.

Code Quality and Security Checks

Before committing any changes, ensure all Go code quality and security checks pass. The make targets below pin each tool to this module's Go version, so they load correctly even when your default go toolchain is older — running the plain go run ...@version form yourself can select the tool module's own (older) toolchain and fail to load this module instead.

  1. Formatting: Run make fmt (or go fmt ./...).
  2. Vetting: Run make vet (or go vet ./...).
  3. Linting: Run make lint-static.
  4. Vulnerability Scan: Run make vulncheck.

Use the repository-managed targets rather than globally installed binaries: the targets apply the module's required Go toolchain as well as the reviewed, pinned tool versions.

The installed binaries land in $GOBIN when it is set, otherwise in $GOPATH/bin (default ~/go/bin). That directory must be on your PATH to run them directly. If it isn't, add it:

gobin="$(go env GOBIN)"; [ -z "$gobin" ] && gobin="$(go env GOPATH)/bin"
case ":$PATH:" in *":$gobin:"*) ;; *) export PATH="$PATH:$gobin" ;; esac

Cross-Compile Examples

go run ./cmd/zero-release build --goos linux --goarch amd64
go run ./cmd/zero-release build --goos windows --goarch amd64 --output dist/zero.exe

Documentation

Community

Real-time chat happens on the Discord server.

Questions, setup help, ideas, and sharing all live in GitHub Discussions:

Category Use it for
Q&A Setup help, provider/model configuration, "how do I" questions
Ideas Feature proposals and design discussion before any PR
Show and tell Your skills, plugins, MCP setups, themes, and workflows
Announcements Releases and project news from the maintainers

For a good Q&A answer fast, include zero --version, your OS and install method, the provider/model in use, and zero doctor output. See SUPPORT.md. Bugs belong in issues; security reports follow SECURITY.md, never a public thread.

Contributing

Contributions are welcome. Read CONTRIBUTING.md, run the relevant tests, and open a focused pull request.

Security reports should follow SECURITY.md.

License

Zero is released under the MIT License.

S
Description
The coding agent that answers to you, your model, your machine, your rules.
Readme MIT
39 MiB
0 Stars 1 Watchers 0 Forks
v0.12.0
Latest
2026-11-07 10:03:43 -10:00
Languages
Go 99.5%
JavaScript 0.3%
Shell 0.1%