detached 9f031f9

docs: complete README, proper capitalization, add 130x130 logo

- Add the zot logo at the top of the README, sized to 130x130 via
  an HTML <img> tag so GitHub honours the dimensions. Reuses the
  existing internal/assets/zot-logo.png that's already embedded in
  the binary for the login callback pages, so the README is
  self-contained.
- Proper English capitalization in prose and section headings
  (intro bullets kept lowercase per the custom intro).
- Drop em-dashes in favour of periods, commas, semicolons.
- No emojis anywhere.
- Fill in the previously-missing flags: --ext / -e, --no-ext,
  --with-skills, --no-skill.
- Add /skills row to the slash-commands table, plus a dedicated
  /skills section describing the picker.
- New dedicated Extensions section documenting zot ext install /
  list / logs / enable / disable / remove, --ext for development,
  and pointing at examples/extensions + docs/extensions.md.
- New dedicated Skills section documenting the discovery layout,
  --with-skills opt-in, and the claude / agents compatibility
  paths.
- Updated $ZOT_HOME tree to include skills/ and extensions/.
- Read-only-while-busy slash list in "Queued messages" updated to
  include /skills.
- Source layout table expanded with internal/agent/extensions,
  internal/extproto, internal/skills, pkg/zotcore, pkg/zotext.
- JSON mode now links docs/rpc.md for the schema instead of the
  stale instructions.md §8 reference.
- ctrl+o description specifies which tools' output it collapses.
commit
9f031f941ab64c7b23b606e516c055335678fd5d
author
patriceckhart · · 2026-04-19 14:25 UTC
parent
b9e7517
signature
unsigned
refs
tag: v0.0.34
1 file · +206 -152
M README.md +206 -152
@@ -1,3 +1,5 @@
1+<img src="internal/assets/zot-logo.png" alt="zot" width="130" height="130" />
2+
13 # zot
24
35 yet another coding agent harness, lightweight and written (vibe-slopped) in go.
@@ -7,39 +9,39 @@ yet another coding agent harness, lightweight and written (vibe-slopped) in go.
79 - four tools (read, write, edit, bash).
810 - three run modes (interactive tui, print, json).
911 - built-in telegram bot.
10-- 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).
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).
1113 - reusable instructions via `SKILL.md` files; see [docs/skills.md](docs/skills.md).
1214 - no community atm.
1315
14-## install
16+## Install
1517
16-### one-liner (macos + linux)
18+### One-liner (macOS, Linux)
1719
1820 ```bash
1921 curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash
2022 ```
2123
22-detects your os/arch, downloads the latest release from github, verifies the sha256 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:
24+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:
2325
2426 ```bash
2527 curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash -s -- v0.0.1 ~/bin
2628 ```
2729
28-### one-liner (windows, powershell)
30+### One-liner (Windows, PowerShell)
2931
3032 ```powershell
3133 iwr -useb https://raw.githubusercontent.com/patriceckhart/zot/main/install.ps1 | iex
3234 ```
3335
34-drops `zot.exe` into `$HOME\bin` and adds it to the user PATH if missing. open a fresh terminal afterwards.
36+Drops `zot.exe` into `$HOME\bin` and adds it to the user PATH if missing. Open a fresh terminal afterwards.
3537
36-### homebrew (macos + linux)
38+### Homebrew (macOS, Linux)
3739
3840 ```bash
3941 brew install patriceckhart/tap/zot
4042 ```
4143
42-the tap lives at `patriceckhart/homebrew-tap`.
44+The tap lives at `patriceckhart/homebrew-tap`.
4345
4446 ### go install
4547
@@ -47,7 +49,7 @@ the tap lives at `patriceckhart/homebrew-tap`.
4749 go install github.com/patriceckhart/zot/cmd/zot@latest
4850 ```
4951
50-### from source
52+### From source
5153
5254 ```bash
5355 git clone https://github.com/patriceckhart/zot
@@ -56,45 +58,45 @@ make build # produces ./bin/zot
5658 make install # into $GOPATH/bin
5759 ```
5860
59-### prebuilt binaries
61+### Prebuilt binaries
6062
61-every release on the [releases page](https://github.com/patriceckhart/zot/releases) ships archives for linux, macos, and windows on amd64 + arm64 (except windows/arm64), plus a `checksums.txt` file. download, verify, `chmod +x`, and drop on your `$PATH`.
63+Every 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`.
6264
63-## authenticate
65+## Authenticate
6466
65-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.
67+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.
6668
67-### credential lookup order
69+### Credential lookup order
6870
6971 1. `--api-key` flag
70-2. `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` env var
71-3. `$ZOT_HOME/auth.json` (api key or oauth token; mode 0600)
72+2. `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` env var
73+3. `$ZOT_HOME/auth.json` (API key or OAuth token; mode 0600)
7274
7375 `$ZOT_HOME` defaults to:
7476 - macOS: `~/Library/Application Support/zot`
75-- linux: `$XDG_STATE_HOME/zot` or `~/.local/state/zot`
76-- windows: `%LOCALAPPDATA%\zot`
77+- Linux: `$XDG_STATE_HOME/zot` or `~/.local/state/zot`
78+- Windows: `%LOCALAPPDATA%\zot`
7779
7880 ### `/login` flow
7981
80-run `zot` and type `/login`. pick one of two methods:
82+Run `zot` and type `/login`. Pick one of two methods:
8183
82-- **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-...` or `sk-...` key. zot probes the provider once and saves it to `auth.json` if accepted.
83-- **subscription** — use your claude pro/max or chatgpt plus/pro subscription. the oauth flow pins the callback to a fixed port per provider (`localhost:53692` for anthropic, `localhost:1455` for openai) because those are the only ports their auth servers will redirect to.
84- - anthropic uses the claude code oauth flow; messages go to `api.anthropic.com` with a bearer token and the claude-code identity headers.
85- - 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.
84+- **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-...` or `sk-...` key. zot probes the provider once and saves it to `auth.json` if accepted.
85+- **Subscription**: use your Claude Pro/Max or ChatGPT Plus/Pro subscription. The OAuth flow pins the callback to a fixed port per provider (`localhost:53692` for Anthropic, `localhost:1455` for OpenAI) because those are the only ports their auth servers will redirect to.
86+ - Anthropic uses the Claude Code OAuth flow. Messages go to `api.anthropic.com` with a bearer token and the Claude Code identity headers.
87+ - 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.
8688
87-> **note on subscription login**: the oauth client ids used are the ones published in anthropic's claude code cli and openai's codex cli. reusing them from a third-party tool is 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.
89+> **Note on subscription login.** The OAuth client IDs used are the ones published in Anthropic's Claude Code CLI and OpenAI's Codex CLI. Reusing them from a third-party tool is 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.
8890
89-### token refresh
91+### Token refresh
9092
91-oauth access tokens are short-lived (anthropic ~8h, openai ~30d). zot refreshes them automatically:
93+OAuth access tokens are short-lived (Anthropic ~8h, OpenAI ~30d). zot refreshes them automatically:
9294
93-- 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` + `expiry` back to `auth.json`, and hands the fresh token to the client.
94-- the telegram bridge additionally refreshes once per turn so a bot that runs for days keeps working without manual intervention.
95-- 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.
95+- 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.
96+- The telegram bridge additionally refreshes once per turn so a bot that runs for days keeps working without manual intervention.
97+- 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.
9698
97-all data lives under `$ZOT_HOME`:
99+All data lives under `$ZOT_HOME`:
98100
99101 ```
100102 $ZOT_HOME/
@@ -103,16 +105,18 @@ $ZOT_HOME/
103105 ├── sessions/ # jsonl transcripts, one dir per cwd
104106 ├── models-cache.json # live /v1/models discovery cache (6h ttl)
105107 ├── SYSTEM.md # optional: replaces the default system prompt
108+├── skills/ # optional: user SKILL.md files (opt in with --with-skills)
109+├── extensions/ # installed extensions, one dir per extension
106110 └── logs/ # app log files
107111 ```
108112
109-drop a `SYSTEM.md` in `$ZOT_HOME` to replace the built-in identity + guidelines for every run. `--system-prompt` still wins per-invocation. delete the file to revert to the default.
113+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.
110114
111-## changelog on update
115+## Changelog on update
112116
113-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.
117+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.
114118
115-## usage
119+## Usage
116120
117121 ```bash
118122 zot # interactive tui
@@ -125,223 +129,273 @@ zot --list-models # show supported models
125129 zot --help
126130 ```
127131
128-## flags
132+## Flags
129133
130-| flag | description |
134+| Flag | Description |
131135 |---|---|
132-| `--provider anthropic\|openai` | pick the provider |
133-| `--model <id>` | pick the model (see `--list-models`) |
134-| `--api-key <key>` | override api key |
135-| `--base-url <url>` | override provider base url (tests / self-hosted) |
136-| `--system-prompt <text>` | replace the default system prompt for this run (also overrides `$ZOT_HOME/SYSTEM.md`) |
137-| `--append-system-prompt <text>` | append text to the system prompt (repeatable) |
138-| `--reasoning low\|medium\|high` | enable reasoning on supported models |
139-| `-c`, `--continue` | resume the latest session for this cwd |
140-| `-r`, `--resume` | pick a session to resume |
141-| `--session <path>` | resume a specific session file |
142-| `--no-session` | don't read or write session files |
143-| `--cwd <path>` | use `<path>` as the working directory |
144-| `--no-tools` | disable all tools |
145-| `--tools <csv>` | only enable the listed tools |
146-| `--max-steps <n>` | cap agent loop iterations (default 50) |
136+| `--provider anthropic\|openai` | Pick the provider. |
137+| `--model <id>` | Pick the model (see `--list-models`). |
138+| `--api-key <key>` | Override the API key. |
139+| `--base-url <url>` | Override the provider base URL (tests, self-hosted). |
140+| `--system-prompt <text>` | Replace the default system prompt for this run (also overrides `$ZOT_HOME/SYSTEM.md`). |
141+| `--append-system-prompt <text>` | Append text to the system prompt (repeatable). |
142+| `--reasoning low\|medium\|high` | Enable reasoning on supported models. |
143+| `-c`, `--continue` | Resume the latest session for this cwd. |
144+| `-r`, `--resume` | Pick a session to resume. |
145+| `--session <path>` | Resume a specific session file. |
146+| `--no-session` | Don't read or write session files. |
147+| `--cwd <path>` | Use `<path>` as the working directory. |
148+| `--no-tools` | Disable all tools. |
149+| `--tools <csv>` | Only enable the listed tools. |
150+| `--max-steps <n>` | Cap agent loop iterations (default 50). |
151+| `-e`, `--ext <path>` | Load an extension from `<path>` for this run (repeatable; wins against installed extensions of the same name). |
152+| `--no-ext` | Skip extension discovery for this run. `--ext` still works on top, so `--no-ext --ext ./x` runs only `x`. |
153+| `--with-skills` | Also load user-installed skills. Without this, only the built-in skills shipped in the binary are loaded. |
154+| `--no-skill` | Disable all skills, including built-ins. No `skill` tool is registered and the system prompt has no skill manifest. |
147155
148-## tools
156+## Tools
149157
150-- `read` — read text files (or inline images: png / jpg / gif / webp)
151-- `write` — create or overwrite files, making parent directories as needed
152-- `edit` — one or more exact-match replacements in an existing file
153-- `bash` — run a shell command in the session cwd, with merged stdout/stderr and a timeout
158+- `read`: read text files, or inline images (PNG, JPEG, GIF, WebP).
159+- `write`: create or overwrite files, making parent directories as needed.
160+- `edit`: one or more exact-match replacements in an existing file.
161+- `bash`: run a shell command in the session cwd, with merged stdout/stderr and a timeout.
154162
155-when the sandbox is on (see `/lock`), all four tools refuse paths outside the session cwd.
163+When the sandbox is on (see `/lock`), all four tools refuse paths outside the session cwd.
156164
157-## modes
165+## Modes
158166
159-- **interactive** (default): chat tui with streaming output, spinner, cost meter, slash commands.
160-- **print**: `zot -p "prompt"` runs the agent to completion and writes only the final assistant text to stdout.
161-- **json**: `zot --json "prompt"` emits one json object per agent event to stdout, newline-delimited. the schema is documented in `instructions.md` §8.
162-- **rpc**: `zot rpc` runs as a long-lived child process; commands in on stdin, events + 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.
167+- **Interactive** (default): chat TUI with streaming output, spinner, cost meter, slash commands.
168+- **Print**: `zot -p "prompt"` runs the agent to completion and writes only the final assistant text to stdout.
169+- **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).
170+- **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.
163171
164-## embedding
172+## Embedding
165173
166-two ways to drive zot from another program:
174+Two ways to drive zot from another program:
167175
168-- **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/`.
169-- **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/`.
176+- **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/`.
177+- **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/`.
170178
171-both interfaces share the same event schema, so transcripts captured by one can be replayed through the other.
179+Both interfaces share the same event schema, so transcripts captured by one can be replayed through the other.
172180
173-## slash commands
181+## Slash commands
174182
175-type `/` in the tui to open the autocomplete popup. available commands:
183+Type `/` in the TUI to open the autocomplete popup. Available commands:
176184
177-| command | description |
185+| Command | Description |
178186 |---|---|
179-| `/help` | show key bindings and commands |
180-| `/login` | log in via api key or subscription (opens a dialog) |
181-| `/logout [provider]` | clear credentials for `anthropic`, `openai`, or all when omitted |
182-| `/model` | pick a model from a list (or `/model <id>` to set directly) |
183-| `/sessions` | resume a previous session for this directory |
184-| `/jump` | scroll the chat to a previous turn (or `/jump <text>` to filter) |
185-| `/btw` | side-chat with full context that doesn't add to the main thread |
186-| `/compact` | summarize the transcript into one message to free up context |
187-| `/lock` | confine tools to the current directory |
188-| `/unlock` | allow tools to touch paths outside again |
189-| `/clear` | clear the chat transcript |
190-| `/exit` | exit zot |
187+| `/help` | Show key bindings and commands. |
188+| `/login` | Log in via API key or subscription (opens a dialog). |
189+| `/logout [provider]` | Clear credentials for `anthropic`, `openai`, or all when omitted. |
190+| `/model` | Pick a model from a list (or `/model <id>` to set directly). |
191+| `/sessions` | Resume a previous session for this directory. |
192+| `/jump` | Scroll the chat to a previous turn (or `/jump <text>` to filter). |
193+| `/btw` | Side chat with full context that doesn't add to the main thread. |
194+| `/skills` | List discovered skills (SKILL.md files) and preview their bodies. |
195+| `/compact` | Summarize the transcript into one message to free up context. |
196+| `/lock` | Confine tools to the current directory. |
197+| `/unlock` | Allow tools to touch paths outside again. |
198+| `/clear` | Clear the chat transcript. |
199+| `/exit` | Exit zot. |
200+
201+Extension-registered commands appear under a divider at the bottom of the popup, sorted by name.
191202
192203 ### `/sessions`
193204
194-shows previous sessions for the current working directory, newest first, with timestamp, model, message count, cost, and the first user prompt. pick one with `↑/↓`, `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.
205+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.
195206
196207 ### `/jump`
197208
198-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. `↑/↓` 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.
209+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.
199210
200-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.
211+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.
201212
202213 ### `/btw`
203214
204-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.
215+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.
205216
206-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.
217+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.
207218
208219 ```
209220 /btw # open the overlay, type questions interactively
210221 /btw does PUT replace the whole resource?
211222 ```
212223
213-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.
224+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.
225+
226+### `/skills`
227+
228+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.
214229
215230 ### `/compact`
216231
217-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. status bar's `ctx N/M (P%)` meter resets. use it when the context meter creeps past ~80%.
232+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%.
218233
219-zot also auto-compacts in the background: after any turn that leaves context usage ≥ **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.
234+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.
220235
221236 ### `/lock`
222237
223-enforces a sandbox rooted at the cwd shown in the status bar. `read` / `write` / `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=/`, etc. status bar shows `· locked · ~/your/cwd` while active.
238+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 `locked, ~/your/cwd` while active.
224239
225-this is a guardrail against accidents, not a hard security boundary. if you need real isolation, run zot under docker or a proper sandbox.
240+This is a guardrail against accidents, not a hard security boundary. If you need real isolation, run zot under docker or a proper sandbox.
226241
227-## sessions
242+## Sessions
228243
229-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.
244+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.
230245
231-## models
246+## Models
232247
233-`--list-models` or the `/model` picker shows the full catalog. three sources:
248+`--list-models` or the `/model` picker shows the full catalog. Three sources:
234249
235-- **catalog** — models baked into zot, always available
236-- **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)
237-- **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
250+- **Catalog**: models baked into zot, always available.
251+- **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).
252+- **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.
238253
239-the context meter in the status line (`ctx N/M (P%)`) uses the model's advertised context window to show how much of it your last turn consumed.
254+The context meter in the status line uses the model's advertised context window to show how much of it your last turn consumed.
240255
241-## inline images
256+## Inline images
242257
243-when a tool returns an image (e.g. `read` on a png), zot renders it inline on terminals that support it: **iterm2**, **wezterm**, **kitty**, **ghostty**. on other terminals you see a text placeholder with mime type, pixel dimensions, and byte size. control with the `ZOT_INLINE_IMAGES` env var:
258+When a tool returns an image (for example `read` on a PNG), zot renders it inline on terminals that support it: **iTerm2**, **WezTerm**, **Kitty**, **Ghostty**. On other terminals you see a text placeholder with MIME type, pixel dimensions, and byte size. Control with the `ZOT_INLINE_IMAGES` env var:
244259
245-| value | effect |
260+| Value | Effect |
246261 |---|---|
247-| unset (default) | auto-detect based on `TERM_PROGRAM` |
248-| `iterm` / `iterm2` | force iterm2 osc 1337 protocol |
249-| `kitty` | force kitty graphics protocol |
250-| `off` / `none` | always use the text placeholder |
262+| unset (default) | Auto-detect based on `TERM_PROGRAM`. |
263+| `iterm`, `iterm2` | Force the iTerm2 OSC 1337 protocol. |
264+| `kitty` | Force the Kitty graphics protocol. |
265+| `off`, `none` | Always use the text placeholder. |
251266
252-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.
267+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.
253268
254-## queued messages
269+## Queued messages
255270
256-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 / ctrl+c cancels the active turn and drops the queue so a runaway turn doesn't flood you with stale follow-ups.
271+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` or `ctrl+c` cancels the active turn and drops the queue so a runaway turn doesn't flood you with stale follow-ups.
257272
258-slash commands also work while the agent is busy. read-only ones (`/help`, `/jump`, `/btw`, `/sessions`, `/lock`, `/unlock`, `/exit`) take effect immediately. destructive ones (`/clear`, `/compact`, `/login`, `/logout`, `/model`) cancel the active turn first and then run.
273+Slash commands also work while the agent is busy. Read-only ones (`/help`, `/jump`, `/btw`, `/sessions`, `/skills`, `/lock`, `/unlock`, `/exit`) take effect immediately. Destructive ones (`/clear`, `/compact`, `/login`, `/logout`, `/model`) cancel the active turn first and then run.
259274
260-## keys (interactive mode)
275+## Keys (interactive mode)
261276
262-### input
277+### Input
263278
264-| key | action |
279+| Key | Action |
265280 |---|---|
266-| `enter` | submit (queued if the agent is busy) |
267-| `alt+enter` | newline |
268-| `tab` | complete the selected slash command |
269-| `esc` | cancel the current turn (while busy); clear input (while idle) |
270-| `ctrl+c` | clear the input + queue (or cancel the current turn). press again within 2s to exit. |
271-| `ctrl+d` | exit on empty input |
272-| `ctrl+l` | redraw the screen |
273-| `ctrl+o` | expand / collapse long tool results |
274-
275-### editor line navigation
276-
277-| key | action |
281+| `enter` | Submit (queued if the agent is busy). |
282+| `alt+enter` | Newline. |
283+| `tab` | Complete the selected slash command. |
284+| `esc` | Cancel the current turn (while busy); clear input (while idle). |
285+| `ctrl+c` | Clear the input and queue (or cancel the current turn). Press again within 2s to exit. |
286+| `ctrl+d` | Exit on empty input. |
287+| `ctrl+l` | Redraw the screen. |
288+| `ctrl+o` | Expand or collapse long tool results (read, write, edit, bash outputs over ~12 lines). |
289+
290+### Editor line navigation
291+
292+| Key | Action |
278293 |---|---|
279-| `ctrl+a` / `ctrl+e` | jump to start / end of line |
280-| `alt+←` / `alt+→` | jump one word back / forward |
281-| `ctrl+u` / `ctrl+k` | delete to start / end of line |
282-| `ctrl+w` · `alt+backspace` | delete the previous word |
283-| `up` / `down` (editor non-empty) | cycle through prompt history |
294+| `ctrl+a`, `ctrl+e` | Jump to start or end of line. |
295+| `alt+left`, `alt+right` | Jump one word back or forward. |
296+| `ctrl+u`, `ctrl+k` | Delete to start or end of line. |
297+| `ctrl+w`, `alt+backspace` | Delete the previous word. |
298+| `up`, `down` (editor non-empty) | Cycle through prompt history. |
284299
285-### chat scroll
300+### Chat scroll
286301
287-| key | action |
302+| Key | Action |
288303 |---|---|
289-| `pgup` / `pgdn` | scroll one page up / down |
290-| `up` / `down` (editor empty) | scroll three lines up / down — this is how the mouse wheel reaches the scroll logic on most terminals |
304+| `pgup`, `pgdn` | Scroll one page up or down. |
305+| `up`, `down` (editor empty) | Scroll three lines up or down. This is how the mouse wheel reaches the scroll logic on most terminals. |
306+
307+## Extensions
308+
309+zot can be extended in any language via a subprocess + JSON-RPC protocol. Extensions can register slash commands, expose tools to the model, and intercept tool calls for permission gates. None are installed by default; opt in explicitly.
310+
311+### Install and manage
312+
313+```bash
314+zot ext install <path|git-url> # copy / clone into $ZOT_HOME/extensions/
315+zot ext list # show installed extensions
316+zot ext logs <name> [-f] # cat or tail the extension's stderr log
317+zot ext enable <name> # re-enable a disabled extension
318+zot ext disable <name> # disable without removing
319+zot ext remove <name> # delete an extension directory
320+```
321+
322+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.
323+
324+### Reference
325+
326+`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.
327+
328+## Skills
329+
330+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.
331+
332+By default only the built-in skills shipped with the zot binary are loaded. Pass `--with-skills` to also load user-installed skills from:
333+
334+- `./.zot/skills/<name>/SKILL.md` (project)
335+- `$ZOT_HOME/skills/<name>/SKILL.md` (global)
336+- `./.claude/skills/<name>/SKILL.md`, `~/.claude/skills/<name>/SKILL.md` (Claude-compatible layout)
337+- `./.agents/skills/<name>/SKILL.md`, `~/.agents/skills/<name>/SKILL.md` (agent-compatible layout)
338+
339+See [docs/skills.md](docs/skills.md) for the frontmatter fields, authoring tips, and example skills under `examples/skills/`.
291340
292-## telegram bot (bridge)
341+## Telegram bot (bridge)
293342
294-zot can run as a telegram bot so you can dm it from your phone. it's a built-in subcommand, not a plugin:
343+zot can run as a telegram bot so you can DM it from your phone. It's a built-in subcommand, not a plugin:
295344
296345 ```bash
297346 zot telegram-bot setup # paste a BotFather token, verify, save
298347 zot telegram-bot run # foreground: long-poll in this terminal (ctrl+c to stop)
299348 zot telegram-bot start # background: detach and return immediately
300-zot telegram-bot stop # sigterm the background bot (sigkill after 5s)
349+zot telegram-bot stop # SIGTERM the background bot (SIGKILL after 5s)
301350 zot telegram-bot logs -f # tail $ZOT_HOME/logs/bot.log (omit -f to just cat)
302351 zot telegram-bot status # config (token masked) + running/stopped
303-zot telegram-bot reset # forget the token + paired user
352+zot telegram-bot reset # forget the token and paired user
304353 # short alias: `zot tg ...` is accepted for every subcommand
305354 ```
306355
307-the background flavor writes the child's pid to `$ZOT_HOME/bot.pid` and redirects stdout+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.
356+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.
308357
309-> **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.
358+> **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.
310359
311-setup flow:
360+Setup flow:
312361
313-1. talk to [@BotFather](https://t.me/BotFather) on telegram, run `/newbot`, copy the token it gives you.
314-2. run `zot telegram-bot setup` and paste the token when prompted.
315-3. run `zot telegram-bot run` in the directory you want the agent to operate in.
316-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.
362+1. Talk to [@BotFather](https://t.me/BotFather) on telegram, run `/newbot`, copy the token it gives you.
363+2. Run `zot telegram-bot setup` and paste the token when prompted.
364+3. Run `zot telegram-bot run` in the directory you want the agent to operate in.
365+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.
317366
318-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).
367+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).
319368
320-bot mode respects the usual zot flags — `--provider`, `--model`, `--cwd`, `--reasoning`, `--continue`, `--no-session`, `--no-tools`, etc. run `zot tg run -c --model claude-opus-4-1` to resume the latest session on opus, for example.
369+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.
321370
322-## development
371+## Development
323372
324373 ```bash
325374 make build # build ./bin/zot
326375 make test # go test -race ./...
327376 make lint # go vet + gofmt check
328377 make fmt # gofmt -w .
329-make release # cross-compile linux/darwin/windows × amd64/arm64
378+make release # cross-compile linux/darwin/windows on amd64 and arm64
330379 ```
331380
332-source layout:
381+Source layout:
333382
334383 ```
335384 cmd/zot/ main()
336385 internal/agent/ cli wiring, arg parsing, system prompt, config
386+internal/agent/extensions/ extension subprocess manager
337387 internal/agent/modes/ interactive tui, print, json, dialogs
338388 internal/agent/tools/ read, write, edit, bash, sandbox
339389 internal/auth/ credential store, api-key probe, oauth, login server
340390 internal/core/ agent loop, sessions, cost tracking
391+internal/extproto/ extension wire-format types
341392 internal/provider/ anthropic + openai streaming clients, model catalog
393+internal/skills/ skill discovery, frontmatter parser, skill tool
342394 internal/tui/ terminal raw-mode, input parser, editor, renderer, markdown, view
395+pkg/zotcore/ public Go SDK for embedding zot in-process
396+pkg/zotext/ public Go SDK for writing extensions
343397 ```
344398
345-## license
399+## License
346400
347401 MIT
@@ -1,3 +1,5 @@
1+<img src="internal/assets/zot-logo.png" alt="zot" width="130" height="130" />
2+
1 # zot3 # zot
2 4
3 yet another coding agent harness, lightweight and written (vibe-slopped) in go.5 yet another coding agent harness, lightweight and written (vibe-slopped) in go.
@@ -7,39 +9,39 @@ yet another coding agent harness, lightweight and written (vibe-slopped) in go.
7 - four tools (read, write, edit, bash).9 - four tools (read, write, edit, bash).
8 - three run modes (interactive tui, print, json).10 - three run modes (interactive tui, print, json).
9 - built-in telegram bot.11 - built-in telegram bot.
10-- 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).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).
11 - reusable instructions via `SKILL.md` files; see [docs/skills.md](docs/skills.md).13 - reusable instructions via `SKILL.md` files; see [docs/skills.md](docs/skills.md).
12 - no community atm.14 - no community atm.
13 15
14-## install16+## Install
15 17
16-### one-liner (macos + linux)18+### One-liner (macOS, Linux)
17 19
18 ```bash20 ```bash
19 curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash21 curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash
20 ```22 ```
21 23
22-detects your os/arch, downloads the latest release from github, verifies the sha256 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:24+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:
23 25
24 ```bash26 ```bash
25 curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash -s -- v0.0.1 ~/bin27 curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash -s -- v0.0.1 ~/bin
26 ```28 ```
27 29
28-### one-liner (windows, powershell)30+### One-liner (Windows, PowerShell)
29 31
30 ```powershell32 ```powershell
31 iwr -useb https://raw.githubusercontent.com/patriceckhart/zot/main/install.ps1 | iex33 iwr -useb https://raw.githubusercontent.com/patriceckhart/zot/main/install.ps1 | iex
32 ```34 ```
33 35
34-drops `zot.exe` into `$HOME\bin` and adds it to the user PATH if missing. open a fresh terminal afterwards.36+Drops `zot.exe` into `$HOME\bin` and adds it to the user PATH if missing. Open a fresh terminal afterwards.
35 37
36-### homebrew (macos + linux)38+### Homebrew (macOS, Linux)
37 39
38 ```bash40 ```bash
39 brew install patriceckhart/tap/zot41 brew install patriceckhart/tap/zot
40 ```42 ```
41 43
42-the tap lives at `patriceckhart/homebrew-tap`.44+The tap lives at `patriceckhart/homebrew-tap`.
43 45
44 ### go install46 ### go install
45 47
@@ -47,7 +49,7 @@ the tap lives at `patriceckhart/homebrew-tap`.
47 go install github.com/patriceckhart/zot/cmd/zot@latest49 go install github.com/patriceckhart/zot/cmd/zot@latest
48 ```50 ```
49 51
50-### from source52+### From source
51 53
52 ```bash54 ```bash
53 git clone https://github.com/patriceckhart/zot55 git clone https://github.com/patriceckhart/zot
@@ -56,45 +58,45 @@ make build # produces ./bin/zot
56 make install # into $GOPATH/bin58 make install # into $GOPATH/bin
57 ```59 ```
58 60
59-### prebuilt binaries61+### Prebuilt binaries
60 62
61-every release on the [releases page](https://github.com/patriceckhart/zot/releases) ships archives for linux, macos, and windows on amd64 + arm64 (except windows/arm64), plus a `checksums.txt` file. download, verify, `chmod +x`, and drop on your `$PATH`.63+Every 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`.
62 64
63-## authenticate65+## Authenticate
64 66
65-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.67+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.
66 68
67-### credential lookup order69+### Credential lookup order
68 70
69 1. `--api-key` flag71 1. `--api-key` flag
70-2. `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` env var72+2. `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` env var
71-3. `$ZOT_HOME/auth.json` (api key or oauth token; mode 0600)73+3. `$ZOT_HOME/auth.json` (API key or OAuth token; mode 0600)
72 74
73 `$ZOT_HOME` defaults to:75 `$ZOT_HOME` defaults to:
74 - macOS: `~/Library/Application Support/zot`76 - macOS: `~/Library/Application Support/zot`
75-- linux: `$XDG_STATE_HOME/zot` or `~/.local/state/zot`77+- Linux: `$XDG_STATE_HOME/zot` or `~/.local/state/zot`
76-- windows: `%LOCALAPPDATA%\zot`78+- Windows: `%LOCALAPPDATA%\zot`
77 79
78 ### `/login` flow80 ### `/login` flow
79 81
80-run `zot` and type `/login`. pick one of two methods:82+Run `zot` and type `/login`. Pick one of two methods:
81 83
82-- **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-...` or `sk-...` key. zot probes the provider once and saves it to `auth.json` if accepted.84+- **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-...` or `sk-...` key. zot probes the provider once and saves it to `auth.json` if accepted.
83-- **subscription** — use your claude pro/max or chatgpt plus/pro subscription. the oauth flow pins the callback to a fixed port per provider (`localhost:53692` for anthropic, `localhost:1455` for openai) because those are the only ports their auth servers will redirect to.85+- **Subscription**: use your Claude Pro/Max or ChatGPT Plus/Pro subscription. The OAuth flow pins the callback to a fixed port per provider (`localhost:53692` for Anthropic, `localhost:1455` for OpenAI) because those are the only ports their auth servers will redirect to.
84- - anthropic uses the claude code oauth flow; messages go to `api.anthropic.com` with a bearer token and the claude-code identity headers.86+ - Anthropic uses the Claude Code OAuth flow. Messages go to `api.anthropic.com` with a bearer token and the Claude Code identity headers.
85- - 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.87+ - 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.
86 88
87-> **note on subscription login**: the oauth client ids used are the ones published in anthropic's claude code cli and openai's codex cli. reusing them from a third-party tool is 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.89+> **Note on subscription login.** The OAuth client IDs used are the ones published in Anthropic's Claude Code CLI and OpenAI's Codex CLI. Reusing them from a third-party tool is 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.
88 90
89-### token refresh91+### Token refresh
90 92
91-oauth access tokens are short-lived (anthropic ~8h, openai ~30d). zot refreshes them automatically:93+OAuth access tokens are short-lived (Anthropic ~8h, OpenAI ~30d). zot refreshes them automatically:
92 94
93-- 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` + `expiry` back to `auth.json`, and hands the fresh token to the client.95+- 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.
94-- the telegram bridge additionally refreshes once per turn so a bot that runs for days keeps working without manual intervention.96+- The telegram bridge additionally refreshes once per turn so a bot that runs for days keeps working without manual intervention.
95-- 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.97+- 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.
96 98
97-all data lives under `$ZOT_HOME`:99+All data lives under `$ZOT_HOME`:
98 100
99 ```101 ```
100 $ZOT_HOME/102 $ZOT_HOME/
@@ -103,16 +105,18 @@ $ZOT_HOME/
103 ├── sessions/ # jsonl transcripts, one dir per cwd105 ├── sessions/ # jsonl transcripts, one dir per cwd
104 ├── models-cache.json # live /v1/models discovery cache (6h ttl)106 ├── models-cache.json # live /v1/models discovery cache (6h ttl)
105 ├── SYSTEM.md # optional: replaces the default system prompt107 ├── SYSTEM.md # optional: replaces the default system prompt
108+├── skills/ # optional: user SKILL.md files (opt in with --with-skills)
109+├── extensions/ # installed extensions, one dir per extension
106 └── logs/ # app log files110 └── logs/ # app log files
107 ```111 ```
108 112
109-drop a `SYSTEM.md` in `$ZOT_HOME` to replace the built-in identity + guidelines for every run. `--system-prompt` still wins per-invocation. delete the file to revert to the default.113+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.
110 114
111-## changelog on update115+## Changelog on update
112 116
113-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.117+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.
114 118
115-## usage119+## Usage
116 120
117 ```bash121 ```bash
118 zot # interactive tui122 zot # interactive tui
@@ -125,223 +129,273 @@ zot --list-models # show supported models
125 zot --help129 zot --help
126 ```130 ```
127 131
128-## flags132+## Flags
129 133
130-| flag | description |134+| Flag | Description |
131 |---|---|135 |---|---|
132-| `--provider anthropic\|openai` | pick the provider |136+| `--provider anthropic\|openai` | Pick the provider. |
133-| `--model <id>` | pick the model (see `--list-models`) |137+| `--model <id>` | Pick the model (see `--list-models`). |
134-| `--api-key <key>` | override api key |138+| `--api-key <key>` | Override the API key. |
135-| `--base-url <url>` | override provider base url (tests / self-hosted) |139+| `--base-url <url>` | Override the provider base URL (tests, self-hosted). |
136-| `--system-prompt <text>` | replace the default system prompt for this run (also overrides `$ZOT_HOME/SYSTEM.md`) |140+| `--system-prompt <text>` | Replace the default system prompt for this run (also overrides `$ZOT_HOME/SYSTEM.md`). |
137-| `--append-system-prompt <text>` | append text to the system prompt (repeatable) |141+| `--append-system-prompt <text>` | Append text to the system prompt (repeatable). |
138-| `--reasoning low\|medium\|high` | enable reasoning on supported models |142+| `--reasoning low\|medium\|high` | Enable reasoning on supported models. |
139-| `-c`, `--continue` | resume the latest session for this cwd |143+| `-c`, `--continue` | Resume the latest session for this cwd. |
140-| `-r`, `--resume` | pick a session to resume |144+| `-r`, `--resume` | Pick a session to resume. |
141-| `--session <path>` | resume a specific session file |145+| `--session <path>` | Resume a specific session file. |
142-| `--no-session` | don't read or write session files |146+| `--no-session` | Don't read or write session files. |
143-| `--cwd <path>` | use `<path>` as the working directory |147+| `--cwd <path>` | Use `<path>` as the working directory. |
144-| `--no-tools` | disable all tools |148+| `--no-tools` | Disable all tools. |
145-| `--tools <csv>` | only enable the listed tools |149+| `--tools <csv>` | Only enable the listed tools. |
146-| `--max-steps <n>` | cap agent loop iterations (default 50) |150+| `--max-steps <n>` | Cap agent loop iterations (default 50). |
151+| `-e`, `--ext <path>` | Load an extension from `<path>` for this run (repeatable; wins against installed extensions of the same name). |
152+| `--no-ext` | Skip extension discovery for this run. `--ext` still works on top, so `--no-ext --ext ./x` runs only `x`. |
153+| `--with-skills` | Also load user-installed skills. Without this, only the built-in skills shipped in the binary are loaded. |
154+| `--no-skill` | Disable all skills, including built-ins. No `skill` tool is registered and the system prompt has no skill manifest. |
147 155
148-## tools156+## Tools
149 157
150-- `read` — read text files (or inline images: png / jpg / gif / webp)158+- `read`: read text files, or inline images (PNG, JPEG, GIF, WebP).
151-- `write` — create or overwrite files, making parent directories as needed159+- `write`: create or overwrite files, making parent directories as needed.
152-- `edit` — one or more exact-match replacements in an existing file160+- `edit`: one or more exact-match replacements in an existing file.
153-- `bash` — run a shell command in the session cwd, with merged stdout/stderr and a timeout161+- `bash`: run a shell command in the session cwd, with merged stdout/stderr and a timeout.
154 162
155-when the sandbox is on (see `/lock`), all four tools refuse paths outside the session cwd.163+When the sandbox is on (see `/lock`), all four tools refuse paths outside the session cwd.
156 164
157-## modes165+## Modes
158 166
159-- **interactive** (default): chat tui with streaming output, spinner, cost meter, slash commands.167+- **Interactive** (default): chat TUI with streaming output, spinner, cost meter, slash commands.
160-- **print**: `zot -p "prompt"` runs the agent to completion and writes only the final assistant text to stdout.168+- **Print**: `zot -p "prompt"` runs the agent to completion and writes only the final assistant text to stdout.
161-- **json**: `zot --json "prompt"` emits one json object per agent event to stdout, newline-delimited. the schema is documented in `instructions.md` §8.169+- **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).
162-- **rpc**: `zot rpc` runs as a long-lived child process; commands in on stdin, events + 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.170+- **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.
163 171
164-## embedding172+## Embedding
165 173
166-two ways to drive zot from another program:174+Two ways to drive zot from another program:
167 175
168-- **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/`.176+- **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/`.
169-- **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/`.177+- **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/`.
170 178
171-both interfaces share the same event schema, so transcripts captured by one can be replayed through the other.179+Both interfaces share the same event schema, so transcripts captured by one can be replayed through the other.
172 180
173-## slash commands181+## Slash commands
174 182
175-type `/` in the tui to open the autocomplete popup. available commands:183+Type `/` in the TUI to open the autocomplete popup. Available commands:
176 184
177-| command | description |185+| Command | Description |
178 |---|---|186 |---|---|
179-| `/help` | show key bindings and commands |187+| `/help` | Show key bindings and commands. |
180-| `/login` | log in via api key or subscription (opens a dialog) |188+| `/login` | Log in via API key or subscription (opens a dialog). |
181-| `/logout [provider]` | clear credentials for `anthropic`, `openai`, or all when omitted |189+| `/logout [provider]` | Clear credentials for `anthropic`, `openai`, or all when omitted. |
182-| `/model` | pick a model from a list (or `/model <id>` to set directly) |190+| `/model` | Pick a model from a list (or `/model <id>` to set directly). |
183-| `/sessions` | resume a previous session for this directory |191+| `/sessions` | Resume a previous session for this directory. |
184-| `/jump` | scroll the chat to a previous turn (or `/jump <text>` to filter) |192+| `/jump` | Scroll the chat to a previous turn (or `/jump <text>` to filter). |
185-| `/btw` | side-chat with full context that doesn't add to the main thread |193+| `/btw` | Side chat with full context that doesn't add to the main thread. |
186-| `/compact` | summarize the transcript into one message to free up context |194+| `/skills` | List discovered skills (SKILL.md files) and preview their bodies. |
187-| `/lock` | confine tools to the current directory |195+| `/compact` | Summarize the transcript into one message to free up context. |
188-| `/unlock` | allow tools to touch paths outside again |196+| `/lock` | Confine tools to the current directory. |
189-| `/clear` | clear the chat transcript |197+| `/unlock` | Allow tools to touch paths outside again. |
190-| `/exit` | exit zot |198+| `/clear` | Clear the chat transcript. |
199+| `/exit` | Exit zot. |
200+
201+Extension-registered commands appear under a divider at the bottom of the popup, sorted by name.
191 202
192 ### `/sessions`203 ### `/sessions`
193 204
194-shows previous sessions for the current working directory, newest first, with timestamp, model, message count, cost, and the first user prompt. pick one with `↑/↓`, `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.205+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.
195 206
196 ### `/jump`207 ### `/jump`
197 208
198-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. `↑/↓` 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.209+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.
199 210
200-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.211+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.
201 212
202 ### `/btw`213 ### `/btw`
203 214
204-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.215+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.
205 216
206-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.217+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.
207 218
208 ```219 ```
209 /btw # open the overlay, type questions interactively220 /btw # open the overlay, type questions interactively
210 /btw does PUT replace the whole resource?221 /btw does PUT replace the whole resource?
211 ```222 ```
212 223
213-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.224+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.
225+
226+### `/skills`
227+
228+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.
214 229
215 ### `/compact`230 ### `/compact`
216 231
217-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. status bar's `ctx N/M (P%)` meter resets. use it when the context meter creeps past ~80%.232+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%.
218 233
219-zot also auto-compacts in the background: after any turn that leaves context usage ≥ **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.234+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.
220 235
221 ### `/lock`236 ### `/lock`
222 237
223-enforces a sandbox rooted at the cwd shown in the status bar. `read` / `write` / `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=/`, etc. status bar shows `· locked · ~/your/cwd` while active.238+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 `locked, ~/your/cwd` while active.
224 239
225-this is a guardrail against accidents, not a hard security boundary. if you need real isolation, run zot under docker or a proper sandbox.240+This is a guardrail against accidents, not a hard security boundary. If you need real isolation, run zot under docker or a proper sandbox.
226 241
227-## sessions242+## Sessions
228 243
229-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.244+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.
230 245
231-## models246+## Models
232 247
233-`--list-models` or the `/model` picker shows the full catalog. three sources:248+`--list-models` or the `/model` picker shows the full catalog. Three sources:
234 249
235-- **catalog** — models baked into zot, always available250+- **Catalog**: models baked into zot, always available.
236-- **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)251+- **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).
237-- **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 them252+- **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.
238 253
239-the context meter in the status line (`ctx N/M (P%)`) uses the model's advertised context window to show how much of it your last turn consumed.254+The context meter in the status line uses the model's advertised context window to show how much of it your last turn consumed.
240 255
241-## inline images256+## Inline images
242 257
243-when a tool returns an image (e.g. `read` on a png), zot renders it inline on terminals that support it: **iterm2**, **wezterm**, **kitty**, **ghostty**. on other terminals you see a text placeholder with mime type, pixel dimensions, and byte size. control with the `ZOT_INLINE_IMAGES` env var:258+When a tool returns an image (for example `read` on a PNG), zot renders it inline on terminals that support it: **iTerm2**, **WezTerm**, **Kitty**, **Ghostty**. On other terminals you see a text placeholder with MIME type, pixel dimensions, and byte size. Control with the `ZOT_INLINE_IMAGES` env var:
244 259
245-| value | effect |260+| Value | Effect |
246 |---|---|261 |---|---|
247-| unset (default) | auto-detect based on `TERM_PROGRAM` |262+| unset (default) | Auto-detect based on `TERM_PROGRAM`. |
248-| `iterm` / `iterm2` | force iterm2 osc 1337 protocol |263+| `iterm`, `iterm2` | Force the iTerm2 OSC 1337 protocol. |
249-| `kitty` | force kitty graphics protocol |264+| `kitty` | Force the Kitty graphics protocol. |
250-| `off` / `none` | always use the text placeholder |265+| `off`, `none` | Always use the text placeholder. |
251 266
252-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.267+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.
253 268
254-## queued messages269+## Queued messages
255 270
256-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 / ctrl+c cancels the active turn and drops the queue so a runaway turn doesn't flood you with stale follow-ups.271+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` or `ctrl+c` cancels the active turn and drops the queue so a runaway turn doesn't flood you with stale follow-ups.
257 272
258-slash commands also work while the agent is busy. read-only ones (`/help`, `/jump`, `/btw`, `/sessions`, `/lock`, `/unlock`, `/exit`) take effect immediately. destructive ones (`/clear`, `/compact`, `/login`, `/logout`, `/model`) cancel the active turn first and then run.273+Slash commands also work while the agent is busy. Read-only ones (`/help`, `/jump`, `/btw`, `/sessions`, `/skills`, `/lock`, `/unlock`, `/exit`) take effect immediately. Destructive ones (`/clear`, `/compact`, `/login`, `/logout`, `/model`) cancel the active turn first and then run.
259 274
260-## keys (interactive mode)275+## Keys (interactive mode)
261 276
262-### input277+### Input
263 278
264-| key | action |279+| Key | Action |
265 |---|---|280 |---|---|
266-| `enter` | submit (queued if the agent is busy) |281+| `enter` | Submit (queued if the agent is busy). |
267-| `alt+enter` | newline |282+| `alt+enter` | Newline. |
268-| `tab` | complete the selected slash command |283+| `tab` | Complete the selected slash command. |
269-| `esc` | cancel the current turn (while busy); clear input (while idle) |284+| `esc` | Cancel the current turn (while busy); clear input (while idle). |
270-| `ctrl+c` | clear the input + queue (or cancel the current turn). press again within 2s to exit. |285+| `ctrl+c` | Clear the input and queue (or cancel the current turn). Press again within 2s to exit. |
271-| `ctrl+d` | exit on empty input |286+| `ctrl+d` | Exit on empty input. |
272-| `ctrl+l` | redraw the screen |287+| `ctrl+l` | Redraw the screen. |
273-| `ctrl+o` | expand / collapse long tool results |288+| `ctrl+o` | Expand or collapse long tool results (read, write, edit, bash outputs over ~12 lines). |
274-289+
275-### editor line navigation290+### Editor line navigation
276-291+
277-| key | action |292+| Key | Action |
278 |---|---|293 |---|---|
279-| `ctrl+a` / `ctrl+e` | jump to start / end of line |294+| `ctrl+a`, `ctrl+e` | Jump to start or end of line. |
280-| `alt+←` / `alt+→` | jump one word back / forward |295+| `alt+left`, `alt+right` | Jump one word back or forward. |
281-| `ctrl+u` / `ctrl+k` | delete to start / end of line |296+| `ctrl+u`, `ctrl+k` | Delete to start or end of line. |
282-| `ctrl+w` · `alt+backspace` | delete the previous word |297+| `ctrl+w`, `alt+backspace` | Delete the previous word. |
283-| `up` / `down` (editor non-empty) | cycle through prompt history |298+| `up`, `down` (editor non-empty) | Cycle through prompt history. |
284 299
285-### chat scroll300+### Chat scroll
286 301
287-| key | action |302+| Key | Action |
288 |---|---|303 |---|---|
289-| `pgup` / `pgdn` | scroll one page up / down |304+| `pgup`, `pgdn` | Scroll one page up or down. |
290-| `up` / `down` (editor empty) | scroll three lines up / down — this is how the mouse wheel reaches the scroll logic on most terminals |305+| `up`, `down` (editor empty) | Scroll three lines up or down. This is how the mouse wheel reaches the scroll logic on most terminals. |
306+
307+## Extensions
308+
309+zot can be extended in any language via a subprocess + JSON-RPC protocol. Extensions can register slash commands, expose tools to the model, and intercept tool calls for permission gates. None are installed by default; opt in explicitly.
310+
311+### Install and manage
312+
313+```bash
314+zot ext install <path|git-url> # copy / clone into $ZOT_HOME/extensions/
315+zot ext list # show installed extensions
316+zot ext logs <name> [-f] # cat or tail the extension's stderr log
317+zot ext enable <name> # re-enable a disabled extension
318+zot ext disable <name> # disable without removing
319+zot ext remove <name> # delete an extension directory
320+```
321+
322+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.
323+
324+### Reference
325+
326+`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.
327+
328+## Skills
329+
330+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.
331+
332+By default only the built-in skills shipped with the zot binary are loaded. Pass `--with-skills` to also load user-installed skills from:
333+
334+- `./.zot/skills/<name>/SKILL.md` (project)
335+- `$ZOT_HOME/skills/<name>/SKILL.md` (global)
336+- `./.claude/skills/<name>/SKILL.md`, `~/.claude/skills/<name>/SKILL.md` (Claude-compatible layout)
337+- `./.agents/skills/<name>/SKILL.md`, `~/.agents/skills/<name>/SKILL.md` (agent-compatible layout)
338+
339+See [docs/skills.md](docs/skills.md) for the frontmatter fields, authoring tips, and example skills under `examples/skills/`.
291 340
292-## telegram bot (bridge)341+## Telegram bot (bridge)
293 342
294-zot can run as a telegram bot so you can dm it from your phone. it's a built-in subcommand, not a plugin:343+zot can run as a telegram bot so you can DM it from your phone. It's a built-in subcommand, not a plugin:
295 344
296 ```bash345 ```bash
297 zot telegram-bot setup # paste a BotFather token, verify, save346 zot telegram-bot setup # paste a BotFather token, verify, save
298 zot telegram-bot run # foreground: long-poll in this terminal (ctrl+c to stop)347 zot telegram-bot run # foreground: long-poll in this terminal (ctrl+c to stop)
299 zot telegram-bot start # background: detach and return immediately348 zot telegram-bot start # background: detach and return immediately
300-zot telegram-bot stop # sigterm the background bot (sigkill after 5s)349+zot telegram-bot stop # SIGTERM the background bot (SIGKILL after 5s)
301 zot telegram-bot logs -f # tail $ZOT_HOME/logs/bot.log (omit -f to just cat)350 zot telegram-bot logs -f # tail $ZOT_HOME/logs/bot.log (omit -f to just cat)
302 zot telegram-bot status # config (token masked) + running/stopped351 zot telegram-bot status # config (token masked) + running/stopped
303-zot telegram-bot reset # forget the token + paired user352+zot telegram-bot reset # forget the token and paired user
304 # short alias: `zot tg ...` is accepted for every subcommand353 # short alias: `zot tg ...` is accepted for every subcommand
305 ```354 ```
306 355
307-the background flavor writes the child's pid to `$ZOT_HOME/bot.pid` and redirects stdout+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.356+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.
308 357
309-> **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.358+> **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.
310 359
311-setup flow:360+Setup flow:
312 361
313-1. talk to [@BotFather](https://t.me/BotFather) on telegram, run `/newbot`, copy the token it gives you.362+1. Talk to [@BotFather](https://t.me/BotFather) on telegram, run `/newbot`, copy the token it gives you.
314-2. run `zot telegram-bot setup` and paste the token when prompted.363+2. Run `zot telegram-bot setup` and paste the token when prompted.
315-3. run `zot telegram-bot run` in the directory you want the agent to operate in.364+3. Run `zot telegram-bot run` in the directory you want the agent to operate in.
316-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.365+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.
317 366
318-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).367+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).
319 368
320-bot mode respects the usual zot flags — `--provider`, `--model`, `--cwd`, `--reasoning`, `--continue`, `--no-session`, `--no-tools`, etc. run `zot tg run -c --model claude-opus-4-1` to resume the latest session on opus, for example.369+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.
321 370
322-## development371+## Development
323 372
324 ```bash373 ```bash
325 make build # build ./bin/zot374 make build # build ./bin/zot
326 make test # go test -race ./...375 make test # go test -race ./...
327 make lint # go vet + gofmt check376 make lint # go vet + gofmt check
328 make fmt # gofmt -w .377 make fmt # gofmt -w .
329-make release # cross-compile linux/darwin/windows × amd64/arm64378+make release # cross-compile linux/darwin/windows on amd64 and arm64
330 ```379 ```
331 380
332-source layout:381+Source layout:
333 382
334 ```383 ```
335 cmd/zot/ main()384 cmd/zot/ main()
336 internal/agent/ cli wiring, arg parsing, system prompt, config385 internal/agent/ cli wiring, arg parsing, system prompt, config
386+internal/agent/extensions/ extension subprocess manager
337 internal/agent/modes/ interactive tui, print, json, dialogs387 internal/agent/modes/ interactive tui, print, json, dialogs
338 internal/agent/tools/ read, write, edit, bash, sandbox388 internal/agent/tools/ read, write, edit, bash, sandbox
339 internal/auth/ credential store, api-key probe, oauth, login server389 internal/auth/ credential store, api-key probe, oauth, login server
340 internal/core/ agent loop, sessions, cost tracking390 internal/core/ agent loop, sessions, cost tracking
391+internal/extproto/ extension wire-format types
341 internal/provider/ anthropic + openai streaming clients, model catalog392 internal/provider/ anthropic + openai streaming clients, model catalog
393+internal/skills/ skill discovery, frontmatter parser, skill tool
342 internal/tui/ terminal raw-mode, input parser, editor, renderer, markdown, view394 internal/tui/ terminal raw-mode, input parser, editor, renderer, markdown, view
395+pkg/zotcore/ public Go SDK for embedding zot in-process
396+pkg/zotext/ public Go SDK for writing extensions
343 ```397 ```
344 398
345-## license399+## License
346 400
347 MIT401 MIT

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