A terminal-based coding agent with multi-model support, mid-session model switching, and a simple CLI for headless coding tasks.
Works on Linux, macOS, and Windows (requires bash; see [Windows Setup](#windows-setup)).
## Table of Contents
- [Getting Started](#getting-started)
- [Installation](#installation)
- [Windows Setup](#windows-setup)
- [Terminal Setup](#terminal-setup)
- [API Keys & OAuth](#api-keys--oauth)
- [Quick Start](#quick-start)
- [Usage](#usage)
- [Slash Commands](#slash-commands)
- [Editor Features](#editor-features)
- [Keyboard Shortcuts](#keyboard-shortcuts)
- [Custom Keybindings](#custom-keybindings)
- [Bash Mode](#bash-mode)
- [Image Support](#image-support)
- [Sessions](#sessions)
- [Session Management](#session-management)
- [Context Compaction](#context-compaction)
- [Branching](#branching)
- [Configuration](#configuration)
- [Project Context Files](#project-context-files)
- [Custom System Prompt](#custom-system-prompt)
- [Custom Models and Providers](#custom-models-and-providers)
- [Settings File](#settings-file)
- [Customization](#customization)
- [Themes](#themes)
- [Prompt Templates](#prompt-templates)
- [Skills](#skills)
- [Extensions](#extensions)
- [CLI Reference](#cli-reference)
- [Tools](#tools)
- [Programmatic Usage](#programmatic-usage)
- [SDK](#sdk)
- [RPC Mode](#rpc-mode)
- [HTML Export](#html-export)
- [Philosophy](#philosophy)
- [Development](#development)
- [License](#license)
---
## Getting Started
### Installation
**npm (recommended):**
```bash
npm install -g @mariozechner/pi-coding-agent
```
**Standalone binary:**
Download from [GitHub Releases](https://github.com/badlogic/pi-mono/releases):
| Platform | Archive |
|----------|---------|
| macOS Apple Silicon | `pi-darwin-arm64.tar.gz` |
| macOS Intel | `pi-darwin-x64.tar.gz` |
| Linux x64 | `pi-linux-x64.tar.gz` |
| Linux ARM64 | `pi-linux-arm64.tar.gz` |
| Windows x64 | `pi-windows-x64.zip` |
```bash
# macOS/Linux
tar -xzf pi-darwin-arm64.tar.gz
./pi
# Windows
unzip pi-windows-x64.zip
pi.exe
```
**macOS note:** The binary is unsigned. If blocked, run: `xattr -c ./pi`
**Build from source** (requires [Bun](https://bun.sh) 1.0+):
```bash
git clone https://github.com/badlogic/pi-mono.git
cd pi-mono && npm install
cd packages/coding-agent && npm run build:binary
./dist/pi
```
### Windows Setup
Pi requires a bash shell on Windows. Checked locations (in order):
1. Custom path from `~/.pi/agent/settings.json`
2. Git Bash (`C:\Program Files\Git\bin\bash.exe`)
3. `bash.exe` on PATH (Cygwin, MSYS2, WSL)
For most users, [Git for Windows](https://git-scm.com/download/win) is sufficient.
**Custom shell path:**
```json
// ~/.pi/agent/settings.json
{
"shellPath": "C:\\cygwin64\\bin\\bash.exe"
}
```
### Terminal Setup
Pi uses the [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) for reliable modifier key detection. Most modern terminals support this protocol, but some require configuration.
**Kitty, iTerm2:** Work out of the box.
**Ghostty:** Add to your Ghostty config (`~/.config/ghostty/config`):
```
keybind = alt+backspace=text:\x1b\x7f
keybind = shift+enter=text:\n
```
**wezterm:** Create `~/.wezterm.lua`:
```lua
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
config.enable_kitty_keyboard = true
return config
```
**Windows Terminal:** Does not support the Kitty keyboard protocol. Shift+Enter cannot be distinguished from Enter. Use Ctrl+Enter for multi-line input instead. All other keybindings work correctly.
### API Keys & OAuth
**Option 1: Auth file** (recommended)
Add API keys to `~/.pi/agent/auth.json`:
```json
{
"anthropic": { "type": "api_key", "key": "sk-ant-..." },
"openai": { "type": "api_key", "key": "sk-..." },
"google": { "type": "api_key", "key": "..." }
}
```
**Option 2: Environment variables**
| Provider | Auth Key | Environment Variable |
|----------|--------------|---------------------|
| Anthropic | `anthropic` | `ANTHROPIC_API_KEY` |
| OpenAI | `openai` | `OPENAI_API_KEY` |
| Google | `google` | `GEMINI_API_KEY` |
| Mistral | `mistral` | `MISTRAL_API_KEY` |
| Groq | `groq` | `GROQ_API_KEY` |
| Cerebras | `cerebras` | `CEREBRAS_API_KEY` |
| xAI | `xai` | `XAI_API_KEY` |
| OpenRouter | `openrouter` | `OPENROUTER_API_KEY` |
| ZAI | `zai` | `ZAI_API_KEY` |
Auth file keys take priority over environment variables.
**OAuth Providers:**
Use `/login` to authenticate with subscription-based or free-tier providers:
| Provider | Models | Cost |
|----------|--------|------|
| Anthropic (Claude Pro/Max) | Claude models via your subscription | Subscription |
| GitHub Copilot | GPT-4o, Claude, Gemini via Copilot subscription | Subscription |
| Google Gemini CLI | Gemini 2.0/2.5 models | Free (Google account) |
| Google Antigravity | Gemini 3, Claude, GPT-OSS | Free (Google account) |
| OpenAI Codex (ChatGPT Plus/Pro) | Codex models via ChatGPT subscription | Subscription |
```bash
pi
/login # Select provider, authorize in browser
```
**Note:** `/login` replaces any existing API key for that provider with OAuth credentials in `auth.json`.
**GitHub Copilot notes:**
- Press Enter for github.com, or enter your GitHub Enterprise Server domain
- If you get "model not supported" error, enable it in VS Code: Copilot Chat → model selector → select model → "Enable"
**Google providers notes:**
- Gemini CLI uses the production Cloud Code Assist endpoint (standard Gemini models)
- Antigravity uses a sandbox endpoint with access to Gemini 3, Claude (sonnet/opus thinking), and GPT-OSS models
- Both are free with any Google account, subject to rate limits
- Paid Cloud Code Assist subscriptions: set `GOOGLE_CLOUD_PROJECT` or `GOOGLE_CLOUD_PROJECT_ID` env var to your project ID
**OpenAI Codex notes:**
- Requires ChatGPT Plus/Pro OAuth (`/login openai-codex`)
- Prompt cache stored under `~/.pi/agent/cache/openai-codex/`
- Intended for personal use with your own subscription; not for resale or multi-user services. For production, use the OpenAI Platform API.
Credentials stored in `~/.pi/agent/auth.json`. Use `/logout` to clear.
**Troubleshooting (OAuth):**
- **Port 1455 in use:** Close the conflicting process or paste the auth code/URL when prompted.
- **Token expired / refresh failed:** Run `/login` again for the provider to refresh credentials.
- **Usage limits (429):** Wait for the reset window; pi will surface a friendly message with the approximate retry time.
### Quick Start
```bash
export ANTHROPIC_API_KEY=sk-ant-...
pi
```
Then chat:
```
You: Create a simple Express server in src/server.ts
```
The agent reads, writes, and edits files, and executes commands via bash.
---
## Usage
### Slash Commands
| Command | Description |
|---------|-------------|
| `/settings` | Open settings menu (thinking, theme, message delivery modes, toggles) |
| `/model` | Switch models mid-session. Use `/model ` or `provider/model` to prefilter/disambiguate. |
| `/export [file]` | Export session to self-contained HTML |
| `/share` | Upload session as secret GitHub gist, get shareable URL (requires `gh` CLI) |
| `/session` | Show session info: path, message counts, token usage, cost |
| `/hotkeys` | Show all keyboard shortcuts |
| `/changelog` | Display full version history |
| `/tree` | Navigate session tree in-place (search, filter, label entries) |
| `/branch` | Create new conversation branch from a previous message |
| `/resume` | Switch to a different session (interactive selector) |
| `/login` | OAuth login for subscription-based models |
| `/logout` | Clear OAuth tokens |
| `/new` | Start a new session |
| `/copy` | Copy last agent message to clipboard |
| `/compact [instructions]` | Manually compact conversation context |
### Editor Features
**File reference (`@`):** Type `@` to fuzzy-search project files. Respects `.gitignore`.
**Path completion (Tab):** Complete relative paths, `../`, `~/`, etc.
**Drag & drop:** Drag files from your file manager into the terminal.
**Multi-line paste:** Pasted content is collapsed to `[paste #N lines]` but sent in full.
**Message queuing:** Submit messages while the agent is working:
- **Enter** queues a *steering* message, delivered after current tool execution (interrupts remaining tools)
- **Alt+Enter** queues a *follow-up* message, delivered only after the agent finishes all work
Both modes are configurable via `/settings`: "one-at-a-time" delivers messages one by one waiting for responses, "all" delivers all queued messages at once. Press Escape to abort and restore queued messages to editor.
### Keyboard Shortcuts
**Navigation:**
| Key | Action |
|-----|--------|
| Arrow keys | Move cursor / browse history (Up when empty) |
| Option+Left/Right | Move by word |
| Ctrl+A / Home / Cmd+Left | Start of line |
| Ctrl+E / End / Cmd+Right | End of line |
**Editing:**
| Key | Action |
|-----|--------|
| Enter | Send message |
| Shift+Enter | New line (Ctrl+Enter on Windows Terminal) |
| Ctrl+W / Option+Backspace | Delete word backwards |
| Ctrl+U | Delete to start of line |
| Ctrl+K | Delete to end of line |
**Other:**
| Key | Action |
|-----|--------|
| Tab | Path completion / accept autocomplete |
| Escape | Cancel autocomplete / abort streaming |
| Ctrl+C | Clear editor (first) / exit (second) |
| Ctrl+D | Exit (when editor is empty) |
| Ctrl+Z | Suspend to background (use `fg` in shell to resume) |
| Shift+Tab | Cycle thinking level |
| Ctrl+P / Shift+Ctrl+P | Cycle models forward/backward (scoped by `--models`) |
| Ctrl+L | Open model selector |
| Ctrl+O | Toggle tool output expansion |
| Ctrl+T | Toggle thinking block visibility |
| Ctrl+G | Edit message in external editor (`$VISUAL` or `$EDITOR`) |
| Ctrl+V | Paste image from clipboard |
### Custom Keybindings
All keyboard shortcuts can be customized via `~/.pi/agent/keybindings.json`. Each action can be bound to one or more keys.
**Key format:** `modifier+key` where modifiers are `ctrl`, `shift`, `alt` and keys are:
- Letters: `a-z`
- Numbers: `0-9`
- Special keys: `escape`, `tab`, `enter`, `space`, `backspace`, `delete`, `home`, `end`, `up`, `down`, `left`, `right`
- Symbol keys: `` ` ``, `-`, `=`, `[`, `]`, `\`, `;`, `'`, `,`, `.`, `/`, `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `(`, `)`, `_`, `+`, `|`, `~`, `{`, `}`, `:`, `<`, `>`, `?`
**Configurable actions:**
| Action | Default | Description |
|--------|---------|-------------|
| `cursorUp` | `up` | Move cursor up |
| `cursorDown` | `down` | Move cursor down |
| `cursorLeft` | `left` | Move cursor left |
| `cursorRight` | `right` | Move cursor right |
| `cursorWordLeft` | `alt+left`, `ctrl+left` | Move cursor word left |
| `cursorWordRight` | `alt+right`, `ctrl+right` | Move cursor word right |
| `cursorLineStart` | `home`, `ctrl+a` | Move to line start |
| `cursorLineEnd` | `end`, `ctrl+e` | Move to line end |
| `deleteCharBackward` | `backspace` | Delete char backward |
| `deleteCharForward` | `delete` | Delete char forward |
| `deleteWordBackward` | `ctrl+w`, `alt+backspace` | Delete word backward |
| `deleteToLineStart` | `ctrl+u` | Delete to line start |
| `deleteToLineEnd` | `ctrl+k` | Delete to line end |
| `newLine` | `shift+enter` | Insert new line |
| `submit` | `enter` | Submit input |
| `tab` | `tab` | Tab/autocomplete |
| `interrupt` | `escape` | Interrupt operation |
| `clear` | `ctrl+c` | Clear editor |
| `exit` | `ctrl+d` | Exit (when empty) |
| `suspend` | `ctrl+z` | Suspend process |
| `cycleThinkingLevel` | `shift+tab` | Cycle thinking level |
| `cycleModelForward` | `ctrl+p` | Next model |
| `cycleModelBackward` | `shift+ctrl+p` | Previous model |
| `selectModel` | `ctrl+l` | Open model selector |
| `expandTools` | `ctrl+o` | Expand tool output |
| `toggleThinking` | `ctrl+t` | Toggle thinking |
| `externalEditor` | `ctrl+g` | Open external editor |
| `followUp` | `alt+enter` | Queue follow-up message |
| `selectUp` | `up` | Move selection up in lists (session picker, model selector) |
| `selectDown` | `down` | Move selection down in lists |
| `selectConfirm` | `enter` | Confirm selection |
| `selectCancel` | `escape`, `ctrl+c` | Cancel selection |
**Example (Emacs-style):**
```json
{
"cursorUp": ["up", "ctrl+p"],
"cursorDown": ["down", "ctrl+n"],
"cursorLeft": ["left", "ctrl+b"],
"cursorRight": ["right", "ctrl+f"],
"cursorWordLeft": ["alt+left", "alt+b"],
"cursorWordRight": ["alt+right", "alt+f"],
"deleteCharForward": ["delete", "ctrl+d"],
"deleteCharBackward": ["backspace", "ctrl+h"],
"newLine": ["shift+enter", "ctrl+j"]
}
```
**Example (Vim-style):**
```json
{
"cursorUp": ["up", "alt+k"],
"cursorDown": ["down", "alt+j"],
"cursorLeft": ["left", "alt+h"],
"cursorRight": ["right", "alt+l"],
"cursorWordLeft": ["alt+left", "alt+b"],
"cursorWordRight": ["alt+right", "alt+w"],
"deleteCharBackward": ["backspace", "ctrl+h"],
"deleteWordBackward": ["ctrl+w", "alt+backspace"]
}
```
**Example (symbol keys):**
```json
{
"submit": ["enter", "ctrl+j"],
"newLine": ["shift+enter", "ctrl+;"],
"toggleThinking": "ctrl+/",
"cycleModelForward": "ctrl+.",
"cycleModelBackward": "ctrl+,",
"interrupt": ["escape", "ctrl+`"]
}
```
> **Note:** Some `ctrl+symbol` combinations overlap with ASCII control characters due to terminal legacy behavior (e.g., `ctrl+[` is the same as Escape, `ctrl+M` is the same as Enter). These can still be used with `ctrl+shift+key` (e.g., `ctrl+shift+]`). See [Kitty keyboard protocol: legacy ctrl mapping of ASCII keys](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#legacy-ctrl-mapping-of-ascii-keys) for all unsupported keys.
### Bash Mode
Prefix commands with `!` to execute them and add output to context:
```
!ls -la
!git status
!cat package.json | jq '.dependencies'
```
Output streams in real-time. Press Escape to cancel. Large outputs truncate at 2000 lines / 50KB.
The output becomes part of your next prompt, formatted as:
```
Ran `ls -la`