@pi-unipi/footer 2.6.1 → 2.9.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/package.json +8 -6
- package/src/help.ts +2 -2
- package/src/index.ts +9 -10
- package/src/presets.ts +1 -27
- package/src/registry/index.ts +0 -23
- package/src/rendering/icons.ts +0 -13
- package/src/rendering/renderer.ts +5 -40
- package/src/rendering/separators.ts +0 -10
- package/src/rendering/theme.ts +1 -15
- package/src/segments/compactor.ts +7 -14
- package/src/segments/core.ts +15 -54
- package/src/segments/kanboard.ts +5 -5
- package/src/segments/mcp.ts +5 -5
- package/src/segments/memory.ts +4 -4
- package/src/segments/notify.ts +2 -2
- package/src/segments/ralph.ts +3 -3
- package/src/segments/status-ext.ts +1 -1
- package/src/segments/workflow.ts +3 -3
- package/src/tps-tracker.ts +0 -8
- package/src/tui/settings-tui.ts +14 -31
- package/src/types.ts +0 -15
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/footer",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Persistent status bar for Unipi
|
|
3
|
+
"version": "2.9.0",
|
|
4
|
+
"description": "Persistent status bar for Unipi \u2014 subscribes to UNIPI_EVENTS and renders key stats from all unipi packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -32,18 +32,20 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@pi-unipi/core": "2.
|
|
35
|
+
"@pi-unipi/core": "2.9.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
39
|
-
"@earendil-works/pi-tui": "^0.
|
|
38
|
+
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
39
|
+
"@earendil-works/pi-tui": "^0.84.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^25.6.0",
|
|
43
43
|
"typescript": "^6.0.0"
|
|
44
44
|
},
|
|
45
45
|
"pi": {
|
|
46
|
-
"extensions": [
|
|
46
|
+
"extensions": [
|
|
47
|
+
"./index.ts"
|
|
48
|
+
],
|
|
47
49
|
"skills": [],
|
|
48
50
|
"prompts": [],
|
|
49
51
|
"themes": []
|
package/src/help.ts
CHANGED
|
@@ -72,10 +72,10 @@ function buildHelpLines(
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/** Simple group lookup for help */
|
|
75
|
-
function getGroupForSegment(segId: string): string {
|
|
75
|
+
export function getGroupForSegment(segId: string): string {
|
|
76
76
|
const coreIds = ["model", "api_state", "tool_count", "git", "context_pct", "cost", "tokens_total", "tokens_in", "tokens_out", "session", "hostname", "time", "tps", "clock", "duration", "thinking_level"];
|
|
77
77
|
if (coreIds.includes(segId)) return "core";
|
|
78
|
-
const compactorIds = ["session_events", "compactions", "tokens_saved", "compression_ratio", "
|
|
78
|
+
const compactorIds = ["session_events", "compactions", "tokens_saved", "compression_ratio", "sandbox_runs", "search_queries"];
|
|
79
79
|
if (compactorIds.includes(segId)) return "compactor";
|
|
80
80
|
if (["project_count", "total_count", "consolidations"].includes(segId)) return "memory";
|
|
81
81
|
if (["servers_total", "servers_active", "tools_total", "servers_failed"].includes(segId)) return "mcp";
|
package/src/index.ts
CHANGED
|
@@ -27,20 +27,19 @@ import { NOTIFY_SEGMENTS } from "./segments/notify.js";
|
|
|
27
27
|
import { STATUS_EXT_SEGMENTS } from "./segments/status-ext.js";
|
|
28
28
|
|
|
29
29
|
import type { FooterGroup, FooterSegment } from "./types.js";
|
|
30
|
-
import { rainbowBorder } from "./segments/core.js";
|
|
31
30
|
import { tpsTracker } from "./tps-tracker.js";
|
|
32
31
|
|
|
33
32
|
/** All segment groups */
|
|
34
33
|
const ALL_GROUPS: FooterGroup[] = [
|
|
35
|
-
{ id: "core", name: "Core",
|
|
36
|
-
{ id: "compactor", name: "Compactor",
|
|
37
|
-
{ id: "memory", name: "Memory",
|
|
38
|
-
{ id: "mcp", name: "MCP",
|
|
39
|
-
{ id: "ralph", name: "Ralph",
|
|
40
|
-
{ id: "workflow", name: "Workflow",
|
|
41
|
-
{ id: "kanboard", name: "Kanboard",
|
|
42
|
-
{ id: "notify", name: "Notify",
|
|
43
|
-
{ id: "status_ext", name: "Extensions",
|
|
34
|
+
{ id: "core", name: "Core", segments: CORE_SEGMENTS, defaultShow: true },
|
|
35
|
+
{ id: "compactor", name: "Compactor", segments: COMPACTOR_SEGMENTS, defaultShow: true },
|
|
36
|
+
{ id: "memory", name: "Memory", segments: MEMORY_SEGMENTS, defaultShow: true },
|
|
37
|
+
{ id: "mcp", name: "MCP", segments: MCP_SEGMENTS, defaultShow: true },
|
|
38
|
+
{ id: "ralph", name: "Ralph", segments: RALPH_SEGMENTS, defaultShow: true },
|
|
39
|
+
{ id: "workflow", name: "Workflow", segments: WORKFLOW_SEGMENTS, defaultShow: true },
|
|
40
|
+
{ id: "kanboard", name: "Kanboard", segments: KANBOARD_SEGMENTS, defaultShow: true },
|
|
41
|
+
{ id: "notify", name: "Notify", segments: NOTIFY_SEGMENTS, defaultShow: false },
|
|
42
|
+
{ id: "status_ext", name: "Extensions", segments: STATUS_EXT_SEGMENTS, defaultShow: true },
|
|
44
43
|
];
|
|
45
44
|
|
|
46
45
|
/** Build a segment lookup from all groups */
|
package/src/presets.ts
CHANGED
|
@@ -73,32 +73,7 @@ const FULL_PRESET: PresetDef = {
|
|
|
73
73
|
secondarySegments: [
|
|
74
74
|
"hostname",
|
|
75
75
|
"tokens_in", "tokens_out",
|
|
76
|
-
"compression_ratio",
|
|
77
|
-
"platforms_enabled", "last_sent",
|
|
78
|
-
"thinking_level",
|
|
79
|
-
],
|
|
80
|
-
colors: getDefaultColors(),
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/** Nerd preset — maximum detail for Nerd Font users */
|
|
84
|
-
const NERD_PRESET: PresetDef = {
|
|
85
|
-
leftSegments: [
|
|
86
|
-
"model", "api_state", "tool_count", "git", "current_command", "session",
|
|
87
|
-
],
|
|
88
|
-
rightSegments: [
|
|
89
|
-
"tps", "context_pct", "cost", "tokens_total",
|
|
90
|
-
"session_events", "compactions", "tokens_saved",
|
|
91
|
-
"project_count", "total_count",
|
|
92
|
-
"servers_total", "servers_active", "tools_total",
|
|
93
|
-
"active_loops", "loop_status",
|
|
94
|
-
"docs_count", "tasks_done", "task_pct",
|
|
95
|
-
"extension_statuses",
|
|
96
|
-
"clock", "duration",
|
|
97
|
-
],
|
|
98
|
-
secondarySegments: [
|
|
99
|
-
"hostname",
|
|
100
|
-
"tokens_in", "tokens_out",
|
|
101
|
-
"compression_ratio", "cocoindex_status",
|
|
76
|
+
"compression_ratio",
|
|
102
77
|
"platforms_enabled", "last_sent",
|
|
103
78
|
"thinking_level",
|
|
104
79
|
],
|
|
@@ -124,7 +99,6 @@ export const PRESETS: Record<string, PresetDef> = {
|
|
|
124
99
|
minimal: MINIMAL_PRESET,
|
|
125
100
|
compact: COMPACT_PRESET,
|
|
126
101
|
full: FULL_PRESET,
|
|
127
|
-
nerd: NERD_PRESET,
|
|
128
102
|
ascii: ASCII_PRESET,
|
|
129
103
|
};
|
|
130
104
|
|
package/src/registry/index.ts
CHANGED
|
@@ -87,15 +87,6 @@ export class FooterRegistry {
|
|
|
87
87
|
this.notifySubscribers();
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/**
|
|
91
|
-
* Clear cached data for a specific group.
|
|
92
|
-
*/
|
|
93
|
-
invalidateGroup(groupId: string): void {
|
|
94
|
-
this.dataCache.delete(groupId);
|
|
95
|
-
this.log("invalidateGroup", groupId);
|
|
96
|
-
this.notifySubscribers();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
90
|
// ─── Reactive Subscriptions ───────────────────────────────────────────────
|
|
100
91
|
|
|
101
92
|
/**
|
|
@@ -144,17 +135,3 @@ export function getFooterRegistry(): FooterRegistry {
|
|
|
144
135
|
}
|
|
145
136
|
return registryInstance;
|
|
146
137
|
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Reset the global registry (for testing).
|
|
150
|
-
*/
|
|
151
|
-
export function resetFooterRegistry(): void {
|
|
152
|
-
registryInstance = null;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// ─── Global reference ───────────────────────────────────────────────────────
|
|
156
|
-
|
|
157
|
-
// Expose on globalThis for cross-package access
|
|
158
|
-
if (typeof globalThis !== "undefined") {
|
|
159
|
-
globalThis.__unipi_footer_registry = getFooterRegistry();
|
|
160
|
-
}
|
package/src/rendering/icons.ts
CHANGED
|
@@ -321,16 +321,3 @@ export function getIcon(segmentId: string, overrideStyle?: "nerd" | "emoji" | "t
|
|
|
321
321
|
const key = segmentId as keyof IconSet;
|
|
322
322
|
return icons[key] ?? "";
|
|
323
323
|
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Get the full icon set based on the configured style.
|
|
327
|
-
*/
|
|
328
|
-
export function getIcons(iconStyle?: "nerd" | "emoji" | "text"): IconSet {
|
|
329
|
-
const style = iconStyle ?? currentIconStyle ?? resolveIconStyle();
|
|
330
|
-
const sets: Record<string, IconSet> = {
|
|
331
|
-
nerd: NERD_ICONS,
|
|
332
|
-
emoji: EMOJI_ICONS,
|
|
333
|
-
text: TEXT_ICONS,
|
|
334
|
-
};
|
|
335
|
-
return sets[style];
|
|
336
|
-
}
|
|
@@ -10,7 +10,8 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
|
10
10
|
import type { PresetDef, FooterSegmentContext, FooterSegment, ColorScheme, RenderedSegment, SegmentZone } from "../types.js";
|
|
11
11
|
import type { FooterRegistry } from "../registry/index.js";
|
|
12
12
|
import { visibleWidth as piVisibleWidth, truncateToWidth } from "@earendil-works/pi-tui";
|
|
13
|
-
import { getSeparator
|
|
13
|
+
import { getSeparator } from "./separators.js";
|
|
14
|
+
import { getGroupForSegment } from "../help.js";
|
|
14
15
|
import { getDefaultColors, setColorMode, refreshColorMode } from "./theme.js";
|
|
15
16
|
import { setIconStyle } from "./icons.js";
|
|
16
17
|
import { getPreset } from "../presets.js";
|
|
@@ -27,7 +28,6 @@ interface SegmentLookup {
|
|
|
27
28
|
interface RenderedSegmentWithWidth {
|
|
28
29
|
content: string;
|
|
29
30
|
width: number;
|
|
30
|
-
visible: boolean;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// ─── ANSI helpers ───────────────────────────────────────────────────────────
|
|
@@ -183,7 +183,7 @@ export class FooterRenderer {
|
|
|
183
183
|
if (this.segmentLookup.allIds) {
|
|
184
184
|
for (const segId of this.segmentLookup.allIds()) {
|
|
185
185
|
if (primaryIds.includes(segId) || secondaryIds.includes(segId)) continue;
|
|
186
|
-
const groupId =
|
|
186
|
+
const groupId = getGroupForSegment(segId);
|
|
187
187
|
if (isSegmentExplicitlyEnabled(groupId, segId)) {
|
|
188
188
|
primaryIds.push(segId);
|
|
189
189
|
}
|
|
@@ -309,7 +309,7 @@ export class FooterRenderer {
|
|
|
309
309
|
fullWidth: number,
|
|
310
310
|
labelMode: "compact" | "labeled",
|
|
311
311
|
): RenderedSegmentWithWidth | null {
|
|
312
|
-
if (!isSegmentEnabled(
|
|
312
|
+
if (!isSegmentEnabled(getGroupForSegment(segId), segId)) return null;
|
|
313
313
|
|
|
314
314
|
const segment = this.segmentLookup.get(segId);
|
|
315
315
|
if (!segment) return null;
|
|
@@ -317,7 +317,7 @@ export class FooterRenderer {
|
|
|
317
317
|
const ctx: FooterSegmentContext = {
|
|
318
318
|
theme: this.getThemeLike(),
|
|
319
319
|
colors,
|
|
320
|
-
data: this.registry.getGroupData(
|
|
320
|
+
data: this.registry.getGroupData(getGroupForSegment(segId)),
|
|
321
321
|
width: fullWidth,
|
|
322
322
|
piContext: this.piContext,
|
|
323
323
|
footerData: this.footerData,
|
|
@@ -330,7 +330,6 @@ export class FooterRenderer {
|
|
|
330
330
|
return {
|
|
331
331
|
content: rendered.content,
|
|
332
332
|
width: visibleWidth(rendered.content),
|
|
333
|
-
visible: true,
|
|
334
333
|
};
|
|
335
334
|
}
|
|
336
335
|
|
|
@@ -441,40 +440,6 @@ export class FooterRenderer {
|
|
|
441
440
|
return truncateToWidth(result, 200);
|
|
442
441
|
}
|
|
443
442
|
|
|
444
|
-
/** Map a segment ID to its group ID */
|
|
445
|
-
private getGroupForSegment(segId: string): string {
|
|
446
|
-
// Core segments
|
|
447
|
-
const coreIds = ["model", "api_state", "tool_count", "git", "context_pct", "cost", "tokens_total", "tokens_in", "tokens_out", "session", "hostname", "time", "tps", "clock", "duration", "thinking_level"];
|
|
448
|
-
if (coreIds.includes(segId)) return "core";
|
|
449
|
-
|
|
450
|
-
// Compactor segments
|
|
451
|
-
const compactorIds = ["session_events", "compactions", "tokens_saved", "compression_ratio", "cocoindex_status", "sandbox_runs", "search_queries"];
|
|
452
|
-
if (compactorIds.includes(segId)) return "compactor";
|
|
453
|
-
|
|
454
|
-
// Memory segments
|
|
455
|
-
if (["project_count", "total_count", "consolidations"].includes(segId)) return "memory";
|
|
456
|
-
|
|
457
|
-
// MCP segments
|
|
458
|
-
if (["servers_total", "servers_active", "tools_total", "servers_failed"].includes(segId)) return "mcp";
|
|
459
|
-
|
|
460
|
-
// Ralph segments
|
|
461
|
-
if (["active_loops", "total_iterations", "loop_status"].includes(segId)) return "ralph";
|
|
462
|
-
|
|
463
|
-
// Workflow segments
|
|
464
|
-
if (["current_command", "sandbox_level", "command_duration"].includes(segId)) return "workflow";
|
|
465
|
-
|
|
466
|
-
// Kanboard segments
|
|
467
|
-
if (["docs_count", "tasks_done", "tasks_total", "task_pct"].includes(segId)) return "kanboard";
|
|
468
|
-
|
|
469
|
-
// Notify segments
|
|
470
|
-
if (["platforms_enabled", "last_sent"].includes(segId)) return "notify";
|
|
471
|
-
|
|
472
|
-
// Status extension
|
|
473
|
-
if (segId === "extension_statuses") return "status_ext";
|
|
474
|
-
|
|
475
|
-
return "core";
|
|
476
|
-
}
|
|
477
|
-
|
|
478
443
|
/** Get a ThemeLike object for rendering context */
|
|
479
444
|
private getThemeLike(): { fg: (color: string, text: string) => string } {
|
|
480
445
|
// Use a minimal theme-like that applies ANSI codes based on color names
|
|
@@ -100,13 +100,3 @@ export function getSeparator(style: SeparatorStyle): SeparatorDef {
|
|
|
100
100
|
return getSeparator("powerline-thin");
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Get the visible width of a separator (ANSI-stripped length).
|
|
106
|
-
*/
|
|
107
|
-
export function separatorVisibleWidth(style: SeparatorStyle): number {
|
|
108
|
-
const sep = getSeparator(style);
|
|
109
|
-
// Strip ANSI codes and measure
|
|
110
|
-
const stripped = sep.left.replace(/\x1b\[[0-9;]*m/g, "");
|
|
111
|
-
return stripped.length;
|
|
112
|
-
}
|
package/src/rendering/theme.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import type { ColorScheme,
|
|
15
|
+
import type { ColorScheme, SemanticColor, ThemeLike } from "../types.js";
|
|
16
16
|
|
|
17
17
|
// ─── Color mode detection ──────────────────────────────────────────────────
|
|
18
18
|
|
|
@@ -244,20 +244,6 @@ export function getDefaultColors(): ColorScheme {
|
|
|
244
244
|
return scheme;
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
/**
|
|
248
|
-
* Resolve a ColorValue to an actual color string using the theme.
|
|
249
|
-
* If the value is a theme color name, uses theme.fg().
|
|
250
|
-
* If it's a hex string, returns it directly.
|
|
251
|
-
*/
|
|
252
|
-
export function resolveColor(color: ColorValue, theme: ThemeLike): string {
|
|
253
|
-
// Check if it's a hex color (starts with #)
|
|
254
|
-
if (color.startsWith("#")) {
|
|
255
|
-
return color;
|
|
256
|
-
}
|
|
257
|
-
// It's a ThemeColor — use theme.fg
|
|
258
|
-
return theme.fg(color as ThemeColor, "").replace(/\x1b\[0m$/, "");
|
|
259
|
-
}
|
|
260
|
-
|
|
261
247
|
/**
|
|
262
248
|
* Wrap `text` in an ANSI escape for the given hex value, chosen to match
|
|
263
249
|
* the active colour mode (truecolor / 256 / none).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @pi-unipi/footer — Compactor segments
|
|
3
3
|
*
|
|
4
4
|
* Segment renderers for the compactor group: session_events, compactions,
|
|
5
|
-
* tokens_saved, compression_ratio,
|
|
5
|
+
* tokens_saved, compression_ratio, sandbox_runs, search_queries.
|
|
6
6
|
*
|
|
7
7
|
* Data sourced from piContext.sessionManager (live session data).
|
|
8
8
|
* Segments without a reliable data source are hidden (visible: false)
|
|
@@ -129,12 +129,6 @@ function renderCompressionRatioSegment(ctx: FooterSegmentContext): RenderedSegme
|
|
|
129
129
|
return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
function renderCocoindexStatusSegment(_ctx: FooterSegmentContext): RenderedSegment {
|
|
133
|
-
// CocoIndex status — would need to query cocoindex bridge at render time.
|
|
134
|
-
// For now, hidden. Use /unipi:cocoindex-status for live status.
|
|
135
|
-
return hidden();
|
|
136
|
-
}
|
|
137
|
-
|
|
138
132
|
function renderSandboxRunsSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
139
133
|
// Count sandbox events from session manager branch
|
|
140
134
|
const events = getSessionEvents(ctx);
|
|
@@ -171,11 +165,10 @@ function renderSearchQueriesSegment(ctx: FooterSegmentContext): RenderedSegment
|
|
|
171
165
|
}
|
|
172
166
|
|
|
173
167
|
export const COMPACTOR_SEGMENTS: FooterSegment[] = [
|
|
174
|
-
{ id: "session_events", label: "Session Events", shortLabel: "EVT", description: "Number of session events", zone: "center",
|
|
175
|
-
{ id: "compactions", label: "Compactions", shortLabel: "CMP", description: "Number of context compactions", zone: "center",
|
|
176
|
-
{ id: "tokens_saved", label: "Tokens Saved", shortLabel: "SVD", description: "Tokens saved by compaction", zone: "center",
|
|
177
|
-
{ id: "compression_ratio", label: "Compression Ratio", shortLabel: "RAT", description: "Last compaction compression ratio", zone: "center",
|
|
178
|
-
{ id: "
|
|
179
|
-
{ id: "
|
|
180
|
-
{ id: "search_queries", label: "Search Queries", shortLabel: "QRY", description: "Number of search queries", zone: "center", icon: "", render: renderSearchQueriesSegment, defaultShow: false },
|
|
168
|
+
{ id: "session_events", label: "Session Events", shortLabel: "EVT", description: "Number of session events", zone: "center", render: renderSessionEventsSegment, defaultShow: true },
|
|
169
|
+
{ id: "compactions", label: "Compactions", shortLabel: "CMP", description: "Number of context compactions", zone: "center", render: renderCompactionsSegment, defaultShow: true },
|
|
170
|
+
{ id: "tokens_saved", label: "Tokens Saved", shortLabel: "SVD", description: "Tokens saved by compaction", zone: "center", render: renderTokensSavedSegment, defaultShow: true },
|
|
171
|
+
{ id: "compression_ratio", label: "Compression Ratio", shortLabel: "RAT", description: "Last compaction compression ratio", zone: "center", render: renderCompressionRatioSegment, defaultShow: false },
|
|
172
|
+
{ id: "sandbox_runs", label: "Sandbox Runs", shortLabel: "SBX", description: "Number of sandbox code runs", zone: "center", render: renderSandboxRunsSegment, defaultShow: false },
|
|
173
|
+
{ id: "search_queries", label: "Search Queries", shortLabel: "QRY", description: "Number of search queries", zone: "center", render: renderSearchQueriesSegment, defaultShow: false },
|
|
181
174
|
];
|
package/src/segments/core.ts
CHANGED
|
@@ -61,45 +61,6 @@ function getUsageStats(piContext: unknown): UsageStats {
|
|
|
61
61
|
return { input, output, cacheRead, cacheWrite, cost };
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// ─── Rainbow helpers (kept for potential future use) ─────────────────────────
|
|
65
|
-
|
|
66
|
-
/** ANSI 256-color rainbow palette */
|
|
67
|
-
const RAINBOW_COLORS = [
|
|
68
|
-
"\x1b[38;5;196m", // red
|
|
69
|
-
"\x1b[38;5;202m", // orange
|
|
70
|
-
"\x1b[38;5;226m", // yellow
|
|
71
|
-
"\x1b[38;5;82m", // green
|
|
72
|
-
"\x1b[38;5;45m", // blue
|
|
73
|
-
"\x1b[38;5;129m", // indigo
|
|
74
|
-
"\x1b[38;5;171m", // violet
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
const ANSI_RESET = "\x1b[0m";
|
|
78
|
-
|
|
79
|
-
/** Apply rainbow coloring to text, cycling through colors per character */
|
|
80
|
-
export function rainbowText(text: string): string {
|
|
81
|
-
let result = "";
|
|
82
|
-
let colorIdx = 0;
|
|
83
|
-
for (const char of text) {
|
|
84
|
-
if (char === " ") {
|
|
85
|
-
result += char;
|
|
86
|
-
} else {
|
|
87
|
-
result += `${RAINBOW_COLORS[colorIdx % RAINBOW_COLORS.length]}${char}${ANSI_RESET}`;
|
|
88
|
-
colorIdx++;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return result;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Render a rainbow border line of the given width */
|
|
95
|
-
export function rainbowBorder(width: number): string {
|
|
96
|
-
let result = "";
|
|
97
|
-
for (let i = 0; i < width; i++) {
|
|
98
|
-
result += `${RAINBOW_COLORS[i % RAINBOW_COLORS.length]}─${ANSI_RESET}`;
|
|
99
|
-
}
|
|
100
|
-
return result;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
64
|
// ─── Segment Renderers ──────────────────────────────────────────────────────
|
|
104
65
|
|
|
105
66
|
function renderModelSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
@@ -324,19 +285,19 @@ function renderThinkingLevelSegment(ctx: FooterSegmentContext): RenderedSegment
|
|
|
324
285
|
// ─── Core segments array ────────────────────────────────────────────────────
|
|
325
286
|
|
|
326
287
|
export const CORE_SEGMENTS: FooterSegment[] = [
|
|
327
|
-
{ id: "model", label: "Model", shortLabel: "MDL", description: "Current model name", zone: "left",
|
|
328
|
-
{ id: "api_state", label: "API", shortLabel: "API", description: "API connection state", zone: "left",
|
|
329
|
-
{ id: "tool_count", label: "Tool Count", shortLabel: "TLS", description: "Number of tools available", zone: "left",
|
|
330
|
-
{ id: "git", label: "Git", shortLabel: "GIT", description: "Current git branch + dirty/clean status", zone: "left",
|
|
331
|
-
{ id: "tps", label: "TPS", shortLabel: "TPS", description: "Tokens per second \u2014 live during generation", zone: "center",
|
|
332
|
-
{ id: "context_pct", label: "Context %", shortLabel: "CTX", description: "Context window usage percentage", zone: "center",
|
|
333
|
-
{ id: "cost", label: "Cost", shortLabel: "CST", description: "Session cost in USD", zone: "center",
|
|
334
|
-
{ id: "tokens_total", label: "Tokens Total", shortLabel: "TOK", description: "Total tokens used this session", zone: "center",
|
|
335
|
-
{ id: "tokens_in", label: "Tokens In", shortLabel: "TIN", description: "Input tokens consumed", zone: "center",
|
|
336
|
-
{ id: "tokens_out", label: "Tokens Out", shortLabel: "TOUT", description: "Output tokens generated", zone: "center",
|
|
337
|
-
{ id: "session", label: "Session", shortLabel: "SES", description: "Session identifier", zone: "left",
|
|
338
|
-
{ id: "hostname", label: "Hostname", shortLabel: "HST", description: "Machine hostname", zone: "left",
|
|
339
|
-
{ id: "clock", label: "Clock", shortLabel: "CLK", description: "Current wall time (HH:MM:SS)", zone: "right",
|
|
340
|
-
{ id: "duration", label: "Duration", shortLabel: "DUR", description: "Session duration", zone: "right",
|
|
341
|
-
{ id: "thinking_level", label: "Thinking", shortLabel: "THK", description: "Current model thinking level", zone: "center",
|
|
288
|
+
{ id: "model", label: "Model", shortLabel: "MDL", description: "Current model name", zone: "left", render: renderModelSegment, defaultShow: true },
|
|
289
|
+
{ id: "api_state", label: "API", shortLabel: "API", description: "API connection state", zone: "left", render: renderApiStateSegment, defaultShow: true },
|
|
290
|
+
{ id: "tool_count", label: "Tool Count", shortLabel: "TLS", description: "Number of tools available", zone: "left", render: renderToolCountSegment, defaultShow: true },
|
|
291
|
+
{ id: "git", label: "Git", shortLabel: "GIT", description: "Current git branch + dirty/clean status", zone: "left", render: renderGitSegment, defaultShow: true },
|
|
292
|
+
{ id: "tps", label: "TPS", shortLabel: "TPS", description: "Tokens per second \u2014 live during generation", zone: "center", render: renderTpsSegment, defaultShow: true },
|
|
293
|
+
{ id: "context_pct", label: "Context %", shortLabel: "CTX", description: "Context window usage percentage", zone: "center", render: renderContextPctSegment, defaultShow: true },
|
|
294
|
+
{ id: "cost", label: "Cost", shortLabel: "CST", description: "Session cost in USD", zone: "center", render: renderCostSegment, defaultShow: true },
|
|
295
|
+
{ id: "tokens_total", label: "Tokens Total", shortLabel: "TOK", description: "Total tokens used this session", zone: "center", render: renderTokensSegment("total"), defaultShow: false },
|
|
296
|
+
{ id: "tokens_in", label: "Tokens In", shortLabel: "TIN", description: "Input tokens consumed", zone: "center", render: renderTokensSegment("in"), defaultShow: false },
|
|
297
|
+
{ id: "tokens_out", label: "Tokens Out", shortLabel: "TOUT", description: "Output tokens generated", zone: "center", render: renderTokensSegment("out"), defaultShow: false },
|
|
298
|
+
{ id: "session", label: "Session", shortLabel: "SES", description: "Session identifier", zone: "left", render: renderSessionSegment, defaultShow: false },
|
|
299
|
+
{ id: "hostname", label: "Hostname", shortLabel: "HST", description: "Machine hostname", zone: "left", render: renderHostnameSegment, defaultShow: false },
|
|
300
|
+
{ id: "clock", label: "Clock", shortLabel: "CLK", description: "Current wall time (HH:MM:SS)", zone: "right", render: renderClockSegment, defaultShow: true },
|
|
301
|
+
{ id: "duration", label: "Duration", shortLabel: "DUR", description: "Session duration", zone: "right", render: renderDurationSegment, defaultShow: true },
|
|
302
|
+
{ id: "thinking_level", label: "Thinking", shortLabel: "THK", description: "Current model thinking level", zone: "center", render: renderThinkingLevelSegment, defaultShow: false },
|
|
342
303
|
];
|
package/src/segments/kanboard.ts
CHANGED
|
@@ -22,7 +22,7 @@ function withIcon(segmentId: string, text: string): string {
|
|
|
22
22
|
*/
|
|
23
23
|
function getKanboardData(): Record<string, unknown> | null {
|
|
24
24
|
try {
|
|
25
|
-
const registry = globalThis.__unipi_kanboard_registry;
|
|
25
|
+
const registry = (globalThis as Record<string, unknown>).__unipi_kanboard_registry;
|
|
26
26
|
if (!registry || typeof registry !== "object") return null;
|
|
27
27
|
return registry as Record<string, unknown>;
|
|
28
28
|
} catch {
|
|
@@ -84,8 +84,8 @@ function renderTaskPctSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
export const KANBOARD_SEGMENTS: FooterSegment[] = [
|
|
87
|
-
{ id: "docs_count", label: "Docs", shortLabel: "DOC", description: "Workflow documents count", zone: "center",
|
|
88
|
-
{ id: "tasks_done", label: "Done", shortLabel: "DNE", description: "Completed tasks", zone: "center",
|
|
89
|
-
{ id: "tasks_total", label: "Total", shortLabel: "TSK", description: "Total tasks", zone: "center",
|
|
90
|
-
{ id: "task_pct", label: "Progress", shortLabel: "PCT", description: "Task completion percentage", zone: "center",
|
|
87
|
+
{ id: "docs_count", label: "Docs", shortLabel: "DOC", description: "Workflow documents count", zone: "center", render: renderDocsCountSegment, defaultShow: true },
|
|
88
|
+
{ id: "tasks_done", label: "Done", shortLabel: "DNE", description: "Completed tasks", zone: "center", render: renderTasksDoneSegment, defaultShow: true },
|
|
89
|
+
{ id: "tasks_total", label: "Total", shortLabel: "TSK", description: "Total tasks", zone: "center", render: renderTasksTotalSegment, defaultShow: true },
|
|
90
|
+
{ id: "task_pct", label: "Progress", shortLabel: "PCT", description: "Task completion percentage", zone: "center", render: renderTaskPctSegment, defaultShow: true },
|
|
91
91
|
];
|
package/src/segments/mcp.ts
CHANGED
|
@@ -37,7 +37,7 @@ function withIcon(segmentId: string, text: string): string {
|
|
|
37
37
|
*/
|
|
38
38
|
function getMcpStats(ctx: FooterSegmentContext): McpStats {
|
|
39
39
|
// Source 1: globalThis escape hatch (future: MCP registry may expose this)
|
|
40
|
-
const global = globalThis.__unipi_mcp_stats;
|
|
40
|
+
const global = (globalThis as Record<string, unknown>).__unipi_mcp_stats;
|
|
41
41
|
if (global && typeof global === "object") {
|
|
42
42
|
return global;
|
|
43
43
|
}
|
|
@@ -102,8 +102,8 @@ function renderServersFailedSegment(ctx: FooterSegmentContext): RenderedSegment
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
export const MCP_SEGMENTS: FooterSegment[] = [
|
|
105
|
-
{ id: "servers_total", label: "Servers", shortLabel: "SRV", description: "Total MCP servers configured", zone: "center",
|
|
106
|
-
{ id: "servers_active", label: "Active", shortLabel: "ACT", description: "Currently connected MCP servers", zone: "center",
|
|
107
|
-
{ id: "tools_total", label: "Tools", shortLabel: "TLS", description: "Total MCP tools available", zone: "center",
|
|
108
|
-
{ id: "servers_failed", label: "Failed", shortLabel: "ERR", description: "Failed MCP server connections", zone: "center",
|
|
105
|
+
{ id: "servers_total", label: "Servers", shortLabel: "SRV", description: "Total MCP servers configured", zone: "center", render: renderServersTotalSegment, defaultShow: true },
|
|
106
|
+
{ id: "servers_active", label: "Active", shortLabel: "ACT", description: "Currently connected MCP servers", zone: "center", render: renderServersActiveSegment, defaultShow: true },
|
|
107
|
+
{ id: "tools_total", label: "Tools", shortLabel: "TLS", description: "Total MCP tools available", zone: "center", render: renderToolsTotalSegment, defaultShow: true },
|
|
108
|
+
{ id: "servers_failed", label: "Failed", shortLabel: "ERR", description: "Failed MCP server connections", zone: "center", render: renderServersFailedSegment, defaultShow: true },
|
|
109
109
|
];
|
package/src/segments/memory.ts
CHANGED
|
@@ -51,7 +51,7 @@ interface InfoRegistryLike {
|
|
|
51
51
|
*/
|
|
52
52
|
function getInfoRegistryMemoryData(): InfoMemoryData | null {
|
|
53
53
|
try {
|
|
54
|
-
const registry = globalThis.__unipi_info_registry;
|
|
54
|
+
const registry = (globalThis as Record<string, unknown>).__unipi_info_registry;
|
|
55
55
|
if (!registry || typeof registry !== "object") return null;
|
|
56
56
|
|
|
57
57
|
// The info registry exposes getCachedData(groupId) synchronously
|
|
@@ -139,7 +139,7 @@ function renderConsolidationsSegment(ctx: FooterSegmentContext): RenderedSegment
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
export const MEMORY_SEGMENTS: FooterSegment[] = [
|
|
142
|
-
{ id: "project_count", label: "Project Memory", shortLabel: "MEM", description: "Memory entries for this project", zone: "center",
|
|
143
|
-
{ id: "total_count", label: "Total Memory", shortLabel: "TOT", description: "Total memory entries across projects", zone: "center",
|
|
144
|
-
{ id: "consolidations", label: "Consolidations", shortLabel: "CNS", description: "Number of memory consolidations", zone: "center",
|
|
142
|
+
{ id: "project_count", label: "Project Memory", shortLabel: "MEM", description: "Memory entries for this project", zone: "center", render: renderProjectCountSegment, defaultShow: true },
|
|
143
|
+
{ id: "total_count", label: "Total Memory", shortLabel: "TOT", description: "Total memory entries across projects", zone: "center", render: renderTotalCountSegment, defaultShow: true },
|
|
144
|
+
{ id: "consolidations", label: "Consolidations", shortLabel: "CNS", description: "Number of memory consolidations", zone: "center", render: renderConsolidationsSegment, defaultShow: false },
|
|
145
145
|
];
|
package/src/segments/notify.ts
CHANGED
|
@@ -56,6 +56,6 @@ function renderLastSentSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export const NOTIFY_SEGMENTS: FooterSegment[] = [
|
|
59
|
-
{ id: "platforms_enabled", label: "Platforms", shortLabel: "NTF", description: "Active notification platforms", zone: "center",
|
|
60
|
-
{ id: "last_sent", label: "Last Sent", shortLabel: "LST", description: "Time of last notification sent", zone: "center",
|
|
59
|
+
{ id: "platforms_enabled", label: "Platforms", shortLabel: "NTF", description: "Active notification platforms", zone: "center", render: renderPlatformsEnabledSegment, defaultShow: true },
|
|
60
|
+
{ id: "last_sent", label: "Last Sent", shortLabel: "LST", description: "Time of last notification sent", zone: "center", render: renderLastSentSegment, defaultShow: true },
|
|
61
61
|
];
|
package/src/segments/ralph.ts
CHANGED
|
@@ -117,7 +117,7 @@ function renderLoopStatusSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
export const RALPH_SEGMENTS: FooterSegment[] = [
|
|
120
|
-
{ id: "active_loops", label: "Loops", shortLabel: "RL", description: "Active Ralph loops", zone: "center",
|
|
121
|
-
{ id: "total_iterations", label: "Iterations", shortLabel: "ITR", description: "Total Ralph loop iterations", zone: "center",
|
|
122
|
-
{ id: "loop_status", label: "Status", shortLabel: "STS", description: "Current loop status", zone: "center",
|
|
120
|
+
{ id: "active_loops", label: "Loops", shortLabel: "RL", description: "Active Ralph loops", zone: "center", render: renderActiveLoopsSegment, defaultShow: true },
|
|
121
|
+
{ id: "total_iterations", label: "Iterations", shortLabel: "ITR", description: "Total Ralph loop iterations", zone: "center", render: renderTotalIterationsSegment, defaultShow: true },
|
|
122
|
+
{ id: "loop_status", label: "Status", shortLabel: "STS", description: "Current loop status", zone: "center", render: renderLoopStatusSegment, defaultShow: true },
|
|
123
123
|
];
|
|
@@ -120,5 +120,5 @@ function renderExtensionStatusesSegment(ctx: FooterSegmentContext): RenderedSegm
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
export const STATUS_EXT_SEGMENTS: FooterSegment[] = [
|
|
123
|
-
{ id: "extension_statuses", label: "Extensions", shortLabel: "EXT", description: "Extension statuses overview", zone: "center",
|
|
123
|
+
{ id: "extension_statuses", label: "Extensions", shortLabel: "EXT", description: "Extension statuses overview", zone: "center", render: renderExtensionStatusesSegment, defaultShow: true },
|
|
124
124
|
];
|
package/src/segments/workflow.ts
CHANGED
|
@@ -118,7 +118,7 @@ function formatDuration(ms: number): string {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
export const WORKFLOW_SEGMENTS: FooterSegment[] = [
|
|
121
|
-
{ id: "current_command", label: "Command", shortLabel: "WRK", description: "Active workflow command", zone: "left",
|
|
122
|
-
{ id: "sandbox_level", label: "Sandbox", shortLabel: "SBX", description: "Sandbox permission level", zone: "center",
|
|
123
|
-
{ id: "command_duration", label: "Duration", shortLabel: "CDUR", description: "Current command duration", zone: "center",
|
|
121
|
+
{ id: "current_command", label: "Command", shortLabel: "WRK", description: "Active workflow command", zone: "left", render: renderCurrentCommandSegment, defaultShow: true },
|
|
122
|
+
{ id: "sandbox_level", label: "Sandbox", shortLabel: "SBX", description: "Sandbox permission level", zone: "center", render: renderSandboxLevelSegment, defaultShow: false },
|
|
123
|
+
{ id: "command_duration", label: "Duration", shortLabel: "CDUR", description: "Current command duration", zone: "center", render: renderCommandDurationSegment, defaultShow: true },
|
|
124
124
|
];
|
package/src/tps-tracker.ts
CHANGED
|
@@ -245,14 +245,6 @@ export class TpsTracker {
|
|
|
245
245
|
return last.startedAt > 0 && last.completedAt === 0;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
/**
|
|
249
|
-
* Whether the model was recently generating tokens.
|
|
250
|
-
* Kept for backward compatibility with renderer.
|
|
251
|
-
*/
|
|
252
|
-
isGenerating(): boolean {
|
|
253
|
-
return this.isStreaming();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
248
|
/**
|
|
257
249
|
* Get total output tokens for the session (estimated).
|
|
258
250
|
*/
|
package/src/tui/settings-tui.ts
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import { matchesKey, SettingsList, type SettingItem, type SettingsListTheme } from "@earendil-works/pi-tui";
|
|
13
|
+
import { Key, matchesKey, SettingsList, type SettingItem, type SettingsListTheme } from "@earendil-works/pi-tui";
|
|
14
14
|
import { loadFooterSettings, saveFooterSettings } from "../config.js";
|
|
15
15
|
import { PRESET_NAMES } from "../presets.js";
|
|
16
16
|
import { setIconStyle } from "../rendering/icons.js";
|
|
17
17
|
import type { FooterGroup, FooterSettings, SeparatorStyle, IconStyle } from "../types.js";
|
|
18
|
-
import { boxInnerWidth } from "@pi-unipi/core";
|
|
18
|
+
import { boxInnerWidth, OverlayTheme } from "@pi-unipi/core";
|
|
19
19
|
|
|
20
20
|
// ─── Section types ─────────────────────────────────────────────────────
|
|
21
21
|
|
|
@@ -43,27 +43,10 @@ const THEME: SettingsListTheme = {
|
|
|
43
43
|
hint: (text) => `\x1b[2m${text}\x1b[0m`,
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
// ───
|
|
46
|
+
// ─── Shared box-drawing helper ────────────────────────────────────────
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
const visLen = visibleWidth(content);
|
|
50
|
-
const pad = Math.max(0, innerWidth - visLen);
|
|
51
|
-
return `\x1b[90m│\x1b[0m${content}${" ".repeat(pad)}\x1b[90m│\x1b[0m`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function ruleLine(innerWidth: number): string {
|
|
55
|
-
return `\x1b[90m├${"─".repeat(innerWidth)}┤\x1b[0m`;
|
|
56
|
-
}
|
|
48
|
+
const overlay = new OverlayTheme();
|
|
57
49
|
|
|
58
|
-
function borderLine(innerWidth: number, edge: "top" | "bottom"): string {
|
|
59
|
-
const left = edge === "top" ? "┌" : "└";
|
|
60
|
-
const right = edge === "top" ? "┐" : "┘";
|
|
61
|
-
return `\x1b[90m${left}${"─".repeat(innerWidth)}${right}\x1b[0m`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function visibleWidth(text: string): number {
|
|
65
|
-
return text.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
66
|
-
}
|
|
67
50
|
|
|
68
51
|
// ─── Show the footer settings overlay ──────────────────────────────────
|
|
69
52
|
|
|
@@ -129,7 +112,7 @@ class FooterSettingsOverlay {
|
|
|
129
112
|
|
|
130
113
|
handleInput(data: string): void {
|
|
131
114
|
// Tab cycles sections
|
|
132
|
-
if (data === "\t" || data
|
|
115
|
+
if (data === "\t" || matchesKey(data, Key.shift("tab"))) {
|
|
133
116
|
const idx = SECTIONS.indexOf(this.section);
|
|
134
117
|
if (data === "\t") {
|
|
135
118
|
this.section = SECTIONS[(idx + 1) % SECTIONS.length];
|
|
@@ -209,7 +192,7 @@ class FooterSettingsOverlay {
|
|
|
209
192
|
}
|
|
210
193
|
|
|
211
194
|
// Left arrow / backspace in segment drill-down — back to groups
|
|
212
|
-
if (this.section === "segments" && this.selectedGroupId && (data
|
|
195
|
+
if (this.section === "segments" && this.selectedGroupId && (matchesKey(data, Key.left) || data === "\x7f")) {
|
|
213
196
|
this.backToGroups();
|
|
214
197
|
return;
|
|
215
198
|
}
|
|
@@ -462,8 +445,8 @@ class FooterSettingsOverlay {
|
|
|
462
445
|
const lines: string[] = [];
|
|
463
446
|
|
|
464
447
|
// Header
|
|
465
|
-
lines.push(borderLine(innerWidth, "top"));
|
|
466
|
-
lines.push(frameLine(`\x1b[1m\x1b[36m⚙ Footer Settings\x1b[0m`, innerWidth));
|
|
448
|
+
lines.push(overlay.borderLine(innerWidth, "top"));
|
|
449
|
+
lines.push(overlay.frameLine(`\x1b[1m\x1b[36m⚙ Footer Settings\x1b[0m`, innerWidth));
|
|
467
450
|
|
|
468
451
|
// Section tabs
|
|
469
452
|
const tabParts = SECTIONS.map((s) => {
|
|
@@ -473,20 +456,20 @@ class FooterSettingsOverlay {
|
|
|
473
456
|
}
|
|
474
457
|
return `\x1b[2m${label}\x1b[0m`;
|
|
475
458
|
});
|
|
476
|
-
lines.push(frameLine(` ${tabParts.join(" ")}`, innerWidth));
|
|
477
|
-
lines.push(ruleLine(innerWidth));
|
|
459
|
+
lines.push(overlay.frameLine(` ${tabParts.join(" ")}`, innerWidth));
|
|
460
|
+
lines.push(overlay.ruleLine(innerWidth));
|
|
478
461
|
|
|
479
462
|
// Section content
|
|
480
463
|
const activeList = this.currentList;
|
|
481
464
|
if (activeList) {
|
|
482
465
|
const contentLines = activeList.render(innerWidth - 2);
|
|
483
466
|
for (const line of contentLines) {
|
|
484
|
-
lines.push(frameLine(` ${line}`, innerWidth));
|
|
467
|
+
lines.push(overlay.frameLine(` ${line}`, innerWidth));
|
|
485
468
|
}
|
|
486
469
|
}
|
|
487
470
|
|
|
488
471
|
// Footer hints
|
|
489
|
-
lines.push(ruleLine(innerWidth));
|
|
472
|
+
lines.push(overlay.ruleLine(innerWidth));
|
|
490
473
|
|
|
491
474
|
let hints: string;
|
|
492
475
|
if (this.section === "segments" && this.selectedGroupId) {
|
|
@@ -496,8 +479,8 @@ class FooterSettingsOverlay {
|
|
|
496
479
|
} else {
|
|
497
480
|
hints = "j/k navigate · Space/Enter change · Tab section · q close";
|
|
498
481
|
}
|
|
499
|
-
lines.push(frameLine(`\x1b[2m${hints}\x1b[0m`, innerWidth));
|
|
500
|
-
lines.push(borderLine(innerWidth, "bottom"));
|
|
482
|
+
lines.push(overlay.frameLine(`\x1b[2m${hints}\x1b[0m`, innerWidth));
|
|
483
|
+
lines.push(overlay.borderLine(innerWidth, "bottom"));
|
|
501
484
|
|
|
502
485
|
return lines;
|
|
503
486
|
}
|
package/src/types.ts
CHANGED
|
@@ -145,8 +145,6 @@ export interface FooterSegment {
|
|
|
145
145
|
description: string;
|
|
146
146
|
/** Layout zone assignment */
|
|
147
147
|
zone: SegmentZone;
|
|
148
|
-
/** Icon glyph (Nerd Font or ASCII) */
|
|
149
|
-
icon: string;
|
|
150
148
|
/** Render function */
|
|
151
149
|
render: SegmentRenderFn;
|
|
152
150
|
/** Whether this segment is shown by default */
|
|
@@ -161,8 +159,6 @@ export interface FooterGroup {
|
|
|
161
159
|
id: string;
|
|
162
160
|
/** Display name */
|
|
163
161
|
name: string;
|
|
164
|
-
/** Group icon */
|
|
165
|
-
icon: string;
|
|
166
162
|
/** Segments within this group */
|
|
167
163
|
segments: FooterSegment[];
|
|
168
164
|
/** Whether this group is shown by default */
|
|
@@ -199,13 +195,6 @@ export interface FooterSettings {
|
|
|
199
195
|
groups: Record<string, FooterGroupSettings>;
|
|
200
196
|
}
|
|
201
197
|
|
|
202
|
-
/** Full footer config (settings + runtime state) */
|
|
203
|
-
export interface FooterConfig {
|
|
204
|
-
settings: FooterSettings;
|
|
205
|
-
/** Runtime: whether footer is currently active */
|
|
206
|
-
active: boolean;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
198
|
// ─── Presets ────────────────────────────────────────────────────────────────
|
|
210
199
|
|
|
211
200
|
/** Preset definition */
|
|
@@ -218,10 +207,6 @@ export interface PresetDef {
|
|
|
218
207
|
secondarySegments: string[];
|
|
219
208
|
/** Color scheme for this preset */
|
|
220
209
|
colors?: ColorScheme;
|
|
221
|
-
/** Per-segment options */
|
|
222
|
-
segmentOptions?: Record<string, Record<string, unknown>>;
|
|
223
|
-
/** Zone order (default: left, center, right) */
|
|
224
|
-
zoneOrder?: ("left" | "center" | "right")[];
|
|
225
210
|
/** Zone separator string (between zones) */
|
|
226
211
|
zoneSeparator?: string;
|
|
227
212
|
}
|