What is it?
Yet another coding agent harness, lightweight and written (vibe-slopped) in go.
- one static binary.
- built-in providers for Anthropic, OpenAI/Codex/Responses, Kimi, DeepSeek, Google Gemini/Vertex, GitHub Copilot, Bedrock, Azure OpenAI, OpenRouter, Yolo-Auto, Groq, Cerebras, xAI, Together, Hugging Face, Mistral, Moonshot, Z.AI, Xiaomi, MiniMax, Fireworks, Vercel AI Gateway, OpenCode, Cloudflare AI, and Ollama/local models.
- built-in tools (read, write, edit, bash, glob).
- three run modes (interactive tui, print, json).
- built-in telegram bot.
- extensions in any language via subprocess + json-rpc. None installed by default; opt in with
zot ext installorzot --ext. See docs/extensions.md. - user and extension themes via JSON; see docs/themes.md.
- standing instructions via
AGENTS.mdfiles (global and per-project); see Persistent instructions. - reusable instructions via
SKILL.mdfiles; see docs/skills.md. - portable agents from local directories,
.zotfiles, or temporary public GitHub downloads; see docs/zotfiles.md. - durable, crash-recoverable runs in a persistent local store via
zot continuous run, or hosted for multiple clients withzot continuous serve,zot continuous attach, andzot --continuous. Interrupted tools are reported, never silently repeated; approvals, steering, budgets, and tasks are persisted. Stores are a local journal or SQLite file, or optionally an external PostgreSQL server (--backend postgres). Experimental, see docs/continuous.md.
Install
One-liner (macOS, Linux)
curl -fsSL https://www.zot.sh/install.sh | bash
Detects your OS and architecture, downloads the latest release from GitHub, verifies the SHA-256 against the release's checksums.txt, extracts the binary, and drops it in /usr/local/bin, ~/.local/bin, or ~/bin, whichever is writable first. Pass a version or prefix to pin:
curl -fsSL https://www.zot.sh/install.sh | bash -s -- v0.0.1 ~/bin
One-liner (Windows, PowerShell)
iwr -useb https://www.zot.sh/install.ps1 | iex
Drops zot.exe into $HOME\bin and adds it to the user PATH if missing. Open a fresh terminal afterwards.
go install
go install github.com/patriceckhart/zot/cmd/zot@latest
The installed binary reports the tagged module version and supports zot update.
From source
git clone https://github.com/patriceckhart/zot
cd zot
make build # produces ./bin/zot
make install # into $GOPATH/bin
Prebuilt binaries
Every release on the releases page ships archives for Linux, macOS, and Windows on amd64 and arm64 (except windows/arm64), plus a checksums.txt file. Download, verify, chmod +x, and drop on your $PATH.
Authenticate
The easiest way is to just run zot and type /login. The TUI opens even without credentials and walks you through a browser-based login flow.
Credential lookup order
--api-keyflag- provider-specific env var (
ANTHROPIC_API_KEY,OPENAI_API_KEY,KIMI_API_KEY,MOONSHOT_API_KEY,DEEPSEEK_API_KEY,GEMINI_API_KEY,GOOGLE_API_KEY,GROQ_API_KEY,OPENROUTER_API_KEY,MISTRAL_API_KEY,XAI_API_KEY,CEREBRAS_API_KEY,TOGETHER_API_KEY,HF_TOKEN,ZAI_API_KEY,XIAOMI_API_KEY,MINIMAX_API_KEY,FIREWORKS_API_KEY,AI_GATEWAY_API_KEY,COPILOT_GITHUB_TOKEN,GITHUB_COPILOT_TOKEN, and others for provider-specific backends) $ZOT_HOME/auth.json(API key or OAuth token; mode 0600)
ZOT_HOME is an optional environment variable that overrides zot's state directory.
When it is unset or empty, zot resolves the directory internally:
- All platforms:
$XDG_STATE_HOME/zotwhenXDG_STATE_HOMEis set - macOS fallback:
~/Library/Application Support/zot - Linux fallback:
~/.local/state/zot - Windows fallback:
%LOCALAPPDATA%\zot
zot does not populate ZOT_HOME in your shell. An empty echo "$ZOT_HOME"
is normal and does not mean configuration is missing. Throughout these docs,
$ZOT_HOME is shorthand for the resolved state directory, not a guarantee that
the environment variable is set.
Before using $ZOT_HOME in shell commands, set it to your existing state
directory. For example, on Linux with the default location:
export ZOT_HOME="${XDG_STATE_HOME:-$HOME/.local/state}/zot"
To make that variable available in future shells, add the export to your shell's startup file. This is optional for running zot. If you already use a custom state directory, use that path instead to avoid switching to a different configuration.
API keys from commands
To keep an API key in a password manager instead of auth.json, configure an api_key_command for the provider:
{
"anthropic": {
"api_key_command": {
"program": "op",
"args": ["read", "op://Work/Anthropic/credential"],
"timeout_ms": 120000
}
}
}
For a provider added through models.json, put the same credential object under additional_api_key_creds using its provider ID. program is executed directly, without a shell, so each argument must be a separate args entry. timeout_ms is optional and defaults to 120 seconds.
zot runs the command only when that provider is selected, not while checking login status or refreshing model catalogs in the background. Successful output is cached in memory for the rest of the zot process and is never written to disk. The command must print one non-empty line to stdout; zot removes trailing CR/LF characters, limits output to 64 KiB, and does not include command output in errors. Saving a normal key through /login replaces the command configuration, and /logout removes it.
Treat auth.json as executable configuration: anyone who can modify it can cause zot to run a program under your user account. zot does not interpret ! prefixes or execute command strings through a shell.
/login flow
Run zot and type /login. Pick one of two methods:
- API key: a small local web server starts on
127.0.0.1:<free-port>, your browser opens a form, you pick a provider from the full API-key provider list, paste the key, and zot saves it toauth.jsonif accepted. Providers with a lightweight model-list endpoint are probed before saving; provider backends that need extra project/account env vars are saved directly. - Subscription: use your Claude Pro/Max, ChatGPT Plus/Pro, Kimi Code, SuperGrok/X Premium, or GitHub Copilot subscription. DeepSeek and Google Gemini do not have a subscription login path. For those, use the API-key flow.
- For Anthropic, choose browser callback (
localhost:53692) or copy code before starting login. Browser callback also accepts a pasted code or full redirect URL from the same authorization link. Copy code uses Anthropic's hosted redirect and works over SSH or on headless machines without a local callback server. Anthropic uses the Claude Code OAuth flow. Messages go toapi.anthropic.comwith a bearer token and the Claude Code identity headers. - OpenAI uses the Codex CLI OAuth flow with a browser callback on
localhost:1455. The same authorization link also accepts a pasted code or full redirect URL if the browser cannot reach zot's callback port (for example, over SSH). Copy the final redirect URL from the browser address bar, even if the page fails to load locally. The callback port must still be available on the machine running zot. Messages go tochatgpt.com/backend-api/codex/responseswith thechatgpt-account-idextracted from the returned id_token. - Kimi uses the Kimi Code device-code OAuth flow. zot opens the verification URL, polls until you approve it in the browser, then sends messages to
api.kimi.com/coding/v1with the Kimi Code identity headers. - xAI uses a device-code OAuth flow. zot opens a prefilled authorization URL, polls for approval, and uses the resulting token with the xAI API.
- GitHub Copilot uses GitHub's device-code login flow. zot stores the GitHub access token and exchanges it for short-lived Copilot inference tokens on demand.
- For Anthropic, choose browser callback (
Note on subscription login. The OAuth client IDs used are the ones published in Anthropic's Claude Code CLI, OpenAI's Codex CLI, Kimi Code CLI, xAI's device flow, and GitHub Copilot's device-code flow. Reusing them from a third-party tool may be against their terms of service and may be revoked at any time. Use it at your own risk; the API-key flow is the safe default.
Token refresh
OAuth access tokens are short-lived (Anthropic ~8h, OpenAI ~30d; Kimi, xAI, and GitHub Copilot also use refresh/exchange flows). zot refreshes or exchanges them automatically:
- At every credential lookup, zot checks the stored
expiryand, if past it (with a 60s safety margin), hits the provider'soauth/tokenendpoint with the storedrefresh_token, persists the newaccess_token,refresh_token, andexpiryback toauth.json, and hands the fresh token to the client. - The telegram bridge additionally refreshes once per turn so a bot that runs for days keeps working without manual intervention.
- If the refresh itself fails (the
refresh_tokenwas revoked, or the account was logged out everywhere), the error bubbles up to the caller: the TUI shows it in the status line, the bot replies with it in your DM. Run/loginto get a fresh token pair.
All data lives under $ZOT_HOME:
$ZOT_HOME/
├── config.json # last-used provider/model/theme, saved automatically
├── models.json # optional: user-defined models and provider endpoints
├── auth.json # api keys and oauth tokens (mode 0600)
├── sessions/ # jsonl transcripts, one dir per cwd
├── models-cache.json # live /v1/models discovery cache (6h ttl)
├── AGENTS.md # optional: global instructions appended to the prompt
├── SYSTEM.md # optional: replaces the default system prompt
├── skills/ # optional: user SKILL.md files
├── themes/ # optional: user theme JSON files
├── extensions/ # installed extensions, one dir per extension
└── logs/ # app log files
Drop a SYSTEM.md in $ZOT_HOME to replace the built-in identity and zot-docs guidance for every run. --system-prompt still wins per-invocation. Pass an empty value (--system-prompt "") to intentionally omit the built-in identity. Custom prompts still receive appended instructions and generated context, including AGENTS.md, skills, auto-swarm guidance when enabled, and the date/cwd footer. Delete the file to revert to the default.
Configuration files
zot keeps both configuration and runtime state under $ZOT_HOME, even when that directory is under the platform's state directory. You can edit configuration files there directly while zot is not running:
config.jsonselects the defaultproviderandmodeland stores UI preferences. zot also updates it when you change settings or select a model interactively. Explicit--providerand--modelflags override the saved selection for a run.models.jsonoptionally defines model metadata and provider endpoints (baseUrl). Adding a model here does not select it as the default. See Custom models.auth.jsonstores credentials, not model definitions. Prefer/loginfor supported providers, and keep secrets out ofconfig.jsonandmodels.json.
Neither config.json nor models.json is required to start zot with an explicit provider and model. Without a saved or explicit provider, zot defaults to Anthropic and may offer login if no usable credentials are available. Ollama needs no login. See Local models with ollama for minimal configuration and remote-server examples.
HTTP proxy
To route zot-managed HTTP and HTTPS requests through one proxy, add http_proxy to $ZOT_HOME/config.json:
{
"http_proxy": "http://127.0.0.1:7890"
}
The setting is applied at startup to both HTTP and HTTPS traffic. Existing HTTP_PROXY, HTTPS_PROXY, http_proxy, and https_proxy environment variables take precedence for their corresponding protocol. NO_PROXY and no_proxy continue to control bypasses. Restart zot after changing the config file. If the URL contains proxy credentials, prefer protected environment variables because config.json is not a credential store.
Configurable shortcuts
Interactive keybindings can be added to $ZOT_HOME/config.json under keymap. Each key is a case-insensitive chord made of at least one modifier (ctrl, alt/option, shift, cmd/super) plus a single character or a named key (enter, tab, space, esc, backspace, delete, up, down, left, right, home, end, pageup, pagedown). Each value is either a slash-command string or a navigation action object. Built-in commands, extension commands, and explicitly invoked /skill:<name> all work as strings.
{
"keymap": {
"ctrl+s": "/settings",
"ctrl+g": "/git-tools:status",
"alt+r": "/skill:code-review",
"ctrl+n": { "action": "next", "context": "navigation" },
"ctrl+p": { "action": "previous", "context": "navigation" }
}
}
Slash-command shortcuts run in the main interactive input and use the same dispatch path as typing the command. They do not apply while a dialog, extension panel, or tool confirmation has focus.
Navigation objects support "next" and "previous" with the required context
"navigation". They act like Down and Up in built-in lists and scrollable
views, including confirmations, and in slash, file, and path suggestion
popups. Filterable lists keep navigation available while editing the filter.
Text-only subviews and the main editor without a popup ignore these actions,
so they do not edit text, browse prompt history, or scroll the main chat.
Extension panels control their own navigation and do not use these actions.
There are no default Ctrl+n/p aliases. Add the entries above to enable them,
or choose other modified chords. Existing arrows continue to work.
Restart zot after changing config.json. /help lists the active bindings.
Unbindable chords, invalid command strings, unsupported actions or contexts,
and the reserved ctrl+c, ctrl+d, and esc chords are skipped and reported
at startup (the message clears with /clear). Unmodified keys such as enter
or g cannot be rebound. Malformed binding values (neither a string nor an
action object, wrong field types, or unknown object fields) cause a config
parse error. Existing string-valued keymaps remain compatible, and saving
other settings preserves both command and action entries. Saving refuses to
overwrite an existing config that cannot be read, parsed, or validated.
Correct the config first, then retry the settings change.
Terminal limitations: without enhanced keyboard reporting (kitty keyboard
protocol or xterm modifyOtherKeys), a terminal cannot distinguish
ctrl+h, ctrl+i, and ctrl+m from Backspace, Tab, and Enter, and it does
not report Shift on Ctrl+letter chords. zot therefore lets a ctrl+shift+<letter>
binding also fire on plain ctrl+<letter> unless a separate ctrl+<letter>
binding exists. Chords the terminal emulator or multiplexer consumes itself
(a tmux prefix, cmd+k clearing scrollback, tab switching) never reach zot.
Persistent instructions (AGENTS.md)
Use AGENTS.md to give zot standing instructions that layer on top of the default system prompt, without replacing it. This is the friendliest way to shape behavior (for example, taming local models that jump straight to code edits) because it adds guidance rather than taking over the base identity the way SYSTEM.md does.
zot discovers AGENTS.md files automatically at startup and loads them in this order:
$ZOT_HOME/AGENTS.md(global, machine-wide instructions that apply to every project).- Every
AGENTS.mdfrom the filesystem root down to the current working directory. More specific (deeper) files may override earlier ones. This includes~/AGENTS.mdwhen the working directory is inside your home directory.
All discovered files are appended to the prompt in that order, so a global baseline can be refined per project. To list the active .zot agent, loaded context paths, extensions, and user-installed skills above the interactive transcript, enable show loaded resources at startup in /settings. The agent section appears only for zot run, the setting is off by default, built-in skills are omitted, and the displayed lists are not sent to the model or stored in the session transcript.
For a general, non-project-specific instruction set, put your rules in $ZOT_HOME/AGENTS.md, for example:
Treat questions and discussions as requests for explanation. Do not edit files or run tools unless explicitly asked to make a change. Ask before modifying code.
AGENTS.md vs the other mechanisms:
| Mechanism | Scope | Effect |
|---|---|---|
$ZOT_HOME/AGENTS.md |
global, every run | appended to the default prompt |
./AGENTS.md (and parent dirs) |
project | appended to the default prompt |
$ZOT_HOME/SYSTEM.md |
global, every run | replaces the built-in identity and zot-docs guidance |
--append-system-prompt <text> |
single run | appends for one invocation (repeatable) |
--system-prompt <text> |
single run | replaces the built-in identity and zot-docs guidance |
Note: zot does not read a
CLAUDE.mdinstruction file. The only Claude-compatible thing it picks up is skills under.claude/skills/. If you are migrating from Claude Code, move that content intoAGENTS.md(global or per-project) and zot will use it.
Changelog on update
The interactive welcome text remains at the start of the scrolling chat, followed by update notices, release notes, startup extension notifications, and loaded-resource information. Late startup results update this section rather than appearing between the conversation and input. Runtime extension messages stay near the input. Extensions with asynchronous initialization should mark their notifications with startup: true (Go SDK: NotifyStartup) so zot can distinguish them from runtime messages after interaction begins. See extension notifications.
The first time you launch a newer zot binary, the TUI shows the GitHub release notes for all published releases newer than the last shown version, up to the running version. Notes are grouped by version, newest first, so upgrading across multiple versions includes the intervening changes. They appear below the welcome text in the scrolling chat without opening an overlay or taking keyboard focus. The version is recorded in config.json's last_changelog_shown when the notes are added to the startup section. Fresh installs don't see a changelog (no upgrade has happened yet). Local builds (0.0.0) show only the latest release. The fetch is best-effort with a four-second overall deadline: a network failure, incomplete pagination, or a missing running release silently skips the notes, with another attempt on the next launch.
Usage
zot # inter
