pi-fancy-footer 0.3.2 → 0.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.
package/README.md
CHANGED
|
@@ -23,6 +23,9 @@ pi install npm:pi-fancy-footer
|
|
|
23
23
|
|
|
24
24
|
## 📸 Screenshots
|
|
25
25
|
|
|
26
|
+
> [!NOTE]
|
|
27
|
+
> Some screenshots include `pi-banner`, which is now a separate extension.
|
|
28
|
+
|
|
26
29
|
<!-- markdownlint-disable MD033 -->
|
|
27
30
|
|
|
28
31
|
<img src="screenshots/light-blocks.png" alt="light theme – blocks style" width="600" /><br/>
|
|
@@ -40,6 +43,7 @@ pi install npm:pi-fancy-footer
|
|
|
40
43
|
## 🎮 Commands
|
|
41
44
|
|
|
42
45
|
- `/fancy-footer` - open interactive footer config editor (small TUI)
|
|
46
|
+
- settings are grouped into General and Widgets sections
|
|
43
47
|
- all widgets are listed directly (with icon prefixes)
|
|
44
48
|
- select a widget and press Enter for detailed settings
|
|
45
49
|
- in widget settings, adjust row/position/align/fill/min-width, visibility,
|
|
@@ -53,7 +57,6 @@ Create `~/.pi/agent/fancy-footer.json`:
|
|
|
53
57
|
```json
|
|
54
58
|
{
|
|
55
59
|
"refreshMs": 3000,
|
|
56
|
-
"showPiBanner": true,
|
|
57
60
|
"iconFamily": "unicode",
|
|
58
61
|
"contextBarStyle": "blocks",
|
|
59
62
|
"defaultTextColor": "dim",
|
|
@@ -80,7 +83,6 @@ Create `~/.pi/agent/fancy-footer.json`:
|
|
|
80
83
|
Top-level settings:
|
|
81
84
|
|
|
82
85
|
- `refreshMs` (number)
|
|
83
|
-
- `showPiBanner` (boolean)
|
|
84
86
|
- `iconFamily`
|
|
85
87
|
(`nerd` | `emoji` | `unicode` | `ascii`)
|
|
86
88
|
- `contextBarStyle`
|
|
@@ -150,7 +150,6 @@ function coerceFooterWidgetOverride(
|
|
|
150
150
|
export function coerceFooterConfig(value: unknown): FooterConfigSnapshot {
|
|
151
151
|
const out: FooterConfigSnapshot = {
|
|
152
152
|
refreshMs: DEFAULT_FOOTER_CONFIG.refreshMs,
|
|
153
|
-
showPiBanner: DEFAULT_FOOTER_CONFIG.showPiBanner,
|
|
154
153
|
iconFamily: DEFAULT_FOOTER_CONFIG.iconFamily,
|
|
155
154
|
contextBarStyle: DEFAULT_FOOTER_CONFIG.contextBarStyle,
|
|
156
155
|
defaultTextColor: DEFAULT_FOOTER_CONFIG.defaultTextColor,
|
|
@@ -172,10 +171,6 @@ export function coerceFooterConfig(value: unknown): FooterConfigSnapshot {
|
|
|
172
171
|
out.refreshMs = Math.max(MIN_FOOTER_REFRESH_MS, refreshMs);
|
|
173
172
|
}
|
|
174
173
|
|
|
175
|
-
if (typeof input.showPiBanner === "boolean") {
|
|
176
|
-
out.showPiBanner = input.showPiBanner;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
174
|
if (isFooterIconFamily(input.iconFamily)) {
|
|
180
175
|
out.iconFamily = input.iconFamily;
|
|
181
176
|
}
|
|
@@ -247,7 +242,6 @@ export function cloneFooterConfig(
|
|
|
247
242
|
|
|
248
243
|
return {
|
|
249
244
|
refreshMs: config.refreshMs,
|
|
250
|
-
showPiBanner: config.showPiBanner,
|
|
251
245
|
iconFamily: config.iconFamily,
|
|
252
246
|
contextBarStyle: config.contextBarStyle,
|
|
253
247
|
defaultTextColor: config.defaultTextColor,
|
|
@@ -285,7 +279,6 @@ function toFooterConfigObject(
|
|
|
285
279
|
MIN_FOOTER_REFRESH_MS,
|
|
286
280
|
MAX_FOOTER_REFRESH_MS,
|
|
287
281
|
),
|
|
288
|
-
showPiBanner: config.showPiBanner,
|
|
289
282
|
iconFamily: config.iconFamily,
|
|
290
283
|
contextBarStyle: config.contextBarStyle,
|
|
291
284
|
defaultTextColor: config.defaultTextColor,
|
|
@@ -740,20 +733,6 @@ export function coerceContextBarStyleValue(
|
|
|
740
733
|
return undefined;
|
|
741
734
|
}
|
|
742
735
|
|
|
743
|
-
export function bannerFooterSettingsItems(
|
|
744
|
-
draft: FooterConfigSnapshot,
|
|
745
|
-
): SettingItem[] {
|
|
746
|
-
return [
|
|
747
|
-
{
|
|
748
|
-
id: "showPiBanner",
|
|
749
|
-
label: "show π banner",
|
|
750
|
-
currentValue: draft.showPiBanner ? "on" : "off",
|
|
751
|
-
values: ["on", "off"],
|
|
752
|
-
description: "Show the rainbow π banner in the header.",
|
|
753
|
-
},
|
|
754
|
-
];
|
|
755
|
-
}
|
|
756
|
-
|
|
757
736
|
export function genericFooterSettingsItems(
|
|
758
737
|
draft: FooterConfigSnapshot,
|
|
759
738
|
): SettingItem[] {
|
|
@@ -833,7 +812,6 @@ export function rootFooterSettingsItems(
|
|
|
833
812
|
applyDraft: () => void,
|
|
834
813
|
): SettingItem[] {
|
|
835
814
|
return [
|
|
836
|
-
...bannerFooterSettingsItems(draft),
|
|
837
815
|
...genericFooterSettingsItems(draft),
|
|
838
816
|
...widgetFooterSettingsItems(draft, theme, applyDraft),
|
|
839
817
|
];
|
|
@@ -351,7 +351,6 @@ export interface FooterWidgetConfigOverride {
|
|
|
351
351
|
|
|
352
352
|
export interface FooterConfigSnapshot {
|
|
353
353
|
refreshMs: number;
|
|
354
|
-
showPiBanner: boolean;
|
|
355
354
|
iconFamily: FooterIconFamily;
|
|
356
355
|
contextBarStyle: ContextBarStyleId;
|
|
357
356
|
defaultTextColor: FooterWidgetColor;
|
|
@@ -361,7 +360,6 @@ export interface FooterConfigSnapshot {
|
|
|
361
360
|
|
|
362
361
|
export const DEFAULT_FOOTER_CONFIG: FooterConfigSnapshot = {
|
|
363
362
|
refreshMs: GIT_REFRESH_MS,
|
|
364
|
-
showPiBanner: true,
|
|
365
363
|
iconFamily: "nerd",
|
|
366
364
|
contextBarStyle: DEFAULT_CONTEXT_BAR_STYLE,
|
|
367
365
|
defaultTextColor: "dim",
|
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
type SessionUsageMetrics,
|
|
25
25
|
} from "./fancy-footer/shared.ts";
|
|
26
26
|
import {
|
|
27
|
-
bannerFooterSettingsItems,
|
|
28
27
|
cloneFooterConfig,
|
|
29
28
|
coerceCompactionSettings,
|
|
30
29
|
coerceContextBarStyleValue,
|
|
@@ -47,12 +46,10 @@ import {
|
|
|
47
46
|
collectSessionUsageMetrics,
|
|
48
47
|
renderFooterLines,
|
|
49
48
|
} from "./fancy-footer/render.ts";
|
|
50
|
-
import { renderBannerLines } from "./fancy-footer/banner.ts";
|
|
51
49
|
|
|
52
50
|
interface ActiveFooterControls {
|
|
53
51
|
requestRender: () => void;
|
|
54
52
|
reschedule: () => void;
|
|
55
|
-
applyHeader: () => void;
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
export default function (pi: ExtensionAPI) {
|
|
@@ -61,7 +58,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
61
58
|
};
|
|
62
59
|
let footerConfig: FooterConfigSnapshot = {
|
|
63
60
|
refreshMs: DEFAULT_FOOTER_CONFIG.refreshMs,
|
|
64
|
-
showPiBanner: DEFAULT_FOOTER_CONFIG.showPiBanner,
|
|
65
61
|
iconFamily: DEFAULT_FOOTER_CONFIG.iconFamily,
|
|
66
62
|
contextBarStyle: DEFAULT_FOOTER_CONFIG.contextBarStyle,
|
|
67
63
|
defaultTextColor: DEFAULT_FOOTER_CONFIG.defaultTextColor,
|
|
@@ -77,22 +73,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
77
73
|
compactionSettings = loadCompactionSettings(ctx.cwd);
|
|
78
74
|
footerConfig = loadFooterConfig();
|
|
79
75
|
|
|
80
|
-
const applyHeader = () => {
|
|
81
|
-
if (!footerConfig.showPiBanner) {
|
|
82
|
-
ctx.ui.setHeader(undefined);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
ctx.ui.setHeader((_tui, theme) => ({
|
|
87
|
-
render(width: number): string[] {
|
|
88
|
-
return renderBannerLines(theme, width);
|
|
89
|
-
},
|
|
90
|
-
invalidate() {},
|
|
91
|
-
}));
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
applyHeader();
|
|
95
|
-
|
|
96
76
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
97
77
|
let currentGit = { ...EMPTY_GIT_INFO };
|
|
98
78
|
let usageMetrics: SessionUsageMetrics = collectSessionUsageMetrics(ctx);
|
|
@@ -214,7 +194,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
214
194
|
activeFooterControls = {
|
|
215
195
|
requestRender,
|
|
216
196
|
reschedule: scheduleRefresh,
|
|
217
|
-
applyHeader,
|
|
218
197
|
};
|
|
219
198
|
|
|
220
199
|
void refreshGit();
|
|
@@ -263,7 +242,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
263
242
|
try {
|
|
264
243
|
writeFooterConfigSnapshot(draft);
|
|
265
244
|
footerConfig = loadFooterConfig();
|
|
266
|
-
activeFooterControls?.applyHeader();
|
|
267
245
|
activeFooterControls?.reschedule();
|
|
268
246
|
activeFooterControls?.requestRender();
|
|
269
247
|
} catch (error) {
|
|
@@ -272,20 +250,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
272
250
|
}
|
|
273
251
|
};
|
|
274
252
|
|
|
275
|
-
type ConfigSection = "
|
|
253
|
+
type ConfigSection = "generic" | "widgets";
|
|
276
254
|
|
|
277
|
-
let activeSection: ConfigSection = "
|
|
255
|
+
let activeSection: ConfigSection = "generic";
|
|
278
256
|
const selection: Record<ConfigSection, number> = {
|
|
279
|
-
banner: 0,
|
|
280
257
|
generic: 0,
|
|
281
258
|
widgets: 0,
|
|
282
259
|
};
|
|
283
260
|
let submenu: Component | undefined;
|
|
284
261
|
|
|
285
262
|
const getSectionItems = (section: ConfigSection): SettingItem[] => {
|
|
286
|
-
if (section === "banner") {
|
|
287
|
-
return bannerFooterSettingsItems(draft);
|
|
288
|
-
}
|
|
289
263
|
if (section === "generic") {
|
|
290
264
|
return genericFooterSettingsItems(draft);
|
|
291
265
|
}
|
|
@@ -318,11 +292,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
318
292
|
return items[selection[activeSection]];
|
|
319
293
|
};
|
|
320
294
|
|
|
321
|
-
const orderedSections: ConfigSection[] = [
|
|
322
|
-
"banner",
|
|
323
|
-
"generic",
|
|
324
|
-
"widgets",
|
|
325
|
-
];
|
|
295
|
+
const orderedSections: ConfigSection[] = ["generic", "widgets"];
|
|
326
296
|
|
|
327
297
|
const getFlatSelections = () => {
|
|
328
298
|
return orderedSections.flatMap((section) => {
|
|
@@ -338,11 +308,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
338
308
|
draft.refreshMs = refreshMs;
|
|
339
309
|
applyDraft();
|
|
340
310
|
}
|
|
341
|
-
} else if (id === "showPiBanner") {
|
|
342
|
-
if (newValue === "on" || newValue === "off") {
|
|
343
|
-
draft.showPiBanner = newValue === "on";
|
|
344
|
-
applyDraft();
|
|
345
|
-
}
|
|
346
311
|
} else if (id === "iconFamily") {
|
|
347
312
|
const iconFamily = coerceIconFamily(newValue);
|
|
348
313
|
if (iconFamily) {
|
|
@@ -502,8 +467,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
502
467
|
),
|
|
503
468
|
truncateToWidth(theme.fg("dim", configPath), width),
|
|
504
469
|
"",
|
|
505
|
-
...renderSection(width, "Banner", "banner"),
|
|
506
|
-
"",
|
|
507
470
|
...renderSection(width, "General", "generic"),
|
|
508
471
|
"",
|
|
509
472
|
...renderSection(width, "Widgets", "widgets"),
|
|
@@ -578,8 +541,4 @@ export default function (pi: ExtensionAPI) {
|
|
|
578
541
|
pi.on("session_start", async (_event, ctx) => {
|
|
579
542
|
installFooter(ctx);
|
|
580
543
|
});
|
|
581
|
-
|
|
582
|
-
pi.on("session_switch", async (_event, ctx) => {
|
|
583
|
-
installFooter(ctx);
|
|
584
|
-
});
|
|
585
544
|
}
|
package/package.json
CHANGED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
|
|
3
|
-
const PI_ART = [
|
|
4
|
-
" 3.141592653589793238462643383279",
|
|
5
|
-
" 5028841971693993751058209749445923",
|
|
6
|
-
" 07816406286208998628034825342117067",
|
|
7
|
-
" 9821 48086 5132",
|
|
8
|
-
" 823 06647 09384",
|
|
9
|
-
"46 09550 58223",
|
|
10
|
-
" 1725 3594",
|
|
11
|
-
" 08128 48111",
|
|
12
|
-
" 74502 84102",
|
|
13
|
-
" 70193 85211 05",
|
|
14
|
-
" 5596446 22948954930381",
|
|
15
|
-
" 9644288 10975665933",
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
function hslToRgb(h: number, s: number, l: number): [number, number, number] {
|
|
19
|
-
h = ((h % 360) + 360) % 360;
|
|
20
|
-
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
21
|
-
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
22
|
-
const m = l - c / 2;
|
|
23
|
-
let r = 0;
|
|
24
|
-
let g = 0;
|
|
25
|
-
let b = 0;
|
|
26
|
-
if (h < 60) [r, g, b] = [c, x, 0];
|
|
27
|
-
else if (h < 120) [r, g, b] = [x, c, 0];
|
|
28
|
-
else if (h < 180) [r, g, b] = [0, c, x];
|
|
29
|
-
else if (h < 240) [r, g, b] = [0, x, c];
|
|
30
|
-
else if (h < 300) [r, g, b] = [x, 0, c];
|
|
31
|
-
else [r, g, b] = [c, 0, x];
|
|
32
|
-
return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const RESET = "\x1b[0m";
|
|
36
|
-
|
|
37
|
-
function rainbowLine(line: string, rowOffset: number): string {
|
|
38
|
-
const totalChars = 50;
|
|
39
|
-
let result = "";
|
|
40
|
-
for (let i = 0; i < line.length; i++) {
|
|
41
|
-
const ch = line[i]!;
|
|
42
|
-
if (ch === " ") {
|
|
43
|
-
result += " ";
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
const hue = ((i + rowOffset * 3) / totalChars) * 360;
|
|
47
|
-
const [r, g, b] = hslToRgb(hue, 0.85, 0.65);
|
|
48
|
-
result += `\x1b[38;2;${r};${g};${b}m${ch}`;
|
|
49
|
-
}
|
|
50
|
-
return result + RESET;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function renderBannerLines(_theme: Theme, width: number): string[] {
|
|
54
|
-
const maxLen = Math.max(...PI_ART.map((line) => line.length));
|
|
55
|
-
const pad = Math.max(0, Math.floor((width - maxLen) / 2));
|
|
56
|
-
const prefix = " ".repeat(pad);
|
|
57
|
-
return ["", ...PI_ART.map((line, row) => prefix + rainbowLine(line, row)), ""];
|
|
58
|
-
}
|