@xynogen/pix-pretty 1.7.10 → 1.7.13

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
@@ -61,7 +61,24 @@ pi install npm:@xynogen/pix-pretty
61
61
 
62
62
  ## Configuration
63
63
 
64
- ### Environment Variables
64
+ Configuration is read from **`~/.pi/agent/pix.json`** (the unified config file hosted by `@xynogen/pix-data/pix-config`). The `pretty` section of that file sets the defaults for theme, icon mode, and preview lines. Environment variables still override `pix.json` values.
65
+
66
+ > **Note:** `pix-config.ts` and `collapse.ts` previously shipped with `pix-pretty` — they have moved to `pix-data` (`@xynogen/pix-data/pix-config` and `@xynogen/pix-data/collapse`). Update any direct imports.
67
+
68
+ ### `pix.json` — `pretty` section
69
+
70
+ ```jsonc
71
+ {
72
+ "pretty": {
73
+ "theme": "monokai", // syntax-highlight theme
74
+ "icons": "nerd", // nerd | unicode | ascii
75
+ "maxPreviewLines": 50,
76
+ "diffColors": true
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### Environment Variables (override `pix.json`)
65
82
 
66
83
  - `PRETTY_THEME` — color theme for syntax highlighting
67
84
  - `PRETTY_MAX_HL_CHARS` — max characters to highlight (default: 80000)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-pretty",
3
- "version": "1.7.10",
3
+ "version": "1.7.13",
4
4
  "description": "Enhanced tool output rendering with syntax highlighting, file icons, tree views, diff rendering, and FFF search",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -58,6 +58,7 @@
58
58
  "access": "public"
59
59
  },
60
60
  "dependencies": {
61
+ "@xynogen/pix-data": "workspace:*",
61
62
  "cli-highlight": "^2.1.11",
62
63
  "@ff-labs/fff-node": "^0.5.2",
63
64
  "diff": "^7.0.0"
package/src/ansi.ts CHANGED
@@ -23,7 +23,7 @@ function parseAnsiRgb(
23
23
  const m = ansi.match(
24
24
  new RegExp(`${ESC_RE}\\[(?:38|48);2;(\\d+);(\\d+);(\\d+)m`),
25
25
  );
26
- return m ? { r: +m[1], g: +m[2], b: +m[3] } : null;
26
+ return m ? { r: +(m[1] ?? 0), g: +(m[2] ?? 0), b: +(m[3] ?? 0) } : null;
27
27
  }
28
28
 
29
29
  function getThemeBgAnsi(theme: BgTheme, key: string): string | null {
@@ -63,7 +63,7 @@ function isLowContrastShikiFg(params: string): boolean {
63
63
  const parts = params.split(";").map(Number);
64
64
  if (parts.length !== 5 || parts.some((n) => !Number.isFinite(n)))
65
65
  return false;
66
- const [, , r, g, b] = parts;
66
+ const [, , r = 0, g = 0, b = 0] = parts;
67
67
  const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
68
68
  return luminance < 72;
69
69
  }
@@ -62,7 +62,7 @@ export function registerPrettyCommand(pi: PiPrettyApi): void {
62
62
  // Headless / no custom-UI host: cycle to the next mode + notify.
63
63
  if (typeof ui.custom !== "function") {
64
64
  const cur = ICON_MODES.indexOf(getIconMode());
65
- const next = ICON_MODES[(cur + 1) % ICON_MODES.length] as IconMode;
65
+ const next = ICON_MODES[(cur + 1) % ICON_MODES.length] ?? "nerd";
66
66
  applyMode(next);
67
67
  ui.notify(`pix-pretty icons: ${next}`, "info");
68
68
  return;
@@ -82,7 +82,7 @@ export function registerPrettyCommand(pi: PiPrettyApi): void {
82
82
 
83
83
  const choose = (i: number) => {
84
84
  selected = (i + ICON_MODES.length) % ICON_MODES.length;
85
- applyMode(ICON_MODES[selected] as IconMode);
85
+ applyMode(ICON_MODES[selected] ?? "nerd");
86
86
  };
87
87
 
88
88
  return {
package/src/config.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
+ import { pixConfig } from "@xynogen/pix-data/pix-config";
4
5
  import type { BundledTheme } from "./types.js";
5
6
 
6
7
  const DEFAULT_THEME: BundledTheme = "github-dark";
@@ -29,8 +30,10 @@ function readThemeFromSettings(agentDir?: string): BundledTheme | undefined {
29
30
  }
30
31
 
31
32
  function resolvePrettyTheme(agentDir?: string): BundledTheme {
33
+ // Precedence: env → pix.json → settings.json → default
32
34
  return (
33
35
  (process.env.PRETTY_THEME as BundledTheme | undefined) ??
36
+ (pixConfig().pretty.theme as BundledTheme) ??
34
37
  readThemeFromSettings(agentDir) ??
35
38
  DEFAULT_THEME
36
39
  );
@@ -49,14 +52,46 @@ export function envInt(name: string, fallback: number): number {
49
52
  return Number.isFinite(v) && v > 0 ? v : fallback;
50
53
  }
51
54
 
52
- export const MAX_HL_CHARS = envInt("PRETTY_MAX_HL_CHARS", 80_000);
55
+ // Precedence for numeric config: env var → pix.json → hardcoded default
56
+ function pixOrEnvInt(
57
+ envName: string,
58
+ pixValue: number,
59
+ fallback: number,
60
+ ): number {
61
+ const env = process.env[envName];
62
+ if (env) {
63
+ const v = Number.parseInt(env, 10);
64
+ if (Number.isFinite(v) && v > 0) return v;
65
+ }
66
+ return pixValue !== fallback ? pixValue : fallback;
67
+ }
68
+
69
+ const pc = pixConfig().pretty;
70
+
71
+ export const MAX_HL_CHARS = pixOrEnvInt(
72
+ "PRETTY_MAX_HL_CHARS",
73
+ pc.maxHighlightChars,
74
+ 80_000,
75
+ );
53
76
 
54
- export const MAX_PREVIEW_LINES = envInt("PRETTY_MAX_PREVIEW_LINES", 80);
77
+ export const MAX_PREVIEW_LINES = pixOrEnvInt(
78
+ "PRETTY_MAX_PREVIEW_LINES",
79
+ pc.maxPreviewLines,
80
+ 80,
81
+ );
55
82
 
56
- export const CACHE_LIMIT = envInt("PRETTY_CACHE_LIMIT", 128);
83
+ export const CACHE_LIMIT = pixOrEnvInt(
84
+ "PRETTY_CACHE_LIMIT",
85
+ pc.cacheLimit,
86
+ 128,
87
+ );
57
88
 
58
89
  // --- Diff rendering limits (edit/write tools) ---
59
- export const MAX_RENDER_LINES = envInt("PRETTY_MAX_RENDER_LINES", 150);
90
+ export const MAX_RENDER_LINES = pixOrEnvInt(
91
+ "PRETTY_MAX_RENDER_LINES",
92
+ pc.maxRenderLines,
93
+ 150,
94
+ );
60
95
 
61
96
  // Word-level emphasis only when paired del/add lines are at least this similar.
62
97
  export const WORD_DIFF_MIN_SIM = 0.15;
@@ -8,6 +8,7 @@
8
8
  // technique below works unchanged — diff backgrounds layer underneath and
9
9
  // persist through fg switches.
10
10
 
11
+ import { pixConfig } from "@xynogen/pix-data/pix-config";
11
12
  import * as Diff from "diff";
12
13
  import { BG_BASE, BOLD, FG_DIM, FG_LNUM, FG_RULE, RST } from "./ansi.js";
13
14
  import { MAX_HL_CHARS, MAX_RENDER_LINES, WORD_DIFF_MIN_SIM } from "./config.js";
@@ -44,21 +45,58 @@ function envBg(name: string, fallback: string): string {
44
45
  }
45
46
 
46
47
  // ---------------------------------------------------------------------------
47
- // Diff-specific ANSI (override via env, hex "#RRGGBB")
48
+ // Diff-specific ANSI (override via env pix.json → hardcoded)
48
49
  // ---------------------------------------------------------------------------
49
50
 
50
51
  const DIM = "\x1b[2m";
51
52
 
53
+ function hexToBg(hex: string): string {
54
+ if (!/^#[0-9a-fA-F]{6}$/.test(hex)) return "";
55
+ const r = Number.parseInt(hex.slice(1, 3), 16);
56
+ const g = Number.parseInt(hex.slice(3, 5), 16);
57
+ const b = Number.parseInt(hex.slice(5, 7), 16);
58
+ return `\x1b[48;2;${r};${g};${b}m`;
59
+ }
60
+
61
+ function hexToFg(hex: string): string {
62
+ if (!/^#[0-9a-fA-F]{6}$/.test(hex)) return "";
63
+ const r = Number.parseInt(hex.slice(1, 3), 16);
64
+ const g = Number.parseInt(hex.slice(3, 5), 16);
65
+ const b = Number.parseInt(hex.slice(5, 7), 16);
66
+ return `\x1b[38;2;${r};${g};${b}m`;
67
+ }
68
+
69
+ const dc = pixConfig().pretty.diff;
70
+
52
71
  // Subtle diff backgrounds — muted tones to let syntax fg shine through.
53
- const BG_ADD = envBg("DIFF_BG_ADD", "\x1b[48;2;22;38;32m"); // muted teal-green
54
- const BG_DEL = envBg("DIFF_BG_DEL", "\x1b[48;2;45;25;25m"); // muted brown-red
55
- const BG_ADD_W = envBg("DIFF_BG_ADD_HL", "\x1b[48;2;35;75;50m"); // word emphasis
56
- const BG_DEL_W = envBg("DIFF_BG_DEL_HL", "\x1b[48;2;80;35;35m");
57
- const BG_GUTTER_ADD = envBg("DIFF_BG_GUTTER_ADD", "\x1b[48;2;18;32;26m");
58
- const BG_GUTTER_DEL = envBg("DIFF_BG_GUTTER_DEL", "\x1b[48;2;38;22;22m");
59
-
60
- const FG_ADD = envFg("DIFF_FG_ADD", "\x1b[38;2;100;180;120m"); // desaturated green
61
- const FG_DEL = envFg("DIFF_FG_DEL", "\x1b[38;2;200;100;100m"); // desaturated red
72
+ // Precedence: env pix.json hardcoded default
73
+ const BG_ADD = envBg("DIFF_BG_ADD", hexToBg(dc.bgAdd) || "\x1b[48;2;22;38;32m");
74
+ const BG_DEL = envBg("DIFF_BG_DEL", hexToBg(dc.bgDel) || "\x1b[48;2;45;25;25m");
75
+ const BG_ADD_W = envBg(
76
+ "DIFF_BG_ADD_HL",
77
+ hexToBg(dc.bgAddHighlight) || "\x1b[48;2;35;75;50m",
78
+ );
79
+ const BG_DEL_W = envBg(
80
+ "DIFF_BG_DEL_HL",
81
+ hexToBg(dc.bgDelHighlight) || "\x1b[48;2;80;35;35m",
82
+ );
83
+ const BG_GUTTER_ADD = envBg(
84
+ "DIFF_BG_GUTTER_ADD",
85
+ hexToBg(dc.bgGutterAdd) || "\x1b[48;2;18;32;26m",
86
+ );
87
+ const BG_GUTTER_DEL = envBg(
88
+ "DIFF_BG_GUTTER_DEL",
89
+ hexToBg(dc.bgGutterDel) || "\x1b[48;2;38;22;22m",
90
+ );
91
+
92
+ const FG_ADD = envFg(
93
+ "DIFF_FG_ADD",
94
+ hexToFg(dc.fgAdd) || "\x1b[38;2;100;180;120m",
95
+ );
96
+ const FG_DEL = envFg(
97
+ "DIFF_FG_DEL",
98
+ hexToFg(dc.fgDel) || "\x1b[38;2;200;100;100m",
99
+ );
62
100
  const FG_STRIPE = "\x1b[38;2;40;40;40m"; // diagonal stripes on filler cells
63
101
 
64
102
  const BORDER_BAR = "▌";
@@ -76,8 +114,11 @@ const MAX_TERM_WIDTH = 210;
76
114
 
77
115
  const MAX_PREVIEW_LINES = envInt("PRETTY_MAX_PREVIEW_LINES", 80);
78
116
 
79
- const SPLIT_MIN_WIDTH = envInt("DIFF_SPLIT_MIN_WIDTH", 150);
80
- const SPLIT_MIN_CODE_WIDTH = envInt("DIFF_SPLIT_MIN_CODE_WIDTH", 60);
117
+ const SPLIT_MIN_WIDTH = envInt("DIFF_SPLIT_MIN_WIDTH", dc.splitMinWidth || 150);
118
+ const SPLIT_MIN_CODE_WIDTH = envInt(
119
+ "DIFF_SPLIT_MIN_CODE_WIDTH",
120
+ dc.splitMinCodeWidth || 60,
121
+ );
81
122
  const SPLIT_MAX_WRAP_RATIO = 0.2;
82
123
  const SPLIT_MAX_WRAP_LINES = 8;
83
124
 
@@ -464,9 +505,12 @@ function injectBg(
464
505
  continue;
465
506
  }
466
507
  }
467
- while (ri < ranges.length && vis >= ranges[ri][1]) ri++;
508
+ while (ri < ranges.length && vis >= (ranges[ri] as [number, number])[1])
509
+ ri++;
468
510
  const want =
469
- ri < ranges.length && vis >= ranges[ri][0] && vis < ranges[ri][1];
511
+ ri < ranges.length &&
512
+ vis >= (ranges[ri] as [number, number])[0] &&
513
+ vis < (ranges[ri] as [number, number])[1];
470
514
  if (want !== inHL) {
471
515
  inHL = want;
472
516
  out += inHL ? hlBg : baseBg;
@@ -497,6 +541,12 @@ function plainWordDiff(
497
541
  return { old: o, new: n };
498
542
  }
499
543
 
544
+ /** Type-safe index into an array that noUncheckedIndexedAccess marks as T|undefined.
545
+ * Only call when the index is provably in-bounds (loop condition, length check, etc.). */
546
+ function at<T>(arr: T[], i: number): T {
547
+ return arr[i] as T;
548
+ }
549
+
500
550
  // ---------------------------------------------------------------------------
501
551
  // Split-vs-unified decision
502
552
  // ---------------------------------------------------------------------------
@@ -605,7 +655,7 @@ export async function renderUnified(
605
655
  }
606
656
 
607
657
  while (idx < vis.length) {
608
- const l = vis[idx];
658
+ const l = at(vis, idx);
609
659
 
610
660
  if (l.type === "sep") {
611
661
  const gap = l.newNum;
@@ -638,29 +688,35 @@ export async function renderUnified(
638
688
  }
639
689
 
640
690
  const dels: Array<{ l: DiffLine; hl: string }> = [];
641
- while (idx < vis.length && vis[idx].type === "del") {
642
- dels.push({ l: vis[idx], hl: oldHL[oI] ?? vis[idx].content });
691
+ while (idx < vis.length) {
692
+ const entry = at(vis, idx);
693
+ if (entry.type !== "del") break;
694
+ dels.push({ l: entry, hl: oldHL[oI] ?? entry.content });
643
695
  oI++;
644
696
  idx++;
645
697
  }
646
698
  const adds: Array<{ l: DiffLine; hl: string }> = [];
647
- while (idx < vis.length && vis[idx].type === "add") {
648
- adds.push({ l: vis[idx], hl: newHL[nI] ?? vis[idx].content });
699
+ while (idx < vis.length) {
700
+ const entry = at(vis, idx);
701
+ if (entry.type !== "add") break;
702
+ adds.push({ l: entry, hl: newHL[nI] ?? entry.content });
649
703
  nI++;
650
704
  idx++;
651
705
  }
652
706
 
653
707
  const isPaired = dels.length === 1 && adds.length === 1;
654
708
  const wd = isPaired
655
- ? wordDiffAnalysis(dels[0].l.content, adds[0].l.content)
709
+ ? wordDiffAnalysis(at(dels, 0).l.content, at(adds, 0).l.content)
656
710
  : null;
657
711
  const wdBalanced = wd && wd.oldRanges.length > 0 && wd.newRanges.length > 0;
658
712
 
659
713
  if (isPaired && wdBalanced && wd.similarity >= WORD_DIFF_MIN_SIM && canHL) {
660
- const delBody = injectBg(dels[0].hl, wd.oldRanges, BG_DEL, BG_DEL_W);
661
- const addBody = injectBg(adds[0].hl, wd.newRanges, BG_ADD, BG_ADD_W);
714
+ const del0 = at(dels, 0);
715
+ const add0 = at(adds, 0);
716
+ const delBody = injectBg(del0.hl, wd.oldRanges, BG_DEL, BG_DEL_W);
717
+ const addBody = injectBg(add0.hl, wd.newRanges, BG_ADD, BG_ADD_W);
662
718
  emitRow(
663
- dels[0].l.oldNum,
719
+ del0.l.oldNum,
664
720
  "-",
665
721
  BG_GUTTER_DEL,
666
722
  `${dc.fgDel}${BOLD}`,
@@ -668,7 +724,7 @@ export async function renderUnified(
668
724
  BG_DEL,
669
725
  );
670
726
  emitRow(
671
- adds[0].l.newNum,
727
+ add0.l.newNum,
672
728
  "+",
673
729
  BG_GUTTER_ADD,
674
730
  `${dc.fgAdd}${BOLD}`,
@@ -683,9 +739,11 @@ export async function renderUnified(
683
739
  wd.similarity >= WORD_DIFF_MIN_SIM &&
684
740
  !canHL
685
741
  ) {
686
- const pwd = plainWordDiff(dels[0].l.content, adds[0].l.content);
742
+ const del0 = at(dels, 0);
743
+ const add0 = at(adds, 0);
744
+ const pwd = plainWordDiff(del0.l.content, add0.l.content);
687
745
  emitRow(
688
- dels[0].l.oldNum,
746
+ del0.l.oldNum,
689
747
  "-",
690
748
  BG_GUTTER_DEL,
691
749
  `${dc.fgDel}${BOLD}`,
@@ -693,7 +751,7 @@ export async function renderUnified(
693
751
  BG_DEL,
694
752
  );
695
753
  emitRow(
696
- adds[0].l.newNum,
754
+ add0.l.newNum,
697
755
  "+",
698
756
  BG_GUTTER_ADD,
699
757
  `${dc.fgAdd}${BOLD}`,
@@ -755,7 +813,7 @@ export async function renderSplit(
755
813
  const rows: Row[] = [];
756
814
  let i = 0;
757
815
  while (i < diff.lines.length) {
758
- const l = diff.lines[i];
816
+ const l = at(diff.lines, i);
759
817
  if (l.type === "sep" || l.type === "ctx") {
760
818
  rows.push({ left: l, right: l });
761
819
  i++;
@@ -763,12 +821,16 @@ export async function renderSplit(
763
821
  }
764
822
  const dels: DiffLine[] = [];
765
823
  const adds: DiffLine[] = [];
766
- while (i < diff.lines.length && diff.lines[i].type === "del") {
767
- dels.push(diff.lines[i]);
824
+ while (i < diff.lines.length) {
825
+ const entry = at(diff.lines, i);
826
+ if (entry.type !== "del") break;
827
+ dels.push(entry);
768
828
  i++;
769
829
  }
770
- while (i < diff.lines.length && diff.lines[i].type === "add") {
771
- adds.push(diff.lines[i]);
830
+ while (i < diff.lines.length) {
831
+ const entry = at(diff.lines, i);
832
+ if (entry.type !== "add") break;
833
+ adds.push(entry);
772
834
  i++;
773
835
  }
774
836
  const n = Math.max(dels.length, adds.length);
package/src/diff.ts CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  import * as Diff from "diff";
6
6
 
7
+ /** Type-safe index into an array that noUncheckedIndexedAccess marks as T|undefined.
8
+ * Only call when the index is provably in-bounds (loop condition, length check, etc.). */
9
+ function at<T>(arr: T[], i: number): T {
10
+ return arr[i] as T;
11
+ }
12
+
7
13
  export interface DiffLine {
8
14
  type: "add" | "del" | "ctx" | "sep";
9
15
  oldNum: number | null;
@@ -35,8 +41,9 @@ export function parseDiff(
35
41
 
36
42
  for (let hi = 0; hi < patch.hunks.length; hi++) {
37
43
  if (hi > 0) {
38
- const prev = patch.hunks[hi - 1];
39
- const gap = patch.hunks[hi].oldStart - (prev.oldStart + prev.oldLines);
44
+ const prev = at(patch.hunks, hi - 1);
45
+ const gap =
46
+ at(patch.hunks, hi).oldStart - (prev.oldStart + prev.oldLines);
40
47
  lines.push({
41
48
  type: "sep",
42
49
  oldNum: null,
@@ -44,7 +51,7 @@ export function parseDiff(
44
51
  content: "",
45
52
  });
46
53
  }
47
- const h = patch.hunks[hi];
54
+ const h = at(patch.hunks, hi);
48
55
  const shift = baseLine > 0 ? baseLine - 1 : 0;
49
56
  let oL = h.oldStart + shift;
50
57
  let nL = h.newStart + shift;
@@ -74,6 +74,9 @@ const CATALOG = {
74
74
  "opt.toon": { nerd: "\u{F05C0}", unicode: `\u2662${VS}`, ascii: "Tn" },
75
75
  "opt.ponytail": { nerd: "\u{F0190}", unicode: `\u2667${VS}`, ascii: "Pt" },
76
76
  "opt.title": { nerd: "\u{F0DAB}", unicode: `\u25C8${VS}`, ascii: "*" },
77
+
78
+ // ── subagent widget (pix-subagent) ────────────────────────────────────
79
+ agent: { nerd: "\u{F0BA0}", unicode: `\u2699${VS}`, ascii: "@" },
77
80
  } as const;
78
81
 
79
82
  /** Every valid semantic icon key. */
@@ -14,8 +14,13 @@
14
14
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
15
  import { dirname, join } from "node:path";
16
16
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
17
+ import { pixConfig } from "@xynogen/pix-data/pix-config";
17
18
  import { ICON_MODES, type IconMode, setIconMode } from "./icon-catalog.js";
18
19
 
20
+ function isIconMode(m: string): m is IconMode {
21
+ return (ICON_MODES as readonly string[]).includes(m);
22
+ }
23
+
19
24
  function statePath(): string {
20
25
  return join(getAgentDir(), "pretty.json");
21
26
  }
@@ -27,9 +32,8 @@ export function loadIconMode(): IconMode | undefined {
27
32
  if (!existsSync(p)) return undefined;
28
33
  const raw = JSON.parse(readFileSync(p, "utf-8")) as { icons?: string };
29
34
  const mode = raw?.icons;
30
- return ICON_MODES.includes(mode as IconMode)
31
- ? (mode as IconMode)
32
- : undefined;
35
+ if (mode == null) return undefined;
36
+ return isIconMode(mode) ? mode : undefined;
33
37
  } catch {
34
38
  return undefined;
35
39
  }
@@ -60,8 +64,16 @@ export function saveIconMode(mode: IconMode): void {
60
64
  /**
61
65
  * Apply the persisted mode (if any) to the catalog. Called once at extension
62
66
  * load so the env-seeded default is overridden by the user's saved choice.
67
+ *
68
+ * Precedence: env PRETTY_ICONS → pretty.json → pix.json pretty.icons → default ("nerd")
63
69
  */
64
70
  export function initIconMode(): void {
65
71
  const saved = loadIconMode();
66
- if (saved) setIconMode(saved);
72
+ if (saved) {
73
+ setIconMode(saved);
74
+ return;
75
+ }
76
+ // No persisted choice — try pix.json
77
+ const pixIcons = pixConfig().pretty.icons;
78
+ if (pixIcons && isIconMode(pixIcons)) setIconMode(pixIcons);
67
79
  }
package/src/progress.ts CHANGED
@@ -89,7 +89,7 @@ export function openProgress(
89
89
  width: mw,
90
90
  lines: [
91
91
  theme.fg(accent, theme.bold(title)),
92
- `${theme.fg(accent, SPINNER[frame])} ${theme.fg("muted", labelValue)}`,
92
+ `${theme.fg(accent, SPINNER[frame] ?? "")} ${theme.fg("muted", labelValue)}`,
93
93
  ],
94
94
  color: (s) => theme.fg(accent, s),
95
95
  bg: (s) => theme.bg("customMessageBg", s),
package/src/renderers.ts CHANGED
@@ -92,7 +92,7 @@ export function renderTree(text: string, _basePath: string): string {
92
92
  const show = lines.slice(0, MAX_PREVIEW_LINES);
93
93
 
94
94
  for (let i = 0; i < show.length; i++) {
95
- const entry = show[i].trim();
95
+ const entry = (show[i] ?? "").trim();
96
96
  const isLast = i === show.length - 1 && total <= MAX_PREVIEW_LINES;
97
97
  const prefix = isLast ? "└── " : "├── ";
98
98
  const connector = `${FG_RULE}${prefix}${RST}`;