ec801665bc
- Migrated environment variable access from direct process.env to centralized getEnv() utility function across all packages. - Renamed environment variable prefix from OMP_ to PI_ throughout codebase (e.g., OMP_CODING_AGENT_DIR -> PI_CODING_AGENT_DIR). - Removed automatic environment variable migration from PI_ to OMP_ prefixes via migrate-env.ts module. - Removed env setting from configuration schema and applyEnvironmentVariables() method from settings. - Updated CI/CD build configuration to use PI_COMPILED flag instead of OMP_COMPILED. - Changed venvPath property in PythonRuntime from nullable (string | null) to optional (string | undefined).
697 lines
23 KiB
Markdown
697 lines
23 KiB
Markdown
> omp can create themes. Ask it to build one for your use case.
|
||
|
||
# OMP 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 (11 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)
|
||
|
||
Used for native syntax highlighting in tool output and editors:
|
||
|
||
| 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) |
|
||
|
||
These create a visual hierarchy: off → minimal → low → medium → high → xhigh
|
||
|
||
### Mode Borders (2 colors)
|
||
|
||
| Token | Purpose |
|
||
| ------------ | ------------------------------------------------ |
|
||
| `bashMode` | Editor border color when in bash mode (! prefix) |
|
||
| `pythonMode` | Editor border color when in python mode (>>>) |
|
||
|
||
### Status Line (14 colors)
|
||
|
||
| Token | Purpose |
|
||
| --------------------- | --------------------------------------- |
|
||
| `statusLineBg` | Status line background |
|
||
| `statusLineSep` | Separators between status line segments |
|
||
| `statusLineModel` | Model segment text |
|
||
| `statusLinePath` | Working directory segment |
|
||
| `statusLineGitClean` | Git segment (clean) |
|
||
| `statusLineGitDirty` | Git segment (dirty) |
|
||
| `statusLineContext` | Context window usage segment |
|
||
| `statusLineSpend` | Token input/total segment |
|
||
| `statusLineStaged` | Git staged count |
|
||
| `statusLineDirty` | Git unstaged count |
|
||
| `statusLineUntracked` | Git untracked count |
|
||
| `statusLineOutput` | Token output/cache output segment |
|
||
| `statusLineCost` | Cost segment |
|
||
| `statusLineSubagents` | Subagent count segment |
|
||
|
||
**Total: 66 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/can1357/oh-my-pi/main/packages/coding-agent/src/modes/theme/theme-schema.json",
|
||
"name": "my-theme",
|
||
"vars": {
|
||
"blue": "#0066cc",
|
||
"gray": 242,
|
||
"brightCyan": 51
|
||
},
|
||
"colors": {
|
||
"accent": "blue",
|
||
"muted": "gray",
|
||
"thinkingText": "gray",
|
||
"text": "",
|
||
...
|
||
}
|
||
}
|
||
```
|
||
|
||
## Symbols
|
||
|
||
Themes can also customize specific UI symbols (icons, separators, bullets, etc.). Use `symbols.preset` (`unicode`, `nerd`, `ascii`) to set a theme default (overridden by the `symbolPreset` setting), and `symbols.overrides` to override individual keys.
|
||
|
||
Example:
|
||
|
||
```json
|
||
{
|
||
"symbols": {
|
||
"preset": "ascii",
|
||
"overrides": {
|
||
"icon.model": "[M]",
|
||
"sep.powerlineLeft": ">",
|
||
"sep.powerlineRight": "<"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Symbol keys by category:
|
||
|
||
- Status: `status.success`, `status.error`, `status.warning`, `status.info`, `status.pending`, `status.disabled`, `status.enabled`, `status.running`, `status.shadowed`, `status.aborted`
|
||
- Navigation: `nav.cursor`, `nav.selected`, `nav.expand`, `nav.collapse`, `nav.back`
|
||
- Tree: `tree.branch`, `tree.last`, `tree.vertical`, `tree.horizontal`, `tree.hook`
|
||
- Boxes (rounded): `boxRound.topLeft`, `boxRound.topRight`, `boxRound.bottomLeft`, `boxRound.bottomRight`, `boxRound.horizontal`, `boxRound.vertical`
|
||
- Boxes (sharp): `boxSharp.topLeft`, `boxSharp.topRight`, `boxSharp.bottomLeft`, `boxSharp.bottomRight`, `boxSharp.horizontal`, `boxSharp.vertical`, `boxSharp.cross`, `boxSharp.teeDown`, `boxSharp.teeUp`, `boxSharp.teeRight`, `boxSharp.teeLeft`
|
||
- Separators: `sep.powerline`, `sep.powerlineThin`, `sep.powerlineLeft`, `sep.powerlineRight`, `sep.powerlineThinLeft`, `sep.powerlineThinRight`, `sep.block`, `sep.space`, `sep.asciiLeft`, `sep.asciiRight`, `sep.dot`, `sep.slash`, `sep.pipe`
|
||
- Icons: `icon.model`, `icon.plan`, `icon.folder`, `icon.file`, `icon.git`, `icon.branch`, `icon.tokens`, `icon.context`, `icon.cost`, `icon.time`, `icon.pi`, `icon.agents`, `icon.cache`, `icon.input`, `icon.output`, `icon.host`, `icon.session`, `icon.package`, `icon.warning`, `icon.rewind`, `icon.auto`, `icon.extensionSkill`, `icon.extensionTool`, `icon.extensionSlashCommand`, `icon.extensionMcp`, `icon.extensionRule`, `icon.extensionHook`, `icon.extensionPrompt`, `icon.extensionContextFile`, `icon.extensionInstruction`
|
||
- Thinking: `thinking.minimal`, `thinking.low`, `thinking.medium`, `thinking.high`, `thinking.xhigh`
|
||
- Checkboxes: `checkbox.checked`, `checkbox.unchecked`
|
||
- Formatting: `format.bullet`, `format.dash`, `format.bracketLeft`, `format.bracketRight`
|
||
- Markdown: `md.quoteBorder`, `md.hrChar`, `md.bullet`
|
||
- Language icons: `lang.default`, `lang.typescript`, `lang.javascript`, `lang.python`, `lang.rust`, `lang.go`, `lang.java`, `lang.c`, `lang.cpp`, `lang.csharp`, `lang.ruby`, `lang.php`, `lang.swift`, `lang.kotlin`, `lang.shell`, `lang.html`, `lang.css`, `lang.json`, `lang.yaml`, `lang.markdown`, `lang.sql`, `lang.docker`, `lang.lua`, `lang.text`, `lang.env`, `lang.toml`, `lang.xml`, `lang.ini`, `lang.conf`, `lang.log`, `lang.csv`, `lang.tsv`, `lang.image`, `lang.pdf`, `lang.archive`, `lang.binary`
|
||
- Settings tabs: `tab.display`, `tab.agent`, `tab.input`, `tab.tools`, `tab.config`, `tab.services`, `tab.bash`, `tab.lsp`, `tab.ttsr`, `tab.status`
|
||
|
||
### 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
|
||
|
||
OMP ships with `dark` (default), `light`, and 90+ curated themes under `src/modes/theme/defaults/`. Examples include:
|
||
|
||
- **Dark themes**: `dark-aurora`, `dark-gruvbox`, `dark-nord`, `dark-tokyo-night`, `dark-catppuccin`, `dark-dracula`, `dark-solarized`, `dark-github`, `dark-monokai`, `dark-synthwave`
|
||
- **Light themes**: `light-solarized`, `light-gruvbox`, `light-github`, `light-catppuccin`, `light-paper`, `light-dawn`, `light-frost`
|
||
- **Neutral/material**: `graphite`, `obsidian`, `onyx`, `titanium`, `marble`, `pearl`, `alabaster`, `anthracite`
|
||
|
||
## Selecting a Theme
|
||
|
||
Themes are configured in the Settings UI (Display → Theme) or via the config CLI:
|
||
|
||
```bash
|
||
omp config set theme dark
|
||
```
|
||
|
||
On first run, OMP uses the terminal background reported by `COLORFGBG` and falls back to `dark` if unavailable.
|
||
|
||
## Custom Themes
|
||
|
||
### Theme Locations
|
||
|
||
Custom themes are loaded from `~/.omp/agent/themes/*.json` by default, or from `$PI_CODING_AGENT_DIR/themes` if that environment variable is set.
|
||
|
||
### Creating a Custom Theme
|
||
|
||
1. **Create theme directory:**
|
||
|
||
```bash
|
||
mkdir -p "${PI_CODING_AGENT_DIR:-~/.omp/agent}/themes"
|
||
```
|
||
|
||
2. **Create theme file:**
|
||
|
||
```bash
|
||
vim "${PI_CODING_AGENT_DIR:-~/.omp/agent}/themes/my-theme.json"
|
||
```
|
||
|
||
3. **Define all colors (see the schema for the full list; snippet below shows structure):**
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/modes/theme/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",
|
||
"toolTitle": "",
|
||
"toolOutput": "",
|
||
// ...
|
||
|
||
"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",
|
||
"thinkingXhigh": "#ff88ff"
|
||
// ... plus bashMode, pythonMode, statusLine* colors
|
||
}
|
||
}
|
||
```
|
||
|
||
4. **Select your theme:**
|
||
- Use the Settings UI (Display → Theme)
|
||
- Or run `omp config set theme my-theme`
|
||
|
||
## 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 `vars`
|
||
- 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
|
||
|
||
OMP prefers 24-bit RGB colors (`\x1b[38;2;R;G;Bm`) and assumes truecolor on modern terminals.
|
||
|
||
Color mode detection:
|
||
|
||
- `COLORTERM=truecolor|24bit` or `WT_SESSION` → truecolor
|
||
- `TERM=dumb`, `TERM=linux`, or empty `TERM` → 256-color fallback
|
||
- Otherwise → truecolor
|
||
|
||
If you need to confirm terminal hints:
|
||
|
||
```bash
|
||
echo $COLORTERM
|
||
```
|
||
|
||
## Example Themes
|
||
|
||
See the built-in themes for complete examples:
|
||
|
||
- [Dark theme](../src/modes/theme/dark.json)
|
||
- [Light theme](../src/modes/theme/light.json)
|
||
- [Defaults library](../src/modes/theme/defaults)
|
||
|
||
## Schema Validation
|
||
|
||
Themes are validated on load using [TypeBox](https://github.com/sinclairzx81/typebox) and the TypeBox compiler.
|
||
|
||
Invalid themes will show an error with details about what's wrong:
|
||
|
||
```
|
||
Invalid theme "my-theme":
|
||
|
||
Missing required color tokens:
|
||
- mdHeading
|
||
- mdLink
|
||
|
||
Other errors:
|
||
- /colors/accent: Expected union value
|
||
```
|
||
|
||
For editor support, the JSON schema is available at:
|
||
|
||
```
|
||
https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/modes/theme/theme-schema.json
|
||
```
|
||
|
||
Add to your theme file for auto-completion and validation:
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/modes/theme/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;
|
||
strikethrough(text: string): string;
|
||
inverse(text: string): string;
|
||
|
||
// Raw ANSI codes (for composing with other formatters)
|
||
getFgAnsi(color: ThemeColor): string;
|
||
getBgAnsi(color: ThemeBg): string;
|
||
|
||
// Symbol access
|
||
symbol(key: SymbolKey): string;
|
||
styledSymbol(key: SymbolKey, color: ThemeColor): string;
|
||
getSymbolPreset(): SymbolPreset;
|
||
|
||
// Category accessors (return grouped symbol objects)
|
||
get status(): { success, error, warning, ... };
|
||
get nav(): { cursor, selected, expand, collapse, back };
|
||
get icon(): { model, folder, file, git, ... };
|
||
get boxRound(): { topLeft, topRight, ... };
|
||
get boxSharp(): { topLeft, topRight, ... };
|
||
get sep(): { powerline, dot, slash, pipe, ... };
|
||
get thinking(): { minimal, low, medium, high, xhigh };
|
||
get spinnerFrames(): string[];
|
||
|
||
// Language icon lookup
|
||
getLangIcon(lang: string | undefined): string;
|
||
}
|
||
```
|
||
|
||
### Global Theme Instance
|
||
|
||
The active theme is available as a global singleton in `coding-agent`:
|
||
|
||
```typescript
|
||
// theme.ts
|
||
export let theme: Theme;
|
||
|
||
export async function initTheme(
|
||
themeName?: string,
|
||
enableWatcher?: boolean,
|
||
symbolPreset?: SymbolPreset,
|
||
colorBlindMode?: boolean
|
||
): Promise<void>;
|
||
export async function setTheme(name: string, enableWatcher?: boolean): Promise<{ success: boolean; error?: string }>;
|
||
|
||
// 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 `@oh-my-pi/pi-tui` package and don't have direct access to the theme. Instead, they define interfaces for the colors they need:
|
||
|
||
```typescript
|
||
// In @oh-my-pi/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;
|
||
highlightCode?: (code: string, lang?: string) => string[];
|
||
getMermaidImage?: (sourceHash: string) => MermaidImage | null;
|
||
symbols: SymbolTheme;
|
||
}
|
||
```
|
||
|
||
The `coding-agent` bridges the theme to TUI components via exported helpers:
|
||
|
||
```typescript
|
||
// Exported helper in theme.ts
|
||
export function getMarkdownTheme(): MarkdownTheme {
|
||
return {
|
||
heading: (text) => theme.fg("mdHeading", text),
|
||
link: (text) => theme.fg("mdLink", text),
|
||
// ... all color mappings ...
|
||
bold: (text) => theme.bold(text),
|
||
italic: (text) => theme.italic(text),
|
||
underline: (text) => theme.underline(text),
|
||
strikethrough: (text) => chalk.strikethrough(text),
|
||
symbols: getSymbolTheme(),
|
||
getMermaidImage,
|
||
highlightCode: (code, lang) => {
|
||
/* uses native syntax highlighter */
|
||
},
|
||
};
|
||
}
|
||
```
|
||
|
||
This approach:
|
||
|
||
- Keeps TUI components theme-agnostic (reusable in other projects)
|
||
- Maintains type safety via interfaces
|
||
- Centralizes theme access in `coding-agent`
|
||
|
||
Similar helpers exist for other TUI components: `getSelectListTheme()`, `getEditorTheme()`, `getSettingsListTheme()`, `getSymbolTheme()`.
|
||
|
||
**Example usage:**
|
||
|
||
```typescript
|
||
await initTheme("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:**
|
||
- `COLORTERM=truecolor|24bit` or `WT_SESSION` → truecolor
|
||
- `TERM=dumb`, `TERM=linux`, or empty `TERM` → 256-color
|
||
- Otherwise → truecolor
|
||
|
||
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:**
|
||
- Empty string (`""`) → terminal default (foreground/background reset)
|
||
- 256-color (`42`) → `\x1b[38;5;42m` / `\x1b[48;5;42m`
|
||
- Hex or resolved vars → `Bun.color(value, "ansi-16m" | "ansi-256")`
|
||
|
||
5. **Cache as `Theme` instance**
|
||
|
||
This ensures themes work correctly regardless of terminal capabilities, with graceful degradation from truecolor to 256-color.
|