v0.1.80 63e28ad

README.md RawHistoryFolder

zot

Yet another coding agent harness, lightweight and written (vibe-slopped) in go.

  • one static binary.
  • built-in providers for anthropic, openai/codex, kimi, deepseek, google gemini, and ollama/openai-compatible local models.
  • four tools (read, write, edit, bash).
  • 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 install or zot --ext. See docs/extensions.md.
  • reusable instructions via SKILL.md files; see docs/skills.md.
  • no community atm.

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

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

  1. --api-key flag
  2. ANTHROPIC_API_KEY, OPENAI_API_KEY, KIMI_API_KEY, MOONSHOT_API_KEY, DEEPSEEK_API_KEY, GEMINI_API_KEY, or GOOGLE_API_KEY env var
  3. $ZOT_HOME/auth.json (API key or OAuth token; mode 0600)

$ZOT_HOME defaults to:

  • macOS: ~/Library/Application Support/zot
  • Linux: $XDG_STATE_HOME/zot or ~/.local/state/zot
  • Windows: %LOCALAPPDATA%\zot

/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 paste your sk-ant-..., sk-..., Kimi/Moonshot key, DeepSeek (sk-...) key, or Google AI Studio (AIza...) Gemini key. zot probes the provider once and saves it to auth.json if accepted.
  • Subscription: use your Claude Pro/Max, ChatGPT Plus/Pro, or Kimi Code subscription. DeepSeek and Google Gemini do not have a subscription login path — the /login subscription step only lists Anthropic, OpenAI, and Kimi. For DeepSeek and Google use the API-key flow.
    • Anthropic and OpenAI pin the browser callback to fixed provider-specific ports (localhost:53692 for Anthropic, localhost:1455 for OpenAI) because those are the only ports their auth servers will redirect to.
    • Anthropic uses the Claude Code OAuth flow. Messages go to api.anthropic.com with a bearer token and the Claude Code identity headers.
    • OpenAI uses the Codex CLI OAuth flow. Messages go to chatgpt.com/backend-api/codex/responses with the chatgpt-account-id extracted 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/v1 with the Kimi Code identity headers.

Note on subscription login. The OAuth client IDs used are the ones published in Anthropic's Claude Code CLI, OpenAI's Codex CLI, and Kimi Code CLI. 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). zot refreshes them automatically:

  • At every credential lookup, zot checks the stored expiry and, if past it (with a 60s safety margin), hits the provider's oauth/token endpoint with the stored refresh_token, persists the new access_token, refresh_token, and expiry back to auth.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_token was 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 /login to get a fresh token pair.

All data lives under $ZOT_HOME:

$ZOT_HOME/
├── config.json         # last-used provider/model/theme, saved automatically
├── 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)
├── SYSTEM.md           # optional: replaces the default system prompt
├── skills/             # optional: user SKILL.md files (opt in with --with-skills)
├── 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 guidelines for every run. --system-prompt still wins per-invocation. Delete the file to revert to the default.

Changelog on update

The first time you launch a newer zot binary, the TUI shows the GitHub release notes once in a dismissible overlay. Press any key to close. The version is recorded in config.json's last_changelog_shown so the same release notes never reappear. Fresh installs don't see a changelog (no upgrade has happened yet). The fetch is best-effort: a network failure or a missing release page silently skips, with another attempt on the next launch.

Usage

zot                              # interactive tui
zot "fix the failing test"       # tui, pre-filled prompt
zot -p "list all go files"       # print final text, exit
zot --json "refactor main.go"    # newline-delimited json events, exit
zot --continue                   # resume the most recent session for this cwd
zot --resume                     # pick a session to resume
zot --list-models                # show supported models
zot --help

Flags

Flag Description
--provider anthropic|openai|kimi|deepseek|google|ollama Pick the provider.
--model <id> Pick the model (see --list-models).
--api-key <key> Override the API key.
--base-url <url> Override the provider base URL (tests, self-hosted).
--system-prompt <text> Replace the default system prompt for this run (also overrides $ZOT_HOME/SYSTEM.md).
--append-system-prompt <text> Append text to the system prompt (repeatable).
--reasoning low|medium|high Enable reasoning on supported models.
-c, --continue Resume the latest session for this cwd.
-r, --resume Pick a session to resume.
--session <path> Resume a specific session file.
--no-session Don't read or write session files.
--cwd <path> Use <path> as the working directory.
--no-tools Disable all tools.
--tools <csv> Only enable the listed tools.
--max-steps <n> Cap agent loop iterations (default 50).
-e, --ext <path> Load an extension from <path> for this run (repeatable; wins against installed extensions of the same name).
--no-ext Skip extension discovery for this run. --ext still works on top, so --no-ext --ext ./x runs only x.
--with-skills Also load user-installed skills. Without this, only the built-in skills shipped in the binary are loaded.
--no-skill Disable all skills, including built-ins. No skill tool is registered and the system prompt has no skill manifest.
--no-yolo Confirm every tool call before it runs (interactive TUI only). A dialog shows the tool name and a one-line preview of its args with four choices: yes, yes-always-this-tool-this-session, yes-always-this-session, no. Ignored with a stderr warning in print / json / rpc modes, where tools still run freely so scripts and automation keep working.

Tools

  • read: read text files, or inline images (PNG, JPEG, GIF, WebP).
  • write: create or overwrite files, making parent directories as needed.
  • edit: one or more exact-match replacements in an existing file.
  • bash: run a shell command in the session cwd, with merged stdout/stderr and a timeout.

When the sandbox is on (see /jail), all four tools refuse paths outside the session cwd.

Modes

  • Interactive (default): chat TUI with streaming output, spinner, cost meter, slash commands.
  • Print: zot -p "prompt" runs the agent to completion and writes only the final assistant text to stdout.
  • JSON: zot --json "prompt" emits one JSON object per agent event to stdout, newline-delimited. The schema is documented in docs/rpc.md.
  • RPC: zot rpc runs as a long-lived child process; commands in on stdin, events and responses out on stdout, both as NDJSON. Designed for embedding zot in third-party apps written in any language. See docs/rpc.md for the wire schema and examples/rpc/{python,node,shell,go} for working clients.

Embedding

Two ways to drive zot from another program:

  • Go in-process: import github.com/patriceckhart/zot/pkg/zotcore. One Runtime per project; Prompt(ctx, text, images) returns a channel of Event. Small example in examples/sdk/.
  • Any language, out-of-process: spawn zot rpc as a subprocess and exchange newline-delimited JSON over its stdin/stdout. Wire format and event schema in docs/rpc.md. Reference clients live under examples/rpc/.

Both interfaces share the same event schema, so transcripts captured by one can be replayed through the other.

Slash commands

Type / in the TUI to open the autocomplete popup. Available commands:

Command Description
/help Show key bindings and commands.
/login Log in via API key or subscription (opens a dialog).
/logout [provider] Clear credentials for anthropic, openai, kimi, deepseek, google, or all when omitted. /logout kimi also disables fallback to the official Kimi Code CLI token until you log in to Kimi through zot again.
/model Pick a model from a list (or /model <id> to set directly).
/sessions Resume a previous session for this directory.
/session Four ops on the current session: export to a portable .zotsession file, import one back in, fork from a past user message into a new branch, tree to switch between branches. Opens a picker without an argument; direct forms: /session export [path], /session import <path>, /session fork, /session tree. Default export destination is ~/Downloads.
/jump Scroll the chat to a previous turn (or /jump <text> to filter).
/btw Side chat with full context that doesn't add to the main thread.
/swarm Spawn, monitor, and chat with background subagents. Each gets its own git worktree and runs in parallel with your main session.
/skills List discovered skills (SKILL.md files) and preview their bodies.
/compact Summarize the transcript into one message to free up context.
/study Run the canned prompt "Read and understand everything in the current directory." so the agent has full project context before you start asking targeted questions.
/jail Confine tools to the current directory.
/unjail Allow tools to touch paths outside again.
/reload-ext Hot-reload all extensions (re-read manifests, respawn subprocesses, rebuild tool registry).
/telegram Connect, disconnect, or show status of the Telegram bridge (takes connect / disconnect / status as an optional argument; opens a picker without one). When connected, DMs from the paired user become prompts in the running session and the assistant's replies are mirrored back to Telegram. Alias: /tg.
/clear Clear the chat transcript.
/exit Exit zot.

Extension-registered commands appear under a divider at the bottom of the popup, sorted by name.

/sessions

Shows previous sessions for the current working directory, newest first, with timestamp, model, message count, cost, and the first user prompt. Pick one with up/down, enter to resume, esc to cancel. zot swaps the current session file for the selected one and replays the full transcript (including tool calls) into the agent. Sessions remember the model they ended on, so resuming picks up on that exact model even if your global default changed.

/session

Four ops on the current session. /session alone opens a picker; each is also runnable directly.

  • /session export [path]. Writes the running transcript to a portable .zotsession file. Default destination is ~/Downloads/<timestamp>-<session-id>-<prompt-slug>.zotsession. Pass a path to override; a directory is fine (a dated name is built inside), a bare name gets .zotsession appended. The meta's cwd is stripped on the way out so the recipient doesn't see your filesystem layout.

    What's included. Only the main chat thread of the running session — messages, tool calls, tool results, compactions, and usage. /swarm subagents are NOT included. Their transcripts, worktrees (which are real git checkouts under $ZOT_HOME/swarm/worktrees/<id> on branch swarm/<id>), unix-socket inboxes, and per-agent session files are all machine-local; a .zotsession is just a chat transcript and has no way to bundle a git worktree or revive a unix socket on another box. If you want to share a subagent's work, use normal git tooling on its branch (git push, git format-patch, etc.); if you want the conversation, copy it out of the dashboard manually.

  • /session import <path>. Copies a .zotsession file into $ZOT_HOME/sessions/<cwd-hash>/ with a fresh id and the current cwd, then switches the running agent onto it. Imported sessions are first-class: they show up in /sessions, /jump, and the tree. Drag-drop paths in the editor are accepted (zot strips the surrounding quotes automatically).

  • /session fork. Opens a turn picker (same shape as /jump). Pick any past user message; zot copies every message up to and including that turn into a new session, records parent + fork_point in the new meta, and switches onto the branch. The parent session stays on disk. Use it to try a different question without polluting the original transcript, or to rewind after the agent went down the wrong path.

  • /session tree. Shows every session in the current cwd arranged by parent/child relationships, depth-first with indent per level. The current session is tagged [current]. Pick any entry to switch into it. Parentless sessions are roots; branches created via /session fork nest under whichever session they were forked from. Orphaned children (whose parent file was deleted) still show as roots so they stay discoverable.

/jump

Opens a turn picker for the current session, one row per user prompt, each showing the turn number, how many tools that turn invoked, and the first line of the prompt. up/down to pick, enter to jump, esc to cancel. Any printable rune while the picker is open extends a filter; backspace narrows it back. /jump <text> pre-applies the filter; if exactly one turn matches, zot jumps straight there without showing the picker.

Jumping is non-destructive. The transcript is untouched, the viewport just scrolls so the chosen turn is at the top. A muted line at the top of the chat reads viewing turn N of M, pgdn to catch up. Scroll back to the bottom with pgdn (or keep scrolling with the arrow keys) and the indicator goes away.

/btw

Opens a side-chat overlay with the full main session as frozen context, so you can ask quick clarifying questions ("does asyncio.gather() catch exceptions?", "btw the bundle budget is 10MB", "what's the default fetch timeout?") without bloating the main thread.

Each question fires a one-off model call against system + main transcript + side-chat history so far. Responses render in the overlay and stay there. When you press esc to close, nothing has been added to the main session and subsequent main-thread turns don't re-read any of the side-chat exchanges, keeping the running context window lean.

/btw                              # open the overlay, type questions interactively
/btw does PUT replace the whole resource?

Inside the overlay: enter sends, esc cancels an in-flight call (or closes the overlay if idle), ctrl+c closes immediately. Side-chat exchanges never touch the transcript and aren't persisted to the session file.

/swarm

Long-running parallel subagents. Each one gets its own git worktree off your current repo (branch swarm/<id>), its own persistent session file, and its own background subprocess driving the model. You stay in the main session and check in on them whenever you want — the dashboard is a /btw-style chat per agent.

Why: “go write the test harness for module X” / “investigate this stack trace” / “draft the migration” are perfect side tasks. Spawn an agent, keep working in your main session, come back to its results later — or chat with it interactively while it works.

/swarm                            # open the dashboard
/swarm new <task>                 # spawn an agent on a fresh worktree
/swarm new --model gpt-5 <task>   # pin the new agent to a specific model
/swarm logs <id>                  # jump straight into one agent's transcript
/swarm send <id> <text>           # send a follow-up without opening the dashboard
/swarm resume                     # pick a stopped agent to bring back
/swarm resume <id>                # bring a specific agent back
/swarm kill <id>                  # stop a running agent (worktree stays)
/swarm remove <id>                # delete the worktree + session for an agent
/swarm list                       # alias for opening the dashboard

Dashboard (/swarm with no arg) — a list of every agent for the current session with status, age, and current activity. Keys:

  • ↑ / ↓ move the cursor between rows.
  • enter opens the highlighted agent's transcript view.
  • n spawns a new agent. Type the task, enter to confirm; the new agent inherits the model your main session is currently on (see /model and the in-editor /model command below).
  • p opens a one-off prompt editor for the selected row (alternative to entering the transcript).
  • R resumes a stopped agent in place.
  • k kills the selected running agent (its worktree and session stay so you can resume later).
  • r removes the selected agent entirely (worktree + session + meta gone).
  • esc closes the dashboard.

Inside an agent's transcript — a chat overlay just like /btw. The agent's conversation flows above an always-on inline composer; type and hit enter to send a follow-up. The view auto-follows streaming output and shows an inline spinner with the agent's current activity (thinking, tool: edit_file, etc.) while it's busy. esc returns to the dashboard.

Switching the spawn model from inside the editor — while composing a task in the n-prompt, type /model on its own line and enter. The same model picker the global /model uses pops up; pick a model, the picker closes, and the editor reopens with your typed task intact and the new model pinned for the spawn.

Session scoping — each agent is stamped with the session that spawned it and only shows up in that session's /swarm dashboard. Swap sessions with /sessions and the dashboard re-narrows accordingly. Agents you spawned in another session keep running and reappear when you switch back. Pre-upgrade agents (no session stamp) are visible from every session as a safety net.

Persistence across zot restarts — every spawn writes a meta.json next to its event log and session file under $ZOT_HOME/swarm/agents/<id>/. On the next zot launch they show up in the dashboard as detached; press R (or /swarm resume <id>) to bring one back. Resumed agents reattach to the same worktree, session, branch, and inbox socket, so the conversation continues from where it left off.

Where their work lives — worktrees go under $ZOT_HOME/swarm/worktrees/<id> on the branch swarm/<id>. Use the normal git tooling to inspect, merge, or rebase (git worktree list, git log swarm/<id>, etc.). /swarm remove deletes both the worktree and the swarm bookkeeping.

/session export does NOT bundle subagents. A .zotsession is just the main chat transcript; swarm worktrees and per-agent state are machine-local (a real git worktree on disk and a unix-socket inbox, neither of which round-trips through a JSONL file). To share a subagent's actual work, push its swarm/<id> branch with the normal git tooling. To share what it said, copy it out of the transcript view manually.

/skills

Opens a picker listing every discovered SKILL.md file, built-ins hidden. Each row shows the skill name, source, and description. enter opens the body inline (scrollable with up/down/pgup/pgdn); esc goes back. Re-runs discovery each time it opens, so edits to a SKILL.md during a session are reflected immediately.

/compact

Sends the current transcript through the model with a structured summarization prompt. The returned summary replaces the transcript as one synthetic user message, with the last few exchanges kept verbatim for continuity. The status bar's context meter resets. Use it when the context meter creeps past ~80%.

zot also auto-compacts in the background: after any turn that leaves context usage at or above 85% of the model's window, the agent kicks off a condense pass on its own. You'll see condensing history, esc to cancel above the status bar and an (auto) tag next to the context percentage; esc aborts it without touching the transcript.

/jail

Enforces a sandbox rooted at the cwd shown in the status bar. read, write, and edit resolve their target path (including through symlinks) and refuse anything outside the sandbox. bash refuses obvious escape patterns: sudo, rm -rf /, leading cd /, cd .., cd ~, chmod -R, dd of=/, and similar. The status bar shows jailed, ~/your/cwd while active.

This is a guardrail against accidents, not a hard security boundary. If you need real isolation, run zot under docker or a proper sandbox.

Sessions

Every interactive or print/json run (unless --no-session) writes a JSONL transcript under $ZOT_HOME/sessions/<cwd-hash>/. Resume any of them with --continue, --resume, --session <path>, or interactively via /sessions inside the TUI. Empty sessions (the user exited without prompting) are deleted on close so the list stays tidy.

Models

--list-models or the /model picker shows the full catalog. Three sources:

  • Catalog: models baked into zot, always available.
  • Live: IDs discovered from GET /v1/models using your stored API key (cached for 6h in $ZOT_HOME/models-cache.json, refreshed in the background on startup).
  • Speculative: IDs that appear in the upstream generator but aren't live on the public API yet. They'll 404 today and start working the moment the provider ships them.

The context meter in the status line uses the model's advertised context window to show how much of it your last turn consumed.

Model fallback (rescue)

When a turn fails because of a recoverable provider error — expired token (401), permission denied (403), rate limit (429), provider outage (502/503/504), or a transient network failure — zot opens a rescue picker over the chat instead of just painting a red banner.

The picker is the same vertical list / fuzzy filter UI as /model, but it only shows models from providers you're currently logged in to (env vars, auth.json, Kimi CLI fallback, ollama). The failed model is excluded. Press ↑/↓ to choose, enter to retry the same prompt on the new model, esc to dismiss.

Before the actual provider request fires, the OpenAI / Anthropic / Kimi / DeepSeek / Google / OpenAI-Codex clients also do up to two silent retries with short backoff (250ms, 750ms) on 502/503/504 and connection-reset / EOF-before-headers errors. Most edge-proxy blips disappear without you ever seeing the rescue picker.

A rescue retry always drops launch-time --api-key and --base-url before rebuilding the agent. Those overrides are usually the reason the rescue triggered (bad key, typo'd base URL, corporate gateway only valid for the originally-picked provider), so the retry re-resolves credentials from env vars / auth.json / provider defaults instead. Use /model if you want overrides to stick.

No configuration is required — the candidate list is built dynamically from your active credentials. Bad-request / context-length / serialization errors are NOT routed to the rescue picker, because switching models won't fix them; those still surface as a normal error.

Custom models

Place a models.json in $ZOT_HOME (macOS: ~/Library/Application Support/zot/, Linux: ~/.local/state/zot/) to add models that aren't in the baked-in catalog or to override existing entries:

{
  "providers": {
    "openai": {
      "models": [
        {
          "id": "gpt-5.5",
          "name": "GPT-5.5",
          "reasoning": true,
          "contextWindow": 400000,
          "maxTokens": 128000
        }
      ]
    }
  }
}

Supported fields per model: id (required), name, reasoning, contextWindow, maxTokens, baseUrl, priceInput, priceOutput, priceCacheRead, priceCacheWrite.

Provider keys are normalized: openai-codex and openai-responses map to openai, anthropic-messages maps to anthropic, moonshot, moonshot-ai, and kimi-code map to kimi, and deepseek-chat and deepseek-ai map to deepseek.

User-defined models show source: user in --list-models and take precedence over both the baked-in catalog and live-discovered models. Missing or invalid files are silently ignored.

Kimi Code

zot has built-in Kimi support through Kimi's OpenAI-compatible chat API.

zot --provider kimi

By default this uses:

  • model: kimi-for-coding
  • base URL: https://api.kimi.com/coding/v1

Credential lookup order for Kimi:

  1. --api-key
  2. KIMI_API_KEY
  3. MOONSHOT_API_KEY
  4. $ZOT_HOME/auth.json
  5. the official Kimi Code CLI token at ~/.kimi/credentials/kimi-code.json, unless disabled by /logout kimi

Use /login for either API-key login or Kimi Code subscription login. The subscription flow uses Kimi Code's device-code OAuth flow: zot opens the verification URL, waits for browser approval, stores the token in auth.json, and refreshes it automatically.

For direct Moonshot API keys or a custom compatible endpoint:

zot --provider kimi --model kimi-k2-0905-preview --base-url https://api.moonshot.ai/v1 --api-key "$KIMI_API_KEY"

You can add additional Kimi/Moonshot model IDs to models.json under the kimi provider.

DeepSeek

zot has built-in DeepSeek support through DeepSeek's OpenAI-compatible chat API.

zot --provider deepseek

By default this uses:

  • model: deepseek-v4-pro
  • base URL: https://api.deepseek.com/v1

Catalog ships with deepseek-v4-pro (reasoning) and deepseek-v4-flash. These are exactly the IDs returned by GET https://api.deepseek.com/models today. You can add additional model IDs to models.json under the deepseek provider.

Credential lookup order for DeepSeek:

  1. --api-key
  2. DEEPSEEK_API_KEY
  3. $ZOT_HOME/auth.json

Use /login and pick api key to paste a DeepSeek key. zot probes /v1/models once and stores the key under deepseek in auth.json.

Auth model: API key only. DeepSeek does not offer a subscription OAuth flow. The /login subscription step lists only Anthropic, OpenAI, and Kimi; DeepSeek shows up only under /login → api key.

Text only at the wire level. DeepSeek's chat-completions endpoint currently rejects the multimodal content schema (unknown variant image_url, expected text). When the active provider is deepseek, zot silently drops ImageBlock parts from outgoing user/tool messages and keeps only the text. Switching back to a vision-capable model (Claude, GPT-4o/5, Gemini) re-sends the image normally because the session file still stores it.

For a custom-compatible endpoint (mirror, gateway, self-host):

zot --provider deepseek --base-url https://my-deepseek-mirror.example.com/v1 --api-key "$DEEPSEEK_API_KEY"

Google Gemini

zot has built-in Google Gemini support through the AI Studio Generative Language API.

zot --provider google

By default this uses:

  • model: gemini-2.5-pro
  • base URL: https://generativelanguage.googleapis.com

Catalog ships with gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite, gemini-2.0-flash, and gemini-2.0-flash-lite. Live discovery against /v1beta/models adds anything else your key can see.

Credential lookup order for Google:

  1. --api-key
  2. GEMINI_API_KEY
  3. GOOGLE_API_KEY
  4. $ZOT_HOME/auth.json

Use /login and pick api key to paste an AI Studio key. zot probes /v1beta/models once and stores the key under google in auth.json.

Auth model: API key only. Google does not issue OAuth tokens for consumer Gemini Advanced / Google One AI Premium subscriptions, so there is no "log in with your Google subscription" flow. Programmatic access requires either an AI Studio API key (this provider) or a Vertex AI / GCP service-account credential (not yet wired up in zot). The /login subscription step quietly downgrades to the api-key form when you pick Google so you don't end up in a dead end.

Free-tier rate limits. AI Studio's free tier has tight per-minute and per-day caps that vary by model: gemini-2.5-pro is the strictest (a few requests per minute, ~50 per day), Flash and Flash-Lite are far more generous. If a Pro turn 429s with "You exceeded your current quota" while Flash on the same key still works, you've hit the Pro free-tier RPD. Either switch to Flash for agent loops, or enable billing on your AI Studio project to flip the same key from free to pay-as-you-go pricing ($1.25/M input, $10/M output for Pro).

Reasoning levels (--reasoning low|medium|high) map differently per generation: 2.5 family uses thinkingBudget token budgets per model (Pro caps at 32k, Flash at 24k); Gemini 3.x uses the thinkingLevel enum (MINIMAL/LOW/MEDIUM/HIGH), with Gemini-3-Pro pinned to LOW minimum and HIGH for any "medium" or "high" request. 2.0-family models have no thinking config at all.

You can add additional Gemini model IDs to models.json under the google provider.

Local models with ollama

zot works with ollama out of the box. Ollama serves an OpenAI-compatible API locally, so any model you have pulled works with zot.

Quick start:

ollama pull qwen3.5:4b
zot --provider ollama --model qwen3.5:4b

That's it. No API key needed for local models. zot defaults to http://localhost:11434.

For a remote ollama instance or one behind auth:

zot --provider ollama --model llama3 --base-url https://my-server.com/v1 --api-key my-token

You can also add models to your models.json so you don't need flags every time:

{
  "providers": {
    "ollama": {
      "models": [
        {
          "id": "qwen3.5:4b",
          "name": "Qwen 3.5 4B",
          "contextWindow": 32768,
          "maxTokens": 8192
        }
      ]
    }
  }
}

The ollama provider uses the OpenAI chat completions protocol internally, so it also works with any OpenAI-compatible server (vLLM, LM Studio, LocalAI, etc.).

Inline images

When a tool returns an image (for example read on a PNG), zot renders it inline on terminals that support it: Ghostty, Kitty, iTerm2, WezTerm. On other terminals you see a text placeholder with MIME type, pixel dimensions, and byte size. Control with the ZOT_INLINE_IMAGES env var:

Value Effect
unset (default) Auto-detect based on TERM_PROGRAM.
iterm, iterm2 Force the iTerm2 OSC 1337 protocol.
kitty Force the Kitty graphics protocol.
off, none Always use the text placeholder.

Frames containing images are full-repainted (no differential diff) to prevent stale image pixels from lingering through scroll. That costs one terminal flash per image-containing frame; set ZOT_INLINE_IMAGES=off if that bothers you.

Queued messages

You can keep typing while the agent is working. Pressing enter during a turn queues the message instead of interrupting: it shows up above the status bar as sliding in: <text> and is delivered as the next user turn the moment the current one finishes. Queue as many as you want; they run in order. esc cancels the active turn and drops the queue so a runaway turn doesn't flood you with stale follow-ups; ctrl+c while busy arms the exit hint instead of interrupting, a second ctrl+c within two seconds exits zot.

To recover the most recently queued message back into the editor (to tweak it before it runs), press Option+↑. In VS Code's integrated terminal that chord doesn't survive xterm.js's macOS key handling — use Option+Shift+↑ there. zot's hint line under the sliding-in queue adapts automatically based on $TERM_PROGRAM.

Slash commands also work while the agent is busy. Read-only ones (/help, /jump, /btw, /sessions, /skills, /jail, /unjail, /exit) take effect immediately. Destructive ones (/clear, /compact, /login, /logout, /model, /reload-ext) cancel the active turn first and then run.

Keys (interactive mode)

Input

Key Action
enter Submit (queued if the agent is busy).
alt+enter Newline.
tab Complete the selected slash command.
esc Cancel the current turn (while busy); clear input (while idle).
ctrl+c Clear the input and queue (while idle) or arm the exit hint (while busy). Press again within 2s to exit. Use esc to cancel a running turn.
ctrl+d Exit on empty input.
ctrl+l Redraw the screen.
ctrl+o Expand or collapse long tool results (read, write, edit, bash outputs over ~12 lines).
@ Open the file picker. Browse files and directories in the working directory.

File picker (@)

Key Action
@ Open the file picker (type after a space or at the start of input).
up, down Navigate the file list.
right Open the selected directory.
left Go back to the parent directory.
enter Select the file or directory and insert it as a chip ([file:name] or [dir:name/]).
esc Close the file picker.

Type @ followed by a filter string to narrow the list (e.g. @read shows only entries containing "read"). Selected files are inserted as compact chips that expand to the full path on submit. Dragged-and-dropped files and directories also collapse to chips automatically.

Editor line navigation

Key Action
ctrl+a, ctrl+e Jump to start or end of line.
alt+left, alt+right Jump one word back or forward.
ctrl+u, ctrl+k Delete to start or end of line.
ctrl+w, alt+backspace Delete the previous word.
up, down (editor non-empty) Cycle through prompt history.

Chat scroll

Key Action
pgup, pgdn Scroll one page up or down.
up, down (editor empty) Scroll three lines up or down. This is how the mouse wheel reaches the scroll logic on most terminals.

Extensions

zot can be extended in any language via a subprocess + JSON-RPC protocol. Extensions can register slash commands, expose tools to the model, intercept tool calls (block or rewrite args), gate whole turns before the model is called, and rewrite the assistant's visible text before it reaches the user. None are installed by default; opt in explicitly. Hot-reload any time with /reload-ext.

Install and manage

zot ext install <path|git-url>   # copy / clone into $ZOT_HOME/extensions/
zot ext list                      # show installed extensions
zot ext logs <name> [-f]          # cat or tail the extension's stderr log
zot ext enable <name>             # re-enable a disabled extension
zot ext disable <name>            # disable without removing
zot ext remove <name>             # delete an extension directory

For development, point zot --ext <path> at a working directory and skip the install step entirely. Repeatable; takes precedence over installed extensions of the same name.

Reference

examples/extensions/ ships reference implementations in Go, TypeScript, Node, and shell. See docs/extensions.md for the full protocol, the SDK API (pkg/zotext), and the phase roadmap.

Skills

A skill is a per-folder SKILL.md file with a YAML frontmatter header. zot discovers skills at startup, surfaces their names in the system prompt, and exposes a built-in skill tool the model uses to load the body on demand.

By default only the built-in skills shipped with the zot binary are loaded. Pass --with-skills to also load user-installed skills from:

  • ./.zot/skills/<name>/SKILL.md (project)
  • $ZOT_HOME/skills/<name>/SKILL.md (global)
  • ./.claude/skills/<name>/SKILL.md, ~/.claude/skills/<name>/SKILL.md (Claude-compatible layout)
  • ./.agents/skills/<name>/SKILL.md, ~/.agents/skills/<name>/SKILL.md (agent-compatible layout)

See docs/skills.md for the frontmatter fields, authoring tips, and example skills under examples/skills/.

Telegram bot (bridge)

zot can run as a telegram bot so you can DM it from your phone. Two ways to run it: from inside the TUI (the running session mirrors into Telegram) or as a standalone background daemon (a headless bot with its own independent agent).

From inside the TUI

Type /telegram in the running TUI to open a picker with connect, disconnect, and status. When connected:

  • DMs from the paired user become prompts in the same session you're typing in, so you can continue a conversation from the terminal on your phone and back again.
  • Messages you type in the TUI are mirrored into the Telegram thread prefixed you: … and the assistant's replies come back prefixed zot: …, so the Telegram chat stays a complete record of both sides of the conversation.
  • Messages sent from Telegram show up as your own bubble in Telegram (no mirror) and the assistant's reply to them comes back bare (no prefix).
  • The status bar shows a - tg - tag while the bridge is active.
  • /telegram connect / /telegram disconnect / /telegram status (or /tg) also work as direct commands without the picker.

The in-TUI bridge refuses to start while the standalone daemon (below) is running, since two concurrent long-poll consumers of the same bot race on every update and silently drop messages.

Standalone daemon

For headless servers or long-running bots unattached to a TUI:

zot telegram-bot setup     # paste a BotFather token, verify, save
zot telegram-bot run       # foreground: long-poll in this terminal (ctrl+c to stop)
zot telegram-bot start     # background: detach and return immediately
zot telegram-bot stop      # SIGTERM the background bot (SIGKILL after 5s)
zot telegram-bot logs -f   # tail $ZOT_HOME/logs/bot.log (omit -f to just cat)
zot telegram-bot status    # config (token masked) + running/stopped
zot telegram-bot reset     # forget the token and paired user
# short alias: `zot tg ...` is accepted for every subcommand

The background flavor writes the child's PID to $ZOT_HOME/bot.pid and redirects stdout and stderr to $ZOT_HOME/logs/bot.log. zot telegram-bot stop reads that PID, sends SIGTERM, waits up to five seconds, then escalates to SIGKILL if the child is still alive. Running two instances at once is refused at startup.

Use the installed binary for start. go run ./cmd/zot telegram-bot start won't work. go run builds a binary in a temp directory and deletes it when it exits, which kills the detached child. Run make install (or go build) first and invoke the installed binary.

Setup flow:

  1. Talk to @BotFather on telegram, run /newbot, copy the token it gives you.
  2. Run zot telegram-bot setup and paste the token when prompted.
  3. Run zot telegram-bot run in the directory you want the agent to operate in.
  4. Open your bot on telegram, send /start. The first user to do this claims the bridge (stored as allowed_user_id); every other user is rejected.

From then on, any DM you send is forwarded to the agent as a user prompt. Attached photos or image/* documents are downloaded and passed to vision-capable models. In-bot telegram commands: /help, /status, /stop (cancel the current turn). Config lives in $ZOT_HOME/bot.json (mode 0600).

Bot mode respects the usual zot flags: --provider, --model, --cwd, --reasoning, --continue, --no-session, --no-tools, and so on. Run zot tg run -c --model claude-opus-4-1 to resume the latest session on Opus, for example.

Development

make build     # build ./bin/zot
make test      # go test -race ./...
make lint      # go vet + gofmt check
make fmt       # gofmt -w .
make release   # cross-compile linux/darwin/windows on amd64 and arm64

Source layout:

cmd/zot/                      main()
internal/agent/               cli wiring, arg parsing, system prompt, config
internal/agent/extensions/    extension subprocess manager
internal/agent/modes/         interactive tui, print, json, dialogs
internal/agent/tools/         read, write, edit, bash, sandbox
internal/auth/                credential store, api-key probe, oauth, login server
internal/core/                agent loop, sessions, cost tracking
internal/extproto/            extension wire-format types
internal/provider/            anthropic + openai-compatible streaming clients, model catalog
internal/skills/              skill discovery, frontmatter parser, skill tool
internal/tui/                 terminal raw-mode, input parser, editor, renderer, markdown, view
pkg/zotcore/                  public Go SDK for embedding zot in-process
pkg/zotext/                   public Go SDK for writing extensions

License

MIT

source
1<img src="internal/assets/zot-logo.png" alt="zot" width="130" height="130" />
2
3# zot
4
5Yet another coding agent harness, lightweight and written (vibe-slopped) in go.
6
7- one static binary.
8- built-in providers for anthropic, openai/codex, kimi, deepseek, google gemini, and ollama/openai-compatible local models.
9- four tools (read, write, edit, bash).
10- three run modes (interactive tui, print, json).
11- built-in telegram bot.
12- extensions in any language via subprocess + json-rpc. None installed by default; opt in with `zot ext install` or `zot --ext`. See [docs/extensions.md](docs/extensions.md).
13- reusable instructions via `SKILL.md` files; see [docs/skills.md](docs/skills.md).
14- no community atm.
15
16## Install
17
18### One-liner (macOS, Linux)
19
20```bash
21curl -fsSL https://www.zot.sh/install.sh | bash
22```
23
24Detects 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:
25
26```bash
27curl -fsSL https://www.zot.sh/install.sh | bash -s -- v0.0.1 ~/bin
28```
29
30### One-liner (Windows, PowerShell)
31
32```powershell
33iwr -useb https://www.zot.sh/install.ps1 | iex
34```
35
36Drops `zot.exe` into `$HOME\bin` and adds it to the user PATH if missing. Open a fresh terminal afterwards.
37
38### go install
39
40```bash
41go install github.com/patriceckhart/zot/cmd/zot@latest
42```
43
44### From source
45
46```bash
47git clone https://github.com/patriceckhart/zot
48cd zot
49make build        # produces ./bin/zot
50make install      # into $GOPATH/bin
51```
52
53### Prebuilt binaries
54
55Every release on the [releases page](https://github.com/patriceckhart/zot/releases) 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`.
56
57## Authenticate
58
59The 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.
60
61### Credential lookup order
62
631. `--api-key` flag
642. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `KIMI_API_KEY`, `MOONSHOT_API_KEY`, `DEEPSEEK_API_KEY`, `GEMINI_API_KEY`, or `GOOGLE_API_KEY` env var
653. `$ZOT_HOME/auth.json` (API key or OAuth token; mode 0600)
66
67`$ZOT_HOME` defaults to:
68- macOS: `~/Library/Application Support/zot`
69- Linux: `$XDG_STATE_HOME/zot` or `~/.local/state/zot`
70- Windows: `%LOCALAPPDATA%\zot`
71
72### `/login` flow
73
74Run `zot` and type `/login`. Pick one of two methods:
75
76- **API key**: a small local web server starts on `127.0.0.1:<free-port>`, your browser opens a form, you paste your `sk-ant-...`, `sk-...`, Kimi/Moonshot key, DeepSeek (`sk-...`) key, or Google AI Studio (`AIza...`) Gemini key. zot probes the provider once and saves it to `auth.json` if accepted.
77- **Subscription**: use your Claude Pro/Max, ChatGPT Plus/Pro, or Kimi Code subscription. DeepSeek and Google Gemini do **not** have a subscription login path — the `/login subscription` step only lists Anthropic, OpenAI, and Kimi. For DeepSeek and Google use the API-key flow.
78  - Anthropic and OpenAI pin the browser callback to fixed provider-specific ports (`localhost:53692` for Anthropic, `localhost:1455` for OpenAI) because those are the only ports their auth servers will redirect to.
79  - Anthropic uses the Claude Code OAuth flow. Messages go to `api.anthropic.com` with a bearer token and the Claude Code identity headers.
80  - OpenAI uses the Codex CLI OAuth flow. Messages go to `chatgpt.com/backend-api/codex/responses` with the `chatgpt-account-id` extracted from the returned id_token.
81  - 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/v1` with the Kimi Code identity headers.
82
83> **Note on subscription login.** The OAuth client IDs used are the ones published in Anthropic's Claude Code CLI, OpenAI's Codex CLI, and Kimi Code CLI. 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.
84
85### Token refresh
86
87OAuth access tokens are short-lived (Anthropic ~8h, OpenAI ~30d). zot refreshes them automatically:
88
89- At every credential lookup, zot checks the stored `expiry` and, if past it (with a 60s safety margin), hits the provider's `oauth/token` endpoint with the stored `refresh_token`, persists the new `access_token`, `refresh_token`, and `expiry` back to `auth.json`, and hands the fresh token to the client.
90- The telegram bridge additionally refreshes once per turn so a bot that runs for days keeps working without manual intervention.
91- If the refresh itself fails (the `refresh_token` was 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 `/login` to get a fresh token pair.
92
93All data lives under `$ZOT_HOME`:
94
95```
96$ZOT_HOME/
97├── config.json         # last-used provider/model/theme, saved automatically
98├── auth.json           # api keys and oauth tokens (mode 0600)
99├── sessions/           # jsonl transcripts, one dir per cwd
100├── models-cache.json   # live /v1/models discovery cache (6h ttl)
101├── SYSTEM.md           # optional: replaces the default system prompt
102├── skills/             # optional: user SKILL.md files (opt in with --with-skills)
103├── extensions/         # installed extensions, one dir per extension
104└── logs/               # app log files
105```
106
107Drop a `SYSTEM.md` in `$ZOT_HOME` to replace the built-in identity and guidelines for every run. `--system-prompt` still wins per-invocation. Delete the file to revert to the default.
108
109## Changelog on update
110
111The first time you launch a newer zot binary, the TUI shows the GitHub release notes once in a dismissible overlay. Press any key to close. The version is recorded in `config.json`'s `last_changelog_shown` so the same release notes never reappear. Fresh installs don't see a changelog (no upgrade has happened yet). The fetch is best-effort: a network failure or a missing release page silently skips, with another attempt on the next launch.
112
113## Usage
114
115```bash
116zot                              # interactive tui
117zot "fix the failing test"       # tui, pre-filled prompt
118zot -p "list all go files"       # print final text, exit
119zot --json "refactor main.go"    # newline-delimited json events, exit
120zot --continue                   # resume the most recent session for this cwd
121zot --resume                     # pick a session to resume
122zot --list-models                # show supported models
123zot --help
124```
125
126## Flags
127
128| Flag | Description |
129|---|---|
130| `--provider anthropic\|openai\|kimi\|deepseek\|google\|ollama` | Pick the provider. |
131| `--model <id>` | Pick the model (see `--list-models`). |
132| `--api-key <key>` | Override the API key. |
133| `--base-url <url>` | Override the provider base URL (tests, self-hosted). |
134| `--system-prompt <text>` | Replace the default system prompt for this run (also overrides `$ZOT_HOME/SYSTEM.md`). |
135| `--append-system-prompt <text>` | Append text to the system prompt (repeatable). |
136| `--reasoning low\|medium\|high` | Enable reasoning on supported models. |
137| `-c`, `--continue` | Resume the latest session for this cwd. |
138| `-r`, `--resume` | Pick a session to resume. |
139| `--session <path>` | Resume a specific session file. |
140| `--no-session` | Don't read or write session files. |
141| `--cwd <path>` | Use `<path>` as the working directory. |
142| `--no-tools` | Disable all tools. |
143| `--tools <csv>` | Only enable the listed tools. |
144| `--max-steps <n>` | Cap agent loop iterations (default 50). |
145| `-e`, `--ext <path>` | Load an extension from `<path>` for this run (repeatable; wins against installed extensions of the same name). |
146| `--no-ext` | Skip extension discovery for this run. `--ext` still works on top, so `--no-ext --ext ./x` runs only `x`. |
147| `--with-skills` | Also load user-installed skills. Without this, only the built-in skills shipped in the binary are loaded. |
148| `--no-skill` | Disable all skills, including built-ins. No `skill` tool is registered and the system prompt has no skill manifest. |
149| `--no-yolo` | Confirm every tool call before it runs (interactive TUI only). A dialog shows the tool name and a one-line preview of its args with four choices: yes, yes-always-this-tool-this-session, yes-always-this-session, no. Ignored with a stderr warning in print / json / rpc modes, where tools still run freely so scripts and automation keep working. |
150
151## Tools
152
153- `read`: read text files, or inline images (PNG, JPEG, GIF, WebP).
154- `write`: create or overwrite files, making parent directories as needed.
155- `edit`: one or more exact-match replacements in an existing file.
156- `bash`: run a shell command in the session cwd, with merged stdout/stderr and a timeout.
157
158When the sandbox is on (see `/jail`), all four tools refuse paths outside the session cwd.
159
160## Modes
161
162- **Interactive** (default): chat TUI with streaming output, spinner, cost meter, slash commands.
163- **Print**: `zot -p "prompt"` runs the agent to completion and writes only the final assistant text to stdout.
164- **JSON**: `zot --json "prompt"` emits one JSON object per agent event to stdout, newline-delimited. The schema is documented in [docs/rpc.md](docs/rpc.md).
165- **RPC**: `zot rpc` runs as a long-lived child process; commands in on stdin, events and responses out on stdout, both as NDJSON. Designed for embedding zot in third-party apps written in any language. See [docs/rpc.md](docs/rpc.md) for the wire schema and `examples/rpc/{python,node,shell,go}` for working clients.
166
167## Embedding
168
169Two ways to drive zot from another program:
170
171- **Go in-process**: import `github.com/patriceckhart/zot/pkg/zotcore`. One `Runtime` per project; `Prompt(ctx, text, images)` returns a channel of `Event`. Small example in `examples/sdk/`.
172- **Any language, out-of-process**: spawn `zot rpc` as a subprocess and exchange newline-delimited JSON over its stdin/stdout. Wire format and event schema in [docs/rpc.md](docs/rpc.md). Reference clients live under `examples/rpc/`.
173
174Both interfaces share the same event schema, so transcripts captured by one can be replayed through the other.
175
176## Slash commands
177
178Type `/` in the TUI to open the autocomplete popup. Available commands:
179
180| Command | Description |
181|---|---|
182| `/help` | Show key bindings and commands. |
183| `/login` | Log in via API key or subscription (opens a dialog). |
184| `/logout [provider]` | Clear credentials for `anthropic`, `openai`, `kimi`, `deepseek`, `google`, or all when omitted. `/logout kimi` also disables fallback to the official Kimi Code CLI token until you log in to Kimi through zot again. |
185| `/model` | Pick a model from a list (or `/model <id>` to set directly). |
186| `/sessions` | Resume a previous session for this directory. |
187| `/session` | Four ops on the current session: `export` to a portable `.zotsession` file, `import` one back in, `fork` from a past user message into a new branch, `tree` to switch between branches. Opens a picker without an argument; direct forms: `/session export [path]`, `/session import <path>`, `/session fork`, `/session tree`. Default export destination is `~/Downloads`. |
188| `/jump` | Scroll the chat to a previous turn (or `/jump <text>` to filter). |
189| `/btw` | Side chat with full context that doesn't add to the main thread. |
190| `/swarm` | Spawn, monitor, and chat with background subagents. Each gets its own git worktree and runs in parallel with your main session. |
191| `/skills` | List discovered skills (SKILL.md files) and preview their bodies. |
192| `/compact` | Summarize the transcript into one message to free up context. |
193| `/study` | Run the canned prompt "Read and understand everything in the current directory." so the agent has full project context before you start asking targeted questions. |
194| `/jail` | Confine tools to the current directory. |
195| `/unjail` | Allow tools to touch paths outside again. |
196| `/reload-ext` | Hot-reload all extensions (re-read manifests, respawn subprocesses, rebuild tool registry). |
197| `/telegram` | Connect, disconnect, or show status of the Telegram bridge (takes `connect` / `disconnect` / `status` as an optional argument; opens a picker without one). When connected, DMs from the paired user become prompts in the running session and the assistant's replies are mirrored back to Telegram. Alias: `/tg`. |
198| `/clear` | Clear the chat transcript. |
199| `/exit` | Exit zot. |
200
201Extension-registered commands appear under a divider at the bottom of the popup, sorted by name.
202
203### `/sessions`
204
205Shows previous sessions for the current working directory, newest first, with timestamp, model, message count, cost, and the first user prompt. Pick one with `up`/`down`, `enter` to resume, `esc` to cancel. zot swaps the current session file for the selected one and replays the full transcript (including tool calls) into the agent. Sessions remember the model they ended on, so resuming picks up on that exact model even if your global default changed.
206
207### `/session`
208
209Four ops on the current session. `/session` alone opens a picker; each is also runnable directly.
210
211- **`/session export [path]`**. Writes the running transcript to a portable `.zotsession` file. Default destination is `~/Downloads/<timestamp>-<session-id>-<prompt-slug>.zotsession`. Pass a path to override; a directory is fine (a dated name is built inside), a bare name gets `.zotsession` appended. The meta's cwd is stripped on the way out so the recipient doesn't see your filesystem layout.
212
213  **What's included.** Only the main chat thread of the running session — messages, tool calls, tool results, compactions, and usage. **`/swarm` subagents are NOT included.** Their transcripts, worktrees (which are real git checkouts under `$ZOT_HOME/swarm/worktrees/<id>` on branch `swarm/<id>`), unix-socket inboxes, and per-agent session files are all machine-local; a `.zotsession` is just a chat transcript and has no way to bundle a git worktree or revive a unix socket on another box. If you want to share a subagent's work, use normal git tooling on its branch (`git push`, `git format-patch`, etc.); if you want the conversation, copy it out of the dashboard manually.
214- **`/session import <path>`**. Copies a `.zotsession` file into `$ZOT_HOME/sessions/<cwd-hash>/` with a fresh id and the current cwd, then switches the running agent onto it. Imported sessions are first-class: they show up in `/sessions`, `/jump`, and the tree. Drag-drop paths in the editor are accepted (zot strips the surrounding quotes automatically).
215- **`/session fork`**. Opens a turn picker (same shape as `/jump`). Pick any past user message; zot copies every message up to and including that turn into a new session, records `parent` + `fork_point` in the new meta, and switches onto the branch. The parent session stays on disk. Use it to try a different question without polluting the original transcript, or to rewind after the agent went down the wrong path.
216- **`/session tree`**. Shows every session in the current cwd arranged by parent/child relationships, depth-first with indent per level. The current session is tagged `[current]`. Pick any entry to switch into it. Parentless sessions are roots; branches created via `/session fork` nest under whichever session they were forked from. Orphaned children (whose parent file was deleted) still show as roots so they stay discoverable.
217
218### `/jump`
219
220Opens a turn picker for the current session, one row per user prompt, each showing the turn number, how many tools that turn invoked, and the first line of the prompt. `up`/`down` to pick, `enter` to jump, `esc` to cancel. Any printable rune while the picker is open extends a filter; backspace narrows it back. `/jump <text>` pre-applies the filter; if exactly one turn matches, zot jumps straight there without showing the picker.
221
222Jumping is non-destructive. The transcript is untouched, the viewport just scrolls so the chosen turn is at the top. A muted line at the top of the chat reads `viewing turn N of M, pgdn to catch up`. Scroll back to the bottom with `pgdn` (or keep scrolling with the arrow keys) and the indicator goes away.
223
224### `/btw`
225
226Opens a side-chat overlay with the full main session as frozen context, so you can ask quick clarifying questions ("does asyncio.gather() catch exceptions?", "btw the bundle budget is 10MB", "what's the default fetch timeout?") without bloating the main thread.
227
228Each question fires a one-off model call against `system + main transcript + side-chat history so far`. Responses render in the overlay and stay there. When you press `esc` to close, **nothing** has been added to the main session and subsequent main-thread turns don't re-read any of the side-chat exchanges, keeping the running context window lean.
229
230```
231/btw                              # open the overlay, type questions interactively
232/btw does PUT replace the whole resource?
233```
234
235Inside the overlay: `enter` sends, `esc` cancels an in-flight call (or closes the overlay if idle), `ctrl+c` closes immediately. Side-chat exchanges never touch the transcript and aren't persisted to the session file.
236
237### `/swarm`
238
239Long-running parallel subagents. Each one gets its own git worktree off your current repo (branch `swarm/<id>`), its own persistent session file, and its own background subprocess driving the model. You stay in the main session and check in on them whenever you want — the dashboard is a `/btw`-style chat per agent.
240
241Why: “go write the test harness for module X” / “investigate this stack trace” / “draft the migration” are perfect side tasks. Spawn an agent, keep working in your main session, come back to its results later — or chat with it interactively while it works.
242
243```
244/swarm                            # open the dashboard
245/swarm new <task>                 # spawn an agent on a fresh worktree
246/swarm new --model gpt-5 <task>   # pin the new agent to a specific model
247/swarm logs <id>                  # jump straight into one agent's transcript
248/swarm send <id> <text>           # send a follow-up without opening the dashboard
249/swarm resume                     # pick a stopped agent to bring back
250/swarm resume <id>                # bring a specific agent back
251/swarm kill <id>                  # stop a running agent (worktree stays)
252/swarm remove <id>                # delete the worktree + session for an agent
253/swarm list                       # alias for opening the dashboard
254```
255
256**Dashboard (`/swarm` with no arg)** — a list of every agent for the current session with status, age, and current activity. Keys:
257
258- `↑` / `↓` move the cursor between rows.
259- `enter` opens the highlighted agent's transcript view.
260- `n` spawns a new agent. Type the task, `enter` to confirm; the new agent inherits the model your main session is currently on (see `/model` and the in-editor `/model` command below).
261- `p` opens a one-off prompt editor for the selected row (alternative to entering the transcript).
262- `R` resumes a stopped agent in place.
263- `k` kills the selected running agent (its worktree and session stay so you can resume later).
264- `r` removes the selected agent entirely (worktree + session + meta gone).
265- `esc` closes the dashboard.
266
267**Inside an agent's transcript** — a chat overlay just like `/btw`. The agent's conversation flows above an always-on inline composer; type and hit `enter` to send a follow-up. The view auto-follows streaming output and shows an inline spinner with the agent's current activity (`thinking`, `tool: edit_file`, etc.) while it's busy. `esc` returns to the dashboard.
268
269**Switching the spawn model from inside the editor** — while composing a task in the `n`-prompt, type `/model` on its own line and `enter`. The same model picker the global `/model` uses pops up; pick a model, the picker closes, and the editor reopens with your typed task intact and the new model pinned for the spawn.
270
271**Session scoping** — each agent is stamped with the session that spawned it and only shows up in that session's `/swarm` dashboard. Swap sessions with `/sessions` and the dashboard re-narrows accordingly. Agents you spawned in another session keep running and reappear when you switch back. Pre-upgrade agents (no session stamp) are visible from every session as a safety net.
272
273**Persistence across zot restarts** — every spawn writes a `meta.json` next to its event log and session file under `$ZOT_HOME/swarm/agents/<id>/`. On the next `zot` launch they show up in the dashboard as **detached**; press `R` (or `/swarm resume <id>`) to bring one back. Resumed agents reattach to the same worktree, session, branch, and inbox socket, so the conversation continues from where it left off.
274
275**Where their work lives** — worktrees go under `$ZOT_HOME/swarm/worktrees/<id>` on the branch `swarm/<id>`. Use the normal git tooling to inspect, merge, or rebase (`git worktree list`, `git log swarm/<id>`, etc.). `/swarm remove` deletes both the worktree and the swarm bookkeeping.
276
277**`/session export` does NOT bundle subagents.** A `.zotsession` is just the main chat transcript; swarm worktrees and per-agent state are machine-local (a real git worktree on disk and a unix-socket inbox, neither of which round-trips through a JSONL file). To share a subagent's actual work, push its `swarm/<id>` branch with the normal git tooling. To share what it said, copy it out of the transcript view manually.
278
279### `/skills`
280
281Opens a picker listing every discovered SKILL.md file, built-ins hidden. Each row shows the skill name, source, and description. `enter` opens the body inline (scrollable with `up`/`down`/`pgup`/`pgdn`); `esc` goes back. Re-runs discovery each time it opens, so edits to a SKILL.md during a session are reflected immediately.
282
283### `/compact`
284
285Sends the current transcript through the model with a structured summarization prompt. The returned summary replaces the transcript as one synthetic user message, with the last few exchanges kept verbatim for continuity. The status bar's context meter resets. Use it when the context meter creeps past ~80%.
286
287zot also auto-compacts in the background: after any turn that leaves context usage at or above **85%** of the model's window, the agent kicks off a condense pass on its own. You'll see `condensing history, esc to cancel` above the status bar and an `(auto)` tag next to the context percentage; `esc` aborts it without touching the transcript.
288
289### `/jail`
290
291Enforces a sandbox rooted at the cwd shown in the status bar. `read`, `write`, and `edit` resolve their target path (including through symlinks) and refuse anything outside the sandbox. `bash` refuses obvious escape patterns: `sudo`, `rm -rf /`, leading `cd /`, `cd ..`, `cd ~`, `chmod -R`, `dd of=/`, and similar. The status bar shows `jailed, ~/your/cwd` while active.
292
293This is a guardrail against accidents, not a hard security boundary. If you need real isolation, run zot under docker or a proper sandbox.
294
295## Sessions
296
297Every interactive or print/json run (unless `--no-session`) writes a JSONL transcript under `$ZOT_HOME/sessions/<cwd-hash>/`. Resume any of them with `--continue`, `--resume`, `--session <path>`, or interactively via `/sessions` inside the TUI. Empty sessions (the user exited without prompting) are deleted on close so the list stays tidy.
298
299## Models
300
301`--list-models` or the `/model` picker shows the full catalog. Three sources:
302
303- **Catalog**: models baked into zot, always available.
304- **Live**: IDs discovered from `GET /v1/models` using your stored API key (cached for 6h in `$ZOT_HOME/models-cache.json`, refreshed in the background on startup).
305- **Speculative**: IDs that appear in the upstream generator but aren't live on the public API yet. They'll 404 today and start working the moment the provider ships them.
306
307The context meter in the status line uses the model's advertised context window to show how much of it your last turn consumed.
308
309### Model fallback (rescue)
310
311When a turn fails because of a recoverable provider error — expired token (`401`), permission denied (`403`), rate limit (`429`), provider outage (`502`/`503`/`504`), or a transient network failure — zot opens a **rescue** picker over the chat instead of just painting a red banner.
312
313The picker is the same vertical list / fuzzy filter UI as `/model`, but it only shows models from providers you're currently logged in to (env vars, `auth.json`, Kimi CLI fallback, ollama). The failed model is excluded. Press `↑`/`↓` to choose, `enter` to retry the **same prompt** on the new model, `esc` to dismiss.
314
315Before the actual provider request fires, the OpenAI / Anthropic / Kimi / DeepSeek / Google / OpenAI-Codex clients also do up to two silent retries with short backoff (250ms, 750ms) on `502`/`503`/`504` and connection-reset / EOF-before-headers errors. Most edge-proxy blips disappear without you ever seeing the rescue picker.
316
317A rescue retry always **drops launch-time `--api-key` and `--base-url`** before rebuilding the agent. Those overrides are usually the reason the rescue triggered (bad key, typo'd base URL, corporate gateway only valid for the originally-picked provider), so the retry re-resolves credentials from env vars / `auth.json` / provider defaults instead. Use `/model` if you want overrides to stick.
318
319No configuration is required — the candidate list is built dynamically from your active credentials. Bad-request / context-length / serialization errors are NOT routed to the rescue picker, because switching models won't fix them; those still surface as a normal error.
320
321### Custom models
322
323Place a `models.json` in `$ZOT_HOME` (macOS: `~/Library/Application Support/zot/`, Linux: `~/.local/state/zot/`) to add models that aren't in the baked-in catalog or to override existing entries:
324
325```json
326{
327  "providers": {
328    "openai": {
329      "models": [
330        {
331          "id": "gpt-5.5",
332          "name": "GPT-5.5",
333          "reasoning": true,
334          "contextWindow": 400000,
335          "maxTokens": 128000
336        }
337      ]
338    }
339  }
340}
341```
342
343Supported fields per model: `id` (required), `name`, `reasoning`, `contextWindow`, `maxTokens`, `baseUrl`, `priceInput`, `priceOutput`, `priceCacheRead`, `priceCacheWrite`.
344
345Provider keys are normalized: `openai-codex` and `openai-responses` map to `openai`, `anthropic-messages` maps to `anthropic`, `moonshot`, `moonshot-ai`, and `kimi-code` map to `kimi`, and `deepseek-chat` and `deepseek-ai` map to `deepseek`.
346
347User-defined models show `source: user` in `--list-models` and take precedence over both the baked-in catalog and live-discovered models. Missing or invalid files are silently ignored.
348
349### Kimi Code
350
351zot has built-in Kimi support through Kimi's OpenAI-compatible chat API.
352
353```bash
354zot --provider kimi
355```
356
357By default this uses:
358
359- model: `kimi-for-coding`
360- base URL: `https://api.kimi.com/coding/v1`
361
362Credential lookup order for Kimi:
363
3641. `--api-key`
3652. `KIMI_API_KEY`
3663. `MOONSHOT_API_KEY`
3674. `$ZOT_HOME/auth.json`
3685. the official Kimi Code CLI token at `~/.kimi/credentials/kimi-code.json`, unless disabled by `/logout kimi`
369
370Use `/login` for either API-key login or Kimi Code subscription login. The subscription flow uses Kimi Code's device-code OAuth flow: zot opens the verification URL, waits for browser approval, stores the token in `auth.json`, and refreshes it automatically.
371
372For direct Moonshot API keys or a custom compatible endpoint:
373
374```bash
375zot --provider kimi --model kimi-k2-0905-preview --base-url https://api.moonshot.ai/v1 --api-key "$KIMI_API_KEY"
376```
377
378You can add additional Kimi/Moonshot model IDs to `models.json` under the `kimi` provider.
379
380### DeepSeek
381
382zot has built-in DeepSeek support through DeepSeek's OpenAI-compatible chat API.
383
384```bash
385zot --provider deepseek
386```
387
388By default this uses:
389
390- model: `deepseek-v4-pro`
391- base URL: `https://api.deepseek.com/v1`
392
393Catalog ships with `deepseek-v4-pro` (reasoning) and `deepseek-v4-flash`. These are exactly the IDs returned by `GET https://api.deepseek.com/models` today. You can add additional model IDs to `models.json` under the `deepseek` provider.
394
395Credential lookup order for DeepSeek:
396
3971. `--api-key`
3982. `DEEPSEEK_API_KEY`
3993. `$ZOT_HOME/auth.json`
400
401Use `/login` and pick **api key** to paste a DeepSeek key. zot probes `/v1/models` once and stores the key under `deepseek` in `auth.json`.
402
403> **Auth model: API key only.** DeepSeek does not offer a subscription OAuth flow. The `/login subscription` step lists only Anthropic, OpenAI, and Kimi; DeepSeek shows up only under `/login → api key`.
404
405> **Text only at the wire level.** DeepSeek's chat-completions endpoint currently rejects the multimodal content schema (`unknown variant image_url, expected text`). When the active provider is `deepseek`, zot silently drops `ImageBlock` parts from outgoing user/tool messages and keeps only the text. Switching back to a vision-capable model (Claude, GPT-4o/5, Gemini) re-sends the image normally because the session file still stores it.
406
407For a custom-compatible endpoint (mirror, gateway, self-host):
408
409```bash
410zot --provider deepseek --base-url https://my-deepseek-mirror.example.com/v1 --api-key "$DEEPSEEK_API_KEY"
411```
412
413### Google Gemini
414
415zot has built-in Google Gemini support through the [AI Studio Generative Language API](https://aistudio.google.com/).
416
417```bash
418zot --provider google
419```
420
421By default this uses:
422
423- model: `gemini-2.5-pro`
424- base URL: `https://generativelanguage.googleapis.com`
425
426Catalog ships with `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`, `gemini-2.0-flash`, and `gemini-2.0-flash-lite`. Live discovery against `/v1beta/models` adds anything else your key can see.
427
428Credential lookup order for Google:
429
4301. `--api-key`
4312. `GEMINI_API_KEY`
4323. `GOOGLE_API_KEY`
4334. `$ZOT_HOME/auth.json`
434
435Use `/login` and pick **api key** to paste an AI Studio key. zot probes `/v1beta/models` once and stores the key under `google` in `auth.json`.
436
437> **Auth model: API key only.** Google does not issue OAuth tokens for consumer Gemini Advanced / Google One AI Premium subscriptions, so there is no "log in with your Google subscription" flow. Programmatic access requires either an AI Studio API key (this provider) or a Vertex AI / GCP service-account credential (not yet wired up in zot). The `/login subscription` step quietly downgrades to the api-key form when you pick Google so you don't end up in a dead end.
438
439> **Free-tier rate limits.** AI Studio's free tier has tight per-minute and per-day caps that vary by model: `gemini-2.5-pro` is the strictest (a few requests per minute, ~50 per day), Flash and Flash-Lite are far more generous. If a Pro turn 429s with `"You exceeded your current quota"` while Flash on the same key still works, you've hit the Pro free-tier RPD. Either switch to Flash for agent loops, or [enable billing](https://aistudio.google.com/app/apikey) on your AI Studio project to flip the same key from free to pay-as-you-go pricing (`$1.25/M` input, `$10/M` output for Pro).
440
441Reasoning levels (`--reasoning low|medium|high`) map differently per generation: 2.5 family uses `thinkingBudget` token budgets per model (Pro caps at 32k, Flash at 24k); Gemini 3.x uses the `thinkingLevel` enum (`MINIMAL`/`LOW`/`MEDIUM`/`HIGH`), with Gemini-3-Pro pinned to `LOW` minimum and `HIGH` for any "medium" or "high" request. 2.0-family models have no thinking config at all.
442
443You can add additional Gemini model IDs to `models.json` under the `google` provider.
444
445### Local models with ollama
446
447zot works with [ollama](https://ollama.com) out of the box. Ollama serves an OpenAI-compatible API locally, so any model you have pulled works with zot.
448
449Quick start:
450
451```bash
452ollama pull qwen3.5:4b
453zot --provider ollama --model qwen3.5:4b
454```
455
456That's it. No API key needed for local models. zot defaults to `http://localhost:11434`.
457
458For a remote ollama instance or one behind auth:
459
460```bash
461zot --provider ollama --model llama3 --base-url https://my-server.com/v1 --api-key my-token
462```
463
464You can also add models to your `models.json` so you don't need flags every time:
465
466```json
467{
468  "providers": {
469    "ollama": {
470      "models": [
471        {
472          "id": "qwen3.5:4b",
473          "name": "Qwen 3.5 4B",
474          "contextWindow": 32768,
475          "maxTokens": 8192
476        }
477      ]
478    }
479  }
480}
481```
482
483The `ollama` provider uses the OpenAI chat completions protocol internally, so it also works with any OpenAI-compatible server (vLLM, LM Studio, LocalAI, etc.).
484
485## Inline images
486
487When a tool returns an image (for example `read` on a PNG), zot renders it inline on terminals that support it: **Ghostty**, **Kitty**, **iTerm2**, **WezTerm**. On other terminals you see a text placeholder with MIME type, pixel dimensions, and byte size. Control with the `ZOT_INLINE_IMAGES` env var:
488
489| Value | Effect |
490|---|---|
491| unset (default) | Auto-detect based on `TERM_PROGRAM`. |
492| `iterm`, `iterm2` | Force the iTerm2 OSC 1337 protocol. |
493| `kitty` | Force the Kitty graphics protocol. |
494| `off`, `none` | Always use the text placeholder. |
495
496Frames containing images are full-repainted (no differential diff) to prevent stale image pixels from lingering through scroll. That costs one terminal flash per image-containing frame; set `ZOT_INLINE_IMAGES=off` if that bothers you.
497
498## Queued messages
499
500You can keep typing while the agent is working. Pressing `enter` during a turn queues the message instead of interrupting: it shows up above the status bar as `sliding in: <text>` and is delivered as the next user turn the moment the current one finishes. Queue as many as you want; they run in order. `esc` cancels the active turn and drops the queue so a runaway turn doesn't flood you with stale follow-ups; `ctrl+c` while busy arms the exit hint instead of interrupting, a second `ctrl+c` within two seconds exits zot.
501
502To recover the most recently queued message back into the editor (to tweak it before it runs), press `Option+↑`. In VS Code's integrated terminal that chord doesn't survive xterm.js's macOS key handling — use `Option+Shift+↑` there. zot's hint line under the sliding-in queue adapts automatically based on `$TERM_PROGRAM`.
503
504Slash commands also work while the agent is busy. Read-only ones (`/help`, `/jump`, `/btw`, `/sessions`, `/skills`, `/jail`, `/unjail`, `/exit`) take effect immediately. Destructive ones (`/clear`, `/compact`, `/login`, `/logout`, `/model`, `/reload-ext`) cancel the active turn first and then run.
505
506
507## Keys (interactive mode)
508
509### Input
510
511| Key | Action |
512|---|---|
513| `enter` | Submit (queued if the agent is busy). |
514| `alt+enter` | Newline. |
515| `tab` | Complete the selected slash command. |
516| `esc` | Cancel the current turn (while busy); clear input (while idle). |
517| `ctrl+c` | Clear the input and queue (while idle) or arm the exit hint (while busy). Press again within 2s to exit. Use `esc` to cancel a running turn. |
518| `ctrl+d` | Exit on empty input. |
519| `ctrl+l` | Redraw the screen. |
520| `ctrl+o` | Expand or collapse long tool results (read, write, edit, bash outputs over ~12 lines). |
521| `@` | Open the file picker. Browse files and directories in the working directory. |
522
523### File picker (`@`)
524
525| Key | Action |
526|---|---|
527| `@` | Open the file picker (type after a space or at the start of input). |
528| `up`, `down` | Navigate the file list. |
529| `right` | Open the selected directory. |
530| `left` | Go back to the parent directory. |
531| `enter` | Select the file or directory and insert it as a chip (`[file:name]` or `[dir:name/]`). |
532| `esc` | Close the file picker. |
533
534Type `@` followed by a filter string to narrow the list (e.g. `@read` shows only entries containing "read"). Selected files are inserted as compact chips that expand to the full path on submit. Dragged-and-dropped files and directories also collapse to chips automatically.
535
536### Editor line navigation
537
538| Key | Action |
539|---|---|
540| `ctrl+a`, `ctrl+e` | Jump to start or end of line. |
541| `alt+left`, `alt+right` | Jump one word back or forward. |
542| `ctrl+u`, `ctrl+k` | Delete to start or end of line. |
543| `ctrl+w`, `alt+backspace` | Delete the previous word. |
544| `up`, `down` (editor non-empty) | Cycle through prompt history. |
545
546### Chat scroll
547
548| Key | Action |
549|---|---|
550| `pgup`, `pgdn` | Scroll one page up or down. |
551| `up`, `down` (editor empty) | Scroll three lines up or down. This is how the mouse wheel reaches the scroll logic on most terminals. |
552
553## Extensions
554
555zot can be extended in any language via a subprocess + JSON-RPC protocol. Extensions can register slash commands, expose tools to the model, intercept tool calls (block or rewrite args), gate whole turns before the model is called, and rewrite the assistant's visible text before it reaches the user. None are installed by default; opt in explicitly. Hot-reload any time with `/reload-ext`.
556
557### Install and manage
558
559```bash
560zot ext install <path|git-url>   # copy / clone into $ZOT_HOME/extensions/
561zot ext list                      # show installed extensions
562zot ext logs <name> [-f]          # cat or tail the extension's stderr log
563zot ext enable <name>             # re-enable a disabled extension
564zot ext disable <name>            # disable without removing
565zot ext remove <name>             # delete an extension directory
566```
567
568For development, point `zot --ext <path>` at a working directory and skip the install step entirely. Repeatable; takes precedence over installed extensions of the same name.
569
570### Reference
571
572`examples/extensions/` ships reference implementations in Go, TypeScript, Node, and shell. See [docs/extensions.md](docs/extensions.md) for the full protocol, the SDK API (`pkg/zotext`), and the phase roadmap.
573
574## Skills
575
576A skill is a per-folder `SKILL.md` file with a YAML frontmatter header. zot discovers skills at startup, surfaces their names in the system prompt, and exposes a built-in `skill` tool the model uses to load the body on demand.
577
578By default only the built-in skills shipped with the zot binary are loaded. Pass `--with-skills` to also load user-installed skills from:
579
580- `./.zot/skills/<name>/SKILL.md` (project)
581- `$ZOT_HOME/skills/<name>/SKILL.md` (global)
582- `./.claude/skills/<name>/SKILL.md`, `~/.claude/skills/<name>/SKILL.md` (Claude-compatible layout)
583- `./.agents/skills/<name>/SKILL.md`, `~/.agents/skills/<name>/SKILL.md` (agent-compatible layout)
584
585See [docs/skills.md](docs/skills.md) for the frontmatter fields, authoring tips, and example skills under `examples/skills/`.
586
587## Telegram bot (bridge)
588
589zot can run as a telegram bot so you can DM it from your phone. Two ways to run it: **from inside the TUI** (the running session mirrors into Telegram) or **as a standalone background daemon** (a headless bot with its own independent agent).
590
591### From inside the TUI
592
593Type `/telegram` in the running TUI to open a picker with **connect**, **disconnect**, and **status**. When connected:
594
595- DMs from the paired user become prompts in the **same** session you're typing in, so you can continue a conversation from the terminal on your phone and back again.
596- Messages you type in the TUI are mirrored into the Telegram thread prefixed `you: …` and the assistant's replies come back prefixed `zot: …`, so the Telegram chat stays a complete record of both sides of the conversation.
597- Messages sent from Telegram show up as your own bubble in Telegram (no mirror) and the assistant's reply to them comes back bare (no prefix).
598- The status bar shows a `- tg -` tag while the bridge is active.
599- `/telegram connect` / `/telegram disconnect` / `/telegram status` (or `/tg`) also work as direct commands without the picker.
600
601The in-TUI bridge refuses to start while the standalone daemon (below) is running, since two concurrent long-poll consumers of the same bot race on every update and silently drop messages.
602
603### Standalone daemon
604
605For headless servers or long-running bots unattached to a TUI:
606
607```bash
608zot telegram-bot setup     # paste a BotFather token, verify, save
609zot telegram-bot run       # foreground: long-poll in this terminal (ctrl+c to stop)
610zot telegram-bot start     # background: detach and return immediately
611zot telegram-bot stop      # SIGTERM the background bot (SIGKILL after 5s)
612zot telegram-bot logs -f   # tail $ZOT_HOME/logs/bot.log (omit -f to just cat)
613zot telegram-bot status    # config (token masked) + running/stopped
614zot telegram-bot reset     # forget the token and paired user
615# short alias: `zot tg ...` is accepted for every subcommand
616```
617
618The background flavor writes the child's PID to `$ZOT_HOME/bot.pid` and redirects stdout and stderr to `$ZOT_HOME/logs/bot.log`. `zot telegram-bot stop` reads that PID, sends SIGTERM, waits up to five seconds, then escalates to SIGKILL if the child is still alive. Running two instances at once is refused at startup.
619
620> **Use the installed binary for `start`.** `go run ./cmd/zot telegram-bot start` won't work. `go run` builds a binary in a temp directory and deletes it when it exits, which kills the detached child. Run `make install` (or `go build`) first and invoke the installed binary.
621
622Setup flow:
623
6241. Talk to [@BotFather](https://t.me/BotFather) on telegram, run `/newbot`, copy the token it gives you.
6252. Run `zot telegram-bot setup` and paste the token when prompted.
6263. Run `zot telegram-bot run` in the directory you want the agent to operate in.
6274. Open your bot on telegram, send `/start`. The first user to do this claims the bridge (stored as `allowed_user_id`); every other user is rejected.
628
629From then on, any DM you send is forwarded to the agent as a user prompt. Attached photos or `image/*` documents are downloaded and passed to vision-capable models. In-bot telegram commands: `/help`, `/status`, `/stop` (cancel the current turn). Config lives in `$ZOT_HOME/bot.json` (mode 0600).
630
631Bot mode respects the usual zot flags: `--provider`, `--model`, `--cwd`, `--reasoning`, `--continue`, `--no-session`, `--no-tools`, and so on. Run `zot tg run -c --model claude-opus-4-1` to resume the latest session on Opus, for example.
632
633## Development
634
635```bash
636make build     # build ./bin/zot
637make test      # go test -race ./...
638make lint      # go vet + gofmt check
639make fmt       # gofmt -w .
640make release   # cross-compile linux/darwin/windows on amd64 and arm64
641```
642
643Source layout:
644
645```
646cmd/zot/                      main()
647internal/agent/               cli wiring, arg parsing, system prompt, config
648internal/agent/extensions/    extension subprocess manager
649internal/agent/modes/         interactive tui, print, json, dialogs
650internal/agent/tools/         read, write, edit, bash, sandbox
651internal/auth/                credential store, api-key probe, oauth, login server
652internal/core/                agent loop, sessions, cost tracking
653internal/extproto/            extension wire-format types
654internal/provider/            anthropic + openai-compatible streaming clients, model catalog
655internal/skills/              skill discovery, frontmatter parser, skill tool
656internal/tui/                 terminal raw-mode, input parser, editor, renderer, markdown, view
657pkg/zotcore/                  public Go SDK for embedding zot in-process
658pkg/zotext/                   public Go SDK for writing extensions
659```
660
661## License
662
663MIT

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close