pi-tui-status-beautifier 1.2.1 → 1.3.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.
@@ -13,7 +13,36 @@ const COMMON_NAMES: Record<string, string> = {
13
13
  "wechat-assistant": "wechat",
14
14
  };
15
15
 
16
- const VALID_STYLES = ["minimal", "glass", "glow", "off"] as const;
16
+ export interface CustomStyleConfig {
17
+ prefix?: string;
18
+ suffix?: string;
19
+ separator?: string;
20
+ brackets?: [string, string];
21
+ inverse?: boolean;
22
+ glyphs?: {
23
+ success?: string;
24
+ warning?: string;
25
+ error?: string;
26
+ accent?: string;
27
+ };
28
+ }
29
+
30
+ const BUILTIN_PRESETS: Record<string, CustomStyleConfig> = {
31
+ minimal: {
32
+ separator: " ❯ "
33
+ },
34
+ glass: {
35
+ prefix: "▕ ",
36
+ suffix: " ▏",
37
+ separator: " "
38
+ },
39
+ glow: {
40
+ brackets: ["[", "]"],
41
+ inverse: true
42
+ }
43
+ };
44
+
45
+ const VALID_STYLES = ["minimal", "glass", "glow", "custom", "off"] as const;
17
46
 
18
47
  // Store original statuses to allow dynamic redrawing on configuration changes.
19
48
  const originalStatuses = new Map<string, string | undefined>();
@@ -39,6 +68,7 @@ const home = process.env.HOME || process.env.USERPROFILE || "";
39
68
  const settingsPath = path.join(home, ".pi/agent/settings.json");
40
69
 
41
70
  let currentStyle = "minimal";
71
+ let customStyleConfig: CustomStyleConfig | undefined;
42
72
 
43
73
  try {
44
74
  if (fs.existsSync(settingsPath)) {
@@ -48,6 +78,9 @@ try {
48
78
  const saved = config.beautifier.style;
49
79
  currentStyle = (VALID_STYLES as readonly string[]).includes(saved) ? saved : "minimal";
50
80
  }
81
+ if (config.beautifier?.custom && typeof config.beautifier.custom === "object") {
82
+ customStyleConfig = config.beautifier.custom;
83
+ }
51
84
  }
52
85
  } catch (_e) {
53
86
  // Graceful fallback to default in case of JSON parse or read errors
@@ -58,7 +91,8 @@ export function beautifyStatus(
58
91
  key: string,
59
92
  originalVal: string | undefined,
60
93
  theme: any,
61
- style: string
94
+ style: string,
95
+ customCfg?: CustomStyleConfig
62
96
  ): string | undefined {
63
97
  if (originalVal === undefined) return undefined;
64
98
  if (style === "off") return originalVal;
@@ -138,38 +172,53 @@ export function beautifyStatus(
138
172
  state = name === "plan" ? "accent" : "success";
139
173
  }
140
174
 
141
- // 4. Map state to target glyph shapes
142
- let char = "●";
143
- if (state === "warning") char = "◌";
144
- else if (state === "error") char = "▲";
175
+ // 4. Determine state glyph & indicator
176
+ const preset =
177
+ style === "custom"
178
+ ? (customCfg || customStyleConfig || {})
179
+ : (BUILTIN_PRESETS[style] || BUILTIN_PRESETS.minimal);
180
+
181
+ const glyphMap = preset.glyphs || {};
182
+ let glyph =
183
+ glyphMap[state] ||
184
+ (state === "warning" ? "◌" : state === "error" ? "▲" : state === "accent" ? "◆" : "●");
145
185
 
146
186
  const hasThemeFg = theme && typeof theme.fg === "function";
147
- const coloredIndicator = hasThemeFg ? theme.fg(state, char) : char;
148
-
149
- // 5. Render layout presets (3 curated baseline layouts: minimal, glass, glow)
150
- switch (style) {
151
- case "glass": {
152
- const prefix = hasThemeFg ? theme.fg("dim", "▕ ") : "▕ ";
153
- const suffix = hasThemeFg ? theme.fg("dim", " ▏") : " ▏";
154
- const dimmedName = hasThemeFg ? theme.fg("muted", visualName) : visualName;
155
- const detailStr = details ? ` (${details})` : "";
156
- return `${prefix}${dimmedName} ${coloredIndicator}${detailStr}${suffix}`;
157
- }
158
- case "glow": {
159
- const label = ` ${char}${details ? ` ${details}` : ""} `;
160
- const hasInverse = theme && typeof theme.inverse === "function";
161
- const coloredBadge = hasInverse && hasThemeFg ? theme.inverse(theme.fg(state, label)) : `[${label}]`;
162
- const dimmedName = hasThemeFg ? theme.fg("muted", visualName) : visualName;
163
- return `${dimmedName} ${coloredBadge}`;
164
- }
165
- case "minimal":
166
- default: {
167
- const separator = hasThemeFg ? theme.fg("dim", " ❯ ") : " ❯ ";
168
- const dimmedName = hasThemeFg ? theme.fg("muted", visualName) : visualName;
169
- const detailStr = details ? ` (${details})` : "";
170
- return `${dimmedName}${separator}${coloredIndicator}${detailStr}`;
171
- }
187
+ const coloredGlyph = hasThemeFg ? theme.fg(state, glyph) : glyph;
188
+ const dimmedName = hasThemeFg ? theme.fg("muted", visualName) : visualName;
189
+
190
+ const openBracket = preset.brackets?.[0] ?? "";
191
+ const closeBracket = preset.brackets?.[1] ?? "";
192
+ const prefix = preset.prefix ?? "";
193
+ const suffix = preset.suffix ?? "";
194
+ const sep = preset.separator ?? (preset.brackets ? " " : " ❯ ");
195
+
196
+ // Inverse pill badge (glow preset)
197
+ if (preset.inverse && theme && typeof theme.inverse === "function") {
198
+ const pillContent = ` ${glyph}${details ? ` ${details}` : ""} `;
199
+ const coloredBadge = hasThemeFg ? theme.inverse(theme.fg(state, pillContent)) : `[${pillContent}]`;
200
+ return `${prefix}${dimmedName} ${coloredBadge}${suffix}`;
201
+ }
202
+
203
+ let badge = coloredGlyph;
204
+ if (details) {
205
+ badge = `${coloredGlyph} ${details}`;
172
206
  }
207
+ if (openBracket && closeBracket) {
208
+ const styledOpen = hasThemeFg ? theme.fg("dim", openBracket) : openBracket;
209
+ const styledClose = hasThemeFg ? theme.fg("dim", closeBracket) : closeBracket;
210
+ badge = `${styledOpen}${badge}${styledClose}`;
211
+ } else if (details && !preset.brackets) {
212
+ const detailStr = hasThemeFg ? theme.fg("dim", ` (${details})`) : ` (${details})`;
213
+ badge = `${coloredGlyph}${detailStr}`;
214
+ }
215
+
216
+ const styledSep =
217
+ hasThemeFg && (sep.includes("│") || sep.includes("⁝") || sep.includes("❯"))
218
+ ? theme.fg("dim", sep)
219
+ : sep;
220
+
221
+ return `${prefix}${dimmedName}${styledSep}${badge}${suffix}`;
173
222
  } catch (_error) {
174
223
  return originalVal;
175
224
  }
@@ -329,15 +378,15 @@ export default function (pi: ExtensionAPI) {
329
378
 
330
379
  if (styleKey === "custom") {
331
380
  const promptRecipe = [
332
- "You are acting as my Pi TUI Status Bar designer.",
333
- "1. Detect my primary language from our recent conversation history or environment locale.",
334
- "2. In that EXACT primary language, proactively ask me what visual aesthetic I want for my status bar, and propose 3 distinct creative ideas (e.g. Cyberpunk Dot-Matrix, Warm Minimalist, High-Contrast Pill).",
335
- "3. Wait for my preference or feedback, then inspect `tui-status-beautifier.ts` and apply the custom layout for me."
381
+ "You are acting as my Pi TUI Status Bar designer for `pi-tui-status-beautifier`.",
382
+ "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.",
383
+ "2. Once I select or describe my aesthetic, save the configuration directly into `settings.json` under `beautifier.style = 'custom'` and `beautifier.custom = { separator, brackets, glyphs, prefix, suffix }`.",
384
+ "Do NOT edit any extension source code files."
336
385
  ].join("\n");
337
386
 
338
387
  if (typeof (ctx.ui as any).setEditorText === "function") {
339
388
  (ctx.ui as any).setEditorText(promptRecipe);
340
- ctx.ui.notify("Press Enter to let your agent consult your style preference in your native language!", "info");
389
+ ctx.ui.notify("Press Enter to let your agent consult your style preference with visual previews!", "info");
341
390
  } else {
342
391
  ctx.ui.notify(promptRecipe, "info");
343
392
  }
@@ -349,14 +398,13 @@ export default function (pi: ExtensionAPI) {
349
398
  lastRenderedStatus.clear();
350
399
  ctx.ui.notify(`TUI status style changed to: ${styleKey}`, "info");
351
400
 
352
- for (const [key, val] of originalStatuses.entries()) {
353
- ctx.ui.setStatus(key, val);
354
- }
355
-
356
401
  try {
357
402
  if (fs.existsSync(settingsPath)) {
358
403
  const data = fs.readFileSync(settingsPath, "utf-8");
359
404
  const config = JSON.parse(data);
405
+ if (config.beautifier?.custom && typeof config.beautifier.custom === "object") {
406
+ customStyleConfig = config.beautifier.custom;
407
+ }
360
408
  if (!config.beautifier) config.beautifier = {};
361
409
  config.beautifier.style = styleKey;
362
410
  const tempSettings = `${settingsPath}.tmp.${process.pid}.${Date.now()}`;
@@ -366,6 +414,10 @@ export default function (pi: ExtensionAPI) {
366
414
  } catch (_e) {
367
415
  // Silently ignore disk write errors
368
416
  }
417
+
418
+ for (const [key, val] of originalStatuses.entries()) {
419
+ ctx.ui.setStatus(key, val);
420
+ }
369
421
  }
370
422
  },
371
423
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-tui-status-beautifier",
3
- "version": "1.2.1",
3
+ "version": "1.3.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": [