@pi-unipi/footer 3.0.0-alpha.3 → 3.0.0-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,149 +1,90 @@
1
- # @pi-unipi/footer
1
+ # Footer
2
2
 
3
- Persistent status bar at the bottom of the terminal. Shows live stats from all Unipi packages — compactor tokens saved, memory count, MCP status, Ralph loops, workflow state, kanboard tasks, notifications.
3
+ Frames the input box and shows live session stats at the bottom of the terminal.
4
4
 
5
- Subscribes to events from every package and renders segments using Pi's `setFooter` + `setWidget` APIs. Responsive layout adjusts to terminal width, with a secondary row for narrow terminals.
5
+ `@pi-unipi/footer` · part of [UniPi](../../README.md)
6
6
 
7
- ## Glance Footer (new in 2.12)
7
+ ![Glance footer: a framed input box with the branch, model and a session stats line](../../docs/assets/screenshots/glance-footer.png)
8
8
 
9
- An experimental input surface, on by default and toggleable in `/unipi:settings` (Footer) → **Glance mode**:
9
+ ## What it does
10
10
 
11
+ - Puts a frame around the input box (**the glance frame**, always on when the footer is enabled).
12
+ - Shows a stats line below the input: input/output tokens, cost, average time to first token (TTFT), tokens per second, turns, steps, model and tool time, cache hit rate, and compactions.
13
+ - Shows a line above the input with counts of background tasks: running, stopped, failed and done.
14
+ - Adapts to the terminal: parts of the stats line drop by priority when the terminal is narrow, badges leave the frame titles before the branch or model truncate, and on very short terminals the stats and task lines hide.
15
+
16
+ ## Quick start
17
+
18
+ UniPi installs this package:
19
+
20
+ ```bash
21
+ pi install npm:@pi-unipi/unipi
11
22
  ```
12
- ╭─ 󰚩 UNIPI │ feat/footer-default-v2 │ ───────────────────────────╮
13
- │ Type your prompt here... │
14
- ╰─ 󰉋 unipi ────────────────────── 42%/1.0M │ GLM-5.3 │ thinking:high ─╯
15
- 2 turn · 20 steps | 00:12:14 · tool 00:02 | 20ms avg ttft · 120 tok/s | 90% cache hit
23
+
24
+ To install this package alone:
25
+
26
+ ```bash
27
+ pi install npm:@pi-unipi/footer
16
28
  ```
17
29
 
18
- - **Top border:** animated lolcat-gradient UNIPI brand + git branch (turns rainbow-frame animated while thinking is max/xhigh)
19
- - **Bottom border:** workspace · context %/window · model · thinking level
20
- - **Session strip:** turns/steps, wall + tool wall time, average TTFT, tok/s, cache hit % — colored per stat, honest across restarts (derived from persisted session timestamps when live hooks are unavailable; provider-reported `usage.output` anchors token counts whenever present)
21
- - **Process line (new in 2.13):** centered one-liner directly above the frame while background work is in flight — `● 3 running ● 1 stopped ● 1 failed ● 2 done` — green ● running, yellow ● stopped (killed), red ● failed, gray ● done. Covers every task type (shell jobs, delegates, fusion workflows) via direct registry reads; zero-count buckets are omitted and the line hides when idle. Counts reset per session.
22
- - The classic segment status line is suppressed while glance mode is on; toggle it back for the classic footer
30
+ The footer starts with the session. Open `/unipi:settings` → **Footer** to change it.
23
31
 
24
32
  ## Commands
25
33
 
26
- | Command | Description |
27
- |---------|-------------|
28
- | `/unipi:footer` | Toggle footer on/off |
29
- | `/unipi:footer on` / `/unipi:footer off` | Enable/disable explicitly |
30
- | `/unipi:settings` | Hub — preset, separator, zone separator, icon style, glance mode, per-segment toggles (Footer group) |
31
- | `/unipi:footer-help` | Show footer segment guide |
32
-
33
- ## Special Triggers
34
-
35
- Footer subscribes to events from every Unipi package:
36
-
37
- | Group | Events | Segments |
38
- |-------|--------|----------|
39
- | core | Pi SDK | model, thinking, path, git, context_pct, cost, tokens, session |
40
- | compactor | Pi session data + `COMPACTOR_COMPACTED` | session_events, compactions, tokens_saved, compression_ratio |
41
- | memory | `MEMORY_STORED`/`DELETED`/`CONSOLIDATED` | project_count, total_count, consolidations |
42
- | mcp | `MCP_SERVER_STARTED`/`STOPPED`/`ERROR` | servers_total, servers_active, tools_total |
43
- | ralph | `RALPH_LOOP_START`/`END`/`ITERATION_DONE` | active_loops, total_iterations, loop_status |
44
- | workflow | `WORKFLOW_START`/`END` | current_command, sandbox_level, command_duration |
45
- | kanboard | Direct registry read | docs_count, tasks_done, tasks_total, task_pct |
46
- | notify | `NOTIFICATION_SENT` | platforms_enabled, last_sent |
47
-
48
- Footer works even if packages load after it — late-arriving events update the cache.
49
-
50
- ## Presets
51
-
52
- | Preset | Description |
53
- |--------|-------------|
54
- | `default` | Glance-era: UNIPI brand, model, thinking, directory, git \| context/tokens, tps, cost, clock |
55
- | `classic` | The pre-2.12 balanced layout: model, api, tools, git \| tps, context, cost + compactor + memory + ralph |
56
- | `minimal` | Essentials only: path, git, context |
57
- | `compact` | Core + key stats: model, git, cost, context |
58
- | `full` | Everything from all groups |
59
- | `ascii` | Core segments with ASCII icons |
60
-
61
- ## Segment Groups
62
-
63
- | Group | Default | Data Source |
64
- |-------|---------|-------------|
65
- | **core** | ON | Pi SDK (ctx.sessionManager, footerData) |
66
- | **compactor** | ON | Live Pi session data; last-compaction event |
67
- | **memory** | ON | `MEMORY_STORED`/`DELETED`/`CONSOLIDATED` events |
68
- | **mcp** | ON | `MCP_SERVER_STARTED`/`STOPPED`/`ERROR` events |
69
- | **ralph** | ON | `RALPH_LOOP_START`/`END`/`ITERATION_DONE` events |
70
- | **workflow** | ON | `WORKFLOW_START`/`END` events |
71
- | **kanboard** | ON | Kanboard registry (direct read) |
72
- | **notify** | OFF | `NOTIFICATION_SENT` event |
73
- | **status_ext** | ON | `footerData.getExtensionStatuses()` |
74
-
75
- ## Configurables
76
-
77
- Settings in `~/.pi/agent/settings.json` under `unipi.footer`:
78
-
79
- ```json
80
- {
81
- "unipi": {
82
- "footer": {
83
- "enabled": true,
84
- "preset": "default",
85
- "glanceMode": true,
86
- "separator": "powerline-thin",
87
- "iconStyle": "nerd",
88
- "colorMode": "auto",
89
- "groups": {
90
- "compactor": {
91
- "show": true,
92
- "segments": {
93
- "session_events": true,
94
- "compactions": true,
95
- "tokens_saved": true
96
- }
97
- }
98
- }
99
- }
100
- }
101
- }
102
- ```
34
+ | Command | What it does |
35
+ |---|---|
36
+ | `/unipi:footer` | Turns the footer on or off. |
37
+ | `/unipi:footer on` | Turns the footer on. |
38
+ | `/unipi:footer off` | Turns the footer off (plain pi editor). |
103
39
 
104
- ### Separator Styles
40
+ ## The glance frame
105
41
 
106
- | Style | Look |
107
- |-------|------|
108
- | `powerline` | Thick powerline arrows |
109
- | `powerline-thin` | Thin powerline arrows (default) |
110
- | `slash` | / |
111
- | `pipe` | \| |
112
- | `dot` | Middle dot |
113
- | `ascii` | > < |
42
+ The frame has three parts:
114
43
 
115
- ### Icon Styles
44
+ - **Top border.** The UNIPI brand, the long-horizon mode, the git branch, and the plan and permission mode. The brand shows a moving rainbow. The frame also shows the rainbow when the thinking level is `xhigh` or `max` (unless **Rainbow** is `brand-only`).
45
+ - **Bottom border.** The workspace name, context use and window size, the model and the thinking level. With a [Fusion](../fusion/README.md) pair, it shows the lead and the sidekick. It also shows Kanboard claims.
46
+ - **Stats line.** The line below the input. A part stays hidden until it has data. For example, compactions show only after the first compaction.
116
47
 
117
- | Style | Description |
118
- |-------|-------------|
119
- | `nerd` | Nerd Font glyphs (auto-detected) |
120
- | `emoji` | Unicode symbols (works on most terminals) |
121
- | `text` | Plain text labels (works everywhere). Glance frame drops the robot glyph: `UNIPI │ branch:main`, `workspace:unipi` |
48
+ The background-task line reads the [Background Tasks](../background-tasks/README.md) registry. It shows nothing when no task exists.
122
49
 
123
- When `iconStyle` is not set, footer auto-detects Nerd Font support and defaults to `nerd` if available, `emoji` otherwise.
50
+ ## Responsive behavior
124
51
 
125
- ### Color Mode
52
+ - **Width.** Each stats-line part has a priority. When the line does not fit, the lowest-priority parts drop whole (never mid-part), in this order: compactions, cache, time, turns, speed, cost — tokens always survive. The frame titles degrade the same way: the Kanboard label, the Fusion pair, the plan/permission cluster and the mode label drop (lowest value first) before the branch or model ever truncate.
53
+ - **Width safety.** Nothing ever writes the last terminal column (a full-width line desyncs wrapping terminals).
54
+ - **Height.** Below 20 terminal rows the stats line and the background-task line hide; the frame stays.
126
55
 
127
- | Mode | Description |
128
- |------|-------------|
129
- | `auto` | Detect terminal support from environment (default) |
130
- | `truecolor` | Force 24-bit ANSI color |
131
- | `256` | Force xterm-256 color fallback |
132
- | `none` | Disable footer color escapes |
56
+ ## Settings
133
57
 
134
- `auto` uses truecolor where supported and downgrades to xterm-256 colors for terminals such as Apple Terminal that do not reliably render 24-bit color escapes.
58
+ Open `/unipi:settings` → **Footer**. The file is `~/.unipi/config/footer/config.json`. A project file at `.unipi/config/footer/config.json` overrides it.
135
59
 
136
- ### Responsive Layout
60
+ | Key | Default | What it does |
61
+ |---|---|---|
62
+ | `enabled` | `true` | Turns the footer on or off. `false` leaves the plain pi editor. |
63
+ | `iconStyle` | `nerd` | Icon set: `nerd` (needs a Nerd Font), `emoji` or `text`. |
64
+ | `colorMode` | `auto` | `auto`, `truecolor`, `256` or `none`. The legacy value `mono` loads as `none`. |
65
+ | `rainbow` | `always` | `always` animates the brand (and the whole frame at `xhigh`/`max` thinking); `brand-only` never animates the whole frame; `off` disables the animation. |
66
+ | `processLine` | `true` | Shows the background-task line above the input. |
67
+ | `strip.turns` | `true` | Turn and step counters. |
68
+ | `strip.time` | `true` | Model time and tool time. |
69
+ | `strip.speed` | `true` | Average TTFT and tokens per second. |
70
+ | `strip.tokens` | `true` | Session input and output tokens. |
71
+ | `strip.cost` | `true` | Session cost, or `sub` when the model runs on a subscription. |
72
+ | `strip.compactions` | `true` | Compaction count, sizes and recency. |
73
+ | `strip.cache` | `true` | Cache hit percentage. |
74
+ | `badges.mode` | `true` | Long-horizon mode label beside the brand. |
75
+ | `badges.planPermission` | `true` | PLAN badge and permission mode in the top border. |
76
+ | `badges.fusion` | `true` | Fusion lead and sidekick in the bottom border. |
77
+ | `badges.kanboard` | `true` | Kanboard claims label in the top border. |
137
78
 
138
- ```
139
- Wide terminal (>120 cols):
140
- model | thinking | path | git | context | cost | compactions | tokens_saved | project_count
79
+ `colorMode: auto` uses 24-bit color where the terminal supports it, 256 colors in terminals such as Apple Terminal, and no color when the `NO_COLOR` environment variable exists.
141
80
 
142
- Narrow terminal (<120 cols):
143
- Row 1: model | thinking | path | git | context | cost
144
- Row 2: compactions | tokens_saved | project_count | ralph | workflow
145
- ```
81
+ Old v2 keys (`preset`, `separator`, `zoneSeparator`, `showFullLabels`, `groups`, `glanceMode`) in existing config files are ignored.
82
+
83
+ ## How it works
84
+
85
+ The footer listens to UniPi events on the [event bus](../../docs/architecture/event-bus.md) for mode, plan and permission state. Usage data comes from an incremental scan of the session branch: each second only new entries are processed (a compaction or branch change triggers a full rescan), the same pass filling a cached snapshot for the stats line. The TPS tracker is fed live by pi's streaming events and reconciled by the same scan. The footer redraws when something it displays changed — plus once a second while the rainbow animates.
146
86
 
147
- ## License
87
+ ## See also
148
88
 
149
- MIT
89
+ - [Info Screen](../info-screen/README.md)
90
+ - [Settings reference](../../docs/reference/settings.md)
package/index.ts CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/footer",
3
- "version": "3.0.0-alpha.3",
3
+ "version": "3.0.0-alpha.30",
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
  "main": "index.ts",
@@ -32,8 +32,8 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "3.0.0-alpha.3",
36
- "@pi-unipi/background-tasks": "3.0.0-alpha.3"
35
+ "@pi-unipi/core": "3.0.0-alpha.30",
36
+ "@pi-unipi/background-tasks": "3.0.0-alpha.30"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
package/src/commands.ts CHANGED
@@ -1,15 +1,14 @@
1
1
  /**
2
2
  * @pi-unipi/footer — Commands
3
3
  *
4
- * Footer commands: /unipi:footer (toggle) and /unipi:footer <preset>.
4
+ * /unipi:footer — toggle the footer on/off.
5
5
  */
6
6
 
7
7
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
8
8
  import { UNIPI_PREFIX, FOOTER_COMMANDS } from "@pi-unipi/core";
9
- import { loadFooterSettings, saveFooterSettings } from "./config.js";
10
- import { showFooterHelp } from "./help.js";
11
- import type { FooterSegment } from "./types.js";
12
- import { applyGlanceMode, type FooterState } from "./index.js";
9
+ import { saveFooterSettings } from "./config.js";
10
+ import type { FooterState } from "./index.js";
11
+ import { fullRescan } from "./index.js";
13
12
 
14
13
  /**
15
14
  * Register footer commands.
@@ -21,34 +20,17 @@ export function registerCommands(pi: ExtensionAPI, state: FooterState): void {
21
20
  handler: async (args, ctx) => {
22
21
  const arg = args?.trim().toLowerCase();
23
22
 
24
- // on
25
- if (arg === "on") {
26
- state.enabled = true;
27
- state.renderer.setActive(true);
28
- saveFooterSettings({ enabled: true });
29
- state.setupUI?.(pi, ctx);
30
- ctx.ui.notify("Footer enabled", "info");
31
- return;
32
- }
23
+ let enable: boolean;
24
+ if (arg === "on") enable = true;
25
+ else if (arg === "off") enable = false;
26
+ else enable = !state.enabled;
33
27
 
34
- // off
35
- if (arg === "off") {
36
- state.enabled = false;
37
- state.renderer.setActive(false);
38
- ctx.ui.setFooter(undefined);
39
- ctx.ui.setWidget("footer-top", undefined);
40
- ctx.ui.setWidget("footer-secondary", undefined);
41
- saveFooterSettings({ enabled: false });
42
- ctx.ui.notify("Footer disabled", "info");
43
- return;
44
- }
45
-
46
- // Toggle (no args or unknown args)
47
- state.enabled = !state.enabled;
48
- state.renderer.setActive(state.enabled);
28
+ state.enabled = enable;
29
+ saveFooterSettings({ enabled: enable });
49
30
 
50
- if (state.enabled) {
31
+ if (enable) {
51
32
  state.setupUI?.(pi, ctx);
33
+ fullRescan(state);
52
34
  ctx.ui.notify("Footer enabled", "info");
53
35
  } else {
54
36
  ctx.ui.setFooter(undefined);
@@ -56,17 +38,6 @@ export function registerCommands(pi: ExtensionAPI, state: FooterState): void {
56
38
  ctx.ui.setWidget("footer-secondary", undefined);
57
39
  ctx.ui.notify("Footer disabled", "info");
58
40
  }
59
-
60
- saveFooterSettings({ enabled: state.enabled });
61
- },
62
- });
63
-
64
- // /unipi:footer-help — show help overlay
65
- pi.registerCommand(`${UNIPI_PREFIX}${FOOTER_COMMANDS.FOOTER_HELP}`, {
66
- description: "Show footer segment guide (icons, labels, descriptions)",
67
- handler: async (_args, _ctx) => {
68
- const allSegments = Array.from(state.segmentLookup.values());
69
- showFooterHelp(pi, allSegments, state.renderer.getPresetName());
70
41
  },
71
42
  });
72
43
  }