13 KiB
pi config (~/.pi)
On-disk configuration for 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(orstow --no-folding -R -d ~/.dots -t ~ common) once to create the symlink. - After editing an extension, prompt, theme, or
keybindings.json, run/reloadinside 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 wherenotify/questionnaire/plan-modecame 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/questionnairecarry no edits worth keeping — just re-copy them. The modal editor used to be a fork ofmodal-editor.tstoo; it was replaced by thepi-vimpackage on 2026-08-05 and the fork deleted on 2026-08-09 —git show c6566af^:common/.pi/agent/extensions/vim-editor.tsbrings 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, 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.
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'sclipboard-mirror.tscallsimport.meta.resolve("@earendil-works/pi-coding-agent")at module top level, so after a barepi installthe resolve throws and the whole extension fails to load withCannot find module '@earendil-works/pi-coding-agent'. No setting avoids it — the call runs at import time. Once per host, matchingpi --versionexactly:cd ~/.pi/agent/npm fnm exec --using=22 -- npm install --save-exact @earendil-works/pi-coding-agent@0.83.0
pi installdeliberately 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 offpi --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.
{
"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 (24k–128k), 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 ids, 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_KEYlives in.config/fish/conf.d/secrets.fish(gitignored) and is only referenced in tracked config.auth.jsonis not tracked (gitignored as of 2026-08-08): pi's interactive/loginrewrites it with the literal key, and a tracked copy would be one stray/loginaway from committing a real token. Bootstrap a new host withcp 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.fishfor fish,~/.bash_profilefor bash.
Replicating just one piece
- Just the status bar: copy
agent/extensions/statusbar.tsinto 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 thepiVimblock fromsettings.jsonfor the same colors and clipboard behaviour, andagent/extensions/vim-toggle.tsif you want/vimto toggle it. - Just plan mode: copy
agent/extensions/plan-mode/and add theapp.thinking.cycleremap to yourkeybindings.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).