> pi can create themes. Ask it to build one for your use case.

# Pi Coding Agent Themes

Themes allow you to customize the colors used throughout the coding agent TUI.

## Color Tokens

Every theme must define all color tokens. There are no optional colors.

### Core UI (10 colors)

| Token | Purpose | Examples |
|-------|---------|----------|
| `accent` | Primary accent color | Logo, selected items, cursor (›) |
| `border` | Normal borders | Selector borders, horizontal lines |
| `borderAccent` | Highlighted borders | Changelog borders, special panels |
| `borderMuted` | Subtle borders | Editor borders, secondary separators |
| `success` | Success states | Success messages, diff additions |
| `error` | Error states | Error messages, diff deletions |
| `warning` | Warning states | Warning messages |
| `muted` | Secondary/dimmed text | Metadata, descriptions, output |
| `dim` | Very dimmed text | Less important info, placeholders |
| `text` | Default text color | Main content (usually `""`) |
| `thinkingText` | Thinking block text | Assistant reasoning traces |

### Backgrounds & Content Text (11 colors)

| Token | Purpose |
|-------|---------|
| `selectedBg` | Selected/active line background (e.g., tree selector) |
| `userMessageBg` | User message background |
| `userMessageText` | User message text color |
| `customMessageBg` | Hook custom message background |
| `customMessageText` | Hook custom message text color |
| `customMessageLabel` | Hook custom message label/type text |
| `toolPendingBg` | Tool execution box (pending state) |
| `toolSuccessBg` | Tool execution box (success state) |
| `toolErrorBg` | Tool execution box (error state) |
| `toolTitle` | Tool execution title/heading (e.g., `$ command`, `read file.txt`) |
| `toolOutput` | Tool execution output text |

### Markdown (10 colors)

| Token | Purpose |
|-------|---------|
| `mdHeading` | Heading text (`#`, `##`, etc) |
| `mdLink` | Link text |
| `mdLinkUrl` | Link URL (in parentheses) |
| `mdCode` | Inline code (backticks) |
| `mdCodeBlock` | Code block content |
| `mdCodeBlockBorder` | Code block fences (```) |
| `mdQuote` | Blockquote text |
| `mdQuoteBorder` | Blockquote border (`│`) |
| `mdHr` | Horizontal rule (`---`) |
| `mdListBullet` | List bullets/numbers |

### Tool Diffs (3 colors)

| Token | Purpose |
|-------|---------|
| `toolDiffAdded` | Added lines in tool diffs |
| `toolDiffRemoved` | Removed lines in tool diffs |
| `toolDiffContext` | Context lines in tool diffs |

Note: Diff colors are specific to tool execution boxes and must work with tool background colors.

### Syntax Highlighting (9 colors)

Future-proofing for syntax highlighting support:

| Token | Purpose |
|-------|---------|
| `syntaxComment` | Comments |
| `syntaxKeyword` | Keywords (`if`, `function`, etc) |
| `syntaxFunction` | Function names |
| `syntaxVariable` | Variable names |
| `syntaxString` | String literals |
| `syntaxNumber` | Number literals |
| `syntaxType` | Type names |
| `syntaxOperator` | Operators (`+`, `-`, etc) |
| `syntaxPunctuation` | Punctuation (`;`, `,`, etc) |

### Thinking Level Borders (6 colors)

Editor border colors that indicate the current thinking/reasoning level:

| Token | Purpose |
|-------|---------|
| `thinkingOff` | Border when thinking is off (most subtle) |
| `thinkingMinimal` | Border for minimal thinking |
| `thinkingLow` | Border for low thinking |
| `thinkingMedium` | Border for medium thinking |
| `thinkingHigh` | Border for high thinking |
| `thinkingXhigh` | Border for xhigh thinking (most prominent, OpenAI codex-max only) |

These create a visual hierarchy: off → minimal → low → medium → high → xhigh

### Bash Mode (1 color)

| Token | Purpose |
|-------|---------|
| `bashMode` | Editor border color when in bash mode (! prefix) |

**Total: 50 color tokens** (all required)

### HTML Export Colors (optional)

The `export` section is optional and controls colors used when exporting sessions to HTML via `/export`. If not specified, these colors are automatically derived from `userMessageBg` based on luminance detection.

| Token | Purpose |
|-------|---------|
| `pageBg` | Page background color |
| `cardBg` | Card/container background (headers, stats boxes) |
| `infoBg` | Info sections background (system prompt, notices, compaction) |

Example:
```json
{
  "export": {
    "pageBg": "#18181e",
    "cardBg": "#1e1e24",
    "infoBg": "#3c3728"
  }
}
```

## Theme Format

Themes are defined in JSON files with the following structure:

```json
{
  "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/theme-schema.json",
  "name": "my-theme",
  "vars": {
    "blue": "#0066cc",
    "gray": 242,
    "brightCyan": 51
  },
  "colors": {
    "accent": "blue",
    "muted": "gray",
    "thinkingText": "gray",
    "text": "",
    ...
  }
}
```

### Color Values

Four formats are supported:

1. **Hex colors**: `"#ff0000"` (6-digit hex RGB)
2. **256-color palette**: `39` (number 0-255, xterm 256-color palette)
3. **Color references**: `"blue"` (must be defined in `vars`)
4. **Terminal default**: `""` (empty string, uses terminal's default color)

### The `vars` Section

The optional `vars` section allows you to define reusable colors:

```json
{
  "vars": {
    "nord0": "#2E3440",
    "nord1": "#3B4252",
    "nord8": "#88C0D0",
    "brightBlue": 39
  },
  "colors": {
    "accent": "nord8",
    "muted": "nord1",
    "mdLink": "brightBlue"
  }
}
```

Benefits:
- Reuse colors across multiple tokens
- Easier to maintain theme consistency
- Can reference standard color palettes

Variables can be hex colors (`"#ff0000"`), 256-color indices (`42`), or references to other variables.

### Terminal Default (empty string)

Use `""` (empty string) to inherit the terminal's default foreground/background color:

```json
{
  "colors": {
    "text": ""  // Uses terminal's default text color
  }
}
```

This is useful for:
- Main text color (adapts to user's terminal theme)
- Creating themes that blend with terminal appearance

## Built-in Themes

Pi comes with two built-in themes:

### `dark` (default)

Optimized for dark terminal backgrounds with bright, saturated colors.

### `light`

Optimized for light terminal backgrounds with darker, muted colors.

## Selecting a Theme

Themes are configured in the settings (accessible via `/settings`):

```json
{
  "theme": "dark"
}
```

Or use the `/theme` command interactively.

On first run, Pi detects your terminal's background and sets a sensible default (`dark` or `light`).

## Custom Themes

### Theme Locations

Custom themes are loaded from `~/.pi/agent/themes/*.json`.

### Creating a Custom Theme

1. **Create theme directory:**
   ```bash
   mkdir -p ~/.pi/agent/themes
   ```

2. **Create theme file:**
   ```bash
   vim ~/.pi/agent/themes/my-theme.json
   ```

3. **Define all colors:**
   ```json
   {
     "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/theme-schema.json",
     "name": "my-theme",
     "vars": {
       "primary": "#00aaff",
       "secondary": 242,
       "brightGreen": 46
     },
     "colors": {
       "accent": "primary",
       "border": "primary",
       "borderAccent": "#00ffff",
       "borderMuted": "secondary",
       "success": "brightGreen",
       "error": "#ff0000",
       "warning": "#ffff00",
       "muted": "secondary",
       "text": "",
       
       "userMessageBg": "#2d2d30",
       "userMessageText": "",
       "toolPendingBg": "#1e1e2e",
       "toolSuccessBg": "#1e2e1e",
       "toolErrorBg": "#2e1e1e",
       "toolText": "",
       
       "mdHeading": "#ffaa00",
       "mdLink": "primary",
       "mdCode": "#00ffff",
       "mdCodeBlock": "#00ff00",
       "mdCodeBlockBorder": "secondary",
       "mdQuote": "secondary",
       "mdQuoteBorder": "secondary",
       "mdHr": "secondary",
       "mdListBullet": "#00ffff",
       
       "toolDiffAdded": "#00ff00",
       "toolDiffRemoved": "#ff0000",
       "toolDiffContext": "secondary",
       
       "syntaxComment": "secondary",
       "syntaxKeyword": "primary",
       "syntaxFunction": "#00aaff",
       "syntaxVariable": "#ffaa00",
       "syntaxString": "#00ff00",
       "syntaxNumber": "#ff00ff",
       "syntaxType": "#00aaff",
       "syntaxOperator": "primary",
       "syntaxPunctuation": "secondary",
       
       "thinkingOff": "secondary",
       "thinkingMinimal": "primary",
       "thinkingLow": "#00aaff",
       "thinkingMedium": "#00ffff",
       "thinkingHigh": "#ff00ff"
     }
   }
   ```

4. **Select your theme:**
   - Use `/settings` command and set `"theme": "my-theme"`
   - Or use `/theme` command interactively

## Tips

### Light vs Dark Themes

**For dark terminals:**
- Use bright, saturated colors
- Higher contrast
- Example: `#00ffff` (bright cyan)

**For light terminals:**
- Use darker, muted colors
- Lower contrast to avoid eye strain
- Example: `#008888` (dark cyan)

### Color Harmony

- Start with a base palette (e.g., Nord, Gruvbox, Tokyo Night)
- Define your palette in `defs`
- Reference colors consistently

### Testing

Test your theme with:
- Different message types (user, assistant, errors)
- Tool executions (success and error states)
- Markdown content (headings, code, lists, etc)
- Long text that wraps

## Color Format Reference

### Hex Colors

Standard 6-digit hex format:
- `"#ff0000"` - Red
- `"#00ff00"` - Green
- `"#0000ff"` - Blue
- `"#808080"` - Gray
- `"#ffffff"` - White
- `"#000000"` - Black

RGB values: `#RRGGBB` where each component is `00-ff` (0-255)

### 256-Color Palette

Use numeric indices (0-255) to reference the xterm 256-color palette:

**Colors 0-15:** Basic ANSI colors (terminal-dependent, may be themed)
- `0` - Black
- `1` - Red
- `2` - Green
- `3` - Yellow
- `4` - Blue
- `5` - Magenta
- `6` - Cyan
- `7` - White
- `8-15` - Bright variants

**Colors 16-231:** 6×6×6 RGB cube (standardized)
- Formula: `16 + 36×R + 6×G + B` where R, G, B are 0-5
- Example: `39` = bright cyan, `196` = bright red

**Colors 232-255:** Grayscale ramp (standardized)
- `232` - Darkest gray
- `255` - Near white

Example usage:
```json
{
  "vars": {
    "gray": 242,
    "brightCyan": 51,
    "darkBlue": 18
  },
  "colors": {
    "muted": "gray",
    "accent": "brightCyan"
  }
}
```

**Benefits:**
- Works everywhere (`TERM=xterm-256color`)
- No truecolor detection needed
- Standardized RGB cube (16-231) looks the same on all terminals

### Terminal Compatibility

Pi uses 24-bit RGB colors (`\x1b[38;2;R;G;Bm`). Most modern terminals support this:

- ✅ iTerm2, Alacritty, Kitty, WezTerm
- ✅ Windows Terminal
- ✅ VS Code integrated terminal
- ✅ Modern GNOME Terminal, Konsole

For older terminals with only 256-color support, Pi automatically falls back to the nearest 256-color approximation.

To check if your terminal supports truecolor:
```bash
echo $COLORTERM  # Should output "truecolor" or "24bit"
```

## Example Themes

See the built-in themes for complete examples:
- [Dark theme](../src/themes/dark.json)
- [Light theme](../src/themes/light.json)

## Schema Validation

Themes are validated on load using [TypeBox](https://github.com/sinclairzx81/typebox) + [Ajv](https://ajv.js.org/).

Invalid themes will show an error with details about what's wrong:
```
Error loading theme 'my-theme':
  - colors.accent: must be string or number
  - colors.mdHeading: required property missing
```

For editor support, the JSON schema is available at:
```
https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/theme-schema.json
```

Add to your theme file for auto-completion and validation:
```json
{
  "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/theme-schema.json",
  ...
}
```

## Implementation

### Theme Class

Themes are loaded and converted to a `Theme` class that provides type-safe color methods:

```typescript
class Theme {
  // Apply foreground color
  fg(color: ThemeColor, text: string): string
  
  // Apply background color
  bg(color: ThemeBg, text: string): string
  
  // Text attributes (preserve current colors)
  bold(text: string): string
  italic(text: string): string
  underline(text: string): string
}
```

### Global Theme Instance

The active theme is available as a global singleton in `coding-agent`:

```typescript
// theme.ts
export let theme: Theme;

export function setTheme(name: string) {
  theme = loadTheme(name);
}

// Usage throughout coding-agent
import { theme } from './theme.js';

theme.fg('accent', 'Selected')
theme.bg('userMessageBg', content)
```

### TUI Component Theming

TUI components (like `Markdown`, `SelectList`, `Editor`) are in the `@mariozechner/pi-tui` package and don't have direct access to the theme. Instead, they define interfaces for the colors they need:

```typescript
// In @mariozechner/pi-tui
export interface MarkdownTheme {
  heading: (text: string) => string;
  link: (text: string) => string;
  linkUrl: (text: string) => string;
  code: (text: string) => string;
  codeBlock: (text: string) => string;
  codeBlockBorder: (text: string) => string;
  quote: (text: string) => string;
  quoteBorder: (text: string) => string;
  hr: (text: string) => string;
  listBullet: (text: string) => string;
  bold: (text: string) => string;
  italic: (text: string) => string;
  strikethrough: (text: string) => string;
  underline: (text: string) => string;
}
```

The `coding-agent` provides themed functions when creating components:

```typescript
// In coding-agent
import { theme } from './theme.js';
import { Markdown } from '@mariozechner/pi-tui';

// Helper to create markdown theme functions
function getMarkdownTheme(): MarkdownTheme {
  return {
    heading: (text) => theme.fg('mdHeading', text),
    link: (text) => theme.fg('mdLink', text),
    linkUrl: (text) => theme.fg('mdLinkUrl', text),
    code: (text) => theme.fg('mdCode', text),
    codeBlock: (text) => theme.fg('mdCodeBlock', text),
    codeBlockBorder: (text) => theme.fg('mdCodeBlockBorder', text),
    quote: (text) => theme.fg('mdQuote', text),
    quoteBorder: (text) => theme.fg('mdQuoteBorder', text),
    hr: (text) => theme.fg('mdHr', text),
    listBullet: (text) => theme.fg('mdListBullet', text),
    bold: (text) => theme.bold(text),
    italic: (text) => theme.italic(text),
    underline: (text) => theme.underline(text),
    strikethrough: (text) => chalk.strikethrough(text),
  };
}

// Create markdown with theme
const md = new Markdown(
  text,
  1, 1,
  { bgColor: theme.bg('userMessageBg') },
  getMarkdownTheme()
);
```

This approach:
- Keeps TUI components theme-agnostic (reusable in other projects)
- Maintains type safety via interfaces
- Allows components to have sensible defaults if no theme provided
- Centralizes theme access in `coding-agent`

**Example usage:**
```typescript
const theme = loadTheme('dark');

// Apply foreground colors
theme.fg('accent', 'Selected')
theme.fg('success', '✓ Done')
theme.fg('error', 'Failed')

// Apply background colors
theme.bg('userMessageBg', content)
theme.bg('toolSuccessBg', output)

// Combine styles
theme.bold(theme.fg('accent', 'Title'))
theme.italic(theme.fg('muted', 'metadata'))

// Nested foreground + background
const userMsg = theme.bg('userMessageBg',
  theme.fg('userMessageText', 'Hello')
)
```

**Color resolution:**

1. **Detect terminal capabilities:**
   - Check `$COLORTERM` env var (`truecolor` or `24bit` → truecolor support)
   - Check `$TERM` env var (`*-256color` → 256-color support)
   - Fallback to 256-color mode if detection fails

2. **Load JSON theme file**

3. **Resolve `vars` references recursively:**
   ```json
   {
     "vars": {
       "primary": "#0066cc",
       "accent": "primary"
     },
     "colors": {
       "accent": "accent"  // → "primary" → "#0066cc"
     }
   }
   ```

4. **Convert colors to ANSI codes based on terminal capability:**
   
   **Truecolor mode (24-bit):**
   - Hex (`"#ff0000"`) → `\x1b[38;2;255;0;0m`
   - 256-color (`42`) → `\x1b[38;5;42m` (keep as-is)
   - Empty string (`""`) → `\x1b[39m`
   
   **256-color mode:**
   - Hex (`"#ff0000"`) → convert to nearest RGB cube color → `\x1b[38;5;196m`
   - 256-color (`42`) → `\x1b[38;5;42m` (keep as-is)
   - Empty string (`""`) → `\x1b[39m`
   
   **Hex to 256-color conversion:**
   ```typescript
   // Convert RGB to 6x6x6 cube (colors 16-231)
   r_index = Math.round(r / 255 * 5)
   g_index = Math.round(g / 255 * 5)
   b_index = Math.round(b / 255 * 5)
   color_index = 16 + 36 * r_index + 6 * g_index + b_index
   ```

5. **Cache as `Theme` instance**

This ensures themes work correctly regardless of terminal capabilities, with graceful degradation from truecolor to 256-color.
