@pi-unipi/footer 0.1.2 → 0.1.4
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 +73 -158
- package/package.json +1 -1
- package/src/commands.ts +38 -120
- package/src/config.ts +10 -6
- package/src/events.ts +34 -34
- package/src/help.ts +160 -0
- package/src/index.ts +46 -10
- package/src/presets.ts +40 -31
- package/src/registry/index.ts +5 -7
- package/src/rendering/icons.ts +125 -107
- package/src/rendering/renderer.ts +198 -79
- package/src/rendering/theme.ts +56 -29
- package/src/segments/compactor.ts +21 -10
- package/src/segments/core.ts +134 -67
- package/src/segments/kanboard.ts +24 -8
- package/src/segments/mcp.ts +25 -8
- package/src/segments/memory.ts +17 -11
- package/src/segments/notify.ts +16 -5
- package/src/segments/ralph.ts +33 -17
- package/src/segments/status-ext.ts +18 -13
- package/src/segments/workflow.ts +44 -21
- package/src/tps-tracker.ts +204 -0
- package/src/tui/settings-tui.ts +389 -157
- package/src/types.ts +51 -12
package/src/rendering/theme.ts
CHANGED
|
@@ -8,39 +8,66 @@
|
|
|
8
8
|
import type { Theme, ThemeColor } from "@mariozechner/pi-coding-agent";
|
|
9
9
|
import type { ColorScheme, ColorValue, SemanticColor, ThemeLike } from "../types.js";
|
|
10
10
|
|
|
11
|
+
/** Wrap text in dim ANSI codes for muted placeholder display */
|
|
12
|
+
export function mutedPlaceholder(text: string): string {
|
|
13
|
+
return `\x1b[2m${text}\x1b[0m`;
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
/** Default semantic-to-theme-color mapping */
|
|
12
|
-
const DEFAULT_COLOR_MAP: Record<SemanticColor, ThemeColor
|
|
13
|
-
|
|
17
|
+
const DEFAULT_COLOR_MAP: Record<SemanticColor, ThemeColor | `#${string}`> = {
|
|
18
|
+
// ── Model & Identity (Left zone) ──
|
|
19
|
+
model: "#c792ea", // Soft purple — model name
|
|
14
20
|
path: "text",
|
|
15
|
-
git: "
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
workflowBrainstorm: "
|
|
24
|
-
workflowPlan: "
|
|
25
|
-
workflowWork: "
|
|
26
|
-
workflowReview: "
|
|
27
|
-
workflowAuto: "
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
git: "#82cc6f", // Green (clean default)
|
|
22
|
+
gitClean: "#82cc6f", // Green — clean branch
|
|
23
|
+
gitDirty: "#e5c07b", // Amber — dirty branch
|
|
24
|
+
session: "#61afef", // Blue — session name
|
|
25
|
+
worktree: "#61afef", // Blue — worktree indicator
|
|
26
|
+
// ── Workflow (Left zone) ──
|
|
27
|
+
workflow: "#c792ea", // Purple (default)
|
|
28
|
+
workflowNone: "#4a6a7a", // Muted teal — idle
|
|
29
|
+
workflowBrainstorm: "#e06c75", // Red
|
|
30
|
+
workflowPlan: "#d19a66", // Orange
|
|
31
|
+
workflowWork: "#e5c07b", // Yellow
|
|
32
|
+
workflowReview: "#82cc6f", // Green
|
|
33
|
+
workflowAuto: "#c792ea", // Purple
|
|
34
|
+
workflowDebug: "#e06c75", // Red
|
|
35
|
+
workflowChoreExec: "#d19a66", // Orange
|
|
36
|
+
workflowOther: "#c792ea", // Purple
|
|
37
|
+
// ── TPS tiers (Center zone) ──
|
|
38
|
+
tpsSlow: "#e06c75", // Red — < 30 t/s
|
|
39
|
+
tpsModerate: "#e5c07b", // Amber — 30-50 t/s
|
|
40
|
+
tpsGood: "#56d4bc", // Teal — 50-100 t/s
|
|
41
|
+
tpsFast: "#82cc6f", // Green — 100-200 t/s
|
|
42
|
+
tpsBlazing: "#c792ea", // Purple — > 200 t/s
|
|
43
|
+
tpsIdle: "#4a6a7a", // Muted teal — session avg when idle
|
|
44
|
+
// ── Metrics (Center zone) ──
|
|
45
|
+
compactor: "#56b6c2", // Cyan — compaction stats
|
|
46
|
+
memory: "#61afef", // Blue — memory count
|
|
47
|
+
mcp: "#82cc6f", // Green — MCP status
|
|
48
|
+
ralph: "#e5c07b", // Amber — ralph loops
|
|
49
|
+
ralphOn: "#82cc6c", // Green — ralph active
|
|
50
|
+
ralphOff: "#e06c75", // Red — ralph inactive
|
|
51
|
+
kanboard: "#c678dd", // Purple — kanboard
|
|
52
|
+
notify: "#56b6c2", // Cyan — notifications
|
|
53
|
+
context: "muted", // Theme token for OK context
|
|
54
|
+
contextWarn: "#e5c07b", // Amber — context 70-90%
|
|
55
|
+
contextError: "#e06c75", // Red — context > 90%
|
|
56
|
+
cost: "#d19a66", // Gold — cost
|
|
57
|
+
tokens: "#abb2bf", // Silver — token counts
|
|
58
|
+
// ── Time (Right zone) ──
|
|
59
|
+
clock: "#abb2bf", // Silver — wall clock
|
|
60
|
+
duration: "#61afef", // Blue — session duration
|
|
61
|
+
// ── Thinking levels ──
|
|
62
|
+
thinking: "#61afef",
|
|
63
|
+
thinkingMinimal: "#56b6c2", // Cyan
|
|
64
|
+
thinkingLow: "#61afef", // Blue
|
|
65
|
+
thinkingMedium: "#c792ea", // Purple
|
|
66
|
+
thinkingHigh: "#d19a66", // Gold
|
|
67
|
+
thinkingXhigh: "#e06c75", // Red
|
|
68
|
+
// ── UI chrome ──
|
|
31
69
|
separator: "dim",
|
|
32
70
|
border: "dim",
|
|
33
|
-
context: "muted",
|
|
34
|
-
contextWarn: "warning",
|
|
35
|
-
contextError: "error",
|
|
36
|
-
cost: "text",
|
|
37
|
-
tokens: "muted",
|
|
38
|
-
thinking: "accent",
|
|
39
|
-
thinkingMinimal: "thinkingMinimal",
|
|
40
|
-
thinkingLow: "thinkingLow",
|
|
41
|
-
thinkingMedium: "thinkingMedium",
|
|
42
|
-
thinkingHigh: "thinkingHigh",
|
|
43
|
-
thinkingXhigh: "thinkingXhigh",
|
|
44
71
|
};
|
|
45
72
|
|
|
46
73
|
/**
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { FooterSegment, FooterSegmentContext, RenderedSegment } from "../types.js";
|
|
13
|
-
import { applyColor } from "../rendering/theme.js";
|
|
13
|
+
import { applyColor, mutedPlaceholder } from "../rendering/theme.js";
|
|
14
14
|
import { getIcon } from "../rendering/icons.js";
|
|
15
|
+
import { isSegmentEnabled } from "../config.js";
|
|
15
16
|
|
|
16
17
|
function withIcon(segmentId: string, text: string): string {
|
|
17
18
|
const icon = getIcon(segmentId);
|
|
@@ -50,7 +51,12 @@ function getSessionEvents(ctx: FooterSegmentContext): any[] {
|
|
|
50
51
|
function renderSessionEventsSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
51
52
|
const events = getSessionEvents(ctx);
|
|
52
53
|
const count = events.length;
|
|
53
|
-
if (count === 0)
|
|
54
|
+
if (count === 0) {
|
|
55
|
+
if (isSegmentEnabled("compactor", "session_events")) {
|
|
56
|
+
return { content: mutedPlaceholder("📈 CMP 0"), visible: true };
|
|
57
|
+
}
|
|
58
|
+
return hidden();
|
|
59
|
+
}
|
|
54
60
|
|
|
55
61
|
const content = withIcon("sessionEvents", `${count}`);
|
|
56
62
|
return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
|
|
@@ -66,7 +72,12 @@ function renderCompactionsSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
66
72
|
compactionCount++;
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
|
-
if (compactionCount === 0)
|
|
75
|
+
if (compactionCount === 0) {
|
|
76
|
+
if (isSegmentEnabled("compactor", "compactions")) {
|
|
77
|
+
return { content: mutedPlaceholder("🗜️ CMP 0"), visible: true };
|
|
78
|
+
}
|
|
79
|
+
return hidden();
|
|
80
|
+
}
|
|
70
81
|
|
|
71
82
|
const content = withIcon("compactions", `${compactionCount}`);
|
|
72
83
|
return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
|
|
@@ -125,11 +136,11 @@ function renderSearchQueriesSegment(_ctx: FooterSegmentContext): RenderedSegment
|
|
|
125
136
|
}
|
|
126
137
|
|
|
127
138
|
export const COMPACTOR_SEGMENTS: FooterSegment[] = [
|
|
128
|
-
{ id: "session_events", label: "Session Events", icon: "", render: renderSessionEventsSegment, defaultShow: true },
|
|
129
|
-
{ id: "compactions", label: "Compactions", icon: "", render: renderCompactionsSegment, defaultShow: true },
|
|
130
|
-
{ id: "tokens_saved", label: "Tokens Saved", icon: "", render: renderTokensSavedSegment, defaultShow: true },
|
|
131
|
-
{ id: "compression_ratio", label: "Compression Ratio", icon: "", render: renderCompressionRatioSegment, defaultShow: false },
|
|
132
|
-
{ id: "indexed_docs", label: "Indexed Docs", icon: "", render: renderIndexedDocsSegment, defaultShow: false },
|
|
133
|
-
{ id: "sandbox_runs", label: "Sandbox Runs", icon: "", render: renderSandboxRunsSegment, defaultShow: false },
|
|
134
|
-
{ id: "search_queries", label: "Search Queries", icon: "", render: renderSearchQueriesSegment, defaultShow: false },
|
|
139
|
+
{ id: "session_events", label: "Session Events", shortLabel: "EVT", description: "Number of session events", zone: "center", icon: "", render: renderSessionEventsSegment, defaultShow: true },
|
|
140
|
+
{ id: "compactions", label: "Compactions", shortLabel: "CMP", description: "Number of context compactions", zone: "center", icon: "", render: renderCompactionsSegment, defaultShow: true },
|
|
141
|
+
{ id: "tokens_saved", label: "Tokens Saved", shortLabel: "SVD", description: "Tokens saved by compaction", zone: "center", icon: "", render: renderTokensSavedSegment, defaultShow: true },
|
|
142
|
+
{ id: "compression_ratio", label: "Compression Ratio", shortLabel: "RAT", description: "Last compaction compression ratio", zone: "center", icon: "", render: renderCompressionRatioSegment, defaultShow: false },
|
|
143
|
+
{ id: "indexed_docs", label: "Indexed Docs", shortLabel: "IDX", description: "Number of indexed documents", zone: "center", icon: "", render: renderIndexedDocsSegment, defaultShow: false },
|
|
144
|
+
{ id: "sandbox_runs", label: "Sandbox Runs", shortLabel: "SBX", description: "Number of sandbox code runs", zone: "center", icon: "", render: renderSandboxRunsSegment, defaultShow: false },
|
|
145
|
+
{ id: "search_queries", label: "Search Queries", shortLabel: "QRY", description: "Number of search queries", zone: "center", icon: "", render: renderSearchQueriesSegment, defaultShow: false },
|
|
135
146
|
];
|
package/src/segments/core.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @pi-unipi/footer — Core segments
|
|
3
3
|
*
|
|
4
|
-
* Segment renderers for the core group: model,
|
|
4
|
+
* Segment renderers for the core group: model, api_state, tool_count, git,
|
|
5
5
|
* context_pct, cost, tokens_total, tokens_in, tokens_out, session,
|
|
6
6
|
* hostname, time.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { hostname as osHostname } from "node:os";
|
|
10
|
-
import { basename } from "node:path";
|
|
11
10
|
import type { FooterSegment, FooterSegmentContext, RenderedSegment, SemanticColor } from "../types.js";
|
|
12
11
|
import { applyColor } from "../rendering/theme.js";
|
|
13
12
|
import { getIcon } from "../rendering/icons.js";
|
|
13
|
+
import { tpsTracker } from "../tps-tracker.js";
|
|
14
14
|
|
|
15
15
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
16
16
|
|
|
@@ -61,9 +61,9 @@ function getUsageStats(piContext: unknown): UsageStats {
|
|
|
61
61
|
return { input, output, cacheRead, cacheWrite, cost };
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// ─── Rainbow helpers for
|
|
64
|
+
// ─── Rainbow helpers (kept for potential future use) ─────────────────────────
|
|
65
65
|
|
|
66
|
-
/** ANSI 256-color rainbow palette
|
|
66
|
+
/** ANSI 256-color rainbow palette */
|
|
67
67
|
const RAINBOW_COLORS = [
|
|
68
68
|
"\x1b[38;5;196m", // red
|
|
69
69
|
"\x1b[38;5;202m", // orange
|
|
@@ -100,14 +100,6 @@ export function rainbowBorder(width: number): string {
|
|
|
100
100
|
return result;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
/** Get the current thinking level from piContext */
|
|
104
|
-
export function getThinkingLevel(piContext: unknown): string {
|
|
105
|
-
const piCtx = piContext as Record<string, unknown> | undefined;
|
|
106
|
-
return typeof piCtx?.getThinkingLevel === "function"
|
|
107
|
-
? (piCtx as any).getThinkingLevel()
|
|
108
|
-
: "off";
|
|
109
|
-
}
|
|
110
|
-
|
|
111
103
|
// ─── Segment Renderers ──────────────────────────────────────────────────────
|
|
112
104
|
|
|
113
105
|
function renderModelSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
@@ -121,49 +113,17 @@ function renderModelSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
121
113
|
return { content: color(ctx, "model", content), visible: true };
|
|
122
114
|
}
|
|
123
115
|
|
|
124
|
-
function
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const levelText: Record<string, string> = {
|
|
130
|
-
minimal: "min", low: "low", medium: "med", high: "high", xhigh: "xhigh",
|
|
131
|
-
};
|
|
132
|
-
const label = levelText[thinkingLevel] || thinkingLevel;
|
|
133
|
-
const icon = getIcon("thinking");
|
|
134
|
-
const text = `think:${label}`;
|
|
135
|
-
const content = icon ? `${icon} ${text}` : text;
|
|
136
|
-
|
|
137
|
-
// xhigh uses rainbow coloring
|
|
138
|
-
if (thinkingLevel === "xhigh") {
|
|
139
|
-
return { content: rainbowText(content), visible: true };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let semanticColor: SemanticColor = "thinking";
|
|
143
|
-
if (thinkingLevel === "minimal") semanticColor = "thinkingMinimal";
|
|
144
|
-
else if (thinkingLevel === "low") semanticColor = "thinkingLow";
|
|
145
|
-
else if (thinkingLevel === "medium") semanticColor = "thinkingMedium";
|
|
146
|
-
else if (thinkingLevel === "high") semanticColor = "thinkingHigh";
|
|
147
|
-
|
|
148
|
-
return { content: color(ctx, semanticColor, content), visible: true };
|
|
116
|
+
function renderApiStateSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
117
|
+
// Show WEB to indicate the web-api package is active.
|
|
118
|
+
const content = "WEB";
|
|
119
|
+
return { content: color(ctx, "model", content), visible: true };
|
|
149
120
|
}
|
|
150
121
|
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (home && pwd.startsWith(home)) {
|
|
158
|
-
pwd = `~${pwd.slice(home.length)}`;
|
|
159
|
-
}
|
|
160
|
-
// For brevity, show basename by default
|
|
161
|
-
if (pwd.length > 30) {
|
|
162
|
-
pwd = `…${pwd.slice(-29)}`;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const content = withIcon("path", pwd);
|
|
166
|
-
return { content: color(ctx, "path", content), visible: true };
|
|
122
|
+
function renderToolCountSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
123
|
+
// Tool count is not directly exposed in piContext yet.
|
|
124
|
+
// TODO: Connect to actual tool count when pi exposes it.
|
|
125
|
+
const content = withIcon("toolCount", "—");
|
|
126
|
+
return { content: color(ctx, "model", content), visible: true };
|
|
167
127
|
}
|
|
168
128
|
|
|
169
129
|
function renderGitSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
@@ -171,8 +131,10 @@ function renderGitSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
171
131
|
const branch = footerData?.getGitBranch?.() ?? null;
|
|
172
132
|
if (!branch) return { content: "", visible: false };
|
|
173
133
|
|
|
134
|
+
const isDirty = footerData?.getGitDirty?.() ?? false;
|
|
135
|
+
const semanticColor: SemanticColor = isDirty ? "gitDirty" : "gitClean";
|
|
174
136
|
const content = withIcon("git", branch);
|
|
175
|
-
return { content: color(ctx,
|
|
137
|
+
return { content: color(ctx, semanticColor, content), visible: true };
|
|
176
138
|
}
|
|
177
139
|
|
|
178
140
|
function renderContextPctSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
@@ -247,7 +209,7 @@ function renderSessionSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
247
209
|
const sessionId = (piCtx?.sessionManager as any)?.getSessionId?.();
|
|
248
210
|
const display = sessionId?.slice(0, 8) || "new";
|
|
249
211
|
const content = withIcon("session", display);
|
|
250
|
-
return { content: color(ctx, "
|
|
212
|
+
return { content: color(ctx, "session", content), visible: true };
|
|
251
213
|
}
|
|
252
214
|
|
|
253
215
|
function renderHostnameSegment(_ctx: FooterSegmentContext): RenderedSegment {
|
|
@@ -265,19 +227,124 @@ function renderTimeSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
265
227
|
return { content, visible: true };
|
|
266
228
|
}
|
|
267
229
|
|
|
230
|
+
// ─── TPS tier color function ────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
function getTpsSemanticColor(tps: number): SemanticColor {
|
|
233
|
+
if (tps > 200) return "tpsBlazing";
|
|
234
|
+
if (tps > 100) return "tpsFast";
|
|
235
|
+
if (tps > 50) return "tpsGood";
|
|
236
|
+
if (tps > 30) return "tpsModerate";
|
|
237
|
+
return "tpsSlow";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function renderTpsSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
241
|
+
const streaming = tpsTracker.isStreaming();
|
|
242
|
+
const liveTps = tpsTracker.getLiveTps();
|
|
243
|
+
const avgTps = tpsTracker.getSessionAvgTps();
|
|
244
|
+
|
|
245
|
+
// No data yet — hide
|
|
246
|
+
if (!tpsTracker.getTotalOutput()) return { content: "", visible: false };
|
|
247
|
+
|
|
248
|
+
const icon = getIcon("tps");
|
|
249
|
+
|
|
250
|
+
if (streaming && liveTps > 0) {
|
|
251
|
+
// Active generation: show live rate + avg
|
|
252
|
+
const liveDisplay = Math.round(liveTps);
|
|
253
|
+
const avgDisplay = Math.round(avgTps);
|
|
254
|
+
const liveText = `\u2191 ${liveDisplay} T/S`;
|
|
255
|
+
const avgText = `AVG ${avgDisplay}`;
|
|
256
|
+
const liveColored = applyColor(getTpsSemanticColor(liveTps), liveText, ctx.theme, ctx.colors);
|
|
257
|
+
const avgColored = applyColor("tpsIdle", avgText, ctx.theme, ctx.colors);
|
|
258
|
+
const content = icon ? `${icon} ${liveColored} \u00b7 ${avgColored}` : `${liveColored} \u00b7 ${avgColored}`;
|
|
259
|
+
return { content, visible: true };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Idle: show session average
|
|
263
|
+
const avgDisplay = Math.round(avgTps);
|
|
264
|
+
const avgText = `AVG ${avgDisplay} T/S`;
|
|
265
|
+
const avgColored = applyColor("tpsIdle", avgText, ctx.theme, ctx.colors);
|
|
266
|
+
const content = icon ? `${icon} ${avgColored}` : avgColored;
|
|
267
|
+
return { content, visible: true };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function renderClockSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
271
|
+
const now = new Date();
|
|
272
|
+
const h = now.getHours().toString().padStart(2, "0");
|
|
273
|
+
const m = now.getMinutes().toString().padStart(2, "0");
|
|
274
|
+
const s = now.getSeconds().toString().padStart(2, "0");
|
|
275
|
+
const timeStr = `${h}:${m}:${s}`;
|
|
276
|
+
const content = withIcon("clock", timeStr);
|
|
277
|
+
return { content: color(ctx, "clock", content), visible: true };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function renderDurationSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
281
|
+
// Derive session duration from sessionManager
|
|
282
|
+
const piCtx = ctx.piContext as Record<string, unknown> | undefined;
|
|
283
|
+
const sessionStart = (piCtx?.sessionManager as any)?.getSessionStartTime?.();
|
|
284
|
+
if (!sessionStart) {
|
|
285
|
+
// Fallback: show current time segment style
|
|
286
|
+
return { content: "", visible: false };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const elapsedMs = Date.now() - sessionStart;
|
|
290
|
+
const totalSeconds = Math.floor(elapsedMs / 1000);
|
|
291
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
292
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
293
|
+
const seconds = totalSeconds % 60;
|
|
294
|
+
|
|
295
|
+
let display: string;
|
|
296
|
+
if (hours > 0) {
|
|
297
|
+
display = `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
298
|
+
} else {
|
|
299
|
+
display = `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const content = withIcon("duration", display);
|
|
303
|
+
return { content: color(ctx, "duration", content), visible: true };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ─── Thinking level ──────────────────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
/** Map thinking level to semantic color */
|
|
309
|
+
function getThinkingSemanticColor(level: string | undefined): SemanticColor {
|
|
310
|
+
switch (level) {
|
|
311
|
+
case "minimal": return "thinkingMinimal";
|
|
312
|
+
case "low": return "thinkingLow";
|
|
313
|
+
case "medium": return "thinkingMedium";
|
|
314
|
+
case "high": return "thinkingHigh";
|
|
315
|
+
case "xhigh": return "thinkingXhigh";
|
|
316
|
+
default: return "thinking";
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function renderThinkingLevelSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
321
|
+
const piCtx = ctx.piContext as Record<string, unknown> | undefined;
|
|
322
|
+
const model = piCtx?.model as Record<string, unknown> | undefined;
|
|
323
|
+
const thinkingLevel = model?.thinkingLevel as string | undefined;
|
|
324
|
+
|
|
325
|
+
if (!thinkingLevel || thinkingLevel === "off") return { content: "", visible: false };
|
|
326
|
+
|
|
327
|
+
const semanticColor = getThinkingSemanticColor(thinkingLevel);
|
|
328
|
+
const content = withIcon("thinkingLevel", thinkingLevel);
|
|
329
|
+
return { content: color(ctx, semanticColor, content), visible: true };
|
|
330
|
+
}
|
|
331
|
+
|
|
268
332
|
// ─── Core segments array ────────────────────────────────────────────────────
|
|
269
333
|
|
|
270
334
|
export const CORE_SEGMENTS: FooterSegment[] = [
|
|
271
|
-
{ id: "model", label: "Model", icon: "", render: renderModelSegment, defaultShow: true },
|
|
272
|
-
{ id: "
|
|
273
|
-
{ id: "
|
|
274
|
-
{ id: "git", label: "Git", icon: "", render: renderGitSegment, defaultShow: true },
|
|
275
|
-
{ id: "
|
|
276
|
-
{ id: "
|
|
277
|
-
{ id: "
|
|
278
|
-
{ id: "
|
|
279
|
-
{ id: "
|
|
280
|
-
{ id: "
|
|
281
|
-
{ id: "
|
|
282
|
-
{ id: "
|
|
335
|
+
{ id: "model", label: "Model", shortLabel: "MDL", description: "Current model name", zone: "left", icon: "", render: renderModelSegment, defaultShow: true },
|
|
336
|
+
{ id: "api_state", label: "API", shortLabel: "API", description: "API connection state", zone: "left", icon: "", render: renderApiStateSegment, defaultShow: true },
|
|
337
|
+
{ id: "tool_count", label: "Tool Count", shortLabel: "TLS", description: "Number of tools available", zone: "left", icon: "", render: renderToolCountSegment, defaultShow: true },
|
|
338
|
+
{ id: "git", label: "Git", shortLabel: "GIT", description: "Current git branch + dirty/clean status", zone: "left", icon: "", render: renderGitSegment, defaultShow: true },
|
|
339
|
+
{ id: "tps", label: "TPS", shortLabel: "TPS", description: "Tokens per second \u2014 live during generation", zone: "center", icon: "", render: renderTpsSegment, defaultShow: true },
|
|
340
|
+
{ id: "context_pct", label: "Context %", shortLabel: "CTX", description: "Context window usage percentage", zone: "center", icon: "", render: renderContextPctSegment, defaultShow: true },
|
|
341
|
+
{ id: "cost", label: "Cost", shortLabel: "CST", description: "Session cost in USD", zone: "center", icon: "", render: renderCostSegment, defaultShow: true },
|
|
342
|
+
{ id: "tokens_total", label: "Tokens Total", shortLabel: "TOK", description: "Total tokens used this session", zone: "center", icon: "", render: renderTokensSegment("total"), defaultShow: false },
|
|
343
|
+
{ id: "tokens_in", label: "Tokens In", shortLabel: "TIN", description: "Input tokens consumed", zone: "center", icon: "", render: renderTokensSegment("in"), defaultShow: false },
|
|
344
|
+
{ id: "tokens_out", label: "Tokens Out", shortLabel: "TOUT", description: "Output tokens generated", zone: "center", icon: "", render: renderTokensSegment("out"), defaultShow: false },
|
|
345
|
+
{ id: "session", label: "Session", shortLabel: "SES", description: "Session identifier", zone: "left", icon: "", render: renderSessionSegment, defaultShow: false },
|
|
346
|
+
{ id: "hostname", label: "Hostname", shortLabel: "HST", description: "Machine hostname", zone: "left", icon: "", render: renderHostnameSegment, defaultShow: false },
|
|
347
|
+
{ id: "clock", label: "Clock", shortLabel: "CLK", description: "Current wall time (HH:MM:SS)", zone: "right", icon: "", render: renderClockSegment, defaultShow: true },
|
|
348
|
+
{ id: "duration", label: "Duration", shortLabel: "DUR", description: "Session duration", zone: "right", icon: "", render: renderDurationSegment, defaultShow: true },
|
|
349
|
+
{ id: "thinking_level", label: "Thinking", shortLabel: "THK", description: "Current model thinking level", zone: "center", icon: "", render: renderThinkingLevelSegment, defaultShow: false },
|
|
283
350
|
];
|
package/src/segments/kanboard.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { FooterSegment, FooterSegmentContext, RenderedSegment } from "../types.js";
|
|
10
|
-
import { applyColor } from "../rendering/theme.js";
|
|
10
|
+
import { applyColor, mutedPlaceholder } from "../rendering/theme.js";
|
|
11
11
|
import { getIcon } from "../rendering/icons.js";
|
|
12
|
+
import { isSegmentEnabled } from "../config.js";
|
|
12
13
|
|
|
13
14
|
function withIcon(segmentId: string, text: string): string {
|
|
14
15
|
const icon = getIcon(segmentId);
|
|
@@ -32,7 +33,12 @@ function getKanboardData(): Record<string, unknown> | null {
|
|
|
32
33
|
function renderDocsCountSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
33
34
|
const kb = getKanboardData();
|
|
34
35
|
const value = kb?.docsCount;
|
|
35
|
-
if (value === undefined || value === null)
|
|
36
|
+
if (value === undefined || value === null) {
|
|
37
|
+
if (isSegmentEnabled("kanboard", "docs_count")) {
|
|
38
|
+
return { content: mutedPlaceholder("KB 0"), visible: true };
|
|
39
|
+
}
|
|
40
|
+
return { content: "", visible: false };
|
|
41
|
+
}
|
|
36
42
|
const content = withIcon("docsCount", `${value}`);
|
|
37
43
|
return { content: applyColor("kanboard", content, ctx.theme, ctx.colors), visible: true };
|
|
38
44
|
}
|
|
@@ -40,7 +46,12 @@ function renderDocsCountSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
40
46
|
function renderTasksDoneSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
41
47
|
const kb = getKanboardData();
|
|
42
48
|
const value = kb?.tasksDone;
|
|
43
|
-
if (value === undefined || value === null)
|
|
49
|
+
if (value === undefined || value === null) {
|
|
50
|
+
if (isSegmentEnabled("kanboard", "tasks_done")) {
|
|
51
|
+
return { content: mutedPlaceholder("KB 0"), visible: true };
|
|
52
|
+
}
|
|
53
|
+
return { content: "", visible: false };
|
|
54
|
+
}
|
|
44
55
|
const content = withIcon("tasksDone", `${value}`);
|
|
45
56
|
return { content: applyColor("kanboard", content, ctx.theme, ctx.colors), visible: true };
|
|
46
57
|
}
|
|
@@ -48,7 +59,12 @@ function renderTasksDoneSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
48
59
|
function renderTasksTotalSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
49
60
|
const kb = getKanboardData();
|
|
50
61
|
const value = kb?.tasksTotal;
|
|
51
|
-
if (value === undefined || value === null)
|
|
62
|
+
if (value === undefined || value === null) {
|
|
63
|
+
if (isSegmentEnabled("kanboard", "tasks_total")) {
|
|
64
|
+
return { content: mutedPlaceholder("KB 0"), visible: true };
|
|
65
|
+
}
|
|
66
|
+
return { content: "", visible: false };
|
|
67
|
+
}
|
|
52
68
|
const content = withIcon("tasksTotal", `${value}`);
|
|
53
69
|
return { content: applyColor("kanboard", content, ctx.theme, ctx.colors), visible: true };
|
|
54
70
|
}
|
|
@@ -68,8 +84,8 @@ function renderTaskPctSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
export const KANBOARD_SEGMENTS: FooterSegment[] = [
|
|
71
|
-
{ id: "docs_count", label: "Docs
|
|
72
|
-
{ id: "tasks_done", label: "
|
|
73
|
-
{ id: "tasks_total", label: "
|
|
74
|
-
{ id: "task_pct", label: "Task
|
|
87
|
+
{ id: "docs_count", label: "Docs", shortLabel: "DOC", description: "Workflow documents count", zone: "center", icon: "", render: renderDocsCountSegment, defaultShow: true },
|
|
88
|
+
{ id: "tasks_done", label: "Done", shortLabel: "DNE", description: "Completed tasks", zone: "center", icon: "", render: renderTasksDoneSegment, defaultShow: true },
|
|
89
|
+
{ id: "tasks_total", label: "Total", shortLabel: "TSK", description: "Total tasks", zone: "center", icon: "", render: renderTasksTotalSegment, defaultShow: true },
|
|
90
|
+
{ id: "task_pct", label: "Progress", shortLabel: "PCT", description: "Task completion percentage", zone: "center", icon: "", render: renderTaskPctSegment, defaultShow: true },
|
|
75
91
|
];
|
package/src/segments/mcp.ts
CHANGED
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { FooterSegment, FooterSegmentContext, RenderedSegment } from "../types.js";
|
|
16
|
-
import { applyColor } from "../rendering/theme.js";
|
|
16
|
+
import { applyColor, mutedPlaceholder } from "../rendering/theme.js";
|
|
17
17
|
import { getIcon } from "../rendering/icons.js";
|
|
18
|
+
import { isSegmentEnabled } from "../config.js";
|
|
18
19
|
|
|
19
20
|
/** Shape of aggregate MCP stats from globalThis or registry */
|
|
20
21
|
interface McpStats {
|
|
@@ -64,21 +65,37 @@ function hasUsefulValue(value: unknown): value is number {
|
|
|
64
65
|
|
|
65
66
|
function renderServersTotalSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
66
67
|
const stats = getMcpStats(ctx);
|
|
67
|
-
if (!hasUsefulValue(stats.serversTotal))
|
|
68
|
+
if (!hasUsefulValue(stats.serversTotal)) {
|
|
69
|
+
if (isSegmentEnabled("mcp", "servers_total")) {
|
|
70
|
+
return { content: mutedPlaceholder("🖥️ MCP 0"), visible: true };
|
|
71
|
+
}
|
|
72
|
+
return { content: "", visible: false };
|
|
73
|
+
}
|
|
68
74
|
const content = withIcon("serversTotal", `${stats.serversTotal}`);
|
|
69
75
|
return { content: applyColor("mcp", content, ctx.theme, ctx.colors), visible: true };
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
function renderServersActiveSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
73
79
|
const stats = getMcpStats(ctx);
|
|
74
|
-
if (!hasUsefulValue(stats.serversActive))
|
|
80
|
+
if (!hasUsefulValue(stats.serversActive)) {
|
|
81
|
+
if (isSegmentEnabled("mcp", "servers_active")) {
|
|
82
|
+
const total = stats.serversTotal ?? 0;
|
|
83
|
+
return { content: mutedPlaceholder(`🖥️ MCP ${total}/0`), visible: true };
|
|
84
|
+
}
|
|
85
|
+
return { content: "", visible: false };
|
|
86
|
+
}
|
|
75
87
|
const content = withIcon("serversActive", `${stats.serversActive}`);
|
|
76
88
|
return { content: applyColor("mcp", content, ctx.theme, ctx.colors), visible: true };
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
function renderToolsTotalSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
80
92
|
const stats = getMcpStats(ctx);
|
|
81
|
-
if (!hasUsefulValue(stats.toolsTotal))
|
|
93
|
+
if (!hasUsefulValue(stats.toolsTotal)) {
|
|
94
|
+
if (isSegmentEnabled("mcp", "tools_total")) {
|
|
95
|
+
return { content: mutedPlaceholder("🖥️ MCP 0"), visible: true };
|
|
96
|
+
}
|
|
97
|
+
return { content: "", visible: false };
|
|
98
|
+
}
|
|
82
99
|
const content = withIcon("toolsTotal", `${stats.toolsTotal}`);
|
|
83
100
|
return { content: applyColor("mcp", content, ctx.theme, ctx.colors), visible: true };
|
|
84
101
|
}
|
|
@@ -93,8 +110,8 @@ function renderServersFailedSegment(ctx: FooterSegmentContext): RenderedSegment
|
|
|
93
110
|
}
|
|
94
111
|
|
|
95
112
|
export const MCP_SEGMENTS: FooterSegment[] = [
|
|
96
|
-
{ id: "servers_total", label: "Servers Total", icon: "", render: renderServersTotalSegment, defaultShow: true },
|
|
97
|
-
{ id: "servers_active", label: "
|
|
98
|
-
{ id: "tools_total", label: "Tools Total", icon: "", render: renderToolsTotalSegment, defaultShow: true },
|
|
99
|
-
{ id: "servers_failed", label: "
|
|
113
|
+
{ id: "servers_total", label: "Servers", shortLabel: "SRV", description: "Total MCP servers configured", zone: "center", icon: "", render: renderServersTotalSegment, defaultShow: true },
|
|
114
|
+
{ id: "servers_active", label: "Active", shortLabel: "ACT", description: "Currently connected MCP servers", zone: "center", icon: "", render: renderServersActiveSegment, defaultShow: true },
|
|
115
|
+
{ id: "tools_total", label: "Tools", shortLabel: "TLS", description: "Total MCP tools available", zone: "center", icon: "", render: renderToolsTotalSegment, defaultShow: true },
|
|
116
|
+
{ id: "servers_failed", label: "Failed", shortLabel: "ERR", description: "Failed MCP server connections", zone: "center", icon: "", render: renderServersFailedSegment, defaultShow: true },
|
|
100
117
|
];
|
package/src/segments/memory.ts
CHANGED
|
@@ -18,11 +18,14 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import type { FooterSegment, FooterSegmentContext, RenderedSegment } from "../types.js";
|
|
21
|
-
import { applyColor } from "../rendering/theme.js";
|
|
21
|
+
import { applyColor, mutedPlaceholder } from "../rendering/theme.js";
|
|
22
22
|
import { getIcon } from "../rendering/icons.js";
|
|
23
|
+
import { isSegmentEnabled } from "../config.js";
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
const
|
|
25
|
+
function withIcon(segmentId: string, text: string): string {
|
|
26
|
+
const icon = getIcon(segmentId);
|
|
27
|
+
return icon ? `${icon} ${text}` : text;
|
|
28
|
+
}
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
* Shape of the info-screen memory group data:
|
|
@@ -79,16 +82,19 @@ function getMemoryCounts(): { project: number | null; total: number | null } {
|
|
|
79
82
|
function renderProjectCountSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
80
83
|
const counts = getMemoryCounts();
|
|
81
84
|
if (counts.project === null) {
|
|
85
|
+
if (isSegmentEnabled("memory", "project_count")) {
|
|
86
|
+
return { content: mutedPlaceholder("🧠 MEM 0"), visible: true };
|
|
87
|
+
}
|
|
82
88
|
return { content: "", visible: false };
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
// Show as 76/102 format when both are available
|
|
86
92
|
if (counts.total !== null) {
|
|
87
|
-
const content = `${
|
|
93
|
+
const content = withIcon("projectCount", `${counts.project}/${counts.total}`);
|
|
88
94
|
return { content: applyColor("memory", content, ctx.theme, ctx.colors), visible: true };
|
|
89
95
|
}
|
|
90
96
|
|
|
91
|
-
const content = `${
|
|
97
|
+
const content = withIcon("projectCount", `${counts.project}`);
|
|
92
98
|
return { content: applyColor("memory", content, ctx.theme, ctx.colors), visible: true };
|
|
93
99
|
}
|
|
94
100
|
|
|
@@ -106,7 +112,7 @@ function renderTotalCountSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
106
112
|
return { content: "", visible: false };
|
|
107
113
|
}
|
|
108
114
|
|
|
109
|
-
const content = `${
|
|
115
|
+
const content = withIcon("totalCount", `${counts.total}`);
|
|
110
116
|
return { content: applyColor("memory", content, ctx.theme, ctx.colors), visible: true };
|
|
111
117
|
}
|
|
112
118
|
|
|
@@ -116,7 +122,7 @@ function renderConsolidationsSegment(ctx: FooterSegmentContext): RenderedSegment
|
|
|
116
122
|
// Check for explicit consolidations stat from info registry
|
|
117
123
|
const consolidationsValue = infoData?.consolidations?.value;
|
|
118
124
|
if (consolidationsValue !== undefined && consolidationsValue !== null) {
|
|
119
|
-
const content =
|
|
125
|
+
const content = withIcon("consolidations", `cns:${consolidationsValue}`);
|
|
120
126
|
return { content: applyColor("memory", content, ctx.theme, ctx.colors), visible: true };
|
|
121
127
|
}
|
|
122
128
|
|
|
@@ -129,12 +135,12 @@ function renderConsolidationsSegment(ctx: FooterSegmentContext): RenderedSegment
|
|
|
129
135
|
return { content: "", visible: false };
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
const content =
|
|
138
|
+
const content = withIcon("consolidations", `cns:${count}`);
|
|
133
139
|
return { content: applyColor("memory", content, ctx.theme, ctx.colors), visible: true };
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
export const MEMORY_SEGMENTS: FooterSegment[] = [
|
|
137
|
-
{ id: "project_count", label: "Project
|
|
138
|
-
{ id: "total_count", label: "Total
|
|
139
|
-
{ id: "consolidations", label: "Consolidations", icon: "", render: renderConsolidationsSegment, defaultShow: false },
|
|
143
|
+
{ id: "project_count", label: "Project Memory", shortLabel: "MEM", description: "Memory entries for this project", zone: "center", icon: "", render: renderProjectCountSegment, defaultShow: true },
|
|
144
|
+
{ id: "total_count", label: "Total Memory", shortLabel: "TOT", description: "Total memory entries across projects", zone: "center", icon: "", render: renderTotalCountSegment, defaultShow: true },
|
|
145
|
+
{ id: "consolidations", label: "Consolidations", shortLabel: "CNS", description: "Number of memory consolidations", zone: "center", icon: "", render: renderConsolidationsSegment, defaultShow: false },
|
|
140
146
|
];
|