Files
dots/common/.pi/README.md
T
2026-09-12 23:38:18 +02:00

223 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# pi config (`~/.pi`)
On-disk configuration for **[pi](https://github.com/earendil-works/pi)**, a TUI coding
agent (binary `/usr/bin/pi`). This tree lives in the dots repo and is surfaced into
`$HOME` by GNU Stow, so `~/.pi/agent/...` symlinks back here — **editing a file here
edits the live config**, no deploy step.
## Install / how it's surfaced
pi reads everything under `$PI_CODING_AGENT_DIR` (default `~/.pi/agent`). Stow
symlinks each file individually (`--no-folding`):
```
~/.pi/agent/settings.json -> ~/.dots/common/.pi/agent/settings.json
~/.pi/agent/extensions/statusbar.ts -> ~/.dots/common/.pi/agent/extensions/statusbar.ts
...
```
- **Editing** an existing tracked file = editing live config immediately.
- **Adding** a new file (e.g. a new extension) is *not* live until you re-stow —
run `./install.sh` (or `stow --no-folding -R -d ~/.dots -t ~ common`) once to
create the symlink.
- After editing an extension, prompt, theme, or `keybindings.json`, run `/reload`
inside pi to apply without restarting.
## What's in here
| Path | Purpose |
|---|---|
| `agent/settings.json` | runtime settings (default model, theme, compaction, retry, timeouts) |
| `agent/models.json` | provider + model catalog (two OpenAI-compatible providers) |
| `agent/auth.json` | provider → credential map (references like `$DUSKADIY_API_KEY`, never literals). **Untracked** — copy `auth.json.example` on a new host |
| `agent/keybindings.json` | key remaps (each entry **replaces** the default for that action) |
| `agent/AGENTS.md` | global system instructions injected into every session |
| `agent/prompts/*.md` | custom `/slash` commands (prompt templates) |
| `agent/themes/*.json` | custom color themes |
| `agent/extensions/**` | auto-discovered TypeScript extensions (loaded via jiti, no build) |
Runtime artifacts (`sessions/`, `npm/`, `git/`, `trust.json`) are gitignored.
---
## Addons vs. stock pi
Stock pi ships with **no extensions enabled, two built-in themes (`dark`/`light`),
default keybindings, and default settings**. Everything below is a deviation added
here. Each is independent — pull the ones you want and delete the rest.
### Extensions
pi **auto-discovers** every `.ts` under `extensions/`. So the universal "remove"
step is: **delete the file (or its folder) and `/reload`.** Toggle-able ones also
have a slash command to disable them for the current session without deleting.
| Extension | What it adds | Origin | Remove |
|---|---|---|---|
| `statusbar.ts` | Replaces the footer with a Claude-Code-style status line (cwd, git branch, model, context %, decode t/s, token counts) in Nerd Font glyphs; adds a streaming pulse + a ≥80%-context warning widget | **custom** | delete file → default footer returns; or `/statusbar` to toggle off for the session |
| `vim-toggle.ts` | `/vim` — drops the modal editor for the rest of the session, and brings it back via `ctx.reload()`. Pairs with the **`pi-vim` package** (below), which registers no command of its own | **custom** | delete file → you lose the toggle, not the editor; remove the package to drop modal editing itself |
| `plan-mode/` | Read-only "plan" mode (disables edit/write, restricts bash) bound to **Shift+Tab**; `/plan` toggles, `--plan` starts in it, `/plan-done` clears a stuck progress checklist | **vendored** copy of pi's bundled `examples/extensions/plan-mode/`, rebound from upstream `Ctrl+Alt+P` | delete the folder **and** revert the `keybindings.json` `app.thinking.cycle` remap (see below) |
| `notify.ts` | Native terminal notification (OSC 777/99, Windows toast) when the agent goes idle | **verbatim** from pi's `examples/extensions/notify.ts` | delete file |
| `questionnaire.ts` | Interactive multi-question overlay the model can call as a tool | **near-verbatim** from pi's `examples/extensions/questionnaire.ts` (one-line fix) | delete file |
| `permission-gate.ts` | Prompts before dangerous `bash` commands (blocks outright with no UI). Local list covers git history rewrites, package managers, `systemctl` and irreversible disk ops on top of upstream's `rm -rf`/`sudo`/`chmod 777` | **adapted** from pi's `examples/extensions/permission-gate.ts` | delete file |
| `protected-paths.ts` | Blocks `write`/`edit` to secrets and repo plumbing (`secrets.fish`, `auth.json`, `.env`, `.ssh/`, `.git/`, …) | **adapted** from pi's `examples/extensions/protected-paths.ts` | delete file |
| `compaction-watch.ts` | Surfaces every compaction and its `reason`; loudly flags `overflow` (recovery rather than prevention), which means `reserveTokens` is below the model's `maxTokens` | **custom** | delete file |
| `confirm-destructive.ts` | Confirms before session clear / switch / fork | **verbatim** from pi's `examples/extensions/confirm-destructive.ts` | delete file |
| `session-name.ts` | `/session-name [name]` — friendly names in the session picker instead of the first message | **verbatim** from pi's `examples/extensions/session-name.ts` | delete file |
| `todo.ts` | A `todo` tool the model calls (add/toggle/list/clear) plus `/todos` to view it; state lives in tool results, so forking rewinds it correctly. Distinct from plan-mode's checklist | **verbatim** from pi's `examples/extensions/todo.ts` | delete file (it also adds a tool schema to every request) |
> The bundled examples live at `/opt/pi-coding-agent/examples/extensions/` — that's
> where `notify`/`questionnaire`/`plan-mode` came from. `plan-mode/` is now the only
> **fork** left, so it's the only one to re-pull after a pi upgrade before re-applying
> the local edits (its header documents them). `notify`/`questionnaire` carry no edits
> worth keeping — just re-copy them. The modal editor used to be a fork of
> `modal-editor.ts` too; it was replaced by the `pi-vim` package on 2026-08-05 and the
> fork deleted on 2026-08-09 — `git show c6566af^:common/.pi/agent/extensions/vim-editor.ts`
> brings it back if ever wanted.
### Modal (vim) input — the `pi-vim` package
Modal editing is **not** an extension in this tree any more; it's the npm package
[`pi-vim`](https://github.com/lajarre/pi-vim), pinned by `settings.json > packages`
and installed into the gitignored `agent/npm/`. That swap is the point: `pi update
npm:pi-vim` instead of re-pulling and re-patching a fork on every pi upgrade.
Five modes (INSERT/NORMAL/VISUAL/V-LINE/EX) with the usual motions, operators, text
objects, undo and `.` repeat; the mode renders as a word label at the editor's
bottom-right, doubling as a pending-command display (` NORMAL 3d2w_ `). `:` dispatches
real pi commands and shells out with `:!`. No search (`/`, `?`, `n`), macros or
visual-block — upstream implements none of those.
```fish
pi install npm:pi-vim
```
> ⚠️ **This pi build needs pi-vim's peer dep installed by hand.** pi ships as a
> Bun-compiled ELF with the `@earendil-works/*` packages *embedded*, not on disk.
> pi-vim's `clipboard-mirror.ts` calls `import.meta.resolve("@earendil-works/pi-coding-agent")`
> at **module top level**, so after a bare `pi install` the resolve throws and the whole
> extension fails to load with `Cannot find module '@earendil-works/pi-coding-agent'`.
> No setting avoids it — the call runs at import time. Once per host, matching
> `pi --version` exactly:
>
> ```fish
> cd ~/.pi/agent/npm
> fnm exec --using=22 -- npm install --save-exact @earendil-works/pi-coding-agent@0.83.0
> ```
>
> `pi install` deliberately doesn't pull peers (it would duplicate the runtime). Costs
> ~170 MB, and wants re-pinning after every pi upgrade — hence `--save-exact`, since a
> caret range would quietly drift off `pi --version`.
Configured under `settings.json > piVim`: `modeColors` (theme tokens, not raw ANSI),
`borderSync`/`labelSync` pinned so only the label is tinted, and
`clipboardMirror: "yank"` — upstream's default `"all"` mirrors deletes too, so every
`dd`/`x` would clobber the OS clipboard. `exCommand.copyInputToClipboard` is left
**off**: it copies the composed prompt out, so upstream only honours it from the
user-global settings file.
**Remove:** `pi remove npm:pi-vim`, and delete `agent/extensions/vim-toggle.ts` plus the
`piVim` block. `@burneikis/pi-vim` and `pi-vimmode` are alternatives that need no peer
install.
### Theme
`themes/catppuccin-mocha.json` is a **custom** theme (stock pi only bundles `dark`
and `light`). `settings.json` sets `"theme": "catppuccin-mocha"`.
**Remove:** delete the file and set `"theme": "dark"` (or `"light"`) in
`settings.json`.
### Custom slash commands (prompt templates)
`prompts/commit.md` and `prompts/review.md` add `/commit` (writes a Conventional
Commits message for the staged diff, never commits) and `/review` (reviews the
working diff). Both are **custom**. Format is YAML frontmatter + body with
`${1:-default}` positional-arg substitution.
**Remove:** delete the file(s). Stock pi has no `/commit` or `/review`.
### Keybindings
`keybindings.json` overrides three actions. **A user entry replaces the default keys
for that action — it does not merge.** Action ids and their stock defaults are in
`/opt/pi-coding-agent/docs/keybindings.md`.
```jsonc
{
"app.thinking.cycle": ["ctrl+shift+t"], // moved OFF Shift+Tab so plan-mode can bind it
"tui.editor.cursorUp": ["up", "ctrl+p"], // adds Ctrl+P as up (Emacs-style)
"app.model.cycleForward": ["alt+p"] // Alt+P cycles models
}
```
The `app.thinking.cycle` remap is **coupled to `plan-mode`**: plan-mode binds
Shift+Tab, so the thinking-cycle default (also Shift+Tab) is moved aside to avoid a
collision. If you drop plan-mode, drop that line too.
**Remove:** delete the whole file to restore all stock keybindings, or delete
individual lines to restore just those.
### `settings.json` tunings
Beyond pointing at local models, these values deviate from pi's defaults:
| Key | Value here | Stock default | Why |
|---|---|---|---|
| `compaction.reserveTokens` | `8192` | 16384 | small local windows (24k128k), but **≥ every model's `maxTokens`** — otherwise a turn can overflow the window before compaction even fires |
| `compaction.keepRecentTokens` | `4000` | 20000 | keep less verbatim, to pay for the larger reserve and leave room after a compaction for the next one |
| `npmCommand` | `fnm exec --using=22 -- npm` | `npm` | pin extension npm installs to Node 22 via fnm |
| `retry` / `httpIdleTimeoutMs` | long (1h provider timeout, 10m idle) | shorter | local models can be slow to first token |
| `defaultThinkingLevel` | `off` | — | most local models here are non-reasoning |
| `packages` | `["npm:pi-vim"]` | `[]` | the modal editor — see the pi-vim section above |
| `piVim` | mode colors, `clipboardMirror: "yank"`, `:` → pi bridge | — | package config, not a pi setting; ignored if the package is removed |
| `collapseChangelog` | `true` | `false` | condensed changelog after an upgrade instead of the full screen |
**Remove:** delete each key to fall back to pi's default (or drop the whole
`compaction`/`retry` block).
### Providers & models
`models.json` + `auth.json` define **two OpenAI-compatible providers** serving the
same catalog of small self-hosted models (Qwen3-Coder-30B, Gemma 4, GLM-4.7-Flash,
gpt-oss-20b, …), all `cost: 0`:
- **`localcpp`** — LAN llama.cpp server, `http://192.168.0.204:11343/v1`, no key.
- **`duskadiy`** — remote, `https://llm.duskadiy.com/api/v1`, key `$DUSKADIY_API_KEY`.
This is the part you'd **replace**, not just delete, to point pi at your own backend:
edit `models.json` (baseUrl + model `id`s, keeping each `id` exactly matching your
server's model id and `contextWindow` matching its loaded `ctx-size`) and set the
matching credential in `auth.json`. `settings.json > enabledModels` is a glob
allowlist for the Ctrl+P picker; `defaultProvider`/`defaultModel` pick the startup
model.
> **Secret hygiene:** `$DUSKADIY_API_KEY` lives in
> `.config/fish/conf.d/secrets.fish` (gitignored) and is only *referenced* in tracked
> config. `auth.json` is **not tracked** (gitignored as of 2026-08-08): pi's interactive
> `/login` rewrites it with the **literal** key, and a tracked copy would be one stray
> `/login` away from committing a real token. Bootstrap a new host with
> `cp auth.json.example auth.json`. The key itself lives in a shell file that is *not*
> tracked and *not* stowed, so it never reaches this repo — `conf.d/secrets.fish` for
> fish, `~/.bash_profile` for bash.
---
## Replicating just one piece
- **Just the status bar:** copy `agent/extensions/statusbar.ts` into your
`~/.pi/agent/extensions/`, re-stow/restart, `/reload`. Colors follow your active
theme automatically. Needs a Nerd Font terminal for the glyphs.
- **Just vim input:** `pi install npm:pi-vim`, then the peer-dep install above (this
build needs it), then `/reload`. Copy the `piVim` block from `settings.json` for the
same colors and clipboard behaviour, and `agent/extensions/vim-toggle.ts` if you want
`/vim` to toggle it.
- **Just plan mode:** copy `agent/extensions/plan-mode/` **and** add the
`app.thinking.cycle` remap to your `keybindings.json` (else Shift+Tab collides).
- **Just the theme:** copy `agent/themes/catppuccin-mocha.json`, set
`"theme": "catppuccin-mocha"`.
- **Just the slash commands:** copy `agent/prompts/commit.md` / `review.md`.
Validate any JSON edit with `jq . agent/settings.json` (there's no build/test step).