Metadata-Version: 2.4
Name: kiwimatecoder
Version: 0.1.0
Summary: AI-powered coding assistant CLI. Bring your own API key.
License: MIT License
        
        Copyright (c) 2026 Kyle8933
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13
Requires-Dist: httpx>=0.27
Requires-Dist: prompt_toolkit>=3.0.52
Provides-Extra: browser
Requires-Dist: playwright>=1.40; extra == "browser"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: click>=8.0; extra == "dev"
Dynamic: license-file

# kiwimatecoder

An agentic AI coding assistant for your terminal. Run one command and drop into an
interactive session that can read, edit, and run code in your project — across the
model provider of your choice. Bring your own API key.

## Installation

```bash
pip install -e .
```

For a regular (non-editable) install:

```bash
pip install .
```

### Installation options

**Install script** (no clone needed; installs from GitHub):

```bash
curl -fsSL https://raw.githubusercontent.com/Kyle8933/kiwimatecoder/main/scripts/install.sh | sh
```

The script checks for Python >= 3.10, prefers `pipx` when available (falling
back to `pip install --user`), verifies `kiwimatecoder --version`, and prints
PATH guidance when the console script is not yet visible. Append `-s <ref>` to
install a tag, branch, or commit, or set `KIWIMATECODER_REF`.

**Docker:**

```bash
docker build -t kiwimatecoder .
docker run --rm -it -v "$PWD:/workspace" -e OPENROUTER_API_KEY kiwimatecoder
docker run --rm -v "$PWD:/workspace" -e OPENROUTER_API_KEY kiwimatecoder \
  -p "Summarize this repository" --model anthropic/claude-sonnet-5
```

The image is `python:3.12-slim`, installs the package, and runs as the
non-root `kiwimate` user with `/workspace` as the working directory. A fresh
container has no saved config, so no model is chosen: pass `--model` (see
[Choosing a model](#choosing-a-model)).

**Homebrew:** `packaging/homebrew/kiwimatecoder.rb` is a virtualenv formula
template pinned to a git tag. It is not published to a tap yet; fill in the
tag revision and generate the `resource` blocks as described in the file, then
install with `brew install --build-from-source ./packaging/homebrew/kiwimatecoder.rb`.

## Quick start

Add an API key for at least one provider, then launch the interactive session:

```bash
kiwimatecoder setup
kiwimatecoder
```

`kiwimatecoder setup` walks you through choosing a provider, saving its API
key, and choosing the model to use with it. There are no default models: after
the key is saved, setup shows the provider's model list (plus **Type a model
id…** for anything unlisted), and your pick is remembered for that provider.
You can also pass the arguments directly:

```bash
kiwimatecoder setup --provider openrouter
kiwimatecoder setup --provider openai --key sk-... --model gpt-5.6-sol   # non-interactive
```

Without a terminal (or if you cancel the picker) and no earlier choice, setup
prints `No model chosen yet` (a local server instead uses the first model it
lists); the REPL then asks for a model when it starts, or you can set one later
with `kiwimatecoder config model set <id>`. At launch the REPL also asks about
any fallback provider on the roster that has no model (failover skips those),
and `kiwimatecoder doctor` warns about them.

Running `kiwimatecoder` with no arguments opens the interactive REPL. It keeps
running until you exit. Type a request in plain language and KiwiMateCoder will
read files, search the codebase, propose edits, and run commands to carry it
out. For larger or ambiguous tasks, it starts with a short plan and gives you a
few concrete options so you can choose the scope and tradeoffs before it
proceeds.

```
kiwi (openrouter:anthropic/claude-sonnet-5 · ask) › add a docstring to main.py
```

- **Ctrl-C** cancels the current turn and returns you to the prompt; whatever
  the model had already streamed is kept in the conversation so you can continue
  from it.
- **Ctrl-D** exits the session.
- While the agent is working you can type ahead: press Enter to send a steering
  message and the model picks it up on its next step, or type a slash command
  (e.g. `/undo`) and it runs as soon as the turn finishes. Ctrl-C during the
  turn cancels it.
- Your prompt history is saved; the up-arrow recalls commands from previous
  sessions.
- Every session is auto-saved when you exit. Pick it back up with
  `kiwimatecoder --continue` (same as `--resume last`), or from inside the REPL
  with `/load last`.
- Type `/` to open the slash-command menu. It filters as you keep typing.
- Run `/model`, `/provider`, or `/mode` without an argument to open a
  keyboard-driven selector. `/provider` is a checklist: check every provider you
  want on the failover roster. The first checked provider is the primary; the
  rest are tried in order if the primary fails. Experimental providers
  (currently KiwiMate) appear in their own **Experimental** box at the top of
  the list. After you confirm, you choose a model for every provider on the
  roster that does not have one yet. `/provider <id>` replaces the roster with
  that single provider (and asks for its model if none is chosen). Arrow keys
  move, Enter selects, and Ctrl-C returns to the prompt without changing
  anything.
- Opening `/model` checks the provider for models released since you last looked,
  and drops any it has retired. `/model refresh` forces the check. The model you
  pick is saved as that provider's model for the next session. Each provider
  remembers its own model, so switching the primary with `/provider` uses the
  model chosen for the new primary instead of carrying a vendor-specific id
  across providers. `/config model reset` forgets the choice: pick another with
  `/model` (local servers fall back to the first model they list).
- `/model search <term>` searches the provider's full catalog by name and opens
  the selector on the matches — useful when a model is older than the newest few
  that `/model` lists.
- Use `/mode plan` when you only want investigation and options, with no edits or
  shell commands.

## Permission modes

The agent can edit files and run shell commands. A permission mode controls how much
it can do without asking, and you can switch it at any time with `/mode`:

| Mode | Behavior |
|------|----------|
| `ask` (default) | Reads run freely; every file write/edit or shell command shows a preview/diff and waits for your approval. |
| `auto-accept` | Actions run without prompting. |
| `plan` | Read-only: the agent can inspect and explain, but cannot write or run anything. |

At an approval prompt, answer `a` (always) to approve that tool for the rest of
the session **and future sessions**. Persisted approvals survive restarts and
provider switches; manage them with `/config permissions` or
`config permissions list|remove|clear`. When a diff spans multiple hunks, answer
`h` to choose which hunks to apply (e.g. `1,3`, `1-2`, `all`, or `none`).

Every mutating action is checkpointed before it runs. Restore the last change
with `/undo`, rewind several steps with `/undo 3`, and list them with
`/checkpoints`.

Reads, writes, edits, listings and searches are sandboxed to the workspace root (via symlink-aware path resolution). `run_bash` commands execute with the workspace root as their cwd but are otherwise unrestricted (subject to approval/mode); use them for git, tests, builds, etc.

### Command rules

Auto-approve trusted commands and hard-block dangerous ones with regex rules
(deny rules block even in `auto-accept` mode):

```text
/config commands allow ^pytest\b
/config commands deny rm\s+-rf
/config commands list
```

### Dry run and trusted workspace

`/dry-run` previews every mutating tool call (diffs and shell commands) without
executing anything or prompting. `/config trust on` lets read-only tools read
paths outside the workspace root; writes stay sandboxed.

## Slash commands

| Command | Description |
|---------|-------------|
| `/help` | Show available commands. |
| `/exit`, `/quit` | Leave the session. |
| `/clear` | Clear the conversation history. |
| `/model [name\|refresh\|list\|search <term>]` | Interactively choose a model (the list is refreshed from the provider), set one by name, refresh/show the list, or search the full catalog by name. The choice is remembered for that provider. |
| `/provider [id]` | Choose a failover roster (checklist; experimental providers sit in their own box at the top), or replace it with one provider by id. Asks for a model for each provider that has none. |
| `/mode [ask\|auto-accept\|plan]` | Interactively choose, or directly set, the permission mode. |
| `/dry-run [on\|off\|toggle]` | Preview mutating actions without running them. |
| `/undo [count]`, `/checkpoints` | Restore files changed by recent tools, or list checkpoints. |
| `/todos` | Show the agent's task list. |
| `/templates` | List custom prompt templates discovered in the workspace and user command directories. |
| `/compact [budget]` | Trim older history to fit a token budget. |
| `/save`, `/load`, `/sessions`, `/fork [name]`, `/export [path]` | Save, resume, branch, or export sessions as Markdown. |
| `/share [gist] [--include-tool-output]`, `/share import <path> [--name NAME]` | Share a redacted session bundle as a local file or a secret GitHub gist, or import one as a saved session. |
| `/sync [status\|push\|pull]` | Show or sync saved sessions across machines through a shared folder (opt-in). |
| `/tools` | List available tools. |
| `/files` | List files changed this session. |
| `/context [list\|add\|remove\|clear]` | Pin files to include as context on every turn. |
| `/image model [list\|refresh\|search <term>\|<id>]`, `/image [--size WxH] [--model id] <prompt>` | Generate an image with the configured media provider and attach it to your next message (opt-in; see [Image and video generation](#image-and-video-generation)). |
| `/video model [list\|refresh\|search <term>\|<id>]`, `/video [--duration s] [--size 720p] [--model id] [--image path] <prompt>`, `/video resume <job-id>` | Generate a short video (billed per second; takes minutes), or fetch a job that was still rendering. |
| `/media [list [count]\|provider [id]]` | Show whether image and video generation are ready, list recently generated files, or switch the media provider. |
| `/memory [list\|add\|add-user\|clear]` | Show or edit persistent project and user memory. |
| `/index [status\|build\|clear]` | Show, refresh, or delete the local codebase index used by semantic search. |
| `/jobs [list\|show <id>\|cancel <id>\|tick\|run <prompt>]` | List or manage detached background agent jobs; `tick` starts due scheduled runs. |
| `/config` | Show or change providers, keys, models, filters, permissions, command rules, sampling, styles, themes, output modes, accessibility, verify, and budgets. |
| `/mcp [list\|reload]` | List configured MCP servers, their status, and registered tools; `reload` reconnects every server. |
| `/cost` | Show token usage, context gauge, and estimated USD cost for this session (per-model pricing). |
| `/doctor` | Run environment, config, provider, and workspace diagnostics. |

Examples:

```text
/context add README.md kiwimatecoder/*.py
/undo 2
/export review.md
/fork experiment
```

Config examples:

```text
/config
/config provider add local "Local Models" http://localhost:1234/v1 local-code LOCAL_API_KEY
/config provider edit local name="Local Models 2" model=local-fast
/config key set local <YOUR_KEY>
/config provider use local
/config model set anthropic/claude-sonnet-5 openrouter
/config model reset openrouter
/config models allow local-code local-fast
/config models deny noisy-model
/config models refresh
/config mode set plan
/config permissions list
/config permissions remove run_bash
/config commands allow ^pytest\b
/config commands deny rm\s+-rf
/config sampling set temperature=0.2 max_tokens=4096
/config sampling reset
/config style set concise
/config ui theme ocean
/config ui output compact
/config ui ascii on
/config prompt set "Prefer functional style; never use classes."
/config prompt clear
/config verify set "pytest -q"
/config budget tokens 500000
/config budget cost 5.00
/config trust on
/config remote host devbox
/config remote user kiwi
/config remote workspace /srv/app
/config remote enable on
/config browser enable on
/config browser headless off
/config browser timeout 20000
/config provider remove local
```

Running bare `/config` opens an interactive menu. Picking an entry lets you
change that setting: toggles and fixed choices (theme, locale, log level, …) are
picked from a list, free-form values (a proxy URL, an image size, a regex) are
typed, and each list shows the current value. Sections with several independent
settings, such as **Media** or **UI**, return to their list after each change
until you pick **Done** (Ctrl-C also leaves; at a typed prompt, Ctrl-C or an
empty line cancels just that entry). **Keys** works the same way: choose a
provider, then set (type a new key, which is not shown as you type) or remove
it. Only **Show** and **help** just print. Every entry also works as a typed
command as shown above, and `/config key set` / `/config key list` always show
which file or environment variable the active key comes from.

## Tools

The assistant has these capabilities, all scoped to the workspace:

- `read_file`, `list_dir`, `search` (grep + glob + semantic) — read-only,
  always allowed; batches of read-only calls run in parallel.
- `view_image` — attach a workspace image (`.png`, `.jpg`, `.jpeg`, `.gif`,
  `.webp`) so a vision-capable model can see it (see below).
- `generate_image`, `generate_video` — create an image or a short video through the
  configured media provider and save it under `.kiwimatecoder/media/` (opt-in and
  approval-gated because they cost money; see below).
- `write_file`, `edit_file` — create/modify files (approval-gated; each is
  checkpointed first so `/undo` can restore it).
- `run_bash` — run shell commands (approval-gated, subject to command rules).
- `shell` — run a command in a persistent shell that remembers `cd` and exported
  environment state between calls (approval-gated; see below).
- `shell_jobs` — start, list, read, or kill detached background commands
  (approval-gated; see below).
- `update_todos` — keep the visible task list in sync with multi-step work.
- `task` — delegate a focused investigation to a subagent with its own context
  (approval-gated; see below).
- `ask_user` — ask a clarifying question with optional choices (interactive
  sessions only).
- `web_fetch`, `web_search` — read a page or search the web; read-only, and
  local/private addresses are blocked by default (see below).
- `browser` — drive an optional headless Chromium page (screenshot, click,
  type, evaluate) when Playwright is installed and browser automation is
  enabled (approval-gated; see below).
- `remember`, `recall` — persist and read durable facts across sessions (see
  below).
- `git`, `git_write` — inspect status/diff/log and stage, unstage, commit, or
  create a branch (see below).
- `forge`, `forge_write` — list/view or create pull requests/merge requests
  and issues through the `gh`/`glab` CLI (see below).
- `read_clipboard`, `write_clipboard` — read the system clipboard or copy text
  to it (see below).
- `http_get`, `http_request` — exercise an HTTP API and inspect the response
  (see below).
- `lsp_diagnostics`, `lsp_definition`, `lsp_references` — language-server
  diagnostics and navigation, when enabled (see below).

## Persistent shell

`run_bash` starts a fresh process per call, so `cd` and exported environment
variables never survive between commands. The `shell` tool instead drives one
long-lived shell per session (`/bin/sh` on POSIX, Git Bash on Windows; install
Git for Windows, or turn it off with `config shell persistent off`) so state
sticks:

```text
shell: cd backend
shell: source .venv/bin/activate
shell: pytest -q        # runs inside backend with the venv active
```

- The shell starts in the session workspace root. Output (stdout + stderr
  combined) is capped at 30 KB with a truncation note, and every call reports
  the exit code.
- A command that exceeds the timeout (default 120 s) is killed and the shell is
  restarted, so a hung command can never wedge the session.
- `shell_jobs` runs commands detached from the shell and captures their output
  under `~/.kiwimatecoder/jobs/shell/`. `action: list` shows tracked jobs,
  `action: output` reads one, and `action: kill` terminates it. At most
  `shell.max_jobs` (default 8) run at once.
- Settings: `/config shell [show|persistent on|off|timeout <s>|max-jobs <n>]`
  in the REPL, or `kiwimatecoder config shell ...` from the shell.
- Exiting the session terminates the persistent shell and every tracked
  background job. `run_bash` is unchanged and still the right tool for
  one-shot commands.

## Command sandboxing

On macOS and Linux you can execute `run_bash` (and the persistent shell and its
background jobs) through an OS-level sandbox. It is **off by default**:

```bash
kiwimatecoder config sandbox show
kiwimatecoder config sandbox enable on
kiwimatecoder config sandbox network off          # block network access
kiwimatecoder config sandbox add-path ~/shared    # extra writable directory
```

The REPL equivalent is `/config sandbox [show|enable on|off|network on|off|
add-path <path>|remove-path <path>|clear-paths]`.

- **macOS** uses seatbelt via `sandbox-exec`: `process*` is allowed, reads are
  broadly allowed, and writes are limited to the workspace, `/tmp`, and
  `extra_writable`.
- **Linux** uses bubblewrap (`bwrap`): `/` is mounted read-only, the workspace
  is read-write, `/tmp` is a private tmpfs, and `--unshare-net` is added when
  the network toggle is off. Install the `bubblewrap` package to use it.
- Windows has no supported backend; enabling it there falls back to running
  unsandboxed with a warning in the tool output.
- When the sandbox is enabled, the approval preview shows the wrapped command
  and the network mode. If the backend is missing or fails to start, the tool
  warns and runs the command unsandboxed rather than failing.
- Config lives in the `sandbox` section:
  `{"enabled": false, "network": true, "extra_writable": []}`.

> **Limitations:** the sandbox is a damage-limiter for ordinary commands, not a
> security boundary against a hostile model or malicious code. Reads stay
> broad, the sandbox runs with your user's privileges (no user namespaces or
> credential isolation on macOS), and an unavailable backend silently degrades
> to an unsandboxed shell with only a warning. Read access to API keys stored
> on disk is not blocked, so use `--mode plan` or command rules when that
> matters.

## Remote and devcontainer execution

Shell commands can run on another machine over SSH or inside a
devcontainer/Docker container. `run_bash`, the persistent `shell`, and
`shell_jobs` all execute remotely when it is configured; the approval preview
shows the wrapper and tool output includes a `[remote]` note. It is **off by
default**:

```bash
kiwimatecoder config remote show
kiwimatecoder config remote host devbox                  # ssh [user@]host
kiwimatecoder config remote user kiwi                    # optional
kiwimatecoder config remote port 2222                    # optional (default 22)
kiwimatecoder config remote identity ~/.ssh/id_ed25519   # optional
kiwimatecoder config remote workspace /srv/app           # cd before running
kiwimatecoder config remote devcontainer auto            # auto|off|container name
kiwimatecoder config remote enable on
```

The REPL equivalent is `/config remote [show|enable on|off|host <h>|user <u>|
port <n>|identity <path>|workspace <path>|devcontainer <auto|off|name>]`.

When `devcontainer` is `auto` (the default), the first usable route wins:

1. a `.devcontainer/devcontainer.json` in the workspace **and** the
   `devcontainer` CLI: `devcontainer exec --workspace-folder <ws> sh -lc ...`;
2. that detected container through `docker exec -w <workspaceFolder>
   <name> ...`;
3. SSH when `remote.host` is set: `ssh -o BatchMode=yes [-p <port>]
   [-i <identity>] [user@]host -- sh -lc ...`, with
   `cd <remote.workspace> && <command>` when a remote workspace is set;
4. local execution with a warning in the tool output.

Set `devcontainer off` to skip detection, or name a container explicitly to
force `docker exec`. Remote wrapping happens first and a remote command is not
additionally wrapped in a local sandbox. If `ssh`, `docker`, or `devcontainer`
is missing, the command runs locally and the output carries the warning.

- Config lives in the `remote` section:
  `{"enabled": false, "host": "", "user": "", "port": 22, "identity": "",
  "workspace": "", "devcontainer": "auto"}`. `identity` must point at an
  existing key file, and enabling remote execution requires a host (or an
  explicit container name).
- The persistent shell starts through the same wrapper, so `cd` and exported
  variables persist on the remote side. SSH uses agent/key auth only
  (`BatchMode=yes`); there is no password prompt.

> **Limitations:** only commands run remotely. File tools (`read_file`,
> `write_file`, `edit_file`, `search`, ...) still read the **locally**
> accessible workspace, so point them at the remote tree with sshfs, a bind
> mount, or a synced checkout. A full SFTP-backed file layer is deliberately
> not shipped: a half-working remote filesystem would silently split reads and
> writes between two machines. Detached agent jobs (`/jobs`) also run locally
> against that same local view. SSH host keys and identity files are the
> user's responsibility; remote command approval and command rules work
> exactly as they do locally.

## Web access

`web_search` searches the web with the DuckDuckGo HTML endpoint (no API key)
and returns titles, URLs, and snippets. `web_fetch` downloads a page,
follows redirects, converts HTML to readable text, and summarizes binary
responses instead of dumping them. Both are read-only tools, so they run
without approval and stay available in plan mode, and both descriptions tell
the model to cite the URLs it relies on.

- Response size is capped with a truncation note; defaults are 50,000
  characters and a 20-second timeout.
- `localhost`, loopback/private/link-local IPs, and `*.local` hosts are
  refused by default. Enable them with `/config web allow-local on` (or
  `kiwimatecoder config web allow-local on`) when you are working against a
  local dev server.
- Settings live under `/config web show|max-chars <n>|timeout <s>|allow-local <on|off>`
  in the REPL, or `kiwimatecoder config web ...` from the shell.
- DuckDuckGo parses the public HTML page, so result extraction is best-effort
  and may need updating if that page's markup changes; `web_fetch` is
  unaffected.

## Proxy, custom CA, and offline mode

Every outbound request — chat streaming, model catalogs, `web_fetch`/
`web_search`, and embeddings — honors the `network` settings in
`~/.kiwimatecoder/config.json`:

```json
{
  "network": {
    "proxy": "http://proxy.corp:8080",
    "ca_bundle": "/etc/ssl/certs/corp-ca.pem",
    "offline": false
  }
}
```

Set them from the REPL with `/config network ...` or from the shell with
`kiwimatecoder config network ...`:

```bash
kiwimatecoder config network show
kiwimatecoder config network proxy http://proxy.corp:8080   # or: clear
kiwimatecoder config network ca /etc/ssl/certs/corp-ca.pem  # or: clear
kiwimatecoder config network offline on                     # or: off
```

- `proxy` routes all requests through an HTTP(S) proxy (`proxy`/`verify` are
  passed to the underlying `httpx` clients).
- `ca_bundle` points at a PEM file used instead of the system trust store for
  TLS verification — for corporate MITM proxies or self-signed CAs. The file
  must exist when set.
- `offline on` is air-gapped mode: cloud chat, model catalogs, web tools, and
  embeddings are refused with an "offline mode is enabled" error before any
  request is made. **Local providers keep working** — `ollama`, `lmstudio`,
  `unsloth`, and custom providers on `localhost`/`*.local` are still reachable
  for chat, catalog, and embedding calls. `web_fetch`/`web_search` always
  refuse offline because they only reach the public internet.

## Browser automation

The `browser` tool drives a headless Chromium page through Playwright when you
opt in. Playwright is not installed by default, and the tool stays hidden until
browser automation is enabled:

```bash
pip install 'kiwimatecoder[browser]'
playwright install chromium
kiwimatecoder config browser enable on
```

Without the extra, the tool returns a clear install hint instead of failing;
the base install keeps the same dependency set.

- The tool is approval-gated because a page can submit forms or trigger
  downloads. In `ask` mode the preview shows `browser <action> <url|selector>`
  (scripts are redacted and capped). It is not advertised while disabled, and
  plan mode only advertises read-only tools.
- `open` accepts only `http(s)` URLs and refuses `localhost`, loopback/private
  IPs, and `*.local` hosts unless `/config web allow-local on` is set. It
  reports the final URL after redirects and the page title.
- `text` returns visible page text capped at ~30,000 characters with a
  truncation note. `click`/`type` take a CSS selector, and `evaluate` runs
  JavaScript and returns a capped JSON-ish result.
- `screenshot` saves a PNG to
  `<workspace>/.kiwimatecoder/screenshots/<timestamp>.png` and attaches it to
  the next request, so a vision-capable model sees it exactly like a
  `view_image` attachment. The `vision` per-turn count and size limits apply.
- One page is shared by successive tool calls in a session; `close` (and
  leaving the REPL) shuts Chromium down. `timeout_ms` can override the
  configured action timeout per call.
- Settings live under `/config browser [show|enable on|off|headless on|off|timeout <ms>]`
  in the REPL, or `kiwimatecoder config browser ...` from the shell (defaults:
  off, headless on, 15,000 ms; timeout range 1s–120s). Changing a setting
  closes the live page so the next call starts with the new values.

```text
Open https://example.com/docs and summarize the visible text.
Screenshot the pricing page and tell me what stands out.
Fill the search box with "playwright" and click Search.
```

## Git and forge tools

The agent can inspect and change git state through dedicated tools instead of
dropping to `run_bash`:

- `git` (read-only) — `status`, `diff` (staged or not, optionally against a
  ref), `log` (1–100 commits), `show <ref>`, and `branches`.
- `git_write` (approval-gated) — `stage`, `unstage`, `commit` (a non-empty
  message is required), and `checkout_branch` (creates a new branch). The
  approval prompt shows the exact `git ...` command line.
- `forge` (read-only) — `pr_list`, `pr_view`, `issue_list`, and `issue_view`
  for the forge detected from the `origin` remote.
- `forge_write` (approval-gated) — `pr_create` and `issue_create`.

Forge integration goes through the official CLI (`gh` for GitHub, `glab` for
GitLab), so your existing CLI login is used and no token is read or stored by
KiwiMateCoder. The matching CLI must be installed and authenticated; on
GitLab, pull requests are handled as merge requests (`glab mr ...`).

Intentionally out of scope: `push`, `reset`, `clean`, and any forge
merge/close/delete command. Those stay manual, or go through `run_bash` where
the command rules and approval prompt apply.

## Clipboard and HTTP tools

Clipboard access uses the platform's standard tool, discovered on `PATH` and
run without a shell: `pbpaste`/`pbcopy` on macOS, `wl-paste`/`wl-copy`
(preferred) or `xclip` on Linux, and PowerShell's `Get-Clipboard`/
`Set-Clipboard` on Windows. If none is installed the tool returns a clear
install hint instead of failing.

- `read_clipboard` is read-only, so it runs without approval.
- `write_clipboard` is approval-gated and the preview shows the redacted text
  (clipped to ~200 characters).

`http_get` and `http_request` exercise HTTP APIs and print the status line,
selected response headers, and the body (JSON is pretty-printed, bodies are
truncated with a note). 4xx/5xx responses are returned for inspection rather
than raised, and ``httpx`` errors are converted to friendly messages.

- `http_get(url, headers?)` is read-only.
- `http_request(method, url, headers?, body?, json?, timeout?)` is
  approval-gated because it can change remote state. Methods are GET, POST,
  PUT, PATCH, and DELETE; `body` (raw text) and `json` are mutually exclusive.
- Both reuse the `web` settings: response size and timeout come from
  `/config web max-chars` / `/config web timeout`, and local/private addresses
  stay blocked unless `/config web allow-local on`.

```text
Read what's on my clipboard and save it to notes.txt.
Copy the test command to my clipboard.
GET https://api.github.com/repos/psf/requests and summarize the JSON.
POST https://httpbin.org/post with json {"hello": "world"}.
```

## Images

Attach images to a message three ways, all sharing the same size and count
limits:

- Type `@path/to/image.png` in a prompt. Existing image files under the
  workspace are attached and removed from the text (a lone `@image.png` sends
  "Please analyze the attached image(s)."). Paths that are missing, outside the
  workspace, or not images are left in the text untouched.
- Paste or drag an image into the terminal. Most terminals insert the file path
  at the cursor; `@`-mention extraction then attaches it on send.
- Let the model call the `view_image` tool when it needs to look at a file.

Each image is validated (extension and magic bytes), base64-encoded, and sent
to the model as an image part on the next request. Both OpenAI-compatible and
native Anthropic providers are supported. Limits live under
`/config vision [show|max-bytes <n>|max-images <n>]` (defaults: 5 MB per image,
4 images per turn); oversized or invalid images produce a clear message instead
of a failed request.

### Image and video generation

Generate images and short videos with your provider's media API and keep them in
the workspace. Generation is **opt-in** and approval-gated because it costs
money:

```text
/config media enable on
/config media provider openai
/image model list                 # pick an image model (or: /image model <id>)
/config media size 1024x1024
/image a red fox reading a book
```

Media models are chosen **per provider**, and there is no default: ids and prices
change quickly, so you pick one yourself. `/image model` shows the current image
model for the media provider; `/image model list` lists that provider's image
models (a picker in an interactive session, a table otherwise), `/image model
refresh` re-fetches the list, `/image model search <term>` filters it, and
`/image model <id>` sets an id directly (a single word: anything else is treated
as a mistyped prompt). `/video model` takes the same arguments for video.
`/media provider` shows the providers with their models (a picker when
interactive); `/media provider <id>` switches the provider the media tools use
and, interactively, offers to choose any missing models. Each provider remembers
its own image and video model, so switching providers does not lose your choice.
Because `model` is a subcommand, a prompt that starts with the word
`model` needs `--` first (`/image -- model railway in the snow`).

`/image [--size WxH] [--model <id>] <prompt>` saves a PNG under
`.kiwimatecoder/media/` and queues it for the next request. Options come before
the prompt, which is taken verbatim (quotes included); use `--` if a prompt
starts with dashes. The same generation is available to the model as the
`generate_image` tool: it asks for approval first, with a preview showing the
provider, model, size, and prompt. Responses are accepted as
`data[0].b64_json` or `data[0].url` (URL downloads are capped at 20 MB and
honor offline mode and the local-address guard). OpenAI-compatible providers
are called at `/images/generations`; OpenRouter at `/images`.

Video uses the same switch and provider but its own model, which you choose
yourself (there is no default: ids and prices change quickly and a clip is billed
per second):

```text
/media provider openrouter
/video model list                 # pick a video model (or: /video model <id>)
/config media duration 5          # seconds
/config media video-size 720p     # or 1280x720; "default" clears it
/video a paper boat drifting down a rainy street
/video --duration 8 --size 1080p --image start.png the boat starts to sail
```

`/video [--duration <s>] [--size <WxH|720p>] [--model <id>] [--image <path>]
<prompt>` submits an asynchronous job, shows live status while it renders
(usually a few minutes), and streams the finished file to
`.kiwimatecoder/media/` with a 500 MB cap. `--image` animates a workspace image
as the first frame (OpenRouter models). Press Ctrl+C to stop waiting: the job
keeps running (and billing) on the provider, and `/video resume <job-id>` fetches
it later without paying twice. Polling is bounded to 15 minutes and tolerates
brief network errors; the API key is only ever sent to the provider's own host,
never to a download CDN. The model can call the approval-gated `generate_video`
tool (its preview states the per-second billing); after a timeout it resumes with
`resume_job_id` rather than regenerating.

Two wire formats are spoken, chosen from the provider's host: OpenRouter
(`duration`, `resolution`/`size`, `frame_images`, `unsigned_urls`) and the OpenAI
Videos API (`seconds`, `size`, multipart form, `/videos/<id>/content`). The OpenAI
format is covered by mocked-transport tests only; please report anything a live
endpoint rejects.

`/media` shows whether image and video generation are ready (and what is missing:
disabled, offline, no API key, no model chosen) and lists the most recent files;
reference an image from the list with `@<path>` to attach it again. Provider auth,
API versioning (Azure-style `?api-version=`), proxy, and custom CA settings all
come from the normal config. Configure it from the shell with
`config media show|enable on|off|model <id|none> [--provider <id>]|provider <id>|size <WxH>|video-model
<id|none> [--provider <id>]|duration <seconds>|video-size <size>|output-dir <path>`
(inside the REPL, `/config media model <id|none> [provider]` and `/config media
video-model <id|none> [provider]` take the provider as an optional trailing word;
`none`, `clear`, `default` or `-` clears the model for that provider).

## Notebooks, PDFs, and docx

`read_file` extracts structured documents to text automatically, so the model
can read them without extra tooling:

- **Jupyter notebooks** (`.ipynb`) render as `## Cell N (type)` sections with
  cell sources plus text and stream outputs; image outputs are summarized as
  `[image/png output, N bytes]`.
- **Word documents** (`.docx`) are unzipped and `word/document.xml` is stripped
  to text with paragraph and line breaks preserved.
- **PDFs** (`.pdf`) use the `pdftotext` CLI (from Poppler) when installed for
  reliable extraction. When it is missing, a conservative stdlib fallback
  inflates FlateDecode streams and reads text from `BT`/`ET` blocks; if nothing
  is parseable the result explains how to install `pdftotext` instead of
  failing.

Document output is capped at ~200 KB with a truncation note, and `offset`/
`limit` are ignored for documents (they apply to plain text files only).
Malformed documents always return a clear message rather than raising.

## Providers

KiwiMateCoder ships a built-in registry of providers. Switch live with `/provider`,
or set the persistent primary from the shell with `config provider use <id>`. An
API key can be saved interactively with `kiwimatecoder setup`, directly with
`config key set <provider> <key>`, or via the provider's environment variable.
You can also add OpenAI-compatible custom providers with `config provider add`.

| Provider id | Key env var | Notes |
|-------------|-------------|-------|
| `kiwimate` **(experimental)** | `KIWIMATE_API_KEY` | [kiwimate.net](https://kiwimate.net); keys start with `sk-km-`. Chat only — no tool use yet (see below). |
| `openai` | `OPENAI_API_KEY` | |
| `anthropic` | `ANTHROPIC_API_KEY` | Native Messages API. |
| `google` | `GEMINI_API_KEY` | |
| `xai` | `XAI_API_KEY` | |
| `mistral` | `MISTRAL_API_KEY` | |
| `deepseek` | `DEEPSEEK_API_KEY` | |
| `qwen` | `DASHSCOPE_API_KEY` | |
| `moonshot` | `MOONSHOT_API_KEY` | |
| `openrouter` | `OPENROUTER_API_KEY` | The provider used until you pick another. |
| `azure` | `AZURE_OPENAI_API_KEY` | Placeholder endpoint; the model is your deployment name. |
| `bedrock` | `AWS_BEARER_TOKEN_BEDROCK` | Placeholder endpoint. |
| `groq` | `GROQ_API_KEY` | |
| `together` | `TOGETHER_API_KEY` | |
| `fireworks` | `FIREWORKS_API_KEY` | |
| `cerebras` | `CEREBRAS_API_KEY` | |
| `deepinfra` | `DEEPINFRA_API_KEY` | |
| `ollama` | `OLLAMA_API_KEY` (optional) | Local. |
| `lmstudio` | `LMSTUDIO_API_KEY` (optional) | Local. |
| `unsloth` | `UNSLOTH_API_KEY` (required) | Local. |

### Choosing a model

There are no default models. You choose the model to use with each provider
when you add it, and the choice is remembered per provider:

- `kiwimatecoder setup` asks you to pick one from the provider's list after it
  saves the key (or pass `--model <id>` to skip the picker).
- `/provider` asks for a model for each provider you add to the roster.
- Custom providers take the model as the fourth argument of
  `config provider add <id> <name> <base_url> <model>`; change it with
  `config provider edit <id> --model <id>` or `/config provider edit <id> model=<id>`.
- `config model set <id> [--provider <id>]` or `/config model set <model>
  [provider]` changes the model for the active (or named) provider, and
  `config model reset [--provider <id>]` / `/config model reset [provider]`
  forgets it. `config provider list` shows each provider's chosen model.

The interactive REPL asks for a model at launch when the primary has none. A
fallback provider without a chosen model is skipped (with a note) during
failover. Headless runs never prompt: `kiwimatecoder -p`, `ask`, and `jobs run`
exit `1` with `No model chosen for <provider>…` when neither `--model` nor a
chosen model is available (`eval run` reports the same message as invalid
input, exit `2`). Local servers are the one exception — with nothing chosen,
the session uses the first model the running server lists.

Groq, Together, Fireworks, Cerebras, and DeepInfra work out of the box once
their API key is set — they speak the standard OpenAI-compatible protocol.

### KiwiMate (experimental)

`kiwimate` is KiwiMate's own OpenAI-compatible API from
[kiwimate.net](https://kiwimate.net). It is listed first in every provider
picker, in its own **Experimental** box, and marked `(experimental)` in
`config provider list`. Create a key at `kiwimate.net/developers/console` (it
starts with `sk-km-`), then run `kiwimatecoder setup --provider kiwimate` (or
set `KIWIMATE_API_KEY`, then `config provider use kiwimate` and `config model
set <id>`). Setup offers the endpoint's models, falling back to the suggested
`kiwimate-mini-1-0`, `kiwimate-small-1-0`, `kiwimate-medium-1-0`, and
`kiwimate-large-1-0`, and reminds you that the provider is experimental.

The endpoint is limited for now:

- **Chat only, no tool use yet.** KiwiMateCoder sends it no tool schemas, so
  with KiwiMate as the provider the agent cannot read or edit files or run
  commands. Tool calls and results already in the conversation are flattened
  into plain text when you switch to it mid-session.
- **Replies are capped at about 1,024 tokens.** The REPL prints `The reply was
  cut off at the provider's output limit.` when that happens.
- **System prompts are ignored by the endpoint**, so project instructions
  (`AGENTS.md`), memory, output styles, and custom prompts do not reach the
  model. Only the last 20 non-system messages of the conversation are
  forwarded to it.

`azure` and `bedrock` ship **placeholder** endpoints because the endpoint is
account-specific (`https://<resource>.openai.azure.com/openai/v1` and
`https://bedrock-runtime.<region>.amazonaws.com/openai/v1`). Point them at your
own resource by adding a custom provider (built-in entries cannot be edited):

```bash
kiwimatecoder config provider add my-azure "My Azure" \
  https://my-resource.openai.azure.com/openai/v1 my-deployment \
  --key-env AZURE_OPENAI_API_KEY --key-header api-key --key-prefix "" \
  --api-version 2024-10-21
kiwimatecoder config key set my-azure <key>

kiwimatecoder config provider add bedrock-eu "Bedrock (eu-central-1)" \
  https://bedrock-runtime.eu-central-1.amazonaws.com/openai/v1 \
  openai.gpt-5.6-sol --key-env AWS_BEARER_TOKEN_BEDROCK
```

Azure OpenAI authenticates with an `api-key` header (no `Bearer` prefix) and
versions its API with `?api-version=`; both are configurable per custom provider
(`--key-header`, `--key-prefix`, `--api-version`, or the slash equivalents).
AWS Bedrock's OpenAI-compatible endpoint accepts a bearer token only: SigV4/IAM
signing, Azure managed identity, Google Vertex AI (project-specific endpoint,
OAuth2-only auth), and interactive OAuth/device-code flows are not implemented
(deferred; see ROADMAP).

Each built-in provider still ships a short list of suggested model ids, used
only to fill the pickers when its live listing is unavailable — none of them is
used unless you choose it. The live catalog below is what `/model` actually
offers once the provider can be queried (see **Needs a key** below).

The `anthropic` provider talks to Anthropic's native Messages API, including
streaming tool calls and native model listing. Custom providers can opt into the
same path with `config provider edit <id> --compat anthropic` (or
`/config provider edit <id> compat=anthropic`); everything else speaks the
OpenAI-compatible chat+tools protocol.

### Local providers (Ollama, LM Studio & Unsloth)

The `ollama`, `lmstudio`, and `unsloth` providers talk to model servers on your
own machine. Make sure the server is running ([Ollama](https://ollama.com) on
`http://localhost:11434`, [LM Studio](https://lmstudio.ai)'s local server on
`http://localhost:1234`, or [Unsloth](https://unsloth.ai)'s Studio API on
`http://localhost:8888`), then:

```text
/provider ollama
```

That's it for Ollama and LM Studio — no API key needed. You can choose a model
like any other provider (`kiwimatecoder setup` offers the server's models when
it is running, and `/model` lists them); until you do, the session uses the
first model the server lists. `kiwimatecoder setup` detects which local servers
are running and marks them in the picker. Tool calling depends on the model you
pick, so choose a tool-capable family (Llama 3.1+, Qwen 3, DeepSeek, ...).

`unsloth` works the same way but **requires a key**: Unsloth enforces auth even
on localhost. Create one in Unsloth under Settings → API (it starts with
`sk-unsloth-`), run `kiwimatecoder setup --provider unsloth` to save it, and
load a GGUF model in Unsloth before chatting. Tip: start Unsloth's server with
`--disable-tools` when an external agent drives it — otherwise Unsloth's own
server-side tools swallow the agent's tool calls.

A key only matters if your server enforces auth (e.g. LM Studio's server token,
or Ollama behind an authenticating proxy) — set `OLLAMA_API_KEY` /
`LMSTUDIO_API_KEY` or `config key set ollama <key>` in that case. Running on a
different port or another machine? Add it as a custom provider instead, with
the model to use as the fourth argument:
`/config provider add mybox "My Box" http://mybox.local:11434/v1 llama3.1:8b` —
any `localhost`/`*.local` provider is treated as keyless automatically.

## Model catalogs

Model ids drift constantly, so the list `/model` offers is fetched from the
provider itself rather than being frozen into the release.

- **Newest first.** Opening `/model` (or running `/model refresh`) calls the
  provider's `/models` endpoint and offers what it serves today, ordered by
  release date, with your current model pinned first. Search
  (`/model search <term>`) bypasses this newest-first cap and scans the whole
  catalog by name.
- **Deprecated ids disappear.** Anything the provider no longer lists is dropped
  from the catalog, and `/model refresh` prints what was removed. If your current
  model is one of them, it says so.
- **Only usable models.** Embedding, image, audio, moderation, and non
  tool-calling models are filtered out — the agent loop needs text chat plus tool
  calls (chat-only providers such as KiwiMate keep models without tool support).
  The newest 60 are offered; any other id still works if you type it.
- **Cached for a day.** Results are stored in `~/.kiwimatecoder/model_cache.json`
  and reused for 24 hours, so the selector stays instant and tab completion never
  touches the network. A failed fetch falls back to the cached list, then to the
  built-in suggestions, and is not retried automatically for 30 minutes.
- **Needs a key.** A cloud provider is only queried once it has an API key
  configured; keyless local servers (`ollama`, `lmstudio`, and any custom
  provider on `localhost` or a `*.local` host) are queried without one —
  `unsloth`, which enforces auth locally, is queried once its key is set.

From the shell, `kiwimatecoder config models show [--provider <id>]` and
`kiwimatecoder config models refresh [--provider <id>]` show or refresh the same
catalog.

The suggested model tuples in `kiwimatecoder/providers.py` are only the offline
fallback for the pickers (never a default), and custom providers can list
theirs via a `"models"` array in `~/.kiwimatecoder/config.json`. `/config models
allow|deny` reshapes what is offered on top of whichever catalog is in use, and
`/model <name>` or `config model set <name>` accepts any id, listed or not.

## One-shot mode

For a quick question without entering the REPL:

```bash
kiwimatecoder ask "how do I reverse a list in python?"
kiwimatecoder ask "review this file" --file app.py --provider openai --model gpt-5.6-sol
```

`ask` uses the model chosen for the provider unless `--model` is passed, and
exits `1` with `No model chosen for <provider>…` when there is neither.

## Headless mode

Run one agentic turn without entering the REPL — useful for scripts and CI:

```bash
kiwimatecoder -p "Summarize the changes in the working tree."
echo "Explain src/app.py" | kiwimatecoder -p -
kiwimatecoder -p "Summarize the changes." --provider openai --model gpt-5.6-sol
```

The prompt is the value of `-p/--print`; `-p -` reads it from stdin. The run
uses the model chosen for the provider (`kiwimatecoder setup` or `config model
set`); pass `--model` to override it, or when none is chosen — headless runs
never prompt for one. Flags:

| Flag | Meaning |
| --- | --- |
| `--output-format text\|json\|stream-json` | `text` streams assistant text to stdout (default); `json` prints one JSON object at the end; `stream-json` prints one compact JSON object per event, ending with a `result` line. |
| `--mode ask\|auto-accept\|plan` | Permission mode for this run (default: the configured mode). |
| `--yes` | Treat approvals as granted (auto-accept) — never prompts. |
| `--max-turns N` | Hard cap on tool-loop iterations (default 30). |
| `--provider`, `--model` | Provider/model overrides for this run. `--provider` makes that provider the primary, ahead of the configured roster; `--model` is required when no model is chosen for it. |
| `--workspace PATH` | Workspace root (default: current directory). |
| `--quiet` | Suppress tool/progress lines on stderr in text mode. |

Tool and progress lines go to **stderr**; only assistant text (text mode) or
JSON (json/stream-json modes) goes to **stdout**. Headless runs never prompt:
actions that would need approval are denied with a note on stderr unless
`--yes` or `--mode auto-accept` is used.

Exit codes: `0` success, `1` runtime failure (missing key or model — `No model
chosen for <provider>…` on stderr — provider error, turn cap without a final
answer), `2` invalid usage (bad format, mode, provider, or workspace).

CI example (a fresh runner has no saved config, so pass `--model`):

```yaml
- name: Review the diff
  run: |
    kiwimatecoder -p "Review the staged diff and list risks." \
      --model anthropic/claude-sonnet-5 \
      --output-format json --max-turns 10 > review.json
    cat review.json
```

More recipes (ready-made GitHub Actions workflows, exit-code gating, and
pre-commit setup) live in [docs/ci.md](docs/ci.md).

## Shell completions and man page

Typer ships shell completion for bash, zsh, fish, and PowerShell:

```bash
kiwimatecoder --install-completion          # detect and install for your shell
kiwimatecoder --show-completion bash        # print the script to stdout instead
```

A troff man page is committed at `docs/kiwimatecoder.1`; view it with `man
./docs/kiwimatecoder.1` (on Linux, `man -l docs/kiwimatecoder.1`). Regenerate it
from the Typer command tree after changing the CLI:

```bash
python scripts/generate_man.py           # rewrite the man page
python scripts/generate_man.py --check   # fail when the committed file is stale
```

Interactive sessions need a real terminal. When stdin is not a TTY (a pipe, a
CI job, cron), the bare command — and `--resume`/`--continue` — exits with code
`2` and a note pointing at headless mode instead of starting prompt_toolkit.
Use `kiwimatecoder -p "..."` or `echo "..." | kiwimatecoder -p -`.

## Python SDK

Embed the agent in a Python program with `kiwimatecoder.sdk`:

```python
from kiwimatecoder.sdk import run_agent_sync

result = run_agent_sync(
    "Summarize README.md in three bullets.",
    workspace=".",
    mode="plan",
    model="anthropic/claude-sonnet-5",  # omit to use the provider's chosen model
)

if result.success:
    print(result.text)
print(result.usage, result.cost_usd)  # tokens and estimated USD (or None)
```

Without `model=`, the run uses the model chosen for the provider. When there
is none, `run_agent`/`run_agent_sync` raise
`kiwimatecoder.config.ModelNotChosenError` (a `ValueError`) before contacting
the provider.

`RunResult` carries `text`, `usage` (`prompt_tokens`/`completion_tokens`),
`cost_usd`, `provider`, `model`, `mode`, `tools_used`, `messages`, `success`,
and `error`. Pass `on_event` to observe the same events as stream-JSON
(`text_delta`, `tool_start`, `tool_end`, `usage`, `done`), `confirm` to
approve actions, and `console` to receive the agent's tool/progress output.
Approvals are denied by default. From async code use `await run_agent(...)`;
`run_agent_sync` raises a clear error if called inside a running loop.

## Editor integration (ACP)

KiwiMateCoder can run as an [Agent Client Protocol](https://agentclientprotocol.com)
(ACP) agent over stdio, so an ACP-capable editor can drive it:

```bash
kiwimatecoder acp [--workspace PATH]
```

A Zed-style agent configuration (`settings.json`) looks like:

```json
{
  "agent": {
    "command": "kiwimatecoder",
    "args": ["acp"]
  }
}
```

The server speaks JSON-RPC 2.0 over newline-delimited JSON on stdin/stdout;
the only thing ever written to stdout is protocol traffic. Implemented subset:

- `initialize` handshake with agent capabilities and version info.
- `session/new` creates one conversation rooted at the client's `cwd`,
  using the **configured** permission mode (never an assumed auto-accept).
- `session/prompt` runs one agent turn, streaming assistant text chunks and
  tool-call progress as `session/update` notifications; the response carries
  `end_turn`, `cancelled`, `refusal`, or `max_tokens`.
- `session/cancel` cancels the running turn; partial assistant text is kept.
- `session/request_permission` bridges every write/command approval to the
  editor when the session runs in `ask` mode. Approving with "always"
  persists the tool allowlist; a cancelled or unanswered request (default
  timeout 300s, `config acp timeout <s>`) is denied exactly like a normal
  denial.

This is a focused subset: terminal and MCP client capabilities advertised by
the editor are not wired through (MCP servers still come from KiwiMateCoder
config), `loadSession` is not supported, image prompt parts are ignored with a
note to the model, and the file tools operate on the local filesystem. When
the client advertises `fs.readTextFile`/`fs.writeTextFile`, the server exposes
`read_text_file`/`write_text_file` helpers that delegate to
`fs/read_text_file`/`fs/write_text_file`; otherwise those helpers use the
local filesystem.

## Background jobs

Run an agent task detached from your terminal, then check on it later:

```bash
kiwimatecoder jobs run "Add type hints to kiwimatecoder/jobs.py and run mypy."
kiwimatecoder jobs run "Fix the failing test." --provider openai --model gpt-5.6-sol
kiwimatecoder jobs list
kiwimatecoder jobs show <id>
kiwimatecoder jobs output <id>
kiwimatecoder jobs cancel <id>
kiwimatecoder jobs clear          # finished jobs only; --all includes running
```

Each job is one headless run (`python -m kiwimatecoder -p ... --output-format
json`) recorded as JSON under `~/.kiwimatecoder/jobs/<id>.json` with its combined
output at `~/.kiwimatecoder/jobs/<id>.log`. Records survive restarts; `jobs list`
refreshes running jobs from their process and parsed result, and corruption in
one record never hides the others. A job uses the model chosen for its provider
unless `--model` is passed; `jobs run` and `jobs schedule` check this before
starting anything and exit `1` with `No model chosen for <provider>…` when
there is neither.

> **Warning:** `jobs run` defaults to `--mode auto-accept`, so an unattended job
> can edit files and run commands without asking. Pass `--mode ask` (approvals
> are then denied, since nothing can prompt) or `--mode plan` (read-only) to
> constrain a job, and only point jobs at workspaces you trust.

Inside a session, `/jobs [list|show <id>|cancel <id>|tick]` does the same, and
`/jobs run <prompt>` starts a job in the session's workspace (still
`auto-accept`). Jobs are marked `running`, `succeeded`, `failed`, or
`cancelled`; `cancelled` covers an explicit `jobs cancel`.

### Scheduling

Scheduling is pull-based — there is no daemon and no platform cron integration.
`jobs schedule "<prompt>" --every <seconds>` starts a run immediately and stores
the interval plus a `next_run_at` timestamp; every later invocation of
`kiwimatecoder jobs tick` (or `/jobs tick`) starts any run that is due. A job
that is still running is skipped, so runs never overlap. A due job that cannot
start (its workspace is gone, its provider was removed, or no model is chosen)
is left in place and reported as `Skipped <id>: <reason>`; `jobs tick` then
exits `1`. Wire `jobs tick` to your own scheduler, for example:

```cron
*/5 * * * * kiwimatecoder jobs tick
```

## Update

Update the CLI from the same Python environment:

```bash
kiwimatecoder update
```

or with a flag:

```bash
kiwimatecoder --update
```

(The older `-update` form still works but is deprecated.) Check the installed
version before/after with:

```bash
kiwimatecoder --version
```

When KiwiMateCoder is running from a Git checkout, the updater first fetches
`origin` and compares the local `HEAD` to `origin/<branch>`:

- **Already up to date** — prints `Already on the latest version (commit <sha>).`
  and exits without pulling or reinstalling.
- **Behind** — prints `Updating from <old-sha> (N commit(s) behind origin/<branch>)…`,
  runs `git pull --ff-only`, reinstalls the checkout with
  `pip install --upgrade -e <path>`, and reports `Updated <old-sha> → <new-sha>.`

KiwiMateCoder is not published to PyPI, so for packaged (non-Git) installs the
fallback runs
`pip install --upgrade --force-reinstall git+https://github.com/Kyle8933/kiwimatecoder.git`.

### Updating and pinning versions

Pin a branch, tag, or commit with `--ref`:

```bash
kiwimatecoder update --ref v0.1.0    # tag
kiwimatecoder update --ref main      # branch
kiwimatecoder update --ref 1a2b3c4   # commit
```

In a Git checkout the updater fetches `origin` and then puts the checkout on
the ref: the current branch is fast-forwarded with `git pull --ff-only` (local
commits are never merged unexpectedly), while every other ref is a plain
`git checkout` followed by `pip install --upgrade -e <path>`. The command
prints the old and new commit SHAs. For packaged installs it reinstalls
`git+https://github.com/Kyle8933/kiwimatecoder.git@<ref>`.

To roll back, pin an older tag or commit the same way; `kiwimatecoder --version`
shows the installed version. Generate a software bill of materials for the
installed environment with:

```bash
python scripts/generate_sbom.py --output sbom.json
```

It is stdlib-only, emits CycloneDX-shaped JSON with the package and direct
dependency versions, and is uploaded as a CI artifact by the `sbom` job.


## Project instructions

Drop an `AGENTS.md` (or `CLAUDE.md`, `.kiwimatecoder/AGENTS.md`,
`.kiwimatecoder/instructions.md`) at the workspace root and its contents are
loaded into the system prompt on every turn — build commands, style rules,
testing conventions. Files are capped at 32KB each and 48KB total.

## Memory

The agent can remember durable facts about you and your projects across
sessions. Memory is plain Markdown, one `- fact` bullet per line, in two
scopes:

- **Project** — `<workspace>/.kiwimatecoder/memory.md`; travels with the
  repository and applies only there.
- **User** — `~/.kiwimatecoder/memory.md`; shared across every workspace.

The model writes with the approval-gated `remember` tool (`scope=project` or
`scope=user`) and reads with the read-only `recall` tool. Active facts are
loaded into the system prompt on every turn, capped at 16KB by default so a
pathological file cannot crowd out the conversation.

Manage memory directly with `/memory`:

```text
/memory                       # show both files and their facts
/memory add The build uses uv, not pip
/memory add-user I prefer concise answers
/memory clear project
/memory clear user
```

Settings live under `kiwimatecoder config memory show|max-bytes <n>|enable on|off`.
Never store secrets or API keys in memory — the prompt section says so
explicitly, and the `remember` preview runs the same secret redaction as audit
output.

## Custom commands and prompt templates

Drop a Markdown file in `<workspace>/.kiwimatecoder/commands/` (or
`~/.kiwimatecoder/commands/`) and its filename becomes a slash command. The
body is the prompt sent as a normal user turn; `$ARGUMENTS` is replaced with
whatever you type after the command, and the arguments are appended when the
token is absent. Workspace templates win over user templates with the same
name, and built-in commands always win over templates. `/templates` lists what
was discovered; `/help` adds a Custom commands group.

```text
# .kiwimatecoder/commands/review.md
# Review the current diff
Review $ARGUMENTS for bugs, security issues, and missing tests.

/review src/app.py
```

## Agent Skills

Skills are Markdown instruction bundles the model loads only when a task
matches, so they cost no tokens on turns that do not use them. Each skill is a
directory containing `SKILL.md`:

```text
<workspace>/.kiwimatecoder/skills/pdf/SKILL.md   # project skill
~/.kiwimatecoder/skills/pdf/SKILL.md             # personal skill
```

The system prompt lists each skill's name and first line only; the model calls
the read-only `load_skill` tool to pull the full body (capped at 32KB).
Workspace skills shadow user skills with the same name.

## Plugins

Plugins are Python files with a `register(api)` entry point that can add tools,
slash commands, and event subscribers without forking the CLI:

```python
from kiwimatecoder.tools.base import FunctionTool, ToolResult

def register(api):
    api.register_tool(FunctionTool(
        name="word_count",
        description="Count words in a file.",
        parameters={
            "type": "object",
            "properties": {"path": {"type": "string"}},
            "required": ["path"],
        },
        func=lambda args, session: ToolResult(content="..."),
    ))
    api.register_command("hello", lambda arg, session, console: "continue")
    api.subscribe("post_tool", lambda event: None)
```

User plugins load from `~/.kiwimatecoder/plugins/*.py`. Project plugins in
`<workspace>/.kiwimatecoder/plugins/*.py` are **disabled by default** — cloning
a repository must never execute code just because the CLI started in it. Opt in
with `"plugins": {"allow_project": true}` in `~/.kiwimatecoder/config.json`, and
skip individual plugins with `"plugins": {"disabled": ["name"]}`. A plugin that
fails to import or register is reported as a dim warning and skipped; it never
prevents startup.

## MCP servers

MCP (Model Context Protocol) servers extend the agent with tools it discovers
at session start. Configure them under `mcp_servers` in
`~/.kiwimatecoder/config.json` — each entry is either a stdio subprocess or an
HTTP endpoint:

```json
{
  "mcp_servers": {
    "files": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
      "env": {"SOME_TOKEN": "..."},
      "disabled": false
    },
    "remote": {
      "url": "https://mcp.example.com/mcp",
      "headers": {"Authorization": "Bearer sk-..."},
      "disabled": false
    }
  }
}
```

From the shell, `kiwimatecoder config mcp add <name>` writes the same entries
(set exactly one of `--command` or `--url`):

```bash
kiwimatecoder config mcp add files --command npx --args "-y @modelcontextprotocol/server-filesystem /tmp"
kiwimatecoder config mcp add remote --url https://mcp.example.com/mcp -H "Authorization: Bearer sk-..."
kiwimatecoder config mcp list
kiwimatecoder config mcp remove remote
```

In the REPL, `/mcp list` shows every configured server, its status (connected,
disabled, or the failure reason), its resources count, and the registered tools.
`/mcp reload` reconnects each enabled server and re-registers its tools without
restarting the session.

Each server tool is registered as `mcp__<server>__<tool>` (both parts sanitized
to `[A-Za-z0-9_]`), with the server's JSON Schema used for arguments. Tools that
declare `annotations.readOnlyHint` run without approval; every other MCP tool
prompts for approval in `ask` mode, exactly like `write_file` or `run_bash`.
A server that fails to start or initialize is reported as a dim warning and
skipped — it never blocks startup. Connections close when the session exits.

Authentication is whatever headers (HTTP) or environment variables (stdio) you
configure. Interactive OAuth is not implemented yet; see ROADMAP.md.

## Language server diagnostics

When enabled, KiwiMateCoder can talk to language servers for compiler-grade
diagnostics and navigation. It is **opt-in** and off by default; no server
process starts until the feature is enabled and a matching file is queried.

- `lsp_diagnostics` — errors and warnings for one file, or for the five most
  recently touched files when no path is given.
- `lsp_definition` / `lsp_references` — where a symbol is defined, and every
  place it is used. Positions are 1-based line and column numbers.

Built-in presets start automatically when their command is on `PATH`:

| Language | Command | Extensions |
|----------|---------|------------|
| python | `pyright-langserver --stdio` | `.py`, `.pyi` |
| typescript | `typescript-language-server --stdio` | `.ts`, `.tsx`, `.js`, `.jsx` |
| go | `gopls` | `.go` |
| rust | `rust-analyzer` | `.rs` |
| c/cpp | `clangd` | `.c`, `.h`, `.cpp`, `.hpp` |

After a successful `write_file`/`edit_file`, diagnostics for the edited file
are appended to the tool result as `LSP:` lines (capped at ten) when
`diagnostics_after_edits` is on. A timeout or missing server appends nothing,
so LSP can never fail a turn or delay it beyond the configured timeout.

Enable it in-session with `/lsp on`, or from the shell:

```bash
kiwimatecoder config lsp enable on            # on | off
kiwimatecoder config lsp after-edits on       # on | off
kiwimatecoder config lsp show
```

Use `/lsp status` to see whether LSP is on, which server commands are
installed, and which clients are running; `/lsp restart` stops running servers
so they start fresh on next use. Settings live under `lsp` in
`~/.kiwimatecoder/config.json`; add your own server or override a preset:

```json
{
  "lsp": {
    "enabled": true,
    "timeout": 10.0,
    "diagnostics_after_edits": true,
    "servers": {
      "python": {
        "command": "basedpyright-langserver",
        "args": ["--stdio"],
        "extensions": [".py", ".pyi"]
      }
    }
  }
}
```

## Codebase index and semantic search

`search` gains a third mode: `mode='semantic'` ranks files against a
natural-language query using a local, persistent index instead of a regex, and
returns paths, line numbers, and source snippets. The agent chooses it for
questions like "where is authentication handled?", and grep/glob keep working
exactly as before.

The index is built automatically the first time semantic search runs, then
refreshed incrementally: only files whose size or modification time changed
are re-tokenized, deleted files are dropped, and `.gitignore` plus the default
skip directories (`node_modules`, `venv`, `__pycache__`, …) are respected.
Binary files, files larger than the byte cap, and anything beyond the file cap
are skipped. It is a cache: the store lives at
`~/.kiwimatecoder/index/<hash-of-workspace>.json` and `clear` simply deletes it.

```text
/index                 # status: files, terms, store size, stale count, embeddings
/index build           # refresh incrementally; prints added/updated/removed
/index clear           # delete the store for this workspace
```

Ranking uses BM25 over file term frequencies, so there are no API calls and
semantic search works out of the box. Settings live under the `index` key:

```json
{
  "index": {
    "enabled": true,
    "max_files": 5000,
    "max_file_bytes": 262144,
    "embeddings": { "provider": "", "model": "", "batch_size": 32 }
  }
}
```

Embeddings are optional and **OpenAI-compatible only**: set a provider id and
an embedding model (the provider must serve `POST /embeddings` and its API key
is read from the same config/env as chat) to additionally store chunk vectors.
When vectors exist, search blends the lexical and vector scores; if the
embedding provider is unavailable or errors for any reason, search silently
falls back to lexical ranking. Embeddings are never required to use the
feature.

```bash
kiwimatecoder config index show
kiwimatecoder config index embed-provider openai
kiwimatecoder config index embed-model text-embedding-3-small
kiwimatecoder config index embed-provider none    # turn embeddings off
kiwimatecoder config index clear
```

## Subagents

The `task` tool delegates a focused, self-contained investigation to a
subagent that runs in its own context with the same workspace, provider, and
tools. The subagent's intermediate tool output stays out of the parent
conversation; it returns a final report (plus a step/token count) as the tool
result. Use it for broad searches or self-contained questions that would
otherwise flood the main context.

- Spawning a subagent is approval-gated like a command because the subagent can
  write files and run commands. Its own actions still go through the normal
  permission gate (including plan mode and command rules), and its edits are
  checkpointed so `/undo` covers them.
- Subagents cannot spawn nested subagents and cannot use `ask_user`; put every
  decision they need in the prompt.
- A subagent shares the parent's remaining token budget and stops at the
  configured step limit, returning a partial report with a note when either cap
  is hit. While subagents are disabled, `task` is not advertised at all.

```json
{
  "subagents": {
    "enabled": true,
    "max_steps": 20,
    "model": ""
  }
}
```

```bash
kiwimatecoder config subagents show
kiwimatecoder config subagents enable on
kiwimatecoder config subagents max-steps 10
kiwimatecoder config subagents model gpt-5-mini
```

Or in the REPL: `/config subagents [show|enable on|off|max-steps <n>]`.

## Sampling and output styles

Sampling parameters apply to every provider and are sent when set:

```bash
kiwimatecoder config sampling set temperature=0.2 top_p=0.9 max_tokens=4096 reasoning_effort=medium
kiwimatecoder config sampling show
kiwimatecoder config sampling reset
```

Output styles shape the agent's replies: `default`, `concise`, `explanatory`,
or `code`. Set one with `config style set <name>` (or `/config style set`). For
a fully custom instruction, add a system-prompt suffix with
`config prompt set "<text>"` and remove it with `config prompt clear`.

## Themes, output modes, and accessibility

Control color independently of your terminal, pick a theme, tune how much the
agent prints, and switch to ASCII glyphs for screen readers and limited
terminals:

```bash
kiwimatecoder config ui show
kiwimatecoder config ui color never        # auto | always | never
kiwimatecoder config ui theme ocean        # default | ocean | magenta | mono
kiwimatecoder config ui output compact     # normal | compact | verbose
kiwimatecoder config ui ascii on           # on | off
kiwimatecoder config ui spinner off        # auto | on | off
```

The same settings are available in-session with `/config ui ...`. Values live
under the `ui` key in `~/.kiwimatecoder/config.json`:

```json
{
  "ui": {
    "theme": "ocean",
    "color": "auto",
    "output_mode": "normal",
    "ascii": false,
    "locale": "en",
    "keybindings": "emacs",
    "notify": "off",
    "notify_after_seconds": 20,
    "spinner": "auto"
  }
}
```

Color precedence: an explicit `ui.color` of `always`/`never` wins; otherwise
`NO_COLOR` set to anything non-empty disables color, then `FORCE_COLOR` set to
anything non-empty enables it. With `auto` and neither variable exported, Rich
still detects a pipe or log file and stays plain.

Output modes shape the tool log. `normal` prints a line per tool with a success
or failure marker. `compact` hides successful tool lines while keeping failures,
the thinking status, and the final answer. `verbose` adds a redacted argument
block (secrets replaced, capped at ~500 characters) before each tool runs and
the result size after.

ASCII mode replaces the check, cross, blocked, folder, and bullet glyphs with
plain text (`[ok]`, `[fail]`, `[blocked]`, no folder prefix, `-`) and the CLI
confirmations follow suit. Color, theme, ASCII, and output-mode changes apply
to the next session.

Animated status spinners can be disabled for screen readers, tmux capture, or
quiet terminals with `config ui spinner off` (or `/config ui spinner off`).
For a fully plain single run, the global `--plain` flag forces ASCII glyphs, no
color, compact tool output, and no spinner without persisting anything:

```bash
kiwimatecoder --plain
kiwimatecoder --plain -p "Summarize this repository"
```

The full audit — what works today, what does not, and the concrete follow-ups
— is in [docs/accessibility.md](docs/accessibility.md).

### Language and locale

A representative subset of user-facing strings is translated: the banner hint,
prompt and approval labels, common errors, `/help` group titles, and key CLI
messages. Pick the language from `ui.locale`, the `KIWIMATECODER_LANG`
environment variable, or English (in that precedence order):

```bash
kiwimatecoder config ui locale de          # en | de | es
KIWIMATECODER_LANG=es kiwimatecoder        # per-run override
```

The migration is incremental: untranslated strings stay in English, and a
locale that is missing a key falls back to English and then to the key itself.
`/config ui locale <en|de|es>` switches the language for the current session
immediately.

### Keybindings and notifications

The prompt is Emacs-style by default; switch to Vim mode with
`config ui keybindings vim` (or `/config ui keybindings vim`). Vim mode keeps
the same completion and Alt+Enter bindings and adds modal editing.

`ui.notify` can ring the terminal bell (`bell`) or send a desktop notification
(`desktop`: OSC 9 when stderr is a TTY, otherwise `osascript` on macOS or
`notify-send` on Linux) when a turn takes at least `ui.notify_after_seconds`
seconds (default 20; `0` notifies after every turn):

```bash
kiwimatecoder config ui notify bell
kiwimatecoder config ui notify-after 30
```

### `@`-mentions

Typing `@` offers workspace file-path completions (gitignored files are
skipped). A sent line containing `@path/to/file` attaches that text file as a
`[mentioned files]` context block ahead of the message — at most 5 files per
turn, 64 KB each, with truncation markers. Image paths (`@shot.png`) use the
image-attachment path instead, so pasting or dragging an image into the
terminal works as before.

## Model routing

Route short, plain requests to a cheaper model while keeping the session model
for real work. Configure it under `model_routing` in
`~/.kiwimatecoder/config.json`:

```json
{
  "model_routing": {
    "enabled": true,
    "simple_model": "gpt-5-mini",
    "simple_max_chars": 200,
    "exclude_keywords": ["refactor", "implement", "debug", "test"]
  }
}
```

A turn is routed when routing is enabled, `simple_model` is set, the message is
at most `simple_max_chars` long, and it contains no code fence, file path,
slash command, or exclude keyword (whole-word, case-insensitive). The override
applies to the primary provider only — fallback providers keep their own
models — and the session's model is never changed.

## Prompt caching

Native Anthropic providers support prompt caching. Enable it with
`kiwimatecoder config cache on` (or `/config cache on`): requests then send the
system prompt as a cacheable content block and mark the last tool definition as
an ephemeral cache breakpoint, cutting latency and input cost when the same
system prompt and tools repeat across turns. The toggle only changes native
Anthropic payloads — OpenAI-compatible providers cache automatically, so their
requests are byte-for-byte identical either way.

## Auto-verify

Point the agent at your test/lint command and it runs automatically after any
successful file edit, feeding the output back so it can fix failures:

```bash
kiwimatecoder config verify set "pytest -q"
kiwimatecoder config verify show
kiwimatecoder config verify clear
```

## Budgets

Cap a session's spend by tokens, cost, or both. The agent warns at 80% and
stops starting new turns once a limit is reached:

```bash
kiwimatecoder config budget tokens 500000
kiwimatecoder config budget cost 5.00
kiwimatecoder config budget show
kiwimatecoder config budget clear
```

A token limit is a whole number from 1 to 1,000,000,000,000,000; use `clear`
for no limit.

## Audit log

Every tool decision (allowed, denied, dry-run, auto-verify) is appended to
`~/.kiwimatecoder/audit.log` as one JSON line per action. Secrets are redacted
before writing; the file is owner-only and safe to delete.

A `pre_tool` hook that exits non-zero blocks the action and is recorded with
the `hook_blocked` decision.

## Telemetry and debug logging

Telemetry is **opt-in, local-only, and off by default**. When enabled, events
from the lifecycle bus (`session_start`, `session_end`, `pre_tool`,
`post_tool`), provider errors and retries, and (at the `debug` level) provider
HTTP metadata (method/host/status/duration only — never bodies or headers) are
appended as one compact JSON object per line to
`~/.kiwimatecoder/logs/kiwimatecoder.log`. Secret-looking strings are redacted
with the same rules as the audit log, the file is owner-only, and it rotates to
`.log.1` once it would grow past `max_log_bytes`:

```bash
kiwimatecoder config telemetry enable on
kiwimatecoder config telemetry level info      # off | error | info | debug
kiwimatecoder config telemetry show
```

Slash equivalents: `/config telemetry show|enable on|off|level <lvl>`. Log
files are never uploaded anywhere. Crash bundles (`crash-<timestamp>.json`
containing a redacted traceback, version, platform, and recent log lines) are
written only while telemetry is enabled at a level other than `off`, and
`/doctor` lists the level, log path, and crash-report count.

Set `KIWIMATECODER_DEBUG=1` to force the `debug` level for one run without
changing config (handy for diagnosing a stubborn provider or proxy issue).

An OTLP exporter is not implemented yet; see the roadmap (5.2 follow-up).

## Lifecycle hooks

Run your own shell commands when the agent reaches a lifecycle event. Hooks
live under the `hooks` key in `~/.kiwimatecoder/config.json` (a project
`.kiwimatecoder.json` may add them too). Each event maps to a list of commands
run with the session workspace as their working directory:

```json
{
  "hooks": {
    "session_start": ["echo session started in $KIWI_WORKSPACE"],
    "session_end": ["echo session ended"],
    "pre_tool": ["test \"$KIWI_TOOL_NAME\" != \"run_bash\" || ./scripts/safety-check.sh"],
    "post_tool": ["echo \"$KIWI_TOOL_NAME\" exited ok=$KIWI_TOOL_OK\" >> .kiwi-hooks.log"]
  }
}
```

Events are `session_start`, `session_end`, `pre_tool`, and `post_tool`. Hooks
receive `KIWI_EVENT` and `KIWI_WORKSPACE`, plus `KIWI_TOOL_NAME`,
`KIWI_TOOL_OK` (`true`/`false`), `KIWI_TOOL_ARGS` (JSON; secrets redacted), and
`KIWI_TOOL_DURATION_MS` for tool events. A non-zero exit from a `pre_tool` hook
blocks the tool call; every hook times out after 60 seconds and never crashes
the agent. Commands run through the platform shell (`/bin/sh` on POSIX,
`cmd.exe` on Windows), so write them in that shell's syntax; the examples above
are POSIX. `/config` does not manage hooks yet — edit the JSON directly.

## Configuration

Global settings live in `~/.kiwimatecoder/config.json` (provider keys, the
default provider, the model chosen for each provider under `provider_models`,
default mode, sampling, output style, theme/color/output/ASCII preferences,
custom prompt, and persisted tool approvals). The original single-key
`~/.kiwimatecoder/config` format is read automatically, so existing setups keep
working. Config files from before per-provider models (version 2 or older) are
migrated on load: a model saved with `config model set` becomes the primary provider's
choice, and a custom provider's `default_model` becomes its chosen model.
Built-in providers that never had a model chosen ask for one. A custom provider
whose id is now built in (a hand-made `kiwimate`) is renamed to
`kiwimate-custom`, keeping its stored key, model, filter, and the profiles that
used it; if it read its key from `KIWIMATE_API_KEY`, it now reads
`KIWIMATE_CUSTOM_API_KEY` so the built-in and the custom provider never share a
key.

A project can pin its own settings in `.kiwimatecoder.json` at the repository
root (or wherever `KIWIMATECODER_PROJECT_CONFIG` points). Project values
override global config — useful for pinning a provider, model, mode, sampling,
or permissions per repository. A pinned `selected_model` applies to the primary
provider only and wins over the model chosen for it. API keys are never read
from project files.

A shared **team policy** file can be layered on top of both (see
[Team policies and session sharing](#team-policies-and-session-sharing)); it is
configured only in the global config, so a cloned repository can never disable
or redirect it.

Fetched model catalogs are cached separately in
`~/.kiwimatecoder/model_cache.json`. It holds no secrets and can be deleted at
any time; the next `/model` rebuilds it.

Prompt history lives in `~/.kiwimatecoder/history` and session autosaves in
`~/.kiwimatecoder/sessions/last.json`. Both can be deleted at any time.

Run `kiwimatecoder doctor` (or `/doctor`) to check config paths, key status,
provider reachability, the model catalog, whether a model is chosen, and the
workspace.

## Profiles

Profiles are named presets of global settings: provider, model, mode, sampling,
output style, custom system prompt, verify command, budget, trusted workspace,
command rules, and persisted `always` approvals. Capture the current effective
settings, then re-apply them anywhere:

```bash
kiwimatecoder config profile save work
kiwimatecoder config profile list
kiwimatecoder config profile show work
kiwimatecoder config profile use work      # writes the preset into config
kiwimatecoder config profile remove work
```

Launch once with a profile without persisting it with `kiwimatecoder --profile
work`. On a fresh session the provider, model, mode, and other session-level
settings come from the profile; with `--resume`/`--continue` only the mode and
model are overlaid so the restored conversation keeps its provider. Sampling
and budget are global settings, so use `config profile use` to apply those.
The REPL equivalent is `/config profile list|show|save|use|remove`.

## Cross-machine session sync

Sync saved sessions across machines through a folder you already share (Dropbox,
iCloud Drive, a network mount, or a git checkout). There is no server component
and nothing is sent anywhere: the feature is off by default and only reads and
writes the folder you point it at.

Enable it with an existing directory:

```bash
kiwimatecoder sync enable ~/Dropbox/kiwimatecoder
```

or edit `~/.kiwimatecoder/config.json` directly:

```json
{
  "sync": {
    "enabled": true,
    "path": "~/Dropbox/kiwimatecoder",
    "machine": "laptop",
    "include_autosave": false
  }
}
```

The folder gets a `kiwimatecoder-sessions/` subdirectory holding the session
JSON files plus a `manifest.json` index. `machine` defaults to a slug of the
hostname; set it explicitly when two machines would otherwise share a name.

```bash
kiwimatecoder sync status    # local/remote counts, pending changes, conflicts
kiwimatecoder sync push      # copy local sessions into the shared folder
kiwimatecoder sync pull      # copy newer shared sessions into the local store
kiwimatecoder sync disable   # opt out; the shared folder is left alone
```

The same actions are available in the REPL as `/sync [status|push|pull]`.
Everything mirrors the local session store except the implicit `last.json`
autosave, which is skipped by default (`include_autosave: true` opts in) because
it changes on every exit. Unreadable or corrupt files are skipped and reported,
never fatal.

**Conflicts.** Each machine remembers what it last pushed or pulled. On the next
`push`, a shared file that changed elsewhere *and* whose local copy also changed
is kept as both: the shared copy stays put and the local copy is uploaded as
`<name>__<machine>.json`. Every other difference is last-write-wins by the
session's `saved_at` timestamp. `pull` uses the same rule but renames the
incoming file to `<name>__<remote-machine>.json` instead of overwriting the
local one. Pass `--force` to `push`/`pull` to make the local copy (push) or the
shared copy (pull) win unconditionally.

## Team policies and session sharing

### Shared team policies

A team can publish a shared policy JSON file (same shape as
`.kiwimatecoder.json`) that is layered **on top of** global and project config,
so policy values win. There is no server component and no identity provider:
the file is read from the local path you configure, and a cloned repository can
never disable or redirect it because `team` settings are taken from the global
config only.

Point the global config at the shared file:

```bash
kiwimatecoder config team set-policy ~/team/kiwimatecoder-policy.json
kiwimatecoder config team enforce on
kiwimatecoder config team show
```

or edit `~/.kiwimatecoder/config.json` directly:

```json
{
  "team": {
    "policy_path": "/Users/you/team/kiwimatecoder-policy.json",
    "enforce": true
  }
}
```

The policy file may only control these keys: `default_mode`,
`selected_provider`, `active_providers`, `selected_model`,
`model_filters`, `command_rules`, `tool_permissions`, `sampling`, `trusted_workspace`,
`output_style`, `system_prompt`, `verify_command`, `budget`, `mcp_servers`,
`plugins`, `subagents`, `sandbox`, `remote`, and `network`. `keys`, `version`,
and `team` are ignored, so a policy can never carry API keys or weaken its own
enforcement. Unknown keys are reported as warnings. The per-provider model
choices (`provider_models`) are deliberately not a policy key — an enforced
policy would replace every provider's choice at once — so a team pins the
primary provider's model with `selected_model`.

- **Advisory** (`enforce: false`): policy values override global and project
  values, but project config still fills in any key the policy does not set.
- **Enforced** (`enforce: true`): each policy key replaces the merged value
  wholesale, and project values for policy keys the policy does not define are
  ignored, so a repository cannot widen or weaken the policy namespace.

A missing or corrupt policy file never blocks startup: it is skipped and
reported by `kiwimatecoder config validate`, `kiwimatecoder doctor`, and
`/config team show`. The REPL equivalents are
`/config team [show|set-policy <path>|enforce on|off]`.

### Session sharing

Share a conversation as a redacted JSON bundle, either as a local file or as a
**secret** GitHub gist through your own `gh` login (no token is read or stored
by KiwiMateCoder):

```bash
kiwimatecoder share create                 # last session -> .kiwimatecoder/shares/
kiwimatecoder share create my-session --output ~/Desktop/review.json
kiwimatecoder share create --gist          # uploads via `gh gist create`
kiwimatecoder share import bundle.share.json --name from-teammate
```

In the REPL, `/share` bundles the current session, `/share gist` uploads it,
and `/share import <path>` turns a bundle into a normal saved session.

Privacy notes:

- Every string is passed through the same secret redactor used for logs, so
  API keys and `token = ...` assignments never leave the machine.
- Tool **results are omitted** (replaced with `[tool output omitted]`) unless
  you pass `--include-tool-output`; when included they are redacted and
  truncated to 2 KB. Tool call names are kept for context, and their arguments
  are redacted and clipped.
- Image attachments are replaced with `[image omitted]` so no base64 image data
  is embedded in the bundle.
- Bundles are capped at ~256 KB; when a conversation is larger the oldest
  messages are dropped and `truncated` / `truncated_messages` record it.
- Sharing is local or gist-based only. **SSO/identity and any hosted sharing
  service are deferred** (see the roadmap) — there is no server to run.

## Config validation

`kiwimatecoder config validate` checks the stored config and prints a table of
issues, exiting non-zero when any error-level problem is found. It surfaces
exactly what the tolerant getters would silently drop: unknown top-level keys
(warning), malformed provider/model-filter/sampling/budget/hook/command-rule/
profile/MCP/plugin entries, a model chosen for an unknown provider in
`provider_models` (warning), invalid regexes, unknown hook events, bad network
(proxy/CA/offline) values, bad ACP permission timeouts, a missing or corrupt
team policy file, and bad default mode,
output style, UI (theme/color/ASCII/output mode), or workspace-flag values.

## Evals

`kiwimatecoder eval` runs declarative regression cases against any configured
provider. A case is a small JSON file with a prompt, optional starting files,
and deterministic expectations about the run:

```json
{
  "name": "create-file-from-prompt",
  "description": "Writes a requested file with exact content.",
  "prompt": "Create hello.txt whose contents are exactly 'hello from the eval'.",
  "files": {"notes.txt": "Starting workspace file."},
  "expect": {
    "text_contains": ["hello"],
    "text_not_contains": ["error"],
    "tools_called": ["write_file"],
    "tools_not_called": ["run_bash"],
    "files": {"hello.txt": ["hello from the eval"]},
    "success": true
  }
}
```

List and run the committed cases:

```bash
kiwimatecoder eval list
kiwimatecoder eval run --provider openrouter --model anthropic/claude-sonnet-5
kiwimatecoder eval run --filter create --json --report eval-report.json
```

Every field in `expect` is optional: `text_contains`/`text_not_contains` check
the final assistant text, `tools_called`/`tools_not_called` check the tools the
run used, `files` maps workspace paths to substrings that must appear in the
file after the run, and `success` checks the run's own success flag. A run
error is always a failure unless the case explicitly expects `success: false`.

Cases live in `evals/cases/*.json` and are discovered sorted by `name`. Copy
one as a starting point, or point `--dir` at another directory. The command
exits `0` when every case passes, `1` when any case fails, and `2` on invalid
input (bad directory, malformed JSON, unknown provider, or no `--model` passed
and none chosen for the provider — `No model chosen for <provider>…`). Eval
runs call a real model, so they need a configured provider key and a chosen
model (`kiwimatecoder setup`) or `--model`; each
case runs in a throwaway temp workspace and nothing is recorded beyond the
report you ask for. Expectations are deterministic — there is no model-graded
scoring yet.

## Development

```bash
pip install -e ".[dev]"
pytest
ruff check kiwimatecoder tests
mypy kiwimatecoder
```

## Roadmap

The full prioritized plan lives in [ROADMAP.md](ROADMAP.md). P0 and P1 are
implemented (checkpoints/undo, command rules, dry-run, redacted audit log,
hunk-level approvals, todos, ask-user, parallel reads, compaction, auto-verify,
budgets, trusted workspace, and message steering with interrupt recovery), and
P2 is complete (event bus + hooks, the extensible tool registry, custom
commands and prompt templates, on-demand Agent Skills, a plugin system, the MCP
client, config profiles with schema validation, per-turn model routing,
Anthropic prompt caching, and themes/output modes/NO_COLOR accessibility).
