pi-tui-status-beautifier 1.3.1 → 1.4.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.
@@ -22,6 +22,7 @@ export interface CustomStyleConfig {
22
22
  separator?: string;
23
23
  brackets?: [string, string];
24
24
  inverse?: boolean;
25
+ padName?: boolean;
25
26
  glyphs?: {
26
27
  success?: string;
27
28
  warning?: string;
@@ -31,21 +32,52 @@ export interface CustomStyleConfig {
31
32
  }
32
33
 
33
34
  const BUILTIN_PRESETS: Record<string, CustomStyleConfig> = {
34
- minimal: {
35
+ stream: {
35
36
  separator: " ❯ ",
36
37
  },
37
- glass: {
38
- prefix: "▕ ",
39
- suffix: " ▏",
38
+ powerline: {
39
+ brackets: ["‹", "›"],
40
+ separator: " ",
41
+ glyphs: {
42
+ success: "◆",
43
+ warning: "◌",
44
+ error: "▲",
45
+ accent: "◈",
46
+ },
47
+ },
48
+ matrix: {
49
+ brackets: ["⦗", "⦘"],
40
50
  separator: " ",
51
+ glyphs: {
52
+ success: "✦",
53
+ warning: "◍",
54
+ error: "✖",
55
+ accent: "✧",
56
+ },
41
57
  },
42
58
  glow: {
43
59
  brackets: ["[", "]"],
44
60
  inverse: true,
45
61
  },
62
+ // Backward-compatible aliases
63
+ minimal: {
64
+ separator: " ❯ ",
65
+ padName: true,
66
+ },
67
+ glass: {
68
+ brackets: ["‹", "›"],
69
+ separator: " ",
70
+ glyphs: {
71
+ success: "◆",
72
+ warning: "◌",
73
+ error: "▲",
74
+ accent: "◈",
75
+ },
76
+ },
46
77
  };
47
78
 
48
- const VALID_STYLES = ["minimal", "glass", "glow", "custom", "off"] as const;
79
+ const VALID_STYLES = ["stream", "powerline", "matrix", "glow", "minimal", "glass", "custom", "off"] as const;
80
+ const OFF_STYLES = new Set(["off", "default", "none", "raw", "vanilla", "disable", "disabled"]);
49
81
 
50
82
  // Store original statuses to allow dynamic redrawing on configuration changes.
51
83
  const originalStatuses = new Map<string, string | undefined>();
@@ -92,8 +124,12 @@ export function loadConfig(cwd: string = process.cwd()): {
92
124
  const data = JSON.parse(fs.readFileSync(globalPath, "utf-8"));
93
125
  if (data.beautifier) {
94
126
  if (typeof data.beautifier.style === "string") {
95
- const s = data.beautifier.style;
96
- if ((VALID_STYLES as readonly string[]).includes(s)) resolvedStyle = s;
127
+ const s = data.beautifier.style.toLowerCase().trim();
128
+ if (OFF_STYLES.has(s)) {
129
+ resolvedStyle = "off";
130
+ } else if ((VALID_STYLES as readonly string[]).includes(s)) {
131
+ resolvedStyle = s;
132
+ }
97
133
  }
98
134
  if (data.beautifier.custom && typeof data.beautifier.custom === "object") {
99
135
  resolvedCustom = data.beautifier.custom;
@@ -109,8 +145,12 @@ export function loadConfig(cwd: string = process.cwd()): {
109
145
  if (data.beautifier) {
110
146
  hasLocalConfig = true;
111
147
  if (typeof data.beautifier.style === "string") {
112
- const s = data.beautifier.style;
113
- if ((VALID_STYLES as readonly string[]).includes(s)) resolvedStyle = s;
148
+ const s = data.beautifier.style.toLowerCase().trim();
149
+ if (OFF_STYLES.has(s)) {
150
+ resolvedStyle = "off";
151
+ } else if ((VALID_STYLES as readonly string[]).includes(s)) {
152
+ resolvedStyle = s;
153
+ }
114
154
  }
115
155
  if (data.beautifier.custom && typeof data.beautifier.custom === "object") {
116
156
  resolvedCustom = { ...(resolvedCustom || {}), ...data.beautifier.custom };
@@ -138,7 +178,7 @@ export function beautifyStatus(
138
178
  customCfg?: CustomStyleConfig
139
179
  ): string | undefined {
140
180
  if (originalVal === undefined) return undefined;
141
- if (style === "off") return originalVal;
181
+ if (style === "off" || OFF_STYLES.has((style || "").toLowerCase().trim())) return originalVal;
142
182
 
143
183
  try {
144
184
  const hasAnsi = originalVal.includes("\u001B") || originalVal.includes("\u009B");
@@ -150,7 +190,7 @@ export function beautifyStatus(
150
190
  return originalVal;
151
191
  }
152
192
 
153
- // 1. Get display name
193
+ // 1. Get display name (compact natural width by default to save precious terminal columns)
154
194
  let name = key.toLowerCase();
155
195
  if (COMMON_NAMES[name]) {
156
196
  name = COMMON_NAMES[name];
@@ -164,7 +204,14 @@ export function beautifyStatus(
164
204
  name = COMMON_NAMES[name];
165
205
  }
166
206
  }
167
- const visualName = padToVisualWidth(sliceToVisualWidth(name, 10), 10);
207
+ const preset =
208
+ style === "custom"
209
+ ? (customCfg || customStyleConfig || {})
210
+ : (BUILTIN_PRESETS[style] || BUILTIN_PRESETS.stream);
211
+
212
+ const visualName = preset.padName === true
213
+ ? padToVisualWidth(sliceToVisualWidth(name, 10), 10)
214
+ : name;
168
215
 
169
216
  // 2. Extract metrics/progress details safely (e.g. (2) or fraction 2/5 or count)
170
217
  let details: string | undefined;
@@ -179,6 +226,14 @@ export function beautifyStatus(
179
226
  const numberMatch = cleanVal.match(NUMBER_PATTERN);
180
227
  if (numberMatch) {
181
228
  details = numberMatch[1];
229
+ } else {
230
+ // If no numeric metric exists, extract the status word descriptor (e.g. "ready", "idle", "cleanup")
231
+ const wordMatch = cleanVal.match(
232
+ /(?::\s*|\b)(ready|idle|ok|done|failed|waiting|cleanup|active|online|offline|running|paused|error|success|connected|disconnected)\b/i
233
+ );
234
+ if (wordMatch) {
235
+ details = wordMatch[1].toLowerCase();
236
+ }
182
237
  }
183
238
  }
184
239
  }
@@ -226,11 +281,6 @@ export function beautifyStatus(
226
281
  }
227
282
 
228
283
  // 4. Determine state glyph & indicator
229
- const preset =
230
- style === "custom"
231
- ? (customCfg || customStyleConfig || {})
232
- : (BUILTIN_PRESETS[style] || BUILTIN_PRESETS.minimal);
233
-
234
284
  const glyphMap = preset.glyphs || {};
235
285
  let glyph =
236
286
  glyphMap[state] ||
@@ -484,7 +534,7 @@ export default function (pi: ExtensionAPI) {
484
534
  originalStatuses.clear();
485
535
  lastRenderedStatus.clear();
486
536
 
487
- // 1. Hook setStatus for granular extension badge formatting
537
+ // Hook setStatus for clean status badge formatting (never hijack Pi's native footer)
488
538
  const originalSetStatus = ctx.ui.setStatus;
489
539
  if (originalSetStatus && !(originalSetStatus as any).__beautifierHooked) {
490
540
  const wrapped = function (key: string, value: string | undefined) {
@@ -503,25 +553,6 @@ export default function (pi: ExtensionAPI) {
503
553
  (wrapped as any).__beautifierHooked = true;
504
554
  ctx.ui.setStatus = wrapped;
505
555
  }
506
-
507
- // 2. Hook setFooter for full-footer beautification
508
- if (typeof (ctx.ui as any).setFooter === "function") {
509
- (ctx.ui as any).setFooter((tui: any, theme: any, footerData: any) => {
510
- const unsub =
511
- typeof footerData?.onBranchChange === "function"
512
- ? footerData.onBranchChange(() => tui.requestRender())
513
- : undefined;
514
-
515
- return {
516
- dispose: unsub,
517
- invalidate() {},
518
- render(width: number): string[] {
519
- if (currentStyle === "off") return [];
520
- return renderBeautifiedFooter(width, tui, theme, footerData, ctx, currentStyle, customStyleConfig);
521
- },
522
- };
523
- });
524
- }
525
556
  });
526
557
 
527
558
  // Dynamic status bar style controller registry
@@ -533,11 +564,12 @@ export default function (pi: ExtensionAPI) {
533
564
  }
534
565
 
535
566
  const styleOptions = [
536
- "custom - [AI Prompt Recipe] Let your agent generate or tailor your own style",
537
- "minimal - Clean chevron indicator (e.g. name ❯ ●)",
538
- "glass - Framed capsule badge (e.g. ▕ name ● ▏)",
539
- "glow - High-contrast inverse pill (e.g. name [ ● ])",
540
- "off - Pass through raw status output",
567
+ "stream - Clean chevron stream (e.g. name ❯ ● ready)",
568
+ "powerline - Angled brackets & diamonds (e.g. name ‹◆ ready›)",
569
+ "matrix - Dot-matrix brackets & stars (e.g. name ⦗✦ ready⦘)",
570
+ "glow - High-contrast inverse pill (e.g. name [ ● ready ])",
571
+ "custom - [AI Prompt Recipe] Let your agent tailor your bespoke style",
572
+ "off - Revert to native Pi status bar & clean settings",
541
573
  ];
542
574
 
543
575
  const choice = await ctx.ui.select(`Choose status style (Current: ${currentStyle}):`, styleOptions);
@@ -546,24 +578,61 @@ export default function (pi: ExtensionAPI) {
546
578
 
547
579
  const styleKey = choice.split(" ")[0].trim();
548
580
 
581
+ if (styleKey === "off") {
582
+ currentStyle = "off";
583
+ customStyleConfig = undefined;
584
+ lastRenderedStatus.clear();
585
+
586
+ // Restore all raw original statuses immediately
587
+ for (const [key, val] of originalStatuses.entries()) {
588
+ ctx.ui.setStatus(key, val);
589
+ }
590
+
591
+ // Zero-trace physical cleanup: completely prune the `beautifier` key from settings.json!
592
+ const { targetSettingsPath } = loadConfig(ctx.cwd || process.cwd());
593
+ try {
594
+ if (fs.existsSync(targetSettingsPath)) {
595
+ const data = fs.readFileSync(targetSettingsPath, "utf-8");
596
+ const config = JSON.parse(data);
597
+ if (config.beautifier) {
598
+ delete config.beautifier;
599
+ const tempSettings = `${targetSettingsPath}.tmp.${process.pid}.${Date.now()}`;
600
+ fs.writeFileSync(tempSettings, JSON.stringify(config, null, 2), "utf-8");
601
+ fs.renameSync(tempSettings, targetSettingsPath);
602
+ }
603
+ }
604
+ } catch (_e) {
605
+ // Silently ignore disk write errors
606
+ }
607
+
608
+ ctx.ui.notify("Reverted to native Pi status bar. Cleaned up settings.json.", "info");
609
+ return;
610
+ }
611
+
549
612
  if (styleKey === "custom") {
550
613
  const promptRecipe = [
551
614
  "You are acting as my Pi TUI Status Bar designer for `pi-tui-status-beautifier`.",
552
- "1. In my primary language, call `ask_user_question` with ASCII visual preview mockups (e.g. Cyber Powerline ‹●›, Nordic Minimalist │ ◈, Capsule Pill [●], Tokyo Night » ✦) to consult my visual taste.",
553
- "2. Once I select or describe my aesthetic, save the configuration directly into `./.pi/settings.json` (or `~/.pi/agent/settings.json`) under `beautifier`:",
615
+ "1. In my primary language, call `ask_user_question` with ASCII visual preview mockups showing ONLY realistic extension statuses (e.g. `chrome`, `toolflow`, `plan`):",
616
+ " - Powerline: chrome ‹◆ ready› | toolflow ‹◈ 2/5›",
617
+ " - Matrix: chrome ⦗✦ ready⦘ | toolflow ⦗◍ 2/5⦘",
618
+ " - Stream: chrome ❯ ● ready | toolflow ❯ ◐ 2/5",
619
+ " - Glow: chrome [ ● ready ] | toolflow [ ◐ 2/5 ]",
620
+ " (Do NOT include git branch or model name in mockups).",
621
+ "2. If I describe or pick a style, write the configuration into `./.pi/settings.json` (or `~/.pi/agent/settings.json`) under `beautifier`:",
554
622
  " {",
555
623
  ' "beautifier": {',
556
624
  ' "style": "custom",',
557
625
  ' "custom": {',
558
626
  ' "separator": " » ",',
559
627
  ' "brackets": ["«", "»"],',
560
- ' "prefix": "✦ ",',
628
+ ' "prefix": "",',
561
629
  ' "suffix": "",',
562
630
  ' "glyphs": { "success": "✦", "warning": "◇", "error": "✖", "accent": "✧" }',
563
631
  " }",
564
632
  " }",
565
633
  " }",
566
- "Do NOT crawl filesystem or edit extension source code files."
634
+ "3. If I say revert, restore, default, off, or cancel, DELETE the `beautifier` key from `settings.json` and exit quietly without asking more questions.",
635
+ "4. Do NOT search filesystem or edit extension source files."
567
636
  ].join("\n");
568
637
 
569
638
  if (typeof (ctx.ui as any).setEditorText === "function") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-tui-status-beautifier",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "Minimalist status bar formatter & AI-customizable layout engine for Pi TUI.",
5
5
  "main": "extensions/tui-status-beautifier.ts",
6
6
  "keywords": [