@mariozechner/pi-coding-agent 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +58 -1
  3. package/dist/cli/args.d.ts +1 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +5 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/config.d.ts +2 -0
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +4 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +30 -2
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +181 -21
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/compaction.d.ts +30 -5
  16. package/dist/core/compaction.d.ts.map +1 -1
  17. package/dist/core/compaction.js +194 -61
  18. package/dist/core/compaction.js.map +1 -1
  19. package/dist/core/hooks/index.d.ts +5 -0
  20. package/dist/core/hooks/index.d.ts.map +1 -0
  21. package/dist/core/hooks/index.js +4 -0
  22. package/dist/core/hooks/index.js.map +1 -0
  23. package/dist/core/hooks/loader.d.ts +56 -0
  24. package/dist/core/hooks/loader.d.ts.map +1 -0
  25. package/dist/core/hooks/loader.js +158 -0
  26. package/dist/core/hooks/loader.js.map +1 -0
  27. package/dist/core/hooks/runner.d.ts +69 -0
  28. package/dist/core/hooks/runner.d.ts.map +1 -0
  29. package/dist/core/hooks/runner.js +203 -0
  30. package/dist/core/hooks/runner.js.map +1 -0
  31. package/dist/core/hooks/tool-wrapper.d.ts +16 -0
  32. package/dist/core/hooks/tool-wrapper.d.ts.map +1 -0
  33. package/dist/core/hooks/tool-wrapper.js +71 -0
  34. package/dist/core/hooks/tool-wrapper.js.map +1 -0
  35. package/dist/core/hooks/types.d.ts +220 -0
  36. package/dist/core/hooks/types.d.ts.map +1 -0
  37. package/dist/core/hooks/types.js +8 -0
  38. package/dist/core/hooks/types.js.map +1 -0
  39. package/dist/core/index.d.ts +1 -0
  40. package/dist/core/index.d.ts.map +1 -1
  41. package/dist/core/index.js +1 -0
  42. package/dist/core/index.js.map +1 -1
  43. package/dist/core/session-manager.d.ts +10 -3
  44. package/dist/core/session-manager.d.ts.map +1 -1
  45. package/dist/core/session-manager.js +78 -28
  46. package/dist/core/session-manager.js.map +1 -1
  47. package/dist/core/settings-manager.d.ts +6 -0
  48. package/dist/core/settings-manager.d.ts.map +1 -1
  49. package/dist/core/settings-manager.js +14 -0
  50. package/dist/core/settings-manager.js.map +1 -1
  51. package/dist/core/system-prompt.d.ts.map +1 -1
  52. package/dist/core/system-prompt.js +5 -3
  53. package/dist/core/system-prompt.js.map +1 -1
  54. package/dist/core/tools/truncate.d.ts +6 -2
  55. package/dist/core/tools/truncate.d.ts.map +1 -1
  56. package/dist/core/tools/truncate.js +11 -1
  57. package/dist/core/tools/truncate.js.map +1 -1
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js.map +1 -1
  61. package/dist/main.d.ts.map +1 -1
  62. package/dist/main.js +23 -12
  63. package/dist/main.js.map +1 -1
  64. package/dist/modes/interactive/components/bash-execution.d.ts +1 -0
  65. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  66. package/dist/modes/interactive/components/bash-execution.js +17 -6
  67. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  68. package/dist/modes/interactive/components/hook-input.d.ts +12 -0
  69. package/dist/modes/interactive/components/hook-input.d.ts.map +1 -0
  70. package/dist/modes/interactive/components/hook-input.js +46 -0
  71. package/dist/modes/interactive/components/hook-input.js.map +1 -0
  72. package/dist/modes/interactive/components/hook-selector.d.ts +16 -0
  73. package/dist/modes/interactive/components/hook-selector.d.ts.map +1 -0
  74. package/dist/modes/interactive/components/hook-selector.js +76 -0
  75. package/dist/modes/interactive/components/hook-selector.js.map +1 -0
  76. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  77. package/dist/modes/interactive/components/tool-execution.js +12 -7
  78. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  79. package/dist/modes/interactive/interactive-mode.d.ts +37 -0
  80. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  81. package/dist/modes/interactive/interactive-mode.js +190 -7
  82. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  83. package/dist/modes/print-mode.d.ts.map +1 -1
  84. package/dist/modes/print-mode.js +15 -0
  85. package/dist/modes/print-mode.js.map +1 -1
  86. package/dist/modes/rpc/rpc-mode.d.ts +2 -1
  87. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  88. package/dist/modes/rpc/rpc-mode.js +118 -3
  89. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  90. package/dist/modes/rpc/rpc-types.d.ts +41 -0
  91. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  92. package/dist/modes/rpc/rpc-types.js.map +1 -1
  93. package/docs/compaction.md +519 -0
  94. package/docs/hooks.md +609 -0
  95. package/docs/rpc.md +870 -0
  96. package/docs/session.md +89 -0
  97. package/docs/theme.md +586 -0
  98. package/docs/truncation.md +235 -0
  99. package/docs/undercompaction.md +313 -0
  100. package/package.json +18 -6
@@ -0,0 +1,89 @@
1
+ # Session File Format
2
+
3
+ Sessions are stored as JSONL (JSON Lines) files. Each line is a JSON object with a `type` field.
4
+
5
+ ## File Location
6
+
7
+ ```
8
+ ~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl
9
+ ```
10
+
11
+ Where `<path>` is the working directory with `/` replaced by `-`.
12
+
13
+ ## Type Definitions
14
+
15
+ - [`src/session-manager.ts`](../src/session-manager.ts) - Session entry types (`SessionHeader`, `SessionMessageEntry`, etc.)
16
+ - [`packages/agent/src/types.ts`](../../agent/src/types.ts) - `AppMessage`, `Attachment`, `ThinkingLevel`
17
+ - [`packages/ai/src/types.ts`](../../ai/src/types.ts) - `UserMessage`, `AssistantMessage`, `ToolResultMessage`, `Usage`, `ToolCall`
18
+
19
+ ## Entry Types
20
+
21
+ ### SessionHeader
22
+
23
+ First line of the file. Defines session metadata.
24
+
25
+ ```json
26
+ {"type":"session","id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","provider":"anthropic","modelId":"claude-sonnet-4-5","thinkingLevel":"off"}
27
+ ```
28
+
29
+ For branched sessions, includes the source session path:
30
+
31
+ ```json
32
+ {"type":"session","id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","provider":"anthropic","modelId":"claude-sonnet-4-5","thinkingLevel":"off","branchedFrom":"/path/to/original/session.jsonl"}
33
+ ```
34
+
35
+ ### SessionMessageEntry
36
+
37
+ A message in the conversation. The `message` field contains an `AppMessage` (see [rpc.md](./rpc.md#message-types)).
38
+
39
+ ```json
40
+ {"type":"message","timestamp":"2024-12-03T14:00:01.000Z","message":{"role":"user","content":"Hello","timestamp":1733234567890}}
41
+ {"type":"message","timestamp":"2024-12-03T14:00:02.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Hi!"}],"api":"anthropic-messages","provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop","timestamp":1733234567891}}
42
+ {"type":"message","timestamp":"2024-12-03T14:00:03.000Z","message":{"role":"toolResult","toolCallId":"call_123","toolName":"bash","content":[{"type":"text","text":"output"}],"isError":false,"timestamp":1733234567900}}
43
+ {"type":"message","timestamp":"2024-12-03T14:00:04.000Z","message":{"role":"bashExecution","command":"ls -la","output":"total 48\n...","exitCode":0,"cancelled":false,"truncated":false,"timestamp":1733234567950}}
44
+ ```
45
+
46
+ The `bashExecution` role is a custom message type for user-executed bash commands (via `!` in TUI or `bash` RPC command). See [rpc.md](./rpc.md#bashexecutionmessage) for the full schema.
47
+
48
+ ### ModelChangeEntry
49
+
50
+ Emitted when the user switches models mid-session.
51
+
52
+ ```json
53
+ {"type":"model_change","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"}
54
+ ```
55
+
56
+ ### ThinkingLevelChangeEntry
57
+
58
+ Emitted when the user changes the thinking/reasoning level.
59
+
60
+ ```json
61
+ {"type":"thinking_level_change","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"}
62
+ ```
63
+
64
+ ## Parsing Example
65
+
66
+ ```typescript
67
+ import { readFileSync } from "fs";
68
+
69
+ const lines = readFileSync("session.jsonl", "utf8").trim().split("\n");
70
+
71
+ for (const line of lines) {
72
+ const entry = JSON.parse(line);
73
+
74
+ switch (entry.type) {
75
+ case "session":
76
+ console.log(`Session: ${entry.id}, Model: ${entry.provider}/${entry.modelId}`);
77
+ break;
78
+ case "message":
79
+ console.log(`${entry.message.role}: ${JSON.stringify(entry.message.content)}`);
80
+ break;
81
+ case "model_change":
82
+ console.log(`Switched to: ${entry.provider}/${entry.modelId}`);
83
+ break;
84
+ case "thinking_level_change":
85
+ console.log(`Thinking: ${entry.thinkingLevel}`);
86
+ break;
87
+ }
88
+ }
89
+ ```
package/docs/theme.md ADDED
@@ -0,0 +1,586 @@
1
+ # Pi Coding Agent Themes
2
+
3
+ Themes allow you to customize the colors used throughout the coding agent TUI.
4
+
5
+ ## Color Tokens
6
+
7
+ Every theme must define all color tokens. There are no optional colors.
8
+
9
+ ### Core UI (10 colors)
10
+
11
+ | Token | Purpose | Examples |
12
+ |-------|---------|----------|
13
+ | `accent` | Primary accent color | Logo, selected items, cursor (›) |
14
+ | `border` | Normal borders | Selector borders, horizontal lines |
15
+ | `borderAccent` | Highlighted borders | Changelog borders, special panels |
16
+ | `borderMuted` | Subtle borders | Editor borders, secondary separators |
17
+ | `success` | Success states | Success messages, diff additions |
18
+ | `error` | Error states | Error messages, diff deletions |
19
+ | `warning` | Warning states | Warning messages |
20
+ | `muted` | Secondary/dimmed text | Metadata, descriptions, output |
21
+ | `dim` | Very dimmed text | Less important info, placeholders |
22
+ | `text` | Default text color | Main content (usually `""`) |
23
+
24
+ ### Backgrounds & Content Text (7 colors)
25
+
26
+ | Token | Purpose |
27
+ |-------|---------|
28
+ | `userMessageBg` | User message background |
29
+ | `userMessageText` | User message text color |
30
+ | `toolPendingBg` | Tool execution box (pending state) |
31
+ | `toolSuccessBg` | Tool execution box (success state) |
32
+ | `toolErrorBg` | Tool execution box (error state) |
33
+ | `toolTitle` | Tool execution title/heading (e.g., `$ command`, `read file.txt`) |
34
+ | `toolOutput` | Tool execution output text |
35
+
36
+ ### Markdown (10 colors)
37
+
38
+ | Token | Purpose |
39
+ |-------|---------|
40
+ | `mdHeading` | Heading text (`#`, `##`, etc) |
41
+ | `mdLink` | Link text |
42
+ | `mdLinkUrl` | Link URL (in parentheses) |
43
+ | `mdCode` | Inline code (backticks) |
44
+ | `mdCodeBlock` | Code block content |
45
+ | `mdCodeBlockBorder` | Code block fences (```) |
46
+ | `mdQuote` | Blockquote text |
47
+ | `mdQuoteBorder` | Blockquote border (`│`) |
48
+ | `mdHr` | Horizontal rule (`---`) |
49
+ | `mdListBullet` | List bullets/numbers |
50
+
51
+ ### Tool Diffs (3 colors)
52
+
53
+ | Token | Purpose |
54
+ |-------|---------|
55
+ | `toolDiffAdded` | Added lines in tool diffs |
56
+ | `toolDiffRemoved` | Removed lines in tool diffs |
57
+ | `toolDiffContext` | Context lines in tool diffs |
58
+
59
+ Note: Diff colors are specific to tool execution boxes and must work with tool background colors.
60
+
61
+ ### Syntax Highlighting (9 colors)
62
+
63
+ Future-proofing for syntax highlighting support:
64
+
65
+ | Token | Purpose |
66
+ |-------|---------|
67
+ | `syntaxComment` | Comments |
68
+ | `syntaxKeyword` | Keywords (`if`, `function`, etc) |
69
+ | `syntaxFunction` | Function names |
70
+ | `syntaxVariable` | Variable names |
71
+ | `syntaxString` | String literals |
72
+ | `syntaxNumber` | Number literals |
73
+ | `syntaxType` | Type names |
74
+ | `syntaxOperator` | Operators (`+`, `-`, etc) |
75
+ | `syntaxPunctuation` | Punctuation (`;`, `,`, etc) |
76
+
77
+ ### Thinking Level Borders (5 colors)
78
+
79
+ Editor border colors that indicate the current thinking/reasoning level:
80
+
81
+ | Token | Purpose |
82
+ |-------|---------|
83
+ | `thinkingOff` | Border when thinking is off (most subtle) |
84
+ | `thinkingMinimal` | Border for minimal thinking |
85
+ | `thinkingLow` | Border for low thinking |
86
+ | `thinkingMedium` | Border for medium thinking |
87
+ | `thinkingHigh` | Border for high thinking (most prominent) |
88
+
89
+ These create a visual hierarchy: off → minimal → low → medium → high
90
+
91
+ **Total: 44 color tokens** (all required)
92
+
93
+ ## Theme Format
94
+
95
+ Themes are defined in JSON files with the following structure:
96
+
97
+ ```json
98
+ {
99
+ "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/theme-schema.json",
100
+ "name": "my-theme",
101
+ "vars": {
102
+ "blue": "#0066cc",
103
+ "gray": 242,
104
+ "brightCyan": 51
105
+ },
106
+ "colors": {
107
+ "accent": "blue",
108
+ "muted": "gray",
109
+ "text": "",
110
+ ...
111
+ }
112
+ }
113
+ ```
114
+
115
+ ### Color Values
116
+
117
+ Four formats are supported:
118
+
119
+ 1. **Hex colors**: `"#ff0000"` (6-digit hex RGB)
120
+ 2. **256-color palette**: `39` (number 0-255, xterm 256-color palette)
121
+ 3. **Color references**: `"blue"` (must be defined in `vars`)
122
+ 4. **Terminal default**: `""` (empty string, uses terminal's default color)
123
+
124
+ ### The `vars` Section
125
+
126
+ The optional `vars` section allows you to define reusable colors:
127
+
128
+ ```json
129
+ {
130
+ "vars": {
131
+ "nord0": "#2E3440",
132
+ "nord1": "#3B4252",
133
+ "nord8": "#88C0D0",
134
+ "brightBlue": 39
135
+ },
136
+ "colors": {
137
+ "accent": "nord8",
138
+ "muted": "nord1",
139
+ "mdLink": "brightBlue"
140
+ }
141
+ }
142
+ ```
143
+
144
+ Benefits:
145
+ - Reuse colors across multiple tokens
146
+ - Easier to maintain theme consistency
147
+ - Can reference standard color palettes
148
+
149
+ Variables can be hex colors (`"#ff0000"`), 256-color indices (`42`), or references to other variables.
150
+
151
+ ### Terminal Default (empty string)
152
+
153
+ Use `""` (empty string) to inherit the terminal's default foreground/background color:
154
+
155
+ ```json
156
+ {
157
+ "colors": {
158
+ "text": "" // Uses terminal's default text color
159
+ }
160
+ }
161
+ ```
162
+
163
+ This is useful for:
164
+ - Main text color (adapts to user's terminal theme)
165
+ - Creating themes that blend with terminal appearance
166
+
167
+ ## Built-in Themes
168
+
169
+ Pi comes with two built-in themes:
170
+
171
+ ### `dark` (default)
172
+
173
+ Optimized for dark terminal backgrounds with bright, saturated colors.
174
+
175
+ ### `light`
176
+
177
+ Optimized for light terminal backgrounds with darker, muted colors.
178
+
179
+ ## Selecting a Theme
180
+
181
+ Themes are configured in the settings (accessible via `/settings`):
182
+
183
+ ```json
184
+ {
185
+ "theme": "dark"
186
+ }
187
+ ```
188
+
189
+ Or use the `/theme` command interactively.
190
+
191
+ On first run, Pi detects your terminal's background and sets a sensible default (`dark` or `light`).
192
+
193
+ ## Custom Themes
194
+
195
+ ### Theme Locations
196
+
197
+ Custom themes are loaded from `~/.pi/agent/themes/*.json`.
198
+
199
+ ### Creating a Custom Theme
200
+
201
+ 1. **Create theme directory:**
202
+ ```bash
203
+ mkdir -p ~/.pi/agent/themes
204
+ ```
205
+
206
+ 2. **Create theme file:**
207
+ ```bash
208
+ vim ~/.pi/agent/themes/my-theme.json
209
+ ```
210
+
211
+ 3. **Define all colors:**
212
+ ```json
213
+ {
214
+ "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/theme-schema.json",
215
+ "name": "my-theme",
216
+ "vars": {
217
+ "primary": "#00aaff",
218
+ "secondary": 242,
219
+ "brightGreen": 46
220
+ },
221
+ "colors": {
222
+ "accent": "primary",
223
+ "border": "primary",
224
+ "borderAccent": "#00ffff",
225
+ "borderMuted": "secondary",
226
+ "success": "brightGreen",
227
+ "error": "#ff0000",
228
+ "warning": "#ffff00",
229
+ "muted": "secondary",
230
+ "text": "",
231
+
232
+ "userMessageBg": "#2d2d30",
233
+ "userMessageText": "",
234
+ "toolPendingBg": "#1e1e2e",
235
+ "toolSuccessBg": "#1e2e1e",
236
+ "toolErrorBg": "#2e1e1e",
237
+ "toolText": "",
238
+
239
+ "mdHeading": "#ffaa00",
240
+ "mdLink": "primary",
241
+ "mdCode": "#00ffff",
242
+ "mdCodeBlock": "#00ff00",
243
+ "mdCodeBlockBorder": "secondary",
244
+ "mdQuote": "secondary",
245
+ "mdQuoteBorder": "secondary",
246
+ "mdHr": "secondary",
247
+ "mdListBullet": "#00ffff",
248
+
249
+ "toolDiffAdded": "#00ff00",
250
+ "toolDiffRemoved": "#ff0000",
251
+ "toolDiffContext": "secondary",
252
+
253
+ "syntaxComment": "secondary",
254
+ "syntaxKeyword": "primary",
255
+ "syntaxFunction": "#00aaff",
256
+ "syntaxVariable": "#ffaa00",
257
+ "syntaxString": "#00ff00",
258
+ "syntaxNumber": "#ff00ff",
259
+ "syntaxType": "#00aaff",
260
+ "syntaxOperator": "primary",
261
+ "syntaxPunctuation": "secondary",
262
+
263
+ "thinkingOff": "secondary",
264
+ "thinkingMinimal": "primary",
265
+ "thinkingLow": "#00aaff",
266
+ "thinkingMedium": "#00ffff",
267
+ "thinkingHigh": "#ff00ff"
268
+ }
269
+ }
270
+ ```
271
+
272
+ 4. **Select your theme:**
273
+ - Use `/settings` command and set `"theme": "my-theme"`
274
+ - Or use `/theme` command interactively
275
+
276
+ ## Tips
277
+
278
+ ### Light vs Dark Themes
279
+
280
+ **For dark terminals:**
281
+ - Use bright, saturated colors
282
+ - Higher contrast
283
+ - Example: `#00ffff` (bright cyan)
284
+
285
+ **For light terminals:**
286
+ - Use darker, muted colors
287
+ - Lower contrast to avoid eye strain
288
+ - Example: `#008888` (dark cyan)
289
+
290
+ ### Color Harmony
291
+
292
+ - Start with a base palette (e.g., Nord, Gruvbox, Tokyo Night)
293
+ - Define your palette in `defs`
294
+ - Reference colors consistently
295
+
296
+ ### Testing
297
+
298
+ Test your theme with:
299
+ - Different message types (user, assistant, errors)
300
+ - Tool executions (success and error states)
301
+ - Markdown content (headings, code, lists, etc)
302
+ - Long text that wraps
303
+
304
+ ## Color Format Reference
305
+
306
+ ### Hex Colors
307
+
308
+ Standard 6-digit hex format:
309
+ - `"#ff0000"` - Red
310
+ - `"#00ff00"` - Green
311
+ - `"#0000ff"` - Blue
312
+ - `"#808080"` - Gray
313
+ - `"#ffffff"` - White
314
+ - `"#000000"` - Black
315
+
316
+ RGB values: `#RRGGBB` where each component is `00-ff` (0-255)
317
+
318
+ ### 256-Color Palette
319
+
320
+ Use numeric indices (0-255) to reference the xterm 256-color palette:
321
+
322
+ **Colors 0-15:** Basic ANSI colors (terminal-dependent, may be themed)
323
+ - `0` - Black
324
+ - `1` - Red
325
+ - `2` - Green
326
+ - `3` - Yellow
327
+ - `4` - Blue
328
+ - `5` - Magenta
329
+ - `6` - Cyan
330
+ - `7` - White
331
+ - `8-15` - Bright variants
332
+
333
+ **Colors 16-231:** 6×6×6 RGB cube (standardized)
334
+ - Formula: `16 + 36×R + 6×G + B` where R, G, B are 0-5
335
+ - Example: `39` = bright cyan, `196` = bright red
336
+
337
+ **Colors 232-255:** Grayscale ramp (standardized)
338
+ - `232` - Darkest gray
339
+ - `255` - Near white
340
+
341
+ Example usage:
342
+ ```json
343
+ {
344
+ "vars": {
345
+ "gray": 242,
346
+ "brightCyan": 51,
347
+ "darkBlue": 18
348
+ },
349
+ "colors": {
350
+ "muted": "gray",
351
+ "accent": "brightCyan"
352
+ }
353
+ }
354
+ ```
355
+
356
+ **Benefits:**
357
+ - Works everywhere (`TERM=xterm-256color`)
358
+ - No truecolor detection needed
359
+ - Standardized RGB cube (16-231) looks the same on all terminals
360
+
361
+ ### Terminal Compatibility
362
+
363
+ Pi uses 24-bit RGB colors (`\x1b[38;2;R;G;Bm`). Most modern terminals support this:
364
+
365
+ - ✅ iTerm2, Alacritty, Kitty, WezTerm
366
+ - ✅ Windows Terminal
367
+ - ✅ VS Code integrated terminal
368
+ - ✅ Modern GNOME Terminal, Konsole
369
+
370
+ For older terminals with only 256-color support, Pi automatically falls back to the nearest 256-color approximation.
371
+
372
+ To check if your terminal supports truecolor:
373
+ ```bash
374
+ echo $COLORTERM # Should output "truecolor" or "24bit"
375
+ ```
376
+
377
+ ## Example Themes
378
+
379
+ See the built-in themes for complete examples:
380
+ - [Dark theme](../src/themes/dark.json)
381
+ - [Light theme](../src/themes/light.json)
382
+
383
+ ## Schema Validation
384
+
385
+ Themes are validated on load using [TypeBox](https://github.com/sinclairzx81/typebox) + [Ajv](https://ajv.js.org/).
386
+
387
+ Invalid themes will show an error with details about what's wrong:
388
+ ```
389
+ Error loading theme 'my-theme':
390
+ - colors.accent: must be string or number
391
+ - colors.mdHeading: required property missing
392
+ ```
393
+
394
+ For editor support, the JSON schema is available at:
395
+ ```
396
+ https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/theme-schema.json
397
+ ```
398
+
399
+ Add to your theme file for auto-completion and validation:
400
+ ```json
401
+ {
402
+ "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/theme-schema.json",
403
+ ...
404
+ }
405
+ ```
406
+
407
+ ## Implementation
408
+
409
+ ### Theme Class
410
+
411
+ Themes are loaded and converted to a `Theme` class that provides type-safe color methods:
412
+
413
+ ```typescript
414
+ class Theme {
415
+ // Apply foreground color
416
+ fg(color: ThemeColor, text: string): string
417
+
418
+ // Apply background color
419
+ bg(color: ThemeBg, text: string): string
420
+
421
+ // Text attributes (preserve current colors)
422
+ bold(text: string): string
423
+ dim(text: string): string
424
+ italic(text: string): string
425
+ }
426
+ ```
427
+
428
+ ### Global Theme Instance
429
+
430
+ The active theme is available as a global singleton in `coding-agent`:
431
+
432
+ ```typescript
433
+ // theme.ts
434
+ export let theme: Theme;
435
+
436
+ export function setTheme(name: string) {
437
+ theme = loadTheme(name);
438
+ }
439
+
440
+ // Usage throughout coding-agent
441
+ import { theme } from './theme.js';
442
+
443
+ theme.fg('accent', 'Selected')
444
+ theme.bg('userMessageBg', content)
445
+ ```
446
+
447
+ ### TUI Component Theming
448
+
449
+ 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:
450
+
451
+ ```typescript
452
+ // In @mariozechner/pi-tui
453
+ export interface MarkdownTheme {
454
+ heading: (text: string) => string;
455
+ link: (text: string) => string;
456
+ code: (text: string) => string;
457
+ codeBlock: (text: string) => string;
458
+ codeBlockBorder: (text: string) => string;
459
+ quote: (text: string) => string;
460
+ quoteBorder: (text: string) => string;
461
+ hr: (text: string) => string;
462
+ listBullet: (text: string) => string;
463
+ }
464
+
465
+ export class Markdown {
466
+ constructor(
467
+ text: string,
468
+ paddingX: number,
469
+ paddingY: number,
470
+ defaultTextStyle?: DefaultTextStyle,
471
+ theme?: MarkdownTheme // Optional theme functions
472
+ )
473
+
474
+ // Usage in component
475
+ renderHeading(text: string) {
476
+ return this.theme.heading(text); // Applies color
477
+ }
478
+ }
479
+ ```
480
+
481
+ The `coding-agent` provides themed functions when creating components:
482
+
483
+ ```typescript
484
+ // In coding-agent
485
+ import { theme } from './theme.js';
486
+ import { Markdown } from '@mariozechner/pi-tui';
487
+
488
+ // Helper to create markdown theme functions
489
+ function getMarkdownTheme(): MarkdownTheme {
490
+ return {
491
+ heading: (text) => theme.fg('mdHeading', text),
492
+ link: (text) => theme.fg('mdLink', text),
493
+ code: (text) => theme.fg('mdCode', text),
494
+ codeBlock: (text) => theme.fg('mdCodeBlock', text),
495
+ codeBlockBorder: (text) => theme.fg('mdCodeBlockBorder', text),
496
+ quote: (text) => theme.fg('mdQuote', text),
497
+ quoteBorder: (text) => theme.fg('mdQuoteBorder', text),
498
+ hr: (text) => theme.fg('mdHr', text),
499
+ listBullet: (text) => theme.fg('mdListBullet', text),
500
+ };
501
+ }
502
+
503
+ // Create markdown with theme
504
+ const md = new Markdown(
505
+ text,
506
+ 1, 1,
507
+ { bgColor: theme.bg('userMessageBg') },
508
+ getMarkdownTheme()
509
+ );
510
+ ```
511
+
512
+ This approach:
513
+ - Keeps TUI components theme-agnostic (reusable in other projects)
514
+ - Maintains type safety via interfaces
515
+ - Allows components to have sensible defaults if no theme provided
516
+ - Centralizes theme access in `coding-agent`
517
+
518
+ **Example usage:**
519
+ ```typescript
520
+ const theme = loadTheme('dark');
521
+
522
+ // Apply foreground colors
523
+ theme.fg('accent', 'Selected')
524
+ theme.fg('success', '✓ Done')
525
+ theme.fg('error', 'Failed')
526
+
527
+ // Apply background colors
528
+ theme.bg('userMessageBg', content)
529
+ theme.bg('toolSuccessBg', output)
530
+
531
+ // Combine styles
532
+ theme.bold(theme.fg('accent', 'Title'))
533
+ theme.dim(theme.fg('muted', 'metadata'))
534
+
535
+ // Nested foreground + background
536
+ const userMsg = theme.bg('userMessageBg',
537
+ theme.fg('userMessageText', 'Hello')
538
+ )
539
+ ```
540
+
541
+ **Color resolution:**
542
+
543
+ 1. **Detect terminal capabilities:**
544
+ - Check `$COLORTERM` env var (`truecolor` or `24bit` → truecolor support)
545
+ - Check `$TERM` env var (`*-256color` → 256-color support)
546
+ - Fallback to 256-color mode if detection fails
547
+
548
+ 2. **Load JSON theme file**
549
+
550
+ 3. **Resolve `vars` references recursively:**
551
+ ```json
552
+ {
553
+ "vars": {
554
+ "primary": "#0066cc",
555
+ "accent": "primary"
556
+ },
557
+ "colors": {
558
+ "accent": "accent" // → "primary" → "#0066cc"
559
+ }
560
+ }
561
+ ```
562
+
563
+ 4. **Convert colors to ANSI codes based on terminal capability:**
564
+
565
+ **Truecolor mode (24-bit):**
566
+ - Hex (`"#ff0000"`) → `\x1b[38;2;255;0;0m`
567
+ - 256-color (`42`) → `\x1b[38;5;42m` (keep as-is)
568
+ - Empty string (`""`) → `\x1b[39m`
569
+
570
+ **256-color mode:**
571
+ - Hex (`"#ff0000"`) → convert to nearest RGB cube color → `\x1b[38;5;196m`
572
+ - 256-color (`42`) → `\x1b[38;5;42m` (keep as-is)
573
+ - Empty string (`""`) → `\x1b[39m`
574
+
575
+ **Hex to 256-color conversion:**
576
+ ```typescript
577
+ // Convert RGB to 6x6x6 cube (colors 16-231)
578
+ r_index = Math.round(r / 255 * 5)
579
+ g_index = Math.round(g / 255 * 5)
580
+ b_index = Math.round(b / 255 * 5)
581
+ color_index = 16 + 36 * r_index + 6 * g_index + b_index
582
+ ```
583
+
584
+ 5. **Cache as `Theme` instance**
585
+
586
+ This ensures themes work correctly regardless of terminal capabilities, with graceful degradation from truecolor to 256-color.