@pi-unipi/footer 0.1.2 → 0.1.3
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 +1 -1
- package/src/commands.ts +3 -0
- package/src/config.ts +6 -6
- package/src/events.ts +34 -34
- package/src/index.ts +21 -9
- package/src/presets.ts +6 -6
- package/src/registry/index.ts +5 -7
- package/src/rendering/icons.ts +88 -88
- package/src/rendering/renderer.ts +4 -4
- package/src/segments/core.ts +14 -55
- package/src/segments/memory.ts +9 -7
- package/src/segments/ralph.ts +9 -10
- package/src/segments/status-ext.ts +17 -12
- package/src/segments/workflow.ts +5 -4
- package/src/tui/settings-tui.ts +216 -155
package/src/rendering/icons.ts
CHANGED
|
@@ -16,8 +16,8 @@ import { detectNerdFontSupport } from "./separators.js";
|
|
|
16
16
|
export interface IconSet {
|
|
17
17
|
// Core segments
|
|
18
18
|
model: string;
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
apiState: string;
|
|
20
|
+
toolCount: string;
|
|
21
21
|
git: string;
|
|
22
22
|
context: string;
|
|
23
23
|
cost: string;
|
|
@@ -80,38 +80,38 @@ export interface IconSet {
|
|
|
80
80
|
/** Nerd Font glyphs — requires a Nerd Font installed in the terminal */
|
|
81
81
|
export const NERD_ICONS: IconSet = {
|
|
82
82
|
// Core
|
|
83
|
-
model: "\
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
git: "\
|
|
87
|
-
context: "\
|
|
88
|
-
cost: "\uF155", //
|
|
89
|
-
tokens: "\
|
|
90
|
-
tokensIn: "\
|
|
91
|
-
tokensOut: "\
|
|
83
|
+
model: "\uDB81\uDE5B", // custom model icon
|
|
84
|
+
apiState: "\uF725", // api state icon
|
|
85
|
+
toolCount: "\uF0AD", // tool count icon
|
|
86
|
+
git: "\uF0E8", // git icon
|
|
87
|
+
context: "\uF8D8", // context icon
|
|
88
|
+
cost: "\uF155", // cost icon
|
|
89
|
+
tokens: "\uF07B", // tokens icon
|
|
90
|
+
tokensIn: "\uF07B", // tokens in icon
|
|
91
|
+
tokensOut: "\uF07B", // tokens out icon
|
|
92
92
|
session: "\uF550", // nf-md-identifier
|
|
93
93
|
hostname: "\uF109", // nf-fa-laptop
|
|
94
94
|
time: "\uF017", // nf-fa-clock_o
|
|
95
95
|
|
|
96
96
|
// Compactor
|
|
97
|
-
sessionEvents: "\
|
|
98
|
-
compactions: "\
|
|
99
|
-
tokensSaved: "\uF155", //
|
|
100
|
-
compressionRatio:"\
|
|
101
|
-
indexedDocs: "\
|
|
102
|
-
sandboxRuns: "\uF121", //
|
|
103
|
-
searchQueries: "\uF002", //
|
|
97
|
+
sessionEvents: "\uDBB1\uDECF", // session events icon
|
|
98
|
+
compactions: "\uDBB1\uDECF", // compactions icon
|
|
99
|
+
tokensSaved: "\uF155", // tokens saved icon
|
|
100
|
+
compressionRatio:"\uDBB1\uDECF", // compression ratio icon
|
|
101
|
+
indexedDocs: "\uDB81\uDE19", // indexed docs icon
|
|
102
|
+
sandboxRuns: "\uF121", // sandbox runs icon
|
|
103
|
+
searchQueries: "\uF002", // search queries icon
|
|
104
104
|
|
|
105
105
|
// Memory
|
|
106
|
-
projectCount: "\
|
|
107
|
-
totalCount: "\
|
|
108
|
-
consolidations: "\
|
|
106
|
+
projectCount: "\uDB81\uDED4", // memory icon
|
|
107
|
+
totalCount: "\uEB9C", // total count icon
|
|
108
|
+
consolidations: "\uDB81\uDED4", // consolidations icon
|
|
109
109
|
|
|
110
110
|
// MCP
|
|
111
|
-
serversTotal: "\
|
|
112
|
-
serversActive: "\uF058", //
|
|
113
|
-
toolsTotal: "\uF0AD", //
|
|
114
|
-
serversFailed: "\
|
|
111
|
+
serversTotal: "\uF0F6", // servers total icon
|
|
112
|
+
serversActive: "\uF058", // servers active icon
|
|
113
|
+
toolsTotal: "\uF0AD", // tools total icon
|
|
114
|
+
serversFailed: "\uF467", // servers failed icon
|
|
115
115
|
|
|
116
116
|
// Ralph
|
|
117
117
|
activeLoops: "\udb81\udf09", // ralph loop icon
|
|
@@ -119,22 +119,22 @@ export const NERD_ICONS: IconSet = {
|
|
|
119
119
|
loopStatus: "\udb81\udf09", // ralph loop icon
|
|
120
120
|
|
|
121
121
|
// Workflow
|
|
122
|
-
currentCommand: "\
|
|
123
|
-
sandboxLevel: "\
|
|
124
|
-
commandDuration: "\
|
|
122
|
+
currentCommand: "\uF0E8", // current command icon
|
|
123
|
+
sandboxLevel: "\uDBB1\uDDFE", // sandbox level icon
|
|
124
|
+
commandDuration: "\uDBB9\uDEAB", // command duration icon
|
|
125
125
|
|
|
126
126
|
// Kanboard
|
|
127
|
-
docsCount: "\
|
|
128
|
-
tasksDone: "\
|
|
129
|
-
tasksTotal: "\
|
|
130
|
-
taskPct: "\
|
|
127
|
+
docsCount: "\uDB81\uDE19", // docs count icon
|
|
128
|
+
tasksDone: "\uF0E8", // tasks done icon
|
|
129
|
+
tasksTotal: "\uF0E8", // tasks total icon
|
|
130
|
+
taskPct: "\uF0E8", // task pct icon
|
|
131
131
|
|
|
132
132
|
// Notify
|
|
133
133
|
platformsEnabled:"\uF0E0", // nf-fa-envelope
|
|
134
134
|
lastSent: "\uF017", // nf-fa-clock_o
|
|
135
135
|
|
|
136
136
|
// Extension status
|
|
137
|
-
extensionStatuses:"\
|
|
137
|
+
extensionStatuses:"\uDBB5\uDEAB", // extension statuses icon
|
|
138
138
|
|
|
139
139
|
separator: "\uE0B1", // nf-pl-left_soft_divider
|
|
140
140
|
};
|
|
@@ -144,12 +144,12 @@ export const NERD_ICONS: IconSet = {
|
|
|
144
144
|
/** Unicode emoji / symbol icons — works on most modern terminals */
|
|
145
145
|
export const EMOJI_ICONS: IconSet = {
|
|
146
146
|
// Core
|
|
147
|
-
model: "",
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
model: "🤖",
|
|
148
|
+
apiState: "🔄",
|
|
149
|
+
toolCount: "🔧",
|
|
150
150
|
git: "⎇",
|
|
151
|
-
context: "",
|
|
152
|
-
cost: "
|
|
151
|
+
context: "🗄️",
|
|
152
|
+
cost: "💲",
|
|
153
153
|
tokens: "⊛",
|
|
154
154
|
tokensIn: "→",
|
|
155
155
|
tokensOut: "←",
|
|
@@ -160,31 +160,31 @@ export const EMOJI_ICONS: IconSet = {
|
|
|
160
160
|
// Compactor
|
|
161
161
|
sessionEvents: "⚡",
|
|
162
162
|
compactions: "◧",
|
|
163
|
-
tokensSaved: "
|
|
163
|
+
tokensSaved: "💲",
|
|
164
164
|
compressionRatio:"⇄",
|
|
165
165
|
indexedDocs: "☰",
|
|
166
166
|
sandboxRuns: "▶",
|
|
167
167
|
searchQueries: "⊗",
|
|
168
168
|
|
|
169
169
|
// Memory
|
|
170
|
-
projectCount: "
|
|
171
|
-
totalCount: "
|
|
172
|
-
consolidations: "
|
|
170
|
+
projectCount: "🧠",
|
|
171
|
+
totalCount: "🧠",
|
|
172
|
+
consolidations: "🧠",
|
|
173
173
|
|
|
174
174
|
// MCP
|
|
175
|
-
serversTotal: "
|
|
175
|
+
serversTotal: "🖥️",
|
|
176
176
|
serversActive: "●",
|
|
177
177
|
toolsTotal: "🔧",
|
|
178
|
-
serversFailed: "
|
|
178
|
+
serversFailed: "⚠️",
|
|
179
179
|
|
|
180
180
|
// Ralph
|
|
181
|
-
activeLoops: "",
|
|
182
|
-
totalIterations: "",
|
|
183
|
-
loopStatus: "",
|
|
181
|
+
activeLoops: "🔁",
|
|
182
|
+
totalIterations: "🔁",
|
|
183
|
+
loopStatus: "🔁",
|
|
184
184
|
|
|
185
185
|
// Workflow
|
|
186
|
-
currentCommand: "",
|
|
187
|
-
sandboxLevel: "
|
|
186
|
+
currentCommand: "▶️",
|
|
187
|
+
sandboxLevel: "🔒",
|
|
188
188
|
commandDuration: "⏱",
|
|
189
189
|
|
|
190
190
|
// Kanboard
|
|
@@ -208,61 +208,61 @@ export const EMOJI_ICONS: IconSet = {
|
|
|
208
208
|
/** Plain text labels — works everywhere, most compact */
|
|
209
209
|
export const TEXT_ICONS: IconSet = {
|
|
210
210
|
// Core
|
|
211
|
-
model: "",
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
git: "",
|
|
215
|
-
context: "",
|
|
216
|
-
cost: "",
|
|
217
|
-
tokens: "",
|
|
218
|
-
tokensIn: "",
|
|
219
|
-
tokensOut: "",
|
|
220
|
-
session: "",
|
|
221
|
-
hostname: "",
|
|
222
|
-
time: "",
|
|
211
|
+
model: "MDL",
|
|
212
|
+
apiState: "API",
|
|
213
|
+
toolCount: "TLS",
|
|
214
|
+
git: "GIT",
|
|
215
|
+
context: "CTX",
|
|
216
|
+
cost: "CST",
|
|
217
|
+
tokens: "TOK",
|
|
218
|
+
tokensIn: "TKI",
|
|
219
|
+
tokensOut: "TKO",
|
|
220
|
+
session: "SES",
|
|
221
|
+
hostname: "HST",
|
|
222
|
+
time: "TIM",
|
|
223
223
|
|
|
224
224
|
// Compactor
|
|
225
|
-
sessionEvents: "
|
|
226
|
-
compactions: "
|
|
227
|
-
tokensSaved: "
|
|
228
|
-
compressionRatio:"
|
|
229
|
-
indexedDocs: "
|
|
230
|
-
sandboxRuns: "
|
|
231
|
-
searchQueries: "
|
|
225
|
+
sessionEvents: "EVT",
|
|
226
|
+
compactions: "CMP",
|
|
227
|
+
tokensSaved: "SVD",
|
|
228
|
+
compressionRatio:"RAT",
|
|
229
|
+
indexedDocs: "IDX",
|
|
230
|
+
sandboxRuns: "SBX",
|
|
231
|
+
searchQueries: "QRY",
|
|
232
232
|
|
|
233
233
|
// Memory
|
|
234
|
-
projectCount: "
|
|
235
|
-
totalCount: "
|
|
236
|
-
consolidations: "
|
|
234
|
+
projectCount: "MEM",
|
|
235
|
+
totalCount: "MEM",
|
|
236
|
+
consolidations: "CNS",
|
|
237
237
|
|
|
238
238
|
// MCP
|
|
239
|
-
serversTotal: "
|
|
240
|
-
serversActive: "
|
|
241
|
-
toolsTotal: "
|
|
242
|
-
serversFailed: "
|
|
239
|
+
serversTotal: "SRV",
|
|
240
|
+
serversActive: "ACT",
|
|
241
|
+
toolsTotal: "TLS",
|
|
242
|
+
serversFailed: "ERR",
|
|
243
243
|
|
|
244
244
|
// Ralph
|
|
245
|
-
activeLoops: "",
|
|
246
|
-
totalIterations: "",
|
|
247
|
-
loopStatus: "",
|
|
245
|
+
activeLoops: "LPS",
|
|
246
|
+
totalIterations: "ITR",
|
|
247
|
+
loopStatus: "STS",
|
|
248
248
|
|
|
249
249
|
// Workflow
|
|
250
|
-
currentCommand: "",
|
|
251
|
-
sandboxLevel: "
|
|
252
|
-
commandDuration: "
|
|
250
|
+
currentCommand: "CMD",
|
|
251
|
+
sandboxLevel: "SBX",
|
|
252
|
+
commandDuration: "DUR",
|
|
253
253
|
|
|
254
254
|
// Kanboard
|
|
255
|
-
docsCount: "
|
|
256
|
-
tasksDone: "
|
|
257
|
-
tasksTotal: "
|
|
258
|
-
taskPct: "
|
|
255
|
+
docsCount: "DOC",
|
|
256
|
+
tasksDone: "DNE",
|
|
257
|
+
tasksTotal: "TSK",
|
|
258
|
+
taskPct: "PCT",
|
|
259
259
|
|
|
260
260
|
// Notify
|
|
261
|
-
platformsEnabled:"
|
|
262
|
-
lastSent: "
|
|
261
|
+
platformsEnabled:"NTF",
|
|
262
|
+
lastSent: "LST",
|
|
263
263
|
|
|
264
264
|
// Extension status
|
|
265
|
-
extensionStatuses:"
|
|
265
|
+
extensionStatuses:"EXT",
|
|
266
266
|
|
|
267
267
|
separator: "|",
|
|
268
268
|
};
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
10
10
|
import type { PresetDef, FooterSegmentContext, FooterSegment, ColorScheme, RenderedSegment } from "../types.js";
|
|
11
11
|
import type { FooterRegistry } from "../registry/index.js";
|
|
12
|
+
import { visibleWidth as piVisibleWidth, truncateToWidth } from "@mariozechner/pi-tui";
|
|
12
13
|
import { getSeparator, separatorVisibleWidth } from "./separators.js";
|
|
13
14
|
import { getDefaultColors } from "./theme.js";
|
|
14
15
|
import { setIconStyle } from "./icons.js";
|
|
@@ -29,10 +30,9 @@ interface RenderedSegmentWithWidth {
|
|
|
29
30
|
|
|
30
31
|
// ─── ANSI helpers ───────────────────────────────────────────────────────────
|
|
31
32
|
|
|
32
|
-
/**
|
|
33
|
+
/** ANSI-aware visible width using pi-tui */
|
|
33
34
|
function visibleWidth(text: string): number {
|
|
34
|
-
|
|
35
|
-
return stripped.length;
|
|
35
|
+
return piVisibleWidth(text);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const ANSI_RESET = "\x1b[0m";
|
|
@@ -265,7 +265,7 @@ export class FooterRenderer {
|
|
|
265
265
|
/** Map a segment ID to its group ID */
|
|
266
266
|
private getGroupForSegment(segId: string): string {
|
|
267
267
|
// Core segments
|
|
268
|
-
const coreIds = ["model", "
|
|
268
|
+
const coreIds = ["model", "api_state", "tool_count", "git", "context_pct", "cost", "tokens_total", "tokens_in", "tokens_out", "session", "hostname", "time"];
|
|
269
269
|
if (coreIds.includes(segId)) return "core";
|
|
270
270
|
|
|
271
271
|
// Compactor segments
|
package/src/segments/core.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
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";
|
|
@@ -61,9 +60,9 @@ function getUsageStats(piContext: unknown): UsageStats {
|
|
|
61
60
|
return { input, output, cacheRead, cacheWrite, cost };
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
// ─── Rainbow helpers for
|
|
63
|
+
// ─── Rainbow helpers (kept for potential future use) ─────────────────────────
|
|
65
64
|
|
|
66
|
-
/** ANSI 256-color rainbow palette
|
|
65
|
+
/** ANSI 256-color rainbow palette */
|
|
67
66
|
const RAINBOW_COLORS = [
|
|
68
67
|
"\x1b[38;5;196m", // red
|
|
69
68
|
"\x1b[38;5;202m", // orange
|
|
@@ -100,14 +99,6 @@ export function rainbowBorder(width: number): string {
|
|
|
100
99
|
return result;
|
|
101
100
|
}
|
|
102
101
|
|
|
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
102
|
// ─── Segment Renderers ──────────────────────────────────────────────────────
|
|
112
103
|
|
|
113
104
|
function renderModelSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
@@ -121,49 +112,17 @@ function renderModelSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
121
112
|
return { content: color(ctx, "model", content), visible: true };
|
|
122
113
|
}
|
|
123
114
|
|
|
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 };
|
|
115
|
+
function renderApiStateSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
116
|
+
// Show WEB to indicate the web-api package is active.
|
|
117
|
+
const content = "WEB";
|
|
118
|
+
return { content: color(ctx, "model", content), visible: true };
|
|
149
119
|
}
|
|
150
120
|
|
|
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 };
|
|
121
|
+
function renderToolCountSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
122
|
+
// Tool count is not directly exposed in piContext yet.
|
|
123
|
+
// TODO: Connect to actual tool count when pi exposes it.
|
|
124
|
+
const content = withIcon("toolCount", "—");
|
|
125
|
+
return { content: color(ctx, "model", content), visible: true };
|
|
167
126
|
}
|
|
168
127
|
|
|
169
128
|
function renderGitSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
@@ -269,8 +228,8 @@ function renderTimeSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
269
228
|
|
|
270
229
|
export const CORE_SEGMENTS: FooterSegment[] = [
|
|
271
230
|
{ id: "model", label: "Model", icon: "", render: renderModelSegment, defaultShow: true },
|
|
272
|
-
{ id: "
|
|
273
|
-
{ id: "
|
|
231
|
+
{ id: "api_state", label: "WEB", icon: "", render: renderApiStateSegment, defaultShow: true },
|
|
232
|
+
{ id: "tool_count", label: "Tool Count", icon: "", render: renderToolCountSegment, defaultShow: true },
|
|
274
233
|
{ id: "git", label: "Git", icon: "", render: renderGitSegment, defaultShow: true },
|
|
275
234
|
{ id: "context_pct", label: "Context %", icon: "", render: renderContextPctSegment, defaultShow: true },
|
|
276
235
|
{ id: "cost", label: "Cost", icon: "", render: renderCostSegment, defaultShow: true },
|
package/src/segments/memory.ts
CHANGED
|
@@ -21,8 +21,10 @@ import type { FooterSegment, FooterSegmentContext, RenderedSegment } from "../ty
|
|
|
21
21
|
import { applyColor } from "../rendering/theme.js";
|
|
22
22
|
import { getIcon } from "../rendering/icons.js";
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
const
|
|
24
|
+
function withIcon(segmentId: string, text: string): string {
|
|
25
|
+
const icon = getIcon(segmentId);
|
|
26
|
+
return icon ? `${icon} ${text}` : text;
|
|
27
|
+
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
30
|
* Shape of the info-screen memory group data:
|
|
@@ -84,11 +86,11 @@ function renderProjectCountSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
84
86
|
|
|
85
87
|
// Show as 76/102 format when both are available
|
|
86
88
|
if (counts.total !== null) {
|
|
87
|
-
const content = `${
|
|
89
|
+
const content = withIcon("projectCount", `${counts.project}/${counts.total}`);
|
|
88
90
|
return { content: applyColor("memory", content, ctx.theme, ctx.colors), visible: true };
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
const content = `${
|
|
93
|
+
const content = withIcon("projectCount", `${counts.project}`);
|
|
92
94
|
return { content: applyColor("memory", content, ctx.theme, ctx.colors), visible: true };
|
|
93
95
|
}
|
|
94
96
|
|
|
@@ -106,7 +108,7 @@ function renderTotalCountSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
106
108
|
return { content: "", visible: false };
|
|
107
109
|
}
|
|
108
110
|
|
|
109
|
-
const content = `${
|
|
111
|
+
const content = withIcon("totalCount", `${counts.total}`);
|
|
110
112
|
return { content: applyColor("memory", content, ctx.theme, ctx.colors), visible: true };
|
|
111
113
|
}
|
|
112
114
|
|
|
@@ -116,7 +118,7 @@ function renderConsolidationsSegment(ctx: FooterSegmentContext): RenderedSegment
|
|
|
116
118
|
// Check for explicit consolidations stat from info registry
|
|
117
119
|
const consolidationsValue = infoData?.consolidations?.value;
|
|
118
120
|
if (consolidationsValue !== undefined && consolidationsValue !== null) {
|
|
119
|
-
const content =
|
|
121
|
+
const content = withIcon("consolidations", `cns:${consolidationsValue}`);
|
|
120
122
|
return { content: applyColor("memory", content, ctx.theme, ctx.colors), visible: true };
|
|
121
123
|
}
|
|
122
124
|
|
|
@@ -129,7 +131,7 @@ function renderConsolidationsSegment(ctx: FooterSegmentContext): RenderedSegment
|
|
|
129
131
|
return { content: "", visible: false };
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
const content =
|
|
134
|
+
const content = withIcon("consolidations", `cns:${count}`);
|
|
133
135
|
return { content: applyColor("memory", content, ctx.theme, ctx.colors), visible: true };
|
|
134
136
|
}
|
|
135
137
|
|
package/src/segments/ralph.ts
CHANGED
|
@@ -14,8 +14,7 @@ import type { FooterSegment, FooterSegmentContext, RenderedSegment, SemanticColo
|
|
|
14
14
|
import { applyColor } from "../rendering/theme.js";
|
|
15
15
|
import { getIcon } from "../rendering/icons.js";
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
const RALPH_ICON = "\udb81\udf09";
|
|
17
|
+
|
|
19
18
|
|
|
20
19
|
/** Green dot indicator (with explicit ANSI codes) */
|
|
21
20
|
const GREEN_DOT = "\x1b[38;5;82m●\x1b[0m";
|
|
@@ -52,6 +51,8 @@ function renderActiveLoopsSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
52
51
|
|
|
53
52
|
const dot = active ? GREEN_DOT : RED_DOT;
|
|
54
53
|
|
|
54
|
+
const ralphIcon = getIcon("activeLoops");
|
|
55
|
+
|
|
55
56
|
if (active) {
|
|
56
57
|
// Active: green dot + iteration stats
|
|
57
58
|
const iterStr = iteration !== undefined
|
|
@@ -59,15 +60,11 @@ function renderActiveLoopsSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
59
60
|
: "";
|
|
60
61
|
const nameStr = name ? ` ${name}` : "";
|
|
61
62
|
// Color the icon and text parts, keep dot's own color
|
|
62
|
-
const
|
|
63
|
-
const coloredText = colorText(ctx, "ralphOn", iconAndText);
|
|
64
|
-
// Insert the dot after the icon
|
|
65
|
-
const content = `${RALPH_ICON} ${dot} ${colorText(ctx, "ralphOn", `${iterStr}${nameStr}`)}`;
|
|
63
|
+
const content = `${ralphIcon} ${dot} ${colorText(ctx, "ralphOn", `${iterStr}${nameStr}`)}`;
|
|
66
64
|
return { content, visible: true };
|
|
67
65
|
} else {
|
|
68
66
|
// Off/inactive: red dot
|
|
69
|
-
|
|
70
|
-
return { content: `${colorText(ctx, "ralphOff", RALPH_ICON)} ${dot}`, visible: true };
|
|
67
|
+
return { content: `${colorText(ctx, "ralphOff", ralphIcon)} ${dot}`, visible: true };
|
|
71
68
|
}
|
|
72
69
|
}
|
|
73
70
|
|
|
@@ -80,9 +77,10 @@ function renderTotalIterationsSegment(ctx: FooterSegmentContext): RenderedSegmen
|
|
|
80
77
|
const maxIterations = data.maxIterations;
|
|
81
78
|
const display = maxIterations ? `${iteration}/${maxIterations}` : `${iteration}`;
|
|
82
79
|
|
|
80
|
+
const ralphIcon = getIcon("activeLoops");
|
|
83
81
|
const dot = active ? GREEN_DOT : RED_DOT;
|
|
84
82
|
const semantic: SemanticColor = active ? "ralphOn" : "ralphOff";
|
|
85
|
-
const content = `${colorText(ctx, semantic,
|
|
83
|
+
const content = `${colorText(ctx, semantic, ralphIcon)} ${dot} ${colorText(ctx, semantic, display)}`;
|
|
86
84
|
return { content, visible: true };
|
|
87
85
|
}
|
|
88
86
|
|
|
@@ -92,13 +90,14 @@ function renderLoopStatusSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
|
92
90
|
const name = data.name as string | undefined;
|
|
93
91
|
if (!status && !name) return { content: "", visible: false };
|
|
94
92
|
|
|
93
|
+
const ralphIcon = getIcon("activeLoops");
|
|
95
94
|
const dot = status === "active" ? GREEN_DOT : status === "completed" ? GREEN_DOT : RED_DOT;
|
|
96
95
|
const statusIcon = status === "active" ? "▶" : status === "paused" ? "⏸" : status === "completed" ? "✓" : "";
|
|
97
96
|
const display = name ? `${statusIcon} ${name}` : `${statusIcon}`;
|
|
98
97
|
|
|
99
98
|
const active = status === "active" || status === "completed";
|
|
100
99
|
const semantic: SemanticColor = active ? "ralphOn" : "ralphOff";
|
|
101
|
-
const content = `${colorText(ctx, semantic,
|
|
100
|
+
const content = `${colorText(ctx, semantic, ralphIcon)} ${dot} ${colorText(ctx, semantic, display)}`;
|
|
102
101
|
return { content, visible: true };
|
|
103
102
|
}
|
|
104
103
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Status keys from packages:
|
|
8
8
|
* "unipi-workflow" → "⚡ wf:brainstorm ✓ rl" (active command shown)
|
|
9
9
|
* "ralph" → "rl:loop-name 3/50"
|
|
10
|
-
* "unipi-memory" → "⚡
|
|
10
|
+
* "unipi-memory" → "⚡ MEM 75p/101all"
|
|
11
11
|
* "subagents" → various
|
|
12
12
|
*/
|
|
13
13
|
|
|
@@ -15,20 +15,20 @@ import type { FooterSegment, FooterSegmentContext, RenderedSegment } from "../ty
|
|
|
15
15
|
import { getIcon } from "../rendering/icons.js";
|
|
16
16
|
import { loadFooterSettings } from "../config.js";
|
|
17
17
|
import { getSeparator } from "../rendering/separators.js";
|
|
18
|
+
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
18
19
|
|
|
19
20
|
/** Map status keys to short display names and segment IDs for icons */
|
|
20
21
|
const STATUS_DISPLAY: Record<string, { short: string; segmentId: string }> = {
|
|
21
|
-
"unipi-workflow": { short: "
|
|
22
|
-
workflow: { short: "
|
|
23
|
-
ralph: { short: "
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
subagents: { short: "sa", segmentId: "extensionStatuses" },
|
|
22
|
+
"unipi-workflow": { short: "WF", segmentId: "currentCommand" },
|
|
23
|
+
workflow: { short: "WF", segmentId: "currentCommand" },
|
|
24
|
+
ralph: { short: "RL", segmentId: "activeLoops" },
|
|
25
|
+
memory: { short: "MEM", segmentId: "projectCount" },
|
|
26
|
+
compactor: { short: "CMP", segmentId: "compactions" },
|
|
27
|
+
mcp: { short: "MCP", segmentId: "serversTotal" },
|
|
28
|
+
notify: { short: "NTF", segmentId: "platformsEnabled" },
|
|
29
|
+
kanboard: { short: "KB", segmentId: "docsCount" },
|
|
30
|
+
info: { short: "INF", segmentId: "extensionStatuses" },
|
|
31
|
+
subagents: { short: "SA", segmentId: "extensionStatuses" },
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
/** Get the separator character for the current settings */
|
|
@@ -110,7 +110,12 @@ function renderExtensionStatusesSegment(ctx: FooterSegmentContext): RenderedSegm
|
|
|
110
110
|
|
|
111
111
|
if (parts.length === 0) return { content: "", visible: false };
|
|
112
112
|
|
|
113
|
+
// Clamp total content to terminal width to prevent TUI crash
|
|
113
114
|
const content = parts.join(` ${sep} `);
|
|
115
|
+
const maxW = ctx.width > 0 ? ctx.width : 120;
|
|
116
|
+
if (visibleWidth(content) > maxW) {
|
|
117
|
+
return { content: truncateToWidth(content, maxW, "…"), visible: true };
|
|
118
|
+
}
|
|
114
119
|
return { content, visible: true };
|
|
115
120
|
}
|
|
116
121
|
|
package/src/segments/workflow.ts
CHANGED
|
@@ -10,8 +10,7 @@ import type { FooterSegment, FooterSegmentContext, RenderedSegment, SemanticColo
|
|
|
10
10
|
import { applyColor } from "../rendering/theme.js";
|
|
11
11
|
import { getIcon } from "../rendering/icons.js";
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
const WORKFLOW_ICON = "\uf52e";
|
|
13
|
+
|
|
15
14
|
|
|
16
15
|
function withIcon(segmentId: string, text: string): string {
|
|
17
16
|
const icon = getIcon(segmentId);
|
|
@@ -46,15 +45,17 @@ function renderCurrentCommandSegment(ctx: FooterSegmentContext): RenderedSegment
|
|
|
46
45
|
const active = data.active === true;
|
|
47
46
|
const command = data.command as string | undefined;
|
|
48
47
|
|
|
48
|
+
const workflowIcon = getIcon("currentCommand");
|
|
49
|
+
|
|
49
50
|
// No workflow — show dash
|
|
50
51
|
if (!command) {
|
|
51
|
-
const content =
|
|
52
|
+
const content = withIcon("currentCommand", "-");
|
|
52
53
|
return { content: applyColor("workflow", content, ctx.theme, ctx.colors), visible: true };
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
const statusPrefix = active ? "▶" : "✓";
|
|
56
57
|
const semanticColor = getWorkflowSemanticColor(command);
|
|
57
|
-
const content = `${
|
|
58
|
+
const content = `${workflowIcon} ${statusPrefix} ${command}`;
|
|
58
59
|
return { content: applyColor(semanticColor, content, ctx.theme, ctx.colors), visible: true };
|
|
59
60
|
}
|
|
60
61
|
|