> 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; 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.