@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/footer",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Persistent status bar for Unipi — subscribes to UNIPI_EVENTS and renders key stats from all unipi packages",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/commands.ts CHANGED
@@ -47,6 +47,7 @@ interface FooterState {
47
47
  resetLayoutCache(): void;
48
48
  };
49
49
  piContext: unknown;
50
+ setupUI: ((pi: ExtensionAPI, ctx: any) => void) | null;
50
51
  }
51
52
 
52
53
  /**
@@ -104,6 +105,7 @@ export function registerCommands(
104
105
  state.renderer.setActive(state.enabled);
105
106
 
106
107
  if (state.enabled) {
108
+ state.setupUI?.(pi, ctx);
107
109
  ctx.ui.notify("Footer enabled", "info");
108
110
  } else {
109
111
  ctx.ui.setFooter(undefined);
@@ -123,6 +125,7 @@ export function registerCommands(
123
125
  state.enabled = true;
124
126
  state.renderer.setActive(true);
125
127
  saveFooterSettings({ enabled: true });
128
+ state.setupUI?.(pi, ctx);
126
129
  ctx.ui.notify("Footer enabled", "info");
127
130
  return;
128
131
  }
package/src/config.ts CHANGED
@@ -48,8 +48,8 @@ function readSettingsFile(): Record<string, unknown> | null {
48
48
  if (!fs.existsSync(settingsPath)) return null;
49
49
  const raw = fs.readFileSync(settingsPath, "utf-8");
50
50
  return JSON.parse(raw) as Record<string, unknown>;
51
- } catch (err) {
52
- console.warn("[footer] Failed to read settings.json:", err);
51
+ } catch {
52
+ // Silently ignore — settings read failure falls back to null.
53
53
  return null;
54
54
  }
55
55
  }
@@ -66,8 +66,8 @@ function writeSettingsFile(settings: Record<string, unknown>): boolean {
66
66
  }
67
67
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
68
68
  return true;
69
- } catch (err) {
70
- console.warn("[footer] Failed to write settings.json:", err);
69
+ } catch {
70
+ // Silently ignore — settings write failure is non-blocking.
71
71
  return false;
72
72
  }
73
73
  }
@@ -97,8 +97,8 @@ export function loadFooterSettings(): FooterSettings {
97
97
  footer.groups as Record<string, FooterGroupSettings> | undefined,
98
98
  ),
99
99
  };
100
- } catch (err) {
101
- console.warn("[footer] Failed to parse footer settings, using defaults:", err);
100
+ } catch {
101
+ // Silently ignore parse failure falls back to defaults.
102
102
  return { ...DEFAULT_FOOTER_SETTINGS };
103
103
  }
104
104
  }
package/src/events.ts CHANGED
@@ -30,8 +30,8 @@ export function subscribeToEvents(
30
30
  pi.events.on(UNIPI_EVENTS.COMPACTOR_STATS_UPDATED, (event: unknown) => {
31
31
  try {
32
32
  registry.updateData("compactor", event);
33
- } catch (err) {
34
- console.error("[footer] Compactor stats handler error:", err);
33
+ } catch {
34
+ // Silently ignore — event handler errors are non-blocking.
35
35
  }
36
36
  })
37
37
  );
@@ -41,8 +41,8 @@ export function subscribeToEvents(
41
41
  try {
42
42
  const existing = registry.getGroupData("compactor") as Record<string, unknown> | undefined;
43
43
  registry.updateData("compactor", { ...existing, lastCompaction: event });
44
- } catch (err) {
45
- console.error("[footer] Compaction handler error:", err);
44
+ } catch {
45
+ // Silently ignore — event handler errors are non-blocking.
46
46
  }
47
47
  })
48
48
  );
@@ -54,8 +54,8 @@ export function subscribeToEvents(
54
54
  try {
55
55
  const existing = registry.getGroupData("memory") as Record<string, unknown> | undefined;
56
56
  registry.updateData("memory", { ...existing, lastStored: event });
57
- } catch (err) {
58
- console.error("[footer] Memory stored handler error:", err);
57
+ } catch {
58
+ // Silently ignore — event handler errors are non-blocking.
59
59
  }
60
60
  })
61
61
  );
@@ -65,8 +65,8 @@ export function subscribeToEvents(
65
65
  try {
66
66
  const existing = registry.getGroupData("memory") as Record<string, unknown> | undefined;
67
67
  registry.updateData("memory", { ...existing, lastDeleted: event });
68
- } catch (err) {
69
- console.error("[footer] Memory deleted handler error:", err);
68
+ } catch {
69
+ // Silently ignore — event handler errors are non-blocking.
70
70
  }
71
71
  })
72
72
  );
@@ -76,8 +76,8 @@ export function subscribeToEvents(
76
76
  try {
77
77
  const existing = registry.getGroupData("memory") as Record<string, unknown> | undefined;
78
78
  registry.updateData("memory", { ...existing, lastConsolidated: event });
79
- } catch (err) {
80
- console.error("[footer] Memory consolidated handler error:", err);
79
+ } catch {
80
+ // Silently ignore — event handler errors are non-blocking.
81
81
  }
82
82
  })
83
83
  );
@@ -94,8 +94,8 @@ export function subscribeToEvents(
94
94
  const serversActive = (typeof existing?.serversActive === "number" ? existing.serversActive : 0) + 1;
95
95
  const toolsTotal = (typeof existing?.toolsTotal === "number" ? existing.toolsTotal : 0) + toolCount;
96
96
  registry.updateData("mcp", { ...existing, serversTotal, serversActive, toolsTotal, lastServerStarted: event });
97
- } catch (err) {
98
- console.error("[footer] MCP server started handler error:", err);
97
+ } catch {
98
+ // Silently ignore event handler errors are non-blocking.
99
99
  }
100
100
  })
101
101
  );
@@ -116,8 +116,8 @@ export function subscribeToEvents(
116
116
  toolsTotal = Math.max(0, toolsTotal - lastStartedCount);
117
117
  }
118
118
  registry.updateData("mcp", { ...existing, serversActive, toolsTotal, lastServerStopped: event });
119
- } catch (err) {
120
- console.error("[footer] MCP server stopped handler error:", err);
119
+ } catch {
120
+ // Silently ignore event handler errors are non-blocking.
121
121
  }
122
122
  })
123
123
  );
@@ -129,8 +129,8 @@ export function subscribeToEvents(
129
129
  const serversTotal = (typeof existing?.serversTotal === "number" ? existing.serversTotal : 0) + 1;
130
130
  const serversFailed = (typeof existing?.serversFailed === "number" ? existing.serversFailed : 0) + 1;
131
131
  registry.updateData("mcp", { ...existing, serversTotal, serversFailed, lastServerError: event });
132
- } catch (err) {
133
- console.error("[footer] MCP server error handler error:", err);
132
+ } catch {
133
+ // Silently ignore event handler errors are non-blocking.
134
134
  }
135
135
  })
136
136
  );
@@ -143,8 +143,8 @@ export function subscribeToEvents(
143
143
  const toolNames = Array.isArray(evt?.toolNames) ? evt.toolNames : [];
144
144
  const toolsTotal = (typeof existing?.toolsTotal === "number" ? existing.toolsTotal : 0) + toolNames.length;
145
145
  registry.updateData("mcp", { ...existing, toolsTotal, lastToolsRegistered: event });
146
- } catch (err) {
147
- console.error("[footer] MCP tools registered handler error:", err);
146
+ } catch {
147
+ // Silently ignore event handler errors are non-blocking.
148
148
  }
149
149
  })
150
150
  );
@@ -157,8 +157,8 @@ export function subscribeToEvents(
157
157
  const toolNames = Array.isArray(evt?.toolNames) ? evt.toolNames : [];
158
158
  const toolsTotal = Math.max(0, (typeof existing?.toolsTotal === "number" ? existing.toolsTotal : 0) - toolNames.length);
159
159
  registry.updateData("mcp", { ...existing, toolsTotal, lastToolsUnregistered: event });
160
- } catch (err) {
161
- console.error("[footer] MCP tools unregistered handler error:", err);
160
+ } catch {
161
+ // Silently ignore event handler errors are non-blocking.
162
162
  }
163
163
  })
164
164
  );
@@ -169,8 +169,8 @@ export function subscribeToEvents(
169
169
  pi.events.on(UNIPI_EVENTS.RALPH_LOOP_START, (event: unknown) => {
170
170
  try {
171
171
  registry.updateData("ralph", { ...(event as Record<string, unknown>), active: true });
172
- } catch (err) {
173
- console.error("[footer] Ralph loop start handler error:", err);
172
+ } catch {
173
+ // Silently ignore event handler errors are non-blocking.
174
174
  }
175
175
  })
176
176
  );
@@ -179,8 +179,8 @@ export function subscribeToEvents(
179
179
  pi.events.on(UNIPI_EVENTS.RALPH_LOOP_END, (event: unknown) => {
180
180
  try {
181
181
  registry.updateData("ralph", { ...(event as Record<string, unknown>), active: false });
182
- } catch (err) {
183
- console.error("[footer] Ralph loop end handler error:", err);
182
+ } catch {
183
+ // Silently ignore event handler errors are non-blocking.
184
184
  }
185
185
  })
186
186
  );
@@ -190,8 +190,8 @@ export function subscribeToEvents(
190
190
  try {
191
191
  const existing = registry.getGroupData("ralph") as Record<string, unknown> | undefined;
192
192
  registry.updateData("ralph", { ...existing, lastIteration: event });
193
- } catch (err) {
194
- console.error("[footer] Ralph iteration handler error:", err);
193
+ } catch {
194
+ // Silently ignore — event handler errors are non-blocking.
195
195
  }
196
196
  })
197
197
  );
@@ -202,8 +202,8 @@ export function subscribeToEvents(
202
202
  pi.events.on(UNIPI_EVENTS.WORKFLOW_START, (event: unknown) => {
203
203
  try {
204
204
  registry.updateData("workflow", { ...(event as Record<string, unknown>), active: true, startTime: Date.now() });
205
- } catch (err) {
206
- console.error("[footer] Workflow start handler error:", err);
205
+ } catch {
206
+ // Silently ignore — event handler errors are non-blocking.
207
207
  }
208
208
  })
209
209
  );
@@ -212,8 +212,8 @@ export function subscribeToEvents(
212
212
  pi.events.on(UNIPI_EVENTS.WORKFLOW_END, (event: unknown) => {
213
213
  try {
214
214
  registry.updateData("workflow", { ...(event as Record<string, unknown>), active: false });
215
- } catch (err) {
216
- console.error("[footer] Workflow end handler error:", err);
215
+ } catch {
216
+ // Silently ignore — event handler errors are non-blocking.
217
217
  }
218
218
  })
219
219
  );
@@ -224,8 +224,8 @@ export function subscribeToEvents(
224
224
  pi.events.on(UNIPI_EVENTS.NOTIFICATION_SENT, (event: unknown) => {
225
225
  try {
226
226
  registry.updateData("notify", event);
227
- } catch (err) {
228
- console.error("[footer] Notification handler error:", err);
227
+ } catch {
228
+ // Silently ignore — event handler errors are non-blocking.
229
229
  }
230
230
  })
231
231
  );
@@ -237,8 +237,8 @@ export function subscribeToEvents(
237
237
  try {
238
238
  // Invalidate all caches when new modules load — they may bring fresh data
239
239
  registry.invalidateAll();
240
- } catch (err) {
241
- console.error("[footer] Module ready handler error:", err);
240
+ } catch {
241
+ // Silently ignore — event handler errors are non-blocking.
242
242
  }
243
243
  })
244
244
  );
package/src/index.ts CHANGED
@@ -26,7 +26,7 @@ import { NOTIFY_SEGMENTS } from "./segments/notify.js";
26
26
  import { STATUS_EXT_SEGMENTS } from "./segments/status-ext.js";
27
27
 
28
28
  import type { FooterGroup, FooterSegment } from "./types.js";
29
- import { getThinkingLevel, rainbowBorder } from "./segments/core.js";
29
+ import { rainbowBorder } from "./segments/core.js";
30
30
 
31
31
  /** All segment groups */
32
32
  const ALL_GROUPS: FooterGroup[] = [
@@ -53,7 +53,7 @@ function buildSegmentLookup(): Map<string, FooterSegment> {
53
53
  }
54
54
 
55
55
  /** Extension state */
56
- interface FooterState {
56
+ export interface FooterState {
57
57
  enabled: boolean;
58
58
  registry: FooterRegistry;
59
59
  renderer: FooterRenderer;
@@ -62,6 +62,9 @@ interface FooterState {
62
62
  piContext: unknown;
63
63
  footerData: unknown;
64
64
  tuiRef: any;
65
+ refreshTimer: ReturnType<typeof setInterval> | null;
66
+ /** Re-register footer + widgets with pi UI (for live enable) */
67
+ setupUI: ((pi: ExtensionAPI, ctx: any) => void) | null;
65
68
  }
66
69
 
67
70
  export default function footerExtension(pi: ExtensionAPI): void {
@@ -82,6 +85,8 @@ export default function footerExtension(pi: ExtensionAPI): void {
82
85
  piContext: null,
83
86
  footerData: null,
84
87
  tuiRef: null,
88
+ refreshTimer: null,
89
+ setupUI: null,
85
90
  };
86
91
 
87
92
  // Register all groups in the registry
@@ -105,6 +110,7 @@ export default function footerExtension(pi: ExtensionAPI): void {
105
110
 
106
111
  // Setup footer + widgets
107
112
  setupFooterUI(pi, ctx, state);
113
+ state.setupUI = (p: ExtensionAPI, c: any) => setupFooterUI(p, c, state);
108
114
  });
109
115
 
110
116
  pi.on("session_shutdown", async () => {
@@ -113,6 +119,10 @@ export default function footerExtension(pi: ExtensionAPI): void {
113
119
  state.unsubscribeEvents = null;
114
120
  state.piContext = null;
115
121
  state.footerData = null;
122
+ if (state.refreshTimer) {
123
+ clearInterval(state.refreshTimer);
124
+ state.refreshTimer = null;
125
+ }
116
126
  state.tuiRef = null;
117
127
  });
118
128
 
@@ -138,6 +148,14 @@ function setupFooterUI(pi: ExtensionAPI, ctx: any, state: FooterState): void {
138
148
  // Register footer (minimal — handles branch changes)
139
149
  ctx.ui.setFooter((tui: any, _theme: Theme, footerData: any) => {
140
150
  state.tuiRef = tui;
151
+
152
+ // Start periodic refresh for time-sensitive segments (e.g. clock)
153
+ if (!state.refreshTimer) {
154
+ state.refreshTimer = setInterval(() => {
155
+ state.renderer.resetLayoutCache();
156
+ state.tuiRef?.requestRender();
157
+ }, 1_000);
158
+ }
141
159
  state.footerData = footerData;
142
160
  state.renderer.setContext(state.piContext, footerData);
143
161
 
@@ -178,7 +196,7 @@ function setupFooterUI(pi: ExtensionAPI, ctx: any, state: FooterState): void {
178
196
  };
179
197
  }, { placement: "aboveEditor" });
180
198
 
181
- // Secondary row widget + rainbow input border for xhigh thinking
199
+ // Secondary row widget
182
200
  ctx.ui.setWidget("footer-secondary", (_tui: any, _theme: Theme) => {
183
201
  return {
184
202
  dispose() {},
@@ -190,12 +208,6 @@ function setupFooterUI(pi: ExtensionAPI, ctx: any, state: FooterState): void {
190
208
 
191
209
  const lines: string[] = [];
192
210
 
193
- // Rainbow border for input bar when thinking level is xhigh
194
- const thinkingLevel = getThinkingLevel(state.piContext);
195
- if (thinkingLevel === "xhigh") {
196
- lines.push(rainbowBorder(width));
197
- }
198
-
199
211
  const layout = state.renderer.computeLayout(width);
200
212
  if (layout.secondaryContent) {
201
213
  lines.push(layout.secondaryContent);
package/src/presets.ts CHANGED
@@ -12,7 +12,7 @@ import { getDefaultColors } from "./rendering/theme.js";
12
12
  /** Default preset — balanced view */
13
13
  const DEFAULT_PRESET: PresetDef = {
14
14
  leftSegments: [
15
- "model", "thinking", "path", "git", "context_pct", "cost",
15
+ "model", "api_state", "tool_count", "git", "context_pct", "cost",
16
16
  ],
17
17
  rightSegments: [
18
18
  "compactions", "tokens_saved", "project_count", "loop_status",
@@ -27,7 +27,7 @@ const DEFAULT_PRESET: PresetDef = {
27
27
  /** Minimal preset — just the essentials */
28
28
  const MINIMAL_PRESET: PresetDef = {
29
29
  leftSegments: [
30
- "path", "git", "context_pct",
30
+ "git", "context_pct",
31
31
  ],
32
32
  rightSegments: [],
33
33
  secondarySegments: [],
@@ -51,7 +51,7 @@ const COMPACT_PRESET: PresetDef = {
51
51
  /** Full preset — everything */
52
52
  const FULL_PRESET: PresetDef = {
53
53
  leftSegments: [
54
- "model", "thinking", "path", "git", "context_pct", "cost",
54
+ "model", "api_state", "tool_count", "git", "context_pct", "cost",
55
55
  "tokens_total", "tokens_in", "tokens_out",
56
56
  ],
57
57
  rightSegments: [
@@ -75,7 +75,7 @@ const FULL_PRESET: PresetDef = {
75
75
  /** Nerd preset — maximum detail for Nerd Font users */
76
76
  const NERD_PRESET: PresetDef = {
77
77
  leftSegments: [
78
- "model", "thinking", "path", "git", "context_pct", "cost",
78
+ "model", "api_state", "tool_count", "git", "context_pct", "cost",
79
79
  "tokens_total",
80
80
  ],
81
81
  rightSegments: [
@@ -88,7 +88,7 @@ const NERD_PRESET: PresetDef = {
88
88
  "extension_statuses",
89
89
  ],
90
90
  secondarySegments: [
91
- "hostname", "time",
91
+ "session", "hostname", "time",
92
92
  "compression_ratio", "indexed_docs",
93
93
  "platforms_enabled", "last_sent",
94
94
  ],
@@ -99,7 +99,7 @@ const NERD_PRESET: PresetDef = {
99
99
  /** ASCII preset — safe for any terminal */
100
100
  const ASCII_PRESET: PresetDef = {
101
101
  leftSegments: [
102
- "model", "path", "git", "context_pct", "cost",
102
+ "model", "git", "context_pct", "cost",
103
103
  ],
104
104
  rightSegments: [
105
105
  "compactions", "tokens_saved", "project_count",
@@ -115,19 +115,17 @@ export class FooterRegistry {
115
115
  for (const callback of this.subscribers) {
116
116
  try {
117
117
  callback();
118
- } catch (err) {
119
- console.error("[footer] Subscriber error:", err);
118
+ } catch {
119
+ // Silently ignore — subscriber errors are non-blocking.
120
120
  }
121
121
  }
122
122
  }
123
123
 
124
124
  // ─── Debug ────────────────────────────────────────────────────────────────
125
125
 
126
- private log(event: string, ...args: unknown[]): void {
127
- if (!this.debug) return;
128
- const ts = new Date().toISOString().slice(11, 23);
129
- const details = args.length > 0 ? " " + JSON.stringify(args) : "";
130
- console.error(`[footer-registry:${ts}] ${event}${details}`);
126
+ private log(_event: string, ..._args: unknown[]): void {
127
+ // Debug logging disabled — was writing to stdout causing TUI rendering issues.
128
+ return;
131
129
  }
132
130
  }
133
131