@heyhuynhgiabuu/pi-pretty 0.1.7 → 0.1.8

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.
package/README.md CHANGED
@@ -1,20 +1,17 @@
1
1
  # pi-pretty
2
2
 
3
- A [pi](https://pi.dev) extension that enhances built-in tool output with **syntax highlighting**, **file-type icons**, **tree views**, and **colored status indicators** — all rendered directly in your terminal.
3
+ [![npm version](https://img.shields.io/npm/v/@heyhuynhgiabuu/pi-pretty)](https://www.npmjs.com/package/@heyhuynhgiabuu/pi-pretty)
4
+ [![GitHub release](https://img.shields.io/github/v/release/heyhuynhgiabuu/pi-pretty)](https://github.com/heyhuynhgiabuu/pi-pretty/releases/latest)
4
5
 
5
- > **Status:** Early release.
6
+ A [pi](https://pi.dev) extension that upgrades built-in tool output in the terminal without changing tool behavior.
6
7
 
7
- > Companion to [@heyhuynhgiabuu/pi-diff](https://github.com/heyhuynhgiabuu/pi-diff) which handles `write`/`edit` diffs.
8
+ It currently enhances:
8
9
 
9
- ## Features
10
+ - **`read`**: syntax-highlighted text previews with line numbers, plus inline image rendering when the terminal supports it
11
+ - **`bash`**: colored exit summary (`exit 0`/`exit 1`) with a preview body of command output
12
+ - **`ls` / `find` / `grep`**: Nerd Font file icons with tree/grouped layouts and clearer match rendering
10
13
 
11
- | Tool | Enhancement |
12
- |------|-------------|
13
- | **read** | Syntax-highlighted file content with line numbers (190+ languages via Shiki) |
14
- | **bash** | Colored exit status (`✓ exit 0` / `✗ exit 1`), line count |
15
- | **ls** | Tree-view directory listing with file-type icons (📁🟦🐍🦀…) |
16
- | **find** | Grouped results by directory with file-type icons |
17
- | **grep** | Highlighted pattern matches with file headers and line numbers |
14
+ > Companion to [@heyhuynhgiabuu/pi-diff](https://github.com/heyhuynhgiabuu/pi-diff) for `write`/`edit` diff rendering.
18
15
 
19
16
  ## Install
20
17
 
@@ -22,168 +19,49 @@ A [pi](https://pi.dev) extension that enhances built-in tool output with **synta
22
19
  pi install npm:@heyhuynhgiabuu/pi-pretty
23
20
  ```
24
21
 
25
- Or load directly for development:
22
+ Latest release: https://github.com/heyhuynhgiabuu/pi-pretty/releases/latest
26
23
 
27
- ```bash
28
- pi -e ./src/index.ts
29
- ```
30
-
31
- ## How It Works
32
-
33
- pi-pretty wraps the built-in SDK tools (`createReadTool`, `createBashTool`, `createLsTool`, `createFindTool`, `createGrepTool`). For each tool:
34
-
35
- 1. **Delegates** to the original `execute()` — no behavior changes
36
- 2. **Attaches metadata** to `result.details` for custom rendering
37
- 3. **Renders** enhanced output via `renderCall` / `renderResult`
38
-
39
- The agent sees the same tool results. Only the TUI display changes.
40
-
41
- ## Configuration
42
-
43
- All settings via environment variables. Add to your shell profile or `.envrc`:
44
-
45
- ### Theme
46
-
47
- | Variable | Default | Description |
48
- |----------|---------|-------------|
49
- | `PRETTY_THEME` | `github-dark` | Shiki theme for syntax highlighting |
50
-
51
- ### Limits
52
-
53
- | Variable | Default | Description |
54
- |----------|---------|-------------|
55
- | `PRETTY_MAX_HL_CHARS` | `80000` | Skip syntax highlighting above this |
56
- | `PRETTY_MAX_PREVIEW_LINES` | `80` | Max lines shown in rendered output |
57
- | `PRETTY_CACHE_LIMIT` | `128` | LRU cache entries for highlighted blocks |
58
-
59
- ### Example `.envrc`
24
+ Or load locally:
60
25
 
61
26
  ```bash
62
- export PRETTY_THEME="catppuccin-mocha"
63
- export PRETTY_MAX_PREVIEW_LINES=120
64
- ```
65
-
66
- ## Tool Details
67
-
68
- ### `read` — Syntax Highlighting
69
-
70
- When the agent reads a file, pi-pretty renders it with:
71
- - Shiki syntax highlighting (190+ languages, auto-detected from extension)
72
- - Line numbers in a left gutter
73
- - Long lines truncated with `›` indicator
74
- - Respects `offset` and `limit` parameters
75
-
76
- ### `bash` — Exit Status
77
-
78
- Bash command results show:
79
- - `✓ exit 0` in green for success
80
- - `✗ exit 1` in red for failure
81
- - `⚡ killed` in yellow for terminated processes
82
- - Line count for multi-line output
83
-
84
- ### `ls` — Tree View
85
-
86
- Directory listings rendered as:
87
- ```
88
- 3 entries
89
- ├── 📁 src
90
- ├── 📦 package.json
91
- └── 📖 README.md
27
+ pi -e ./src/index.ts
92
28
  ```
93
29
 
94
- File-type icons auto-detected from extension and filename.
30
+ ## Screenshots
95
31
 
96
- ### `find` Grouped Results
32
+ ![Bash and read rendering](media/bash-and-read.png)
33
+ *`bash` exit summary + output preview, and syntax-highlighted `read` text output.*
97
34
 
98
- Find results grouped by directory:
99
- ```
100
- 5 files
101
- 📁 src/
102
- ├── 🟦 index.ts
103
- └── 🟦 utils.ts
104
- 📁 test/
105
- ├── 🟦 index.test.ts
106
- └── 🟦 utils.test.ts
107
- ```
35
+ ![Icons and grep rendering](media/icons-and-grep.png)
36
+ *`ls`/`find`/`grep` with Nerd Font icons and grouped/tree-oriented rendering.*
108
37
 
109
- ### `grep` — Highlighted Matches
38
+ ![Inline image rendering](media/inline-image.png)
39
+ *`read` rendering an image inline in supported terminals.*
110
40
 
111
- Grep results with file headers and matched text highlighted:
112
- ```
113
- 3 matches
114
- 🟦 src/index.ts
115
- 12 │ const result = await createReadTool(cwd);
116
- 45 │ export function createReadTool(path: string) {
117
-
118
- 🟦 src/utils.ts
119
- 8 │ import { createReadTool } from "./index";
120
- ```
41
+ ## Terminal support for inline images
121
42
 
122
- ## File-Type Icons
123
-
124
- | Icon | Extensions |
125
- |------|-----------|
126
- | 🟦 | `.ts`, `.tsx`, `tsconfig.json` |
127
- | 🟨 | `.js`, `.jsx`, `.mjs`, `.cjs` |
128
- | 🐍 | `.py`, `pyproject.toml` |
129
- | 🦀 | `.rs`, `Cargo.toml` |
130
- | 🔵 | `.go`, `go.mod` |
131
- | ☕ | `.java` |
132
- | 🍊 | `.swift` |
133
- | 💎 | `.rb` |
134
- | 🌐 | `.html` |
135
- | 🎨 | `.css`, `.scss`, `.less` |
136
- | 📋 | `.json`, `.yaml`, `.toml` |
137
- | 📝 | `.md`, `.mdx` |
138
- | 🐚 | `.sh`, `.bash`, `.zsh` |
139
- | 🖼️ | `.png`, `.jpg`, `.svg`, `.webp` |
140
- | 📦 | `package.json` |
141
- | 🐳 | `Dockerfile` |
142
- | 🔐 | `.env`, `.envrc` |
143
- | 📖 | `README.md` |
144
- | ⚖️ | `LICENSE` |
145
-
146
- ## Architecture
43
+ Inline image previews are supported in **Ghostty**, **Kitty**, **iTerm2**, and **WezTerm**.
44
+ When running in **tmux**, pi-pretty uses passthrough escape sequences so inline image protocols still work.
147
45
 
148
- ```
149
- src/
150
- └── index.ts # Extension entry — wraps read/bash/ls/find/grep with pretty rendering
151
- ```
46
+ ## Configuration
152
47
 
153
- ### Key internals
48
+ Optional environment variables:
154
49
 
155
- | Component | Purpose |
156
- |-----------|---------|
157
- | `hlBlock()` | Shiki ANSI highlighting with LRU cache |
158
- | `renderFileContent()` | Line-numbered syntax-highlighted file display |
159
- | `renderBashOutput()` | Colored exit status + stderr detection |
160
- | `renderTree()` | Tree-view with connectors and file icons |
161
- | `renderFindResults()` | Directory-grouped file list with icons |
162
- | `renderGrepResults()` | Pattern-highlighted matches with file headers |
163
- | `fileIcon()` | Extension → emoji icon mapper |
164
- | `lang()` | Extension → Shiki language mapper |
50
+ - `PRETTY_THEME` (default: `github-dark`)
51
+ - `PRETTY_MAX_HL_CHARS` (default: `80000`)
52
+ - `PRETTY_MAX_PREVIEW_LINES` (default: `80`)
53
+ - `PRETTY_CACHE_LIMIT` (default: `128`)
54
+ - `PRETTY_ICONS` (`nerd` by default, set to `none` to disable icons)
165
55
 
166
56
  ## Development
167
57
 
168
58
  ```bash
169
- git clone https://github.com/heyhuynhgiabuu/pi-pretty.git
170
- cd pi-pretty
171
59
  npm install
172
60
  npm run typecheck
173
61
  npm run lint
174
62
  npm test
175
63
  ```
176
64
 
177
- ### Load in pi for testing
178
-
179
- ```bash
180
- pi -e ./src/index.ts
181
- ```
182
-
183
- ## Related
184
-
185
- - [@heyhuynhgiabuu/pi-diff](https://github.com/heyhuynhgiabuu/pi-diff) — Syntax-highlighted diffs for `write`/`edit` tools
186
-
187
65
  ## License
188
66
 
189
67
  MIT — [huynhgiabuu](https://github.com/heyhuynhgiabuu)
package/biome.json CHANGED
@@ -1,17 +1,22 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
3
- "organizeImports": {
4
- "enabled": true
5
- },
6
- "linter": {
7
- "enabled": true,
8
- "rules": {
9
- "recommended": true
10
- }
11
- },
12
- "formatter": {
13
- "enabled": true,
14
- "indentStyle": "tab",
15
- "lineWidth": 120
16
- }
2
+ "$schema": "https://biomejs.dev/schemas/2.4.8/schema.json",
3
+ "assist": {
4
+ "enabled": true,
5
+ "actions": {
6
+ "source": {
7
+ "organizeImports": "on"
8
+ }
9
+ }
10
+ },
11
+ "linter": {
12
+ "enabled": true,
13
+ "rules": {
14
+ "recommended": true
15
+ }
16
+ },
17
+ "formatter": {
18
+ "enabled": true,
19
+ "indentStyle": "tab",
20
+ "lineWidth": 120
21
+ }
17
22
  }
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,50 +1,50 @@
1
1
  {
2
- "name": "@heyhuynhgiabuu/pi-pretty",
3
- "version": "0.1.7",
4
- "description": "Pretty terminal output for pi — syntax-highlighted file reads, colored bash output, tree-view directory listings, and more.",
5
- "author": "huynhgiabuu",
6
- "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/heyhuynhgiabuu/pi-pretty.git"
10
- },
11
- "homepage": "https://github.com/heyhuynhgiabuu/pi-pretty#readme",
12
- "bugs": {
13
- "url": "https://github.com/heyhuynhgiabuu/pi-pretty/issues"
14
- },
15
- "keywords": [
16
- "pi-package",
17
- "pi",
18
- "pi-extension",
19
- "syntax-highlighting",
20
- "shiki",
21
- "terminal",
22
- "pretty-print"
23
- ],
24
- "dependencies": {
25
- "@shikijs/cli": "^4.0.2"
26
- },
27
- "peerDependencies": {
28
- "@mariozechner/pi-coding-agent": "*",
29
- "@mariozechner/pi-tui": "*"
30
- },
31
- "devDependencies": {
32
- "@types/node": "^20.0.0",
33
- "typescript": "^5.0.0",
34
- "@biomejs/biome": "^2.3.5",
35
- "vitest": "^4.0.18"
36
- },
37
- "scripts": {
38
- "build": "tsc",
39
- "typecheck": "tsc --noEmit",
40
- "lint": "biome check src/",
41
- "lint:fix": "biome check --fix src/",
42
- "test": "vitest run",
43
- "test:watch": "vitest"
44
- },
45
- "pi": {
46
- "extensions": [
47
- "./src/index.ts"
48
- ]
49
- }
2
+ "name": "@heyhuynhgiabuu/pi-pretty",
3
+ "version": "0.1.8",
4
+ "description": "Pretty terminal output for pi — syntax-highlighted file reads, colored bash output, tree-view directory listings, and more.",
5
+ "author": "huynhgiabuu",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/heyhuynhgiabuu/pi-pretty.git"
10
+ },
11
+ "homepage": "https://github.com/heyhuynhgiabuu/pi-pretty#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/heyhuynhgiabuu/pi-pretty/issues"
14
+ },
15
+ "keywords": [
16
+ "pi-package",
17
+ "pi",
18
+ "pi-extension",
19
+ "syntax-highlighting",
20
+ "shiki",
21
+ "terminal",
22
+ "pretty-print"
23
+ ],
24
+ "dependencies": {
25
+ "@shikijs/cli": "^4.0.2"
26
+ },
27
+ "peerDependencies": {
28
+ "@mariozechner/pi-coding-agent": "*",
29
+ "@mariozechner/pi-tui": "*"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.0.0",
33
+ "typescript": "^5.0.0",
34
+ "@biomejs/biome": "^2.3.5",
35
+ "vitest": "^4.0.18"
36
+ },
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "typecheck": "tsc --noEmit",
40
+ "lint": "biome check src/",
41
+ "lint:fix": "biome check --fix src/",
42
+ "test": "vitest run --passWithNoTests",
43
+ "test:watch": "vitest --passWithNoTests"
44
+ },
45
+ "pi": {
46
+ "extensions": [
47
+ "./src/index.ts"
48
+ ]
49
+ }
50
50
  }
package/src/index.ts CHANGED
@@ -33,8 +33,7 @@ import type { BundledLanguage, BundledTheme } from "shiki";
33
33
  // Config
34
34
  // ---------------------------------------------------------------------------
35
35
 
36
- const THEME: BundledTheme =
37
- (process.env.PRETTY_THEME as BundledTheme | undefined) ?? "github-dark";
36
+ const THEME: BundledTheme = (process.env.PRETTY_THEME as BundledTheme | undefined) ?? "github-dark";
38
37
 
39
38
  function envInt(name: string, fallback: number): number {
40
39
  const v = Number.parseInt(process.env[name] ?? "", 10);
@@ -49,7 +48,7 @@ const CACHE_LIMIT = envInt("PRETTY_CACHE_LIMIT", 128);
49
48
  // ANSI
50
49
  // ---------------------------------------------------------------------------
51
50
 
52
- const RST = "\x1b[0m";
51
+ let RST = "\x1b[0m";
53
52
  const BOLD = "\x1b[1m";
54
53
  const DIM = "\x1b[2m";
55
54
  const ITALIC = "\x1b[3m";
@@ -68,7 +67,34 @@ const FG_PURPLE = "\x1b[38;2;170;120;200m";
68
67
 
69
68
  const BG_STDERR = "\x1b[48;2;40;25;25m";
70
69
 
71
- const ANSI_RE = /\x1b\[[0-9;]*m/g;
70
+ const BG_DEFAULT = "\x1b[49m";
71
+ let BG_BASE = BG_DEFAULT; // tool box base bg — updated from theme's toolSuccessBg
72
+
73
+ /** Parse an ANSI 24-bit color escape into { r, g, b }. Handles both fg (38;2) and bg (48;2). */
74
+ function parseAnsiRgb(ansi: string): { r: number; g: number; b: number } | null {
75
+ const m = ansi.match(/\x1b\[(?:38|48);2;(\d+);(\d+);(\d+)m/);
76
+ return m ? { r: +m[1], g: +m[2], b: +m[3] } : null;
77
+ }
78
+
79
+ /** Read toolSuccessBg from the pi theme and update BG_BASE + RST.
80
+ * Call once when theme is first available. Idempotent. */
81
+ let _bgBaseResolved = false;
82
+ function resolveBaseBackground(theme: any): void {
83
+ if (_bgBaseResolved || !theme?.getBgAnsi) return;
84
+ _bgBaseResolved = true;
85
+ try {
86
+ const bgAnsi = theme.getBgAnsi("toolSuccessBg");
87
+ const parsed = parseAnsiRgb(bgAnsi);
88
+ if (parsed) {
89
+ BG_BASE = bgAnsi;
90
+ RST = `\x1b[0m${BG_BASE}`;
91
+ }
92
+ } catch { /* ignore — keep defaults */ }
93
+ }
94
+
95
+ const ESC_RE = "\u001b";
96
+ const ANSI_RE = new RegExp(`${ESC_RE}\\[[0-9;]*m`, "g");
97
+ const ANSI_CAPTURE_RE = new RegExp(`${ESC_RE}\\[([0-9;]*)m`, "g");
72
98
 
73
99
  // ---------------------------------------------------------------------------
74
100
  // Low-contrast fix (same as pi-diff)
@@ -79,19 +105,14 @@ function isLowContrastShikiFg(params: string): boolean {
79
105
  if (params === "38;5;0" || params === "38;5;8") return true;
80
106
  if (!params.startsWith("38;2;")) return false;
81
107
  const parts = params.split(";").map(Number);
82
- if (parts.length !== 5 || parts.some((n) => !Number.isFinite(n)))
83
- return false;
108
+ if (parts.length !== 5 || parts.some((n) => !Number.isFinite(n))) return false;
84
109
  const [, , r, g, b] = parts;
85
110
  const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
86
111
  return luminance < 72;
87
112
  }
88
113
 
89
114
  function normalizeShikiContrast(ansi: string): string {
90
- return ansi.replace(
91
- /\x1b\[([0-9;]*)m/g,
92
- (seq, params: string) =>
93
- isLowContrastShikiFg(params) ? FG_MUTED : seq,
94
- );
115
+ return ansi.replace(ANSI_CAPTURE_RE, (seq, params: string) => (isLowContrastShikiFg(params) ? FG_MUTED : seq));
95
116
  }
96
117
 
97
118
  // ---------------------------------------------------------------------------
@@ -104,10 +125,7 @@ function strip(s: string): string {
104
125
 
105
126
  function termW(): number {
106
127
  const raw =
107
- process.stdout.columns ||
108
- (process.stderr as any).columns ||
109
- Number.parseInt(process.env.COLUMNS ?? "", 10) ||
110
- 200;
128
+ process.stdout.columns || (process.stderr as any).columns || Number.parseInt(process.env.COLUMNS ?? "", 10) || 200;
111
129
  return Math.max(80, Math.min(raw - 4, 210));
112
130
  }
113
131
 
@@ -132,19 +150,54 @@ function lnum(n: number, w: number): string {
132
150
  // ---------------------------------------------------------------------------
133
151
 
134
152
  const EXT_LANG: Record<string, BundledLanguage> = {
135
- ts: "typescript", tsx: "tsx", js: "javascript", jsx: "jsx",
136
- mjs: "javascript", cjs: "javascript",
137
- py: "python", rb: "ruby", rs: "rust", go: "go", java: "java",
138
- c: "c", cpp: "cpp", h: "c", hpp: "cpp", cs: "csharp",
139
- swift: "swift", kt: "kotlin",
140
- html: "html", css: "css", scss: "scss", less: "css",
141
- json: "json", jsonc: "jsonc", yaml: "yaml", yml: "yaml", toml: "toml",
142
- md: "markdown", mdx: "mdx", sql: "sql", sh: "bash", bash: "bash", zsh: "bash",
143
- lua: "lua", php: "php", dart: "dart", xml: "xml",
144
- graphql: "graphql", svelte: "svelte", vue: "vue",
145
- dockerfile: "dockerfile", makefile: "make",
146
- zig: "zig", nim: "nim", elixir: "elixir", ex: "elixir",
147
- erb: "erb", hbs: "handlebars",
153
+ ts: "typescript",
154
+ tsx: "tsx",
155
+ js: "javascript",
156
+ jsx: "jsx",
157
+ mjs: "javascript",
158
+ cjs: "javascript",
159
+ py: "python",
160
+ rb: "ruby",
161
+ rs: "rust",
162
+ go: "go",
163
+ java: "java",
164
+ c: "c",
165
+ cpp: "cpp",
166
+ h: "c",
167
+ hpp: "cpp",
168
+ cs: "csharp",
169
+ swift: "swift",
170
+ kt: "kotlin",
171
+ html: "html",
172
+ css: "css",
173
+ scss: "scss",
174
+ less: "css",
175
+ json: "json",
176
+ jsonc: "jsonc",
177
+ yaml: "yaml",
178
+ yml: "yaml",
179
+ toml: "toml",
180
+ md: "markdown",
181
+ mdx: "mdx",
182
+ sql: "sql",
183
+ sh: "bash",
184
+ bash: "bash",
185
+ zsh: "bash",
186
+ lua: "lua",
187
+ php: "php",
188
+ dart: "dart",
189
+ xml: "xml",
190
+ graphql: "graphql",
191
+ svelte: "svelte",
192
+ vue: "vue",
193
+ dockerfile: "dockerfile",
194
+ makefile: "make",
195
+ zig: "zig",
196
+ nim: "nim",
197
+ elixir: "elixir",
198
+ ex: "elixir",
199
+ erb: "erb",
200
+ hbs: "handlebars",
148
201
  };
149
202
 
150
203
  function lang(fp: string): BundledLanguage | undefined {
@@ -208,7 +261,7 @@ function detectImageProtocol(): ImageProtocol {
208
261
  function tmuxWrap(seq: string): string {
209
262
  if (!IS_TMUX) return seq;
210
263
  // Double all ESC chars inside the sequence
211
- const escaped = seq.replace(/\x1b/g, "\x1b\x1b");
264
+ const escaped = seq.split("\x1b").join("\x1b\x1b");
212
265
  return `\x1bPtmux;${escaped}\x1b\\`;
213
266
  }
214
267
 
@@ -216,14 +269,11 @@ function tmuxWrap(seq: string): string {
216
269
  * Render base64 image inline using iTerm2 inline image protocol.
217
270
  * Protocol: ESC ] 1337 ; File=[args] : base64data BEL
218
271
  */
219
- function renderIterm2Image(
220
- base64Data: string,
221
- opts: { width?: string; name?: string } = {},
222
- ): string {
272
+ function renderIterm2Image(base64Data: string, opts: { width?: string; name?: string } = {}): string {
223
273
  const args: string[] = ["inline=1", "preserveAspectRatio=1"];
224
274
  if (opts.width) args.push(`width=${opts.width}`);
225
275
  if (opts.name) args.push(`name=${Buffer.from(opts.name).toString("base64")}`);
226
- const byteSize = Math.ceil(base64Data.length * 3 / 4);
276
+ const byteSize = Math.ceil((base64Data.length * 3) / 4);
227
277
  args.push(`size=${byteSize}`);
228
278
  const seq = `\x1b]1337;File=${args.join(";")}:${base64Data}\x07`;
229
279
  return tmuxWrap(seq);
@@ -235,10 +285,7 @@ function renderIterm2Image(
235
285
  * Chunked in 4096-byte pieces as required by protocol.
236
286
  * Supported by: Kitty, Ghostty
237
287
  */
238
- function renderKittyImage(
239
- base64Data: string,
240
- opts: { cols?: number } = {},
241
- ): string {
288
+ function renderKittyImage(base64Data: string, opts: { cols?: number } = {}): string {
242
289
  const chunks: string[] = [];
243
290
  const CHUNK_SIZE = 4096;
244
291
 
@@ -279,96 +326,96 @@ const ICONS_MODE = (process.env.PRETTY_ICONS ?? "nerd").toLowerCase();
279
326
  const USE_ICONS = ICONS_MODE !== "none" && ICONS_MODE !== "off";
280
327
 
281
328
  // Nerd Font codepoints + ANSI color per file type
282
- const NF_DIR = `${FG_BLUE}\ue5ff${RST}`; // folder
283
- const NF_DIR_OPEN = `${FG_BLUE}\ue5fe${RST}`; // folder open
284
- const NF_DEFAULT = `${FG_DIM}\uf15b${RST}`; // generic file
329
+ const NF_DIR = `${FG_BLUE}\ue5ff${RST}`; // folder
330
+ const NF_DIR_OPEN = `${FG_BLUE}\ue5fe${RST}`; // folder open
331
+ const NF_DEFAULT = `${FG_DIM}\uf15b${RST}`; // generic file
285
332
 
286
333
  const EXT_ICON: Record<string, string> = {
287
334
  // TypeScript / JavaScript
288
- ts: `\x1b[38;2;49;120;198m\ue628${RST}`, // blue
289
- tsx: `\x1b[38;2;49;120;198m\ue7ba${RST}`, // react blue
290
- js: `\x1b[38;2;241;224;90m\ue74e${RST}`, // yellow
291
- jsx: `\x1b[38;2;97;218;251m\ue7ba${RST}`, // react cyan
335
+ ts: `\x1b[38;2;49;120;198m\ue628${RST}`, // blue
336
+ tsx: `\x1b[38;2;49;120;198m\ue7ba${RST}`, // react blue
337
+ js: `\x1b[38;2;241;224;90m\ue74e${RST}`, // yellow
338
+ jsx: `\x1b[38;2;97;218;251m\ue7ba${RST}`, // react cyan
292
339
  mjs: `\x1b[38;2;241;224;90m\ue74e${RST}`,
293
340
  cjs: `\x1b[38;2;241;224;90m\ue74e${RST}`,
294
341
 
295
342
  // Systems / Backend
296
- py: `\x1b[38;2;55;118;171m\ue73c${RST}`, // python blue
297
- rs: `\x1b[38;2;222;165;132m\ue7a8${RST}`, // rust orange
298
- go: `\x1b[38;2;0;173;216m\ue724${RST}`, // go cyan
299
- java: `\x1b[38;2;204;62;68m\ue738${RST}`, // java red
300
- swift: `\x1b[38;2;255;172;77m\ue755${RST}`, // swift orange
301
- rb: `\x1b[38;2;204;52;45m\ue739${RST}`, // ruby red
302
- kt: `\x1b[38;2;126;103;200m\ue634${RST}`, // kotlin purple
303
- c: `\x1b[38;2;85;154;211m\ue61e${RST}`, // c blue
304
- cpp: `\x1b[38;2;85;154;211m\ue61d${RST}`, // cpp blue
305
- h: `\x1b[38;2;140;160;185m\ue61e${RST}`, // header muted
306
- hpp: `\x1b[38;2;140;160;185m\ue61d${RST}`,
307
- cs: `\x1b[38;2;104;33;122m\ue648${RST}`, // c# purple
343
+ py: `\x1b[38;2;55;118;171m\ue73c${RST}`, // python blue
344
+ rs: `\x1b[38;2;222;165;132m\ue7a8${RST}`, // rust orange
345
+ go: `\x1b[38;2;0;173;216m\ue724${RST}`, // go cyan
346
+ java: `\x1b[38;2;204;62;68m\ue738${RST}`, // java red
347
+ swift: `\x1b[38;2;255;172;77m\ue755${RST}`, // swift orange
348
+ rb: `\x1b[38;2;204;52;45m\ue739${RST}`, // ruby red
349
+ kt: `\x1b[38;2;126;103;200m\ue634${RST}`, // kotlin purple
350
+ c: `\x1b[38;2;85;154;211m\ue61e${RST}`, // c blue
351
+ cpp: `\x1b[38;2;85;154;211m\ue61d${RST}`, // cpp blue
352
+ h: `\x1b[38;2;140;160;185m\ue61e${RST}`, // header muted
353
+ hpp: `\x1b[38;2;140;160;185m\ue61d${RST}`,
354
+ cs: `\x1b[38;2;104;33;122m\ue648${RST}`, // c# purple
308
355
 
309
356
  // Web
310
- html: `\x1b[38;2;228;77;38m\ue736${RST}`, // html orange
311
- css: `\x1b[38;2;66;165;245m\ue749${RST}`, // css blue
312
- scss: `\x1b[38;2;207;100;154m\ue749${RST}`, // scss pink
313
- less: `\x1b[38;2;66;165;245m\ue749${RST}`,
314
- vue: `\x1b[38;2;65;184;131m\ue6a0${RST}`, // vue green
315
- svelte: `\x1b[38;2;255;62;0m\ue697${RST}`, // svelte red-orange
357
+ html: `\x1b[38;2;228;77;38m\ue736${RST}`, // html orange
358
+ css: `\x1b[38;2;66;165;245m\ue749${RST}`, // css blue
359
+ scss: `\x1b[38;2;207;100;154m\ue749${RST}`, // scss pink
360
+ less: `\x1b[38;2;66;165;245m\ue749${RST}`,
361
+ vue: `\x1b[38;2;65;184;131m\ue6a0${RST}`, // vue green
362
+ svelte: `\x1b[38;2;255;62;0m\ue697${RST}`, // svelte red-orange
316
363
 
317
364
  // Config / Data
318
- json: `\x1b[38;2;241;224;90m\ue60b${RST}`, // json yellow
365
+ json: `\x1b[38;2;241;224;90m\ue60b${RST}`, // json yellow
319
366
  jsonc: `\x1b[38;2;241;224;90m\ue60b${RST}`,
320
- yaml: `\x1b[38;2;160;116;196m\ue6a8${RST}`, // yaml purple
321
- yml: `\x1b[38;2;160;116;196m\ue6a8${RST}`,
322
- toml: `\x1b[38;2;160;116;196m\ue6b2${RST}`, // toml purple
323
- xml: `\x1b[38;2;228;77;38m\ue619${RST}`, // xml orange
324
- sql: `\x1b[38;2;218;218;218m\ue706${RST}`, // sql gray
367
+ yaml: `\x1b[38;2;160;116;196m\ue6a8${RST}`, // yaml purple
368
+ yml: `\x1b[38;2;160;116;196m\ue6a8${RST}`,
369
+ toml: `\x1b[38;2;160;116;196m\ue6b2${RST}`, // toml purple
370
+ xml: `\x1b[38;2;228;77;38m\ue619${RST}`, // xml orange
371
+ sql: `\x1b[38;2;218;218;218m\ue706${RST}`, // sql gray
325
372
 
326
373
  // Markdown / Docs
327
- md: `\x1b[38;2;66;165;245m\ue73e${RST}`, // markdown blue
374
+ md: `\x1b[38;2;66;165;245m\ue73e${RST}`, // markdown blue
328
375
  mdx: `\x1b[38;2;66;165;245m\ue73e${RST}`,
329
376
 
330
377
  // Shell / Scripts
331
- sh: `\x1b[38;2;137;180;130m\ue795${RST}`, // shell green
378
+ sh: `\x1b[38;2;137;180;130m\ue795${RST}`, // shell green
332
379
  bash: `\x1b[38;2;137;180;130m\ue795${RST}`,
333
- zsh: `\x1b[38;2;137;180;130m\ue795${RST}`,
380
+ zsh: `\x1b[38;2;137;180;130m\ue795${RST}`,
334
381
  fish: `\x1b[38;2;137;180;130m\ue795${RST}`,
335
- lua: `\x1b[38;2;81;160;207m\ue620${RST}`, // lua blue
336
- php: `\x1b[38;2;137;147;186m\ue73d${RST}`, // php purple
337
- dart: `\x1b[38;2;87;182;240m\ue798${RST}`, // dart blue
382
+ lua: `\x1b[38;2;81;160;207m\ue620${RST}`, // lua blue
383
+ php: `\x1b[38;2;137;147;186m\ue73d${RST}`, // php purple
384
+ dart: `\x1b[38;2;87;182;240m\ue798${RST}`, // dart blue
338
385
 
339
386
  // Images
340
- png: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
341
- jpg: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
387
+ png: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
388
+ jpg: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
342
389
  jpeg: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
343
- gif: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
344
- svg: `\x1b[38;2;255;180;50m\uf1c5${RST}`,
390
+ gif: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
391
+ svg: `\x1b[38;2;255;180;50m\uf1c5${RST}`,
345
392
  webp: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
346
- ico: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
393
+ ico: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
347
394
 
348
395
  // Misc
349
- lock: `\x1b[38;2;130;130;130m\uf023${RST}`, // lock gray
350
- env: `\x1b[38;2;241;224;90m\ue615${RST}`, // env yellow
351
- graphql: `\x1b[38;2;224;51;144m\ue662${RST}`, // graphql pink
396
+ lock: `\x1b[38;2;130;130;130m\uf023${RST}`, // lock gray
397
+ env: `\x1b[38;2;241;224;90m\ue615${RST}`, // env yellow
398
+ graphql: `\x1b[38;2;224;51;144m\ue662${RST}`, // graphql pink
352
399
  dockerfile: `\x1b[38;2;56;152;236m\ue7b0${RST}`,
353
400
  };
354
401
 
355
402
  const NAME_ICON: Record<string, string> = {
356
- "package.json": `\x1b[38;2;137;180;130m\ue71e${RST}`, // npm green
357
- "package-lock.json": `\x1b[38;2;130;130;130m\ue71e${RST}`, // npm gray
358
- "tsconfig.json": `\x1b[38;2;49;120;198m\ue628${RST}`, // ts blue
359
- "biome.json": `\x1b[38;2;96;165;250m\ue615${RST}`, // config blue
360
- ".gitignore": `\x1b[38;2;222;165;132m\ue702${RST}`, // git orange
361
- ".git": `\x1b[38;2;222;165;132m\ue702${RST}`,
362
- ".env": `\x1b[38;2;241;224;90m\ue615${RST}`, // env yellow
363
- ".envrc": `\x1b[38;2;241;224;90m\ue615${RST}`,
364
- "dockerfile": `\x1b[38;2;56;152;236m\ue7b0${RST}`, // docker blue
365
- "makefile": `\x1b[38;2;130;130;130m\ue615${RST}`, // make gray
366
- "gnumakefile": `\x1b[38;2;130;130;130m\ue615${RST}`,
367
- "readme.md": `\x1b[38;2;66;165;245m\ue73e${RST}`, // readme blue
368
- "license": `\x1b[38;2;218;218;218m\ue60a${RST}`, // license white
369
- "cargo.toml": `\x1b[38;2;222;165;132m\ue7a8${RST}`, // rust
370
- "go.mod": `\x1b[38;2;0;173;216m\ue724${RST}`, // go
371
- "pyproject.toml": `\x1b[38;2;55;118;171m\ue73c${RST}`, // python
403
+ "package.json": `\x1b[38;2;137;180;130m\ue71e${RST}`, // npm green
404
+ "package-lock.json": `\x1b[38;2;130;130;130m\ue71e${RST}`, // npm gray
405
+ "tsconfig.json": `\x1b[38;2;49;120;198m\ue628${RST}`, // ts blue
406
+ "biome.json": `\x1b[38;2;96;165;250m\ue615${RST}`, // config blue
407
+ ".gitignore": `\x1b[38;2;222;165;132m\ue702${RST}`, // git orange
408
+ ".git": `\x1b[38;2;222;165;132m\ue702${RST}`,
409
+ ".env": `\x1b[38;2;241;224;90m\ue615${RST}`, // env yellow
410
+ ".envrc": `\x1b[38;2;241;224;90m\ue615${RST}`,
411
+ dockerfile: `\x1b[38;2;56;152;236m\ue7b0${RST}`, // docker blue
412
+ makefile: `\x1b[38;2;130;130;130m\ue615${RST}`, // make gray
413
+ gnumakefile: `\x1b[38;2;130;130;130m\ue615${RST}`,
414
+ "readme.md": `\x1b[38;2;66;165;245m\ue73e${RST}`, // readme blue
415
+ license: `\x1b[38;2;218;218;218m\ue60a${RST}`, // license white
416
+ "cargo.toml": `\x1b[38;2;222;165;132m\ue7a8${RST}`, // rust
417
+ "go.mod": `\x1b[38;2;0;173;216m\ue724${RST}`, // go
418
+ "pyproject.toml": `\x1b[38;2;55;118;171m\ue73c${RST}`, // python
372
419
  };
373
420
 
374
421
  function fileIcon(fp: string): string {
@@ -403,10 +450,7 @@ function _touch(k: string, v: string[]): string[] {
403
450
  return v;
404
451
  }
405
452
 
406
- async function hlBlock(
407
- code: string,
408
- language: BundledLanguage | undefined,
409
- ): Promise<string[]> {
453
+ async function hlBlock(code: string, language: BundledLanguage | undefined): Promise<string[]> {
410
454
  if (!code) return [""];
411
455
  if (!language || code.length > MAX_HL_CHARS) return code.split("\n");
412
456
 
@@ -470,32 +514,24 @@ async function renderFileContent(
470
514
  vis++;
471
515
  j++;
472
516
  }
473
- display = code.slice(0, j) + RST + FG_DIM + "›" + RST;
517
+ display = `${code.slice(0, j)}${RST}${FG_DIM}›${RST}`;
474
518
  }
475
519
  out.push(`${lnum(ln, nw)} ${FG_RULE}│${RST} ${display}${RST}`);
476
520
  }
477
521
 
478
522
  out.push(rule(tw));
479
523
  if (total > maxLines) {
480
- out.push(
481
- `${FG_DIM} … ${total - maxLines} more lines (${total} total)${RST}`,
482
- );
524
+ out.push(`${FG_DIM} … ${total - maxLines} more lines (${total} total)${RST}`);
483
525
  }
484
526
  return out.join("\n");
485
527
  }
486
528
 
487
529
  /** Render bash output with colored exit code and stderr highlighting. */
488
- function renderBashOutput(
489
- text: string,
490
- exitCode: number | null,
491
- ): { summary: string; body: string } {
530
+ function renderBashOutput(text: string, exitCode: number | null): { summary: string; body: string } {
492
531
  const isOk = exitCode === 0;
493
532
  const statusFg = isOk ? FG_GREEN : FG_RED;
494
533
  const statusIcon = isOk ? "✓" : "✗";
495
- const codeStr =
496
- exitCode !== null
497
- ? `${statusFg}${statusIcon} exit ${exitCode}${RST}`
498
- : `${FG_YELLOW}⚡ killed${RST}`;
534
+ const codeStr = exitCode !== null ? `${statusFg}${statusIcon} exit ${exitCode}${RST}` : `${FG_YELLOW}⚡ killed${RST}`;
499
535
 
500
536
  const lines = text.split("\n");
501
537
  const maxShow = MAX_PREVIEW_LINES;
@@ -536,9 +572,7 @@ function renderTree(text: string, basePath: string): string {
536
572
  }
537
573
 
538
574
  if (total > MAX_PREVIEW_LINES) {
539
- out.push(
540
- `${FG_RULE}└── ${RST}${FG_DIM}… ${total - MAX_PREVIEW_LINES} more entries${RST}`,
541
- );
575
+ out.push(`${FG_RULE}└── ${RST}${FG_DIM}… ${total - MAX_PREVIEW_LINES} more entries${RST}`);
542
576
  }
543
577
 
544
578
  return out.join("\n");
@@ -567,9 +601,7 @@ function renderFindResults(text: string): string {
567
601
  out.push(`${dirIcon()}${FG_BLUE}${BOLD}${dir}/${RST}`);
568
602
  for (let i = 0; i < files.length; i++) {
569
603
  if (count >= MAX_PREVIEW_LINES) {
570
- out.push(
571
- ` ${FG_DIM}… ${lines.length - count} more files${RST}`,
572
- );
604
+ out.push(` ${FG_DIM}… ${lines.length - count} more files${RST}`);
573
605
  return out.join("\n");
574
606
  }
575
607
  const isLast = i === files.length - 1;
@@ -584,13 +616,9 @@ function renderFindResults(text: string): string {
584
616
  }
585
617
 
586
618
  /** Render grep results with highlighted matches and line numbers. */
587
- async function renderGrepResults(
588
- text: string,
589
- pattern: string,
590
- ): Promise<string> {
619
+ async function renderGrepResults(text: string, pattern: string): Promise<string> {
591
620
  const lines = text.split("\n");
592
- if (!lines.length || (lines.length === 1 && !lines[0].trim()))
593
- return `${FG_DIM}(no matches)${RST}`;
621
+ if (!lines.length || (lines.length === 1 && !lines[0].trim())) return `${FG_DIM}(no matches)${RST}`;
594
622
 
595
623
  const tw = termW();
596
624
  const out: string[] = [];
@@ -612,7 +640,7 @@ async function renderGrepResults(
612
640
  }
613
641
 
614
642
  // ripgrep-style: "file:line:content" or "file-line-content" or just "file"
615
- const fileMatch = line.match(/^(.+?)[:\-](\d+)[:\-](.*)$/);
643
+ const fileMatch = line.match(/^(.+?)[:-](\d+)[:-](.*)$/);
616
644
  if (fileMatch) {
617
645
  const [, file, lineNo, content] = fileMatch;
618
646
  if (file !== currentFile) {
@@ -625,10 +653,7 @@ async function renderGrepResults(
625
653
  const nw = Math.max(3, lineNo.length);
626
654
  let display = content;
627
655
  if (re) {
628
- display = content.replace(
629
- re,
630
- `${RST}${FG_YELLOW}${BOLD}$1${RST}`,
631
- );
656
+ display = content.replace(re, `${RST}${FG_YELLOW}${BOLD}$1${RST}`);
632
657
  }
633
658
  out.push(` ${lnum(Number(lineNo), nw)} ${FG_RULE}│${RST} ${display}${RST}`);
634
659
  count++;
@@ -722,24 +747,25 @@ export default function piPrettyExtension(pi: any): void {
722
747
  },
723
748
 
724
749
  renderCall(args: any, theme: any, ctx: any) {
750
+ resolveBaseBackground(theme);
725
751
  const fp = args?.path ?? "";
726
752
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
727
753
  const offset = args?.offset ? ` ${theme.fg("muted", `from line ${args.offset}`)}` : "";
728
754
  const limit = args?.limit ? ` ${theme.fg("muted", `(${args.limit} lines)`)}` : "";
729
- text.setText(
730
- `${theme.fg("toolTitle", theme.bold("read"))} ${theme.fg("accent", sp(fp))}${offset}${limit}`,
731
- );
755
+ text.setText(`${theme.fg("toolTitle", theme.bold("read"))} ${theme.fg("accent", sp(fp))}${offset}${limit}`);
732
756
  return text;
733
757
  },
734
758
 
735
759
  renderResult(result: any, _opt: any, theme: any, ctx: any) {
760
+ resolveBaseBackground(theme);
736
761
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
737
762
 
738
763
  if (ctx.isError) {
739
- const e = result.content
740
- ?.filter((c: any) => c.type === "text")
741
- .map((c: any) => c.text || "")
742
- .join("\n") ?? "Error";
764
+ const e =
765
+ result.content
766
+ ?.filter((c: any) => c.type === "text")
767
+ .map((c: any) => c.text || "")
768
+ .join("\n") ?? "Error";
743
769
  text.setText(`\n${theme.fg("error", e)}`);
744
770
  return text;
745
771
  }
@@ -751,7 +777,7 @@ export default function piPrettyExtension(pi: any): void {
751
777
  const tw = termW();
752
778
  const out: string[] = [];
753
779
  const fname = basename(d.filePath);
754
- const byteSize = Math.ceil((d.data as string).length * 3 / 4);
780
+ const byteSize = Math.ceil(((d.data as string).length * 3) / 4);
755
781
  const sizeStr = humanSize(byteSize);
756
782
  const mimeStr = d.mimeType ?? "image";
757
783
 
@@ -764,10 +790,12 @@ export default function piPrettyExtension(pi: any): void {
764
790
  out.push(renderKittyImage(d.data, { cols: imgCols }));
765
791
  } else if (protocol === "iterm2") {
766
792
  const imgWidth = Math.min(tw - 4, 80);
767
- out.push(renderIterm2Image(d.data, {
768
- width: `${imgWidth}`,
769
- name: fname,
770
- }));
793
+ out.push(
794
+ renderIterm2Image(d.data, {
795
+ width: `${imgWidth}`,
796
+ name: fname,
797
+ }),
798
+ );
771
799
  } else {
772
800
  out.push(` ${FG_DIM}(Inline image preview requires Ghostty, iTerm2, WezTerm, or Kitty)${RST}`);
773
801
  }
@@ -826,15 +854,10 @@ export default function piPrettyExtension(pi: any): void {
826
854
  // Try to extract exit code from the output
827
855
  let exitCode: number | null = 0;
828
856
  if (textContent) {
829
- const exitMatch = textContent.match(
830
- /(?:exit code|exited with|exit status)[:\s]*(\d+)/i,
831
- );
857
+ const exitMatch = textContent.match(/(?:exit code|exited with|exit status)[:\s]*(\d+)/i);
832
858
  if (exitMatch) exitCode = Number(exitMatch[1]);
833
859
  // Check for common error indicators
834
- if (
835
- textContent.includes("command not found") ||
836
- textContent.includes("No such file")
837
- ) {
860
+ if (textContent.includes("command not found") || textContent.includes("No such file")) {
838
861
  exitCode = 1;
839
862
  }
840
863
  }
@@ -850,11 +873,10 @@ export default function piPrettyExtension(pi: any): void {
850
873
  },
851
874
 
852
875
  renderCall(args: any, theme: any, ctx: any) {
876
+ resolveBaseBackground(theme);
853
877
  const cmd = args?.command ?? "";
854
878
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
855
- const timeout = args?.timeout
856
- ? ` ${theme.fg("muted", `(${args.timeout}s timeout)`)}`
857
- : "";
879
+ const timeout = args?.timeout ? ` ${theme.fg("muted", `(${args.timeout}s timeout)`)}` : "";
858
880
  text.setText(
859
881
  `${theme.fg("toolTitle", theme.bold("bash"))} ${theme.fg("accent", cmd.length > 80 ? cmd.slice(0, 77) + "…" : cmd)}${timeout}`,
860
882
  );
@@ -862,13 +884,15 @@ export default function piPrettyExtension(pi: any): void {
862
884
  },
863
885
 
864
886
  renderResult(result: any, _opt: any, theme: any, ctx: any) {
887
+ resolveBaseBackground(theme);
865
888
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
866
889
 
867
890
  if (ctx.isError) {
868
- const e = result.content
869
- ?.filter((c: any) => c.type === "text")
870
- .map((c: any) => c.text || "")
871
- .join("\n") ?? "Error";
891
+ const e =
892
+ result.content
893
+ ?.filter((c: any) => c.type === "text")
894
+ .map((c: any) => c.text || "")
895
+ .join("\n") ?? "Error";
872
896
  text.setText(`\n${theme.fg("error", e)}`);
873
897
  return text;
874
898
  }
@@ -902,9 +926,7 @@ export default function piPrettyExtension(pi: any): void {
902
926
  }
903
927
 
904
928
  const fallback = result.content?.[0]?.text ?? "done";
905
- text.setText(
906
- ` ${theme.fg("dim", String(fallback).slice(0, 120))}`,
907
- );
929
+ text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
908
930
  return text;
909
931
  },
910
932
  });
@@ -930,9 +952,7 @@ export default function piPrettyExtension(pi: any): void {
930
952
  .join("\n");
931
953
 
932
954
  const fp = params.path ?? cwd;
933
- const entryCount = textContent
934
- ? textContent.trim().split("\n").filter(Boolean).length
935
- : 0;
955
+ const entryCount = textContent ? textContent.trim().split("\n").filter(Boolean).length : 0;
936
956
 
937
957
  (result as any).details = {
938
958
  _type: "lsResult",
@@ -945,22 +965,23 @@ export default function piPrettyExtension(pi: any): void {
945
965
  },
946
966
 
947
967
  renderCall(args: any, theme: any, ctx: any) {
968
+ resolveBaseBackground(theme);
948
969
  const fp = args?.path ?? ".";
949
970
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
950
- text.setText(
951
- `${theme.fg("toolTitle", theme.bold("ls"))} ${theme.fg("accent", sp(fp))}`,
952
- );
971
+ text.setText(`${theme.fg("toolTitle", theme.bold("ls"))} ${theme.fg("accent", sp(fp))}`);
953
972
  return text;
954
973
  },
955
974
 
956
975
  renderResult(result: any, _opt: any, theme: any, ctx: any) {
976
+ resolveBaseBackground(theme);
957
977
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
958
978
 
959
979
  if (ctx.isError) {
960
- const e = result.content
961
- ?.filter((c: any) => c.type === "text")
962
- .map((c: any) => c.text || "")
963
- .join("\n") ?? "Error";
980
+ const e =
981
+ result.content
982
+ ?.filter((c: any) => c.type === "text")
983
+ .map((c: any) => c.text || "")
984
+ .join("\n") ?? "Error";
964
985
  text.setText(`\n${theme.fg("error", e)}`);
965
986
  return text;
966
987
  }
@@ -974,9 +995,7 @@ export default function piPrettyExtension(pi: any): void {
974
995
  }
975
996
 
976
997
  const fallback = result.content?.[0]?.text ?? "listed";
977
- text.setText(
978
- ` ${theme.fg("dim", String(fallback).slice(0, 120))}`,
979
- );
998
+ text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
980
999
  return text;
981
1000
  },
982
1001
  });
@@ -1001,9 +1020,7 @@ export default function piPrettyExtension(pi: any): void {
1001
1020
  .map((c: any) => c.text || "")
1002
1021
  .join("\n");
1003
1022
 
1004
- const matchCount = textContent
1005
- ? textContent.trim().split("\n").filter(Boolean).length
1006
- : 0;
1023
+ const matchCount = textContent ? textContent.trim().split("\n").filter(Boolean).length : 0;
1007
1024
 
1008
1025
  (result as any).details = {
1009
1026
  _type: "findResult",
@@ -1016,23 +1033,24 @@ export default function piPrettyExtension(pi: any): void {
1016
1033
  },
1017
1034
 
1018
1035
  renderCall(args: any, theme: any, ctx: any) {
1036
+ resolveBaseBackground(theme);
1019
1037
  const pattern = args?.pattern ?? "";
1020
1038
  const path = args?.path ? ` ${theme.fg("muted", `in ${sp(args.path)}`)}` : "";
1021
1039
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
1022
- text.setText(
1023
- `${theme.fg("toolTitle", theme.bold("find"))} ${theme.fg("accent", pattern)}${path}`,
1024
- );
1040
+ text.setText(`${theme.fg("toolTitle", theme.bold("find"))} ${theme.fg("accent", pattern)}${path}`);
1025
1041
  return text;
1026
1042
  },
1027
1043
 
1028
1044
  renderResult(result: any, _opt: any, theme: any, ctx: any) {
1045
+ resolveBaseBackground(theme);
1029
1046
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
1030
1047
 
1031
1048
  if (ctx.isError) {
1032
- const e = result.content
1033
- ?.filter((c: any) => c.type === "text")
1034
- .map((c: any) => c.text || "")
1035
- .join("\n") ?? "Error";
1049
+ const e =
1050
+ result.content
1051
+ ?.filter((c: any) => c.type === "text")
1052
+ .map((c: any) => c.text || "")
1053
+ .join("\n") ?? "Error";
1036
1054
  text.setText(`\n${theme.fg("error", e)}`);
1037
1055
  return text;
1038
1056
  }
@@ -1046,9 +1064,7 @@ export default function piPrettyExtension(pi: any): void {
1046
1064
  }
1047
1065
 
1048
1066
  const fallback = result.content?.[0]?.text ?? "found";
1049
- text.setText(
1050
- ` ${theme.fg("dim", String(fallback).slice(0, 120))}`,
1051
- );
1067
+ text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
1052
1068
  return text;
1053
1069
  },
1054
1070
  });
@@ -1077,8 +1093,7 @@ export default function piPrettyExtension(pi: any): void {
1077
1093
  ? textContent
1078
1094
  .trim()
1079
1095
  .split("\n")
1080
- .filter((l: string) => l.match(/^.+?[:\-]\d+[:\-]/))
1081
- .length
1096
+ .filter((l: string) => l.match(/^.+?[:\-]\d+[:\-]/)).length
1082
1097
  : 0;
1083
1098
 
1084
1099
  (result as any).details = {
@@ -1092,24 +1107,25 @@ export default function piPrettyExtension(pi: any): void {
1092
1107
  },
1093
1108
 
1094
1109
  renderCall(args: any, theme: any, ctx: any) {
1110
+ resolveBaseBackground(theme);
1095
1111
  const pattern = args?.pattern ?? "";
1096
1112
  const path = args?.path ? ` ${theme.fg("muted", `in ${sp(args.path)}`)}` : "";
1097
1113
  const glob = args?.glob ? ` ${theme.fg("muted", `(${args.glob})`)}` : "";
1098
1114
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
1099
- text.setText(
1100
- `${theme.fg("toolTitle", theme.bold("grep"))} ${theme.fg("accent", pattern)}${path}${glob}`,
1101
- );
1115
+ text.setText(`${theme.fg("toolTitle", theme.bold("grep"))} ${theme.fg("accent", pattern)}${path}${glob}`);
1102
1116
  return text;
1103
1117
  },
1104
1118
 
1105
1119
  renderResult(result: any, _opt: any, theme: any, ctx: any) {
1120
+ resolveBaseBackground(theme);
1106
1121
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
1107
1122
 
1108
1123
  if (ctx.isError) {
1109
- const e = result.content
1110
- ?.filter((c: any) => c.type === "text")
1111
- .map((c: any) => c.text || "")
1112
- .join("\n") ?? "Error";
1124
+ const e =
1125
+ result.content
1126
+ ?.filter((c: any) => c.type === "text")
1127
+ .map((c: any) => c.text || "")
1128
+ .join("\n") ?? "Error";
1113
1129
  text.setText(`\n${theme.fg("error", e)}`);
1114
1130
  return text;
1115
1131
  }
@@ -1135,9 +1151,7 @@ export default function piPrettyExtension(pi: any): void {
1135
1151
  }
1136
1152
 
1137
1153
  const fallback = result.content?.[0]?.text ?? "searched";
1138
- text.setText(
1139
- ` ${theme.fg("dim", String(fallback).slice(0, 120))}`,
1140
- );
1154
+ text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
1141
1155
  return text;
1142
1156
  },
1143
1157
  });