@aemonge-dev/pi-stardeck 0.0.0-stage → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +239 -2
- package/demo/pi-stardeck.tape +59 -0
- package/docs/architecture.md +103 -0
- package/docs/assets/pi-stardeck.gif +0 -0
- package/docs/development.md +128 -0
- package/licenses/README.md +17 -0
- package/licenses/pi-fancy-footer-LICENSE +21 -0
- package/licenses/pi-vim-LICENSE +21 -0
- package/package.json +81 -4
- package/packages/pi-fancy-footer-full-palette/LICENSE +21 -0
- package/packages/pi-fancy-footer-full-palette/LOCAL-CHANGES.md +41 -0
- package/packages/pi-fancy-footer-full-palette/README.md +468 -0
- package/packages/pi-fancy-footer-full-palette/package.json +52 -0
- package/packages/pi-fancy-footer-full-palette/src/api.ts +204 -0
- package/packages/pi-fancy-footer-full-palette/src/config-editor.ts +634 -0
- package/packages/pi-fancy-footer-full-palette/src/config.ts +852 -0
- package/packages/pi-fancy-footer-full-palette/src/data-widgets.ts +259 -0
- package/packages/pi-fancy-footer-full-palette/src/git.ts +130 -0
- package/packages/pi-fancy-footer-full-palette/src/index.ts +479 -0
- package/packages/pi-fancy-footer-full-palette/src/layout.ts +309 -0
- package/packages/pi-fancy-footer-full-palette/src/provider-status.ts +1466 -0
- package/packages/pi-fancy-footer-full-palette/src/render.ts +1061 -0
- package/packages/pi-fancy-footer-full-palette/src/shared.ts +903 -0
- package/packages/pi-stardeck/README.md +219 -0
- package/packages/pi-stardeck/index.ts +691 -0
- package/packages/pi-stardeck/package.json +32 -0
- package/packages/pi-stardeck/src/activity-age.ts +184 -0
- package/packages/pi-stardeck/src/capabilities.ts +105 -0
- package/packages/pi-stardeck/src/deck.ts +807 -0
- package/packages/pi-stardeck/src/gauge.ts +1323 -0
- package/packages/pi-stardeck/src/git-refresh.ts +5 -0
- package/packages/pi-stardeck/src/process-resources.ts +428 -0
- package/packages/pi-stardeck/src/title-state.ts +195 -0
- package/packages/pi-stardeck/src/title.ts +443 -0
- package/packages/pi-vim-top-border/LICENSE +21 -0
- package/packages/pi-vim-top-border/LOCAL-CHANGES.md +71 -0
- package/packages/pi-vim-top-border/README.md +577 -0
- package/packages/pi-vim-top-border/clipboard-mirror.ts +451 -0
- package/packages/pi-vim-top-border/clipboard-policy.ts +23 -0
- package/packages/pi-vim-top-border/cursor-shape.ts +131 -0
- package/packages/pi-vim-top-border/index.ts +4195 -0
- package/packages/pi-vim-top-border/input-keys.ts +49 -0
- package/packages/pi-vim-top-border/mode-change-command.ts +120 -0
- package/packages/pi-vim-top-border/mode-colors.ts +97 -0
- package/packages/pi-vim-top-border/mode-label.ts +62 -0
- package/packages/pi-vim-top-border/motions.ts +297 -0
- package/packages/pi-vim-top-border/package.json +47 -0
- package/packages/pi-vim-top-border/prompt-external-editor.ts +152 -0
- package/packages/pi-vim-top-border/prompt-rail.ts +334 -0
- package/packages/pi-vim-top-border/prompt-status.ts +131 -0
- package/packages/pi-vim-top-border/settings.ts +287 -0
- package/packages/pi-vim-top-border/text-objects.ts +389 -0
- package/packages/pi-vim-top-border/types.ts +51 -0
- package/packages/pi-vim-top-border/visual.ts +65 -0
- package/packages/pi-vim-top-border/word-boundary-cache.ts +196 -0
- package/packages/status/README.md +398 -0
- package/packages/status/index.ts +1717 -0
- package/packages/status/package.json +33 -0
- package/packages/status/src/active-work.ts +235 -0
- package/packages/status/src/config.ts +218 -0
- package/packages/status/src/diagnostics.ts +238 -0
- package/packages/status/src/goal-block.ts +377 -0
- package/packages/status/src/goal.ts +170 -0
- package/packages/status/src/ledger.ts +101 -0
- package/packages/status/src/openspec.ts +394 -0
- package/packages/status/src/orchestration.ts +237 -0
- package/packages/status/src/publisher.ts +1294 -0
- package/packages/status/src/reducer.ts +190 -0
- package/packages/status/src/runtime-runs.ts +267 -0
- package/packages/status/src/state.ts +178 -0
- package/packages/status/src/title-activity.ts +224 -0
- package/packages/status/src/title-state.ts +218 -0
- package/packages/status/src/turn-cost.ts +63 -0
- package/packages/status/src/types.ts +228 -0
- package/src/deck-surface.ts +46 -0
- package/src/health.ts +228 -0
- package/src/index.ts +28 -0
- package/src/pi-status.ts +334 -0
package/package.json
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aemonge-dev/pi-stardeck",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "A Vim- and tmux-inspired command deck for Pi",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"vim",
|
|
10
|
+
"tmux",
|
|
11
|
+
"cockpit"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"files": [
|
|
15
|
+
"README.md",
|
|
16
|
+
"docs/architecture.md",
|
|
17
|
+
"docs/development.md",
|
|
18
|
+
"docs/assets/pi-stardeck.gif",
|
|
19
|
+
"demo/pi-stardeck.tape",
|
|
20
|
+
"licenses",
|
|
21
|
+
"src/*.ts",
|
|
22
|
+
"!src/local-load-probe.ts",
|
|
23
|
+
"packages/*/*.ts",
|
|
24
|
+
"packages/*/src/**/*.ts",
|
|
25
|
+
"!packages/*/src/**/*.test.ts",
|
|
26
|
+
"packages/*/package.json",
|
|
27
|
+
"packages/*/README.md",
|
|
28
|
+
"packages/*/LICENSE",
|
|
29
|
+
"packages/*/LOCAL-CHANGES.md"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=24.0.0"
|
|
33
|
+
},
|
|
34
|
+
"workspaces": [
|
|
35
|
+
"packages/*"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"format": "prettier --write . --ignore-path .prettierignore",
|
|
39
|
+
"format:check": "prettier --check . --ignore-path .prettierignore",
|
|
40
|
+
"lint": "biome check --config-path=biome.json packages/pi-vim-top-border && eslint packages scripts src test",
|
|
41
|
+
"typecheck": "tsc --noEmit && npm run typecheck --workspaces --if-present",
|
|
42
|
+
"test": "npm run test:tooling && npm run test:components",
|
|
43
|
+
"check": "npm run check:baseline && npm run check:package:load && npm run format:check && npm run lint && npm run typecheck && npm test && npm run check:composition && npm run check:openspec",
|
|
44
|
+
"check:baseline": "node scripts/check-baseline.mjs",
|
|
45
|
+
"check:package": "node scripts/check-package.mjs",
|
|
46
|
+
"check:package:load": "node scripts/check-package.mjs --load",
|
|
47
|
+
"check:composition": "node --import tsx/esm src/local-load-probe.ts",
|
|
48
|
+
"check:openspec": "node scripts/check-openspec.mjs",
|
|
49
|
+
"test:tooling": "node --import tsx/esm --test test/*.test.ts",
|
|
50
|
+
"test:status": "npm --prefix packages/status test",
|
|
51
|
+
"test:stardeck": "npm --prefix packages/pi-stardeck test",
|
|
52
|
+
"test:footer": "npm --prefix packages/pi-fancy-footer-full-palette test",
|
|
53
|
+
"test:vim": "npm --prefix packages/pi-vim-top-border test",
|
|
54
|
+
"test:components": "npm run test:status && npm run test:stardeck && npm run test:footer && npm run test:vim"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@earendil-works/pi-ai": "*",
|
|
58
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
59
|
+
"@earendil-works/pi-tui": "*",
|
|
60
|
+
"typebox": "*"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@biomejs/biome": "2.4.8",
|
|
64
|
+
"@earendil-works/pi-ai": "0.84.4",
|
|
65
|
+
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
66
|
+
"@earendil-works/pi-tui": "0.84.4",
|
|
67
|
+
"@eslint/js": "9.39.5",
|
|
68
|
+
"@types/node": "24.13.3",
|
|
69
|
+
"eslint": "9.39.5",
|
|
70
|
+
"lefthook": "2.1.12",
|
|
71
|
+
"prettier": "3.9.6",
|
|
72
|
+
"tsx": "4.23.13",
|
|
73
|
+
"typebox": "1.3.25",
|
|
74
|
+
"typescript": "5.9.3",
|
|
75
|
+
"typescript-eslint": "8.69.0"
|
|
76
|
+
},
|
|
77
|
+
"pi": {
|
|
78
|
+
"image": "docs/assets/pi-stardeck.gif",
|
|
79
|
+
"extensions": [
|
|
80
|
+
"./src/index.ts"
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthias Vallentin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Local changes
|
|
2
|
+
|
|
3
|
+
This is a Galactica-managed local fork of `pi-fancy-footer` 3.0.1.
|
|
4
|
+
|
|
5
|
+
## Full Pi theme palette
|
|
6
|
+
|
|
7
|
+
The extension-widget and configuration color schema accepts Pi's complete foreground
|
|
8
|
+
theme-token palette instead of limiting footer widgets to seven generic colors. This
|
|
9
|
+
lets Galactica use the configured cyan, purple, orange, green, red, and teal palette
|
|
10
|
+
without embedding ANSI escapes or falling back to gray text.
|
|
11
|
+
|
|
12
|
+
## Extension-widget emphasis
|
|
13
|
+
|
|
14
|
+
Protocol-1 extension widgets accept `style.bold: true`. The renderer applies
|
|
15
|
+
theme-native bold after effective text-color resolution, allowing Galactica's PWD widget
|
|
16
|
+
to stand out without embedding unsafe ANSI escapes or bolding its icon.
|
|
17
|
+
|
|
18
|
+
## Compact metric presentation
|
|
19
|
+
|
|
20
|
+
The `percent` provider-status display mode joins the most constrained known quota to the
|
|
21
|
+
LLM group as `› 4%`, with no window analysis. Its five-state battery depletes from ``
|
|
22
|
+
through `` and changes semantic color as remaining quota falls. Cache accounting
|
|
23
|
+
remains available even when its persistent widget is disabled. Session cost uses a
|
|
24
|
+
leading major `⟩`, no treasure-chest glyph, and compact trailing-zero-free formatting of
|
|
25
|
+
Pi's native USD estimate (`$0`, `$0.1`, `$1`, or `$1.23`). Cache and cost remain neutral
|
|
26
|
+
local telemetry instead of inheriting unrelated context-pressure warnings; context, CPU,
|
|
27
|
+
and RAM own their warning colors independently. Galactica now keeps exactly one footer
|
|
28
|
+
row: project PWD and Git state on the left, with model, reasoning, and quota on the
|
|
29
|
+
right. On wide terminals, the footer ends with the four-cell tail `⟨ ` after model
|
|
30
|
+
quota: a purple left-pointing boundary and the static violet ladybug as Ramona's compact
|
|
31
|
+
avatar, with no trailing cell after her. The bounded tail keeps the avatar distinct
|
|
32
|
+
without producing a large empty region. Work progress, cost, MCP availability, runtime,
|
|
33
|
+
resources, and context are still published as bounded widget snapshots but are hidden
|
|
34
|
+
from the footer and consumed by the prompt rails. The renderer omits trailing rows that
|
|
35
|
+
contain no visible widgets, allowing the configured footer to occupy one terminal row
|
|
36
|
+
without changing the upstream two-row default. Non-growing widgets continue to honor
|
|
37
|
+
`minWidth` where configured.
|
|
38
|
+
|
|
39
|
+
The package is loaded from `./packages/pi-fancy-footer-full-palette` in `settings.json`.
|
|
40
|
+
Do not replace it with the npm package without first preserving the expanded
|
|
41
|
+
`FOOTER_WIDGET_COLORS` contract. Run the package tests and `/reload` after updates.
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# ✨ pi-fancy-footer
|
|
2
|
+
|
|
3
|
+
A [Pi](https://pi.dev) extension that replaces the default footer with a compact,
|
|
4
|
+
configurable fancy status footer. Its defaults use two rows; trailing rows with no
|
|
5
|
+
visible widgets are omitted, so a row-zero-only layout occupies exactly one row.
|
|
6
|
+
|
|
7
|
+
<!-- markdownlint-disable MD033 -->
|
|
8
|
+
|
|
9
|
+
<picture>
|
|
10
|
+
<source media="(prefers-color-scheme: dark)" srcset="screenshots/editor-dark.png">
|
|
11
|
+
<source media="(prefers-color-scheme: light)" srcset="screenshots/editor-light.png">
|
|
12
|
+
<img alt="Pi editor with the fancy footer" src="screenshots/editor-light.png">
|
|
13
|
+
</picture>
|
|
14
|
+
|
|
15
|
+
<!-- markdownlint-enable MD033 -->
|
|
16
|
+
|
|
17
|
+
## 🚀 Installation
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
pi install npm:pi-fancy-footer
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 📊 What it shows
|
|
24
|
+
|
|
25
|
+
- Active model + thinking level, plus the model's provider (hidden by default)
|
|
26
|
+
- Provider quota status for OpenAI Codex, Claude, and z.ai GLM models
|
|
27
|
+
- A mini gauge of used context, which can optionally grow into a full-width bar, plus an
|
|
28
|
+
optional context-capacity widget (hidden by default)
|
|
29
|
+
- Total session cost with compact trailing-zero-free USD formatting
|
|
30
|
+
- Prompt-cache statistics: cumulative cache-read/write tokens and the latest turn's
|
|
31
|
+
cache hit rate
|
|
32
|
+
- Current path, branch, and optional commit SHA (hidden by default)
|
|
33
|
+
- Git diff stats and ahead/behind status
|
|
34
|
+
|
|
35
|
+
Pull request widgets come from [pi-prs](https://github.com/mavam/pi-prs), which owns
|
|
36
|
+
GitHub polling and publishes them into this footer.
|
|
37
|
+
|
|
38
|
+
## 📸 Configuration editor
|
|
39
|
+
|
|
40
|
+
<!-- markdownlint-disable MD033 -->
|
|
41
|
+
|
|
42
|
+
<picture>
|
|
43
|
+
<source media="(prefers-color-scheme: dark)" srcset="screenshots/config-dark.png">
|
|
44
|
+
<source media="(prefers-color-scheme: light)" srcset="screenshots/config-light.png">
|
|
45
|
+
<img alt="Fancy Footer configuration editor" src="screenshots/config-light.png">
|
|
46
|
+
</picture>
|
|
47
|
+
|
|
48
|
+
<!-- markdownlint-enable MD033 -->
|
|
49
|
+
|
|
50
|
+
## 🎮 Commands
|
|
51
|
+
|
|
52
|
+
- `/fancy-footer` - open interactive footer config editor (small TUI)
|
|
53
|
+
- widgets appear as a micro-view of the footer: same rows, alignment groups, and
|
|
54
|
+
ordering as the real footer, which updates live below
|
|
55
|
+
- use `←→↑↓` to select a widget (shown inverted), then:
|
|
56
|
+
- `l`/`r` - move it left/right; at a group edge it flows into the adjacent
|
|
57
|
+
alignment group (left ↔ middle ↔ right)
|
|
58
|
+
- `u`/`d` - move it up/down a row; `d` on the bottom row hides it into the
|
|
59
|
+
`hidden` strip, `u` from there brings it back
|
|
60
|
+
- `a` - cycle alignment (left → middle → right)
|
|
61
|
+
- `f` - toggle fill (`none` ↔ `grow`)
|
|
62
|
+
- `x` or Space - toggle visibility
|
|
63
|
+
- Enter - open widget-specific settings (visibility, icon, icon color, text
|
|
64
|
+
color, min width, and the provider-status reset threshold)
|
|
65
|
+
- arrow down past the widgets to reach the General settings (refresh, icon family,
|
|
66
|
+
gauge style/width/colors, default colors); Enter/Space cycles values
|
|
67
|
+
|
|
68
|
+
## ⚙️ Configuration
|
|
69
|
+
|
|
70
|
+
Create `~/.pi/agent/fancy-footer.json`:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"refreshMs": 3000,
|
|
75
|
+
"iconFamily": "unicode",
|
|
76
|
+
"gaugeStyle": "blocks",
|
|
77
|
+
"gaugeWidth": 5,
|
|
78
|
+
"gaugeColors": {
|
|
79
|
+
"ok": "accent",
|
|
80
|
+
"warning": "warning",
|
|
81
|
+
"error": "error"
|
|
82
|
+
},
|
|
83
|
+
"defaultTextColor": "dim",
|
|
84
|
+
"defaultIconColor": "text",
|
|
85
|
+
"providerStatus": {
|
|
86
|
+
"refreshMs": 60000,
|
|
87
|
+
"cacheTtlMs": 60000,
|
|
88
|
+
"providers": ["openai-codex", "anthropic", "zai"],
|
|
89
|
+
"display": "gauge",
|
|
90
|
+
"showCredits": false,
|
|
91
|
+
"showReset": "all",
|
|
92
|
+
"resetMinUsedPercent": 75
|
|
93
|
+
},
|
|
94
|
+
"widgets": {
|
|
95
|
+
"context-bar": {
|
|
96
|
+
"align": "left",
|
|
97
|
+
"row": 0,
|
|
98
|
+
"position": 0,
|
|
99
|
+
"fill": "grow"
|
|
100
|
+
},
|
|
101
|
+
"total-cost": {
|
|
102
|
+
"enabled": false
|
|
103
|
+
},
|
|
104
|
+
"commit": {
|
|
105
|
+
"enabled": true
|
|
106
|
+
},
|
|
107
|
+
"branch": {
|
|
108
|
+
"icon": "hide",
|
|
109
|
+
"textColor": "muted"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"extensionWidgets": {
|
|
113
|
+
"acme.build-status": {
|
|
114
|
+
"row": 1,
|
|
115
|
+
"position": 8,
|
|
116
|
+
"align": "right"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Top-level settings:
|
|
123
|
+
|
|
124
|
+
> [!NOTE] `fancy-footer.json` is validated strictly. Use only the documented keys and
|
|
125
|
+
> values. Invalid configuration falls back to defaults and logs a warning.
|
|
126
|
+
|
|
127
|
+
> [!WARNING] `providerStatus.showReset` no longer accepts booleans. Before upgrading,
|
|
128
|
+
> replace `true` with `"primary"` and `false` with `"off"`. An invalid value causes the
|
|
129
|
+
> entire file to fall back to defaults.
|
|
130
|
+
|
|
131
|
+
- `refreshMs` (number) - interval for background Git refreshes. Model, thinking-level,
|
|
132
|
+
provider-response, and extension-widget changes update from events without waiting for
|
|
133
|
+
this interval
|
|
134
|
+
- `iconFamily` (`nerd` | `emoji` | `unicode` | `ascii`)
|
|
135
|
+
- `gaugeStyle` (`blocks` | `lines` | `circles` | `parallelograms` | `diamonds` | `bars`
|
|
136
|
+
| `stars` | `specks`)
|
|
137
|
+
- `gaugeWidth` - cells spanned by the provider status gauges and the compact context
|
|
138
|
+
gauge (3-40, default 5); a context bar with `fill` set to `grow` spans the row instead
|
|
139
|
+
- `gaugeColors` - fill colors per gauge severity; each of `ok`, `warning`, and `error`
|
|
140
|
+
accepts a widget color. Defaults to `accent` / `warning` / `error`, so healthy gauges
|
|
141
|
+
blend into the theme and only stand out when running low
|
|
142
|
+
- `defaultTextColor` (`text` | `accent` | `muted` | `dim` | `success` | `error` |
|
|
143
|
+
`warning`)
|
|
144
|
+
- `defaultIconColor` (`text` | `accent` | `muted` | `dim` | `success` | `error` |
|
|
145
|
+
`warning`)
|
|
146
|
+
- `providerStatus`:
|
|
147
|
+
- `refreshMs` - provider status refresh interval in milliseconds
|
|
148
|
+
- `cacheTtlMs` - cache freshness window in milliseconds
|
|
149
|
+
- `providers` - supported provider adapters (`openai-codex`, `anthropic`, `zai`)
|
|
150
|
+
- `display` - render quota windows as a mini `gauge` (default) or plain `text`
|
|
151
|
+
- `showCredits` - include a provider-specific credit balance when available
|
|
152
|
+
- `showReset` - control which relative reset countdowns are eligible:
|
|
153
|
+
- `"off"` - hide all countdowns
|
|
154
|
+
- `"primary"` - show the primary window countdown
|
|
155
|
+
- `"all"` - show every reported window countdown (default)
|
|
156
|
+
- `resetMinUsedPercent` - show an eligible countdown once its window reaches this
|
|
157
|
+
used quota percentage (0-100, default 75). The comparison is inclusive and uses
|
|
158
|
+
the displayed percentage. A window without a reported usage value counts as 0%.
|
|
159
|
+
Set this to `0` to always show eligible countdowns.
|
|
160
|
+
|
|
161
|
+
Supported per-widget overrides for both `widgets` and `extensionWidgets`:
|
|
162
|
+
|
|
163
|
+
- `enabled` (boolean)
|
|
164
|
+
- `row` (number)
|
|
165
|
+
- `position` (number, ordering within an aligned row group)
|
|
166
|
+
- `align` (`left` | `middle` | `right`)
|
|
167
|
+
- `fill` (`none` | `grow`)
|
|
168
|
+
- `minWidth` (number)
|
|
169
|
+
- `icon` (`default` | `hide`)
|
|
170
|
+
- `iconColor` (`text` | `accent` | `muted` | `dim` | `success` | `error` | `warning`)
|
|
171
|
+
- `textColor` (`text` | `accent` | `muted` | `dim` | `success` | `error` | `warning`)
|
|
172
|
+
|
|
173
|
+
Built-in widget IDs:
|
|
174
|
+
|
|
175
|
+
- `provider`
|
|
176
|
+
- `model`
|
|
177
|
+
- `thinking`
|
|
178
|
+
- `context-capacity`
|
|
179
|
+
- `context-bar`
|
|
180
|
+
- `total-cost`
|
|
181
|
+
- `cache-read`
|
|
182
|
+
- `cache-write`
|
|
183
|
+
- `cache-hit-rate`
|
|
184
|
+
- `location`
|
|
185
|
+
- `branch`
|
|
186
|
+
- `commit`
|
|
187
|
+
- `provider-status`
|
|
188
|
+
- `diff-added`
|
|
189
|
+
- `diff-removed`
|
|
190
|
+
- `git-status`
|
|
191
|
+
|
|
192
|
+
3rd-party widget IDs are extension-defined and live under `extensionWidgets`.
|
|
193
|
+
|
|
194
|
+
Non-empty left, middle, and right alignment groups reserve four display cells between
|
|
195
|
+
groups. `fill: "none"` widgets pad to `minWidth`, keeping following columns stable as
|
|
196
|
+
content changes. When a row can fit at declared minimum widths, `fill: "grow"` widgets
|
|
197
|
+
yield down to `minWidth` with an ANSI-safe `...` before fixed telemetry is truncated.
|
|
198
|
+
This keeps the right group complete while retaining a visible left/right split.
|
|
199
|
+
|
|
200
|
+
### Provider quota sources
|
|
201
|
+
|
|
202
|
+
- `openai-codex` and `anthropic` read OAuth credentials from Pi's auth file (or
|
|
203
|
+
`~/.codex/auth.json` for Codex) and refresh them automatically.
|
|
204
|
+
- `zai` reads a static API key from Pi's auth file (`zai.key`). The value may be a
|
|
205
|
+
literal key or a `${ENV_VAR}` reference resolved from the environment at runtime.
|
|
206
|
+
Quota comes from the z.ai monitor endpoint: the token or credit limit with the
|
|
207
|
+
nearest reset paints as the `5h` window and the next one as `7d`; the monthly
|
|
208
|
+
`TIME_LIMIT` allowance is not rendered. Without a key, offline, or on error the
|
|
209
|
+
provider fails soft and the quota widget stays hidden.
|
|
210
|
+
|
|
211
|
+
The telemetry channel also publishes the model-relevant quota windows (`5h` and
|
|
212
|
+
`7d` with used percent and reset deadline) alongside the collapsed single percent,
|
|
213
|
+
so deck surfaces can paint both windows as separate tiles.
|
|
214
|
+
|
|
215
|
+
## 🧩 Extension widgets
|
|
216
|
+
|
|
217
|
+
Other pi extensions can contribute fancy-footer widgets.
|
|
218
|
+
|
|
219
|
+
### For users
|
|
220
|
+
|
|
221
|
+
- Contributed widgets appear alongside built-in widgets in the `/fancy-footer`
|
|
222
|
+
micro-view.
|
|
223
|
+
- Their overrides are stored in `extensionWidgets` inside
|
|
224
|
+
`~/.pi/agent/fancy-footer.json`.
|
|
225
|
+
- They use the same layout controls as built-in widgets, so you can mix and match them
|
|
226
|
+
on any footer row.
|
|
227
|
+
|
|
228
|
+
### For extension developers
|
|
229
|
+
|
|
230
|
+
Publish complete widget snapshots over pi's in-process event bus. Producers do not need
|
|
231
|
+
to depend on `pi-fancy-footer`:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
235
|
+
|
|
236
|
+
const protocol = 1;
|
|
237
|
+
const widgetChannel = 'pi-fancy-footer:widget';
|
|
238
|
+
const readyChannel = 'pi-fancy-footer:ready';
|
|
239
|
+
|
|
240
|
+
export default function (pi: ExtensionAPI) {
|
|
241
|
+
let status = 'passing';
|
|
242
|
+
|
|
243
|
+
const publish = () => {
|
|
244
|
+
pi.events.emit(widgetChannel, {
|
|
245
|
+
protocol,
|
|
246
|
+
type: 'upsert',
|
|
247
|
+
widget: {
|
|
248
|
+
id: 'acme.build-status',
|
|
249
|
+
label: 'Build status',
|
|
250
|
+
description: 'Current build result',
|
|
251
|
+
content: {
|
|
252
|
+
type: 'text',
|
|
253
|
+
text: status,
|
|
254
|
+
href: 'https://ci.example.com/builds/latest',
|
|
255
|
+
},
|
|
256
|
+
icon: {
|
|
257
|
+
glyphs: {
|
|
258
|
+
nerd: '',
|
|
259
|
+
emoji: '🧪',
|
|
260
|
+
unicode: '◈',
|
|
261
|
+
ascii: 'B',
|
|
262
|
+
},
|
|
263
|
+
color: 'success',
|
|
264
|
+
},
|
|
265
|
+
style: { textColor: 'success', bold: true },
|
|
266
|
+
layout: { row: 1, position: 8, align: 'right' },
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const stopReady = pi.events.on(readyChannel, (message) => {
|
|
272
|
+
if (
|
|
273
|
+
typeof message === 'object' &&
|
|
274
|
+
message !== null &&
|
|
275
|
+
'protocol' in message &&
|
|
276
|
+
message.protocol === protocol
|
|
277
|
+
) {
|
|
278
|
+
publish();
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Publish once for a footer that is already listening. Publish again when
|
|
283
|
+
// state changes; the footer does not poll producers.
|
|
284
|
+
publish();
|
|
285
|
+
|
|
286
|
+
pi.on('session_shutdown', () => {
|
|
287
|
+
stopReady();
|
|
288
|
+
pi.events.emit(widgetChannel, {
|
|
289
|
+
protocol,
|
|
290
|
+
type: 'remove',
|
|
291
|
+
id: 'acme.build-status',
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The protocol uses these channels:
|
|
298
|
+
|
|
299
|
+
- `pi-fancy-footer:widget` accepts protocol-1 `upsert` and `remove` messages.
|
|
300
|
+
- `pi-fancy-footer:ready` announces that the footer is listening. Producers should
|
|
301
|
+
republish their current snapshot when they receive it.
|
|
302
|
+
|
|
303
|
+
`style.bold: true` applies theme-native bold emphasis to widget text after its effective
|
|
304
|
+
text color is resolved; icons remain independently styled.
|
|
305
|
+
|
|
306
|
+
Each `upsert` replaces the complete snapshot for its `id`; the latest command wins if
|
|
307
|
+
multiple producers use the same ID. IDs must be at most 128 ASCII characters and contain
|
|
308
|
+
two or more dot-separated segments. Each segment starts with a letter or digit and may
|
|
309
|
+
also contain letters, digits, underscores, and hyphens, for example `acme.build-status`.
|
|
310
|
+
An empty `content.text` creates an icon-only widget when the selected icon family has a
|
|
311
|
+
glyph. Widgets with `layout.enabled: false` stay hidden while their text is empty, even
|
|
312
|
+
when a user enables them. This keeps opt-in activity widgets out of the footer while
|
|
313
|
+
they have nothing to report. Without a glyph, an empty widget also stays hidden. Set
|
|
314
|
+
`content.href` to an HTTP or HTTPS URL to make the complete widget, including its icon,
|
|
315
|
+
clickable. Invalid links are omitted without dropping the widget update. A `remove`
|
|
316
|
+
message drops the live widget definition. Widget text is limited to 512 Unicode code
|
|
317
|
+
points and terminal control characters are stripped.
|
|
318
|
+
|
|
319
|
+
The structured snapshot can provide `label`, `description`, an icon glyph or per-family
|
|
320
|
+
glyph map, icon and text colors, and layout defaults (`enabled`, `row`, `position`,
|
|
321
|
+
`align`, `fill`, and `minWidth`). Saved user settings override event-provided defaults.
|
|
322
|
+
Use `layout.enabled: false` for an opt-in widget.
|
|
323
|
+
|
|
324
|
+
Extensions that already depend on this package may use `createFancyFooterClient` from
|
|
325
|
+
`pi-fancy-footer/api` for typed `upsert`, `remove`, and `onReady` helpers. It speaks the
|
|
326
|
+
same event protocol.
|
|
327
|
+
|
|
328
|
+
## 🔣 Icon families
|
|
329
|
+
|
|
330
|
+
The following table shows the symbol used by each widget for each icon family. For
|
|
331
|
+
`git-status`, the table shows the rendered status symbols rather than a leading widget
|
|
332
|
+
icon.
|
|
333
|
+
|
|
334
|
+
> [!NOTE] Some glyphs, especially in the `nerd` family, may not render in your browser.
|
|
335
|
+
> If a cell looks blank or shows a replacement box, check the table in a terminal with
|
|
336
|
+
> the relevant font installed.
|
|
337
|
+
|
|
338
|
+
<!-- markdownlint-disable MD013 MD060 -->
|
|
339
|
+
|
|
340
|
+
| Widget | nerd | emoji | unicode | ascii |
|
|
341
|
+
| ------------------ | ------- | ---------- | ------- | -------- |
|
|
342
|
+
| `context-bar` | `` | `🔋` | `◧` | `\|` |
|
|
343
|
+
| `context-capacity` | `` | `💾` | `□` | `[]` |
|
|
344
|
+
| `provider-status` | `` | `📊` | `%` | `%` |
|
|
345
|
+
| `cache-read` | `` | `📥` | `↧` | `R` |
|
|
346
|
+
| `cache-write` | `` | `📤` | `↥` | `W` |
|
|
347
|
+
| `cache-hit-rate` | `` | `🎯` | `◎` | `H` |
|
|
348
|
+
| `total-cost` | `` | `💲` | `$` | `$` |
|
|
349
|
+
| `location` | `` | `📁` | `⌂` | `/` |
|
|
350
|
+
| `branch` | `` | `🌿` | `⎇` | `*` |
|
|
351
|
+
| `commit` | `` | `🔖` | `#` | `#` |
|
|
352
|
+
| `diff-added` | `↗` | `➕` | `+` | `+` |
|
|
353
|
+
| `diff-removed` | `↘` | `➖` | `−` | `-` |
|
|
354
|
+
| `git-status` | `//` | `🔼/🔽/🔀` | `↑/↓/↕` | `^/_/<>` |
|
|
355
|
+
| `provider` | `` | `☁️` | `☁` | `&` |
|
|
356
|
+
| `model` | `` | `🤖` | `◉` | `%` |
|
|
357
|
+
| `thinking` | `` | `🧠` | `✦` | `?` |
|
|
358
|
+
|
|
359
|
+
<!-- markdownlint-enable MD013 MD060 -->
|
|
360
|
+
|
|
361
|
+
Notes:
|
|
362
|
+
|
|
363
|
+
- Most widgets use a leading icon.
|
|
364
|
+
- `context-bar` renders a mini gauge of used context, e.g. `■■□□□ 40%`, spanning
|
|
365
|
+
`gaugeWidth` cells with the glyphs from `gaugeStyle` (not `iconFamily`). Filled cells
|
|
366
|
+
and the percentage show the consumed share and fill up from the left, colored via
|
|
367
|
+
`gaugeColors` by how close the context is to exhaustion; empty cells stay dim. It sits
|
|
368
|
+
on the left of the top row by default, with provider quota gauges beside it. Set the
|
|
369
|
+
widget's `fill` to `grow` (via `/fancy-footer` or the configuration file) to expand it
|
|
370
|
+
into a full-width bar with the used tokens in front, e.g. `246k ██████████░░░`.
|
|
371
|
+
Immediately after compaction, the gauge resets to empty (`0%`) while Pi waits for the
|
|
372
|
+
next model response to report post-compaction usage.
|
|
373
|
+
- `context-capacity` shows the total context window in compact SI form (`200k`, `1M`).
|
|
374
|
+
It is hidden by default since the context bar already conveys usage; enable it via
|
|
375
|
+
`/fancy-footer` (it starts in the `hidden` strip) or with
|
|
376
|
+
`"context-capacity": { "enabled": true }`. It then sits between the context bar and
|
|
377
|
+
the provider quota gauges.
|
|
378
|
+
- `commit` shows the short Git commit SHA. It is hidden by default; enable it via
|
|
379
|
+
`/fancy-footer` or with `"commit": { "enabled": true }`.
|
|
380
|
+
- `provider` shows the display name of the active model's provider (`Anthropic`,
|
|
381
|
+
`OpenAI Codex`, …), which the model name alone does not reveal. Providers defined only
|
|
382
|
+
in `models.json` fall back to their id. The widget is hidden by default; enable it via
|
|
383
|
+
`/fancy-footer` or with `"provider": { "enabled": true }`. It then sits left of the
|
|
384
|
+
model on the bottom row.
|
|
385
|
+
- `cache-read` and `cache-write` show cumulative prompt-cache tokens for the session in
|
|
386
|
+
compact form (e.g. `246k`, `1.2M`). `cache-hit-rate` shows the latest turn's cache hit
|
|
387
|
+
rate, computed as `cacheRead / (input + cacheRead + cacheWrite)`, matching the `R` /
|
|
388
|
+
`W` / `CH` stats in pi's built-in footer. All three sit on the right of the top row by
|
|
389
|
+
default, before `total-cost` (which stays rightmost), and hide when the session has no
|
|
390
|
+
cache activity or the terminal is narrower than 60 columns.
|
|
391
|
+
- `total-cost` uses Pi's native estimated session cost and keeps useful nonzero
|
|
392
|
+
precision while removing trailing fractional zeros: `$0`, `$0.1`, `$1`, or `$1.23`.
|
|
393
|
+
Its text includes the major scope separator (`⟩ $1.23`); hide the built-in icon for
|
|
394
|
+
that exact presentation. Zero-cost sessions retain the widget's existing hidden
|
|
395
|
+
behavior.
|
|
396
|
+
- `git-status` uses symbols for ahead / behind / diverged status.
|
|
397
|
+
- `provider-status` shows provider quota windows for OpenAI Codex and Claude models as
|
|
398
|
+
mini gauges per window, for example `5h ▰▰▰▰▱ 80% ~1h12m 7d ▰▰▱▱▱ 38%`. Filled cells
|
|
399
|
+
show the used quota, growing from the left like the context bar, and each window is
|
|
400
|
+
colored by how close it is to exhaustion. In gauge mode, a dim reset countdown belongs
|
|
401
|
+
to the window immediately before it. The default `resetMinUsedPercent: 75` shows a
|
|
402
|
+
countdown only when that window is at least 75% used. The default `showReset: "all"`
|
|
403
|
+
makes every reported window eligible; use `"primary"` to allow only the primary window
|
|
404
|
+
or `"off"` to hide countdowns. You can change the threshold in the `provider-status`
|
|
405
|
+
widget's interactive settings. Set it to `0` to restore the previous always-show
|
|
406
|
+
behavior. The gauge spans `gaugeWidth` cells and reuses the configured `gaugeStyle`
|
|
407
|
+
glyphs. Set `providerStatus.display` to `text` for the compact
|
|
408
|
+
`qta 5h:80% ~1h12m 7d:38%` form. Text mode keeps the existing provider severity color
|
|
409
|
+
for the whole widget. The local `percent` mode shows only the most constrained known
|
|
410
|
+
usage as `› 80%`, omitting window analysis and reset countdowns. Its five-state
|
|
411
|
+
battery depletes from `` through `` as used quota rises, and changes from success to
|
|
412
|
+
warning to error as remaining quota falls. Countdown placement, thresholds, and modes
|
|
413
|
+
apply to Claude and Codex in gauge and text styles. The widget renders only the
|
|
414
|
+
windows that the provider reports. If Codex omits its 5-hour window and promotes the
|
|
415
|
+
weekly window to primary, the footer removes the stale 5-hour value and shows only
|
|
416
|
+
`7d`. Because that weekly window is primary, it receives a countdown under the default
|
|
417
|
+
mode. In an output such as `▱▱▱▱▱ 0% 7d ▰▰▰▰▱ 84% ~1d7h`, `0%` is the share of pi's
|
|
418
|
+
context window in use and `84%` is the used weekly Codex quota. Codex uses existing pi
|
|
419
|
+
OpenAI Codex credentials from `~/.pi/agent/auth.json`, falling back to Codex CLI
|
|
420
|
+
credentials in `~/.codex/auth.json`. Claude uses pi Anthropic OAuth credentials from
|
|
421
|
+
`~/.pi/agent/auth.json` and reads Claude.ai usage for the 5-hour and weekly windows.
|
|
422
|
+
When Claude reports a limit that applies only to the active model, such as the weekly
|
|
423
|
+
Fable cap, that window replaces the account-wide window with the same label instead of
|
|
424
|
+
adding a gauge, so `7d` shows the quota that actually limits the current model. The
|
|
425
|
+
stricter of the two always wins, because whichever runs out first ends the session,
|
|
426
|
+
and the value follows `/model` switches without another usage request. Status is
|
|
427
|
+
cached under `~/.cache/pi-fancy-footer/provider-status/`; when a refresh fails, cached
|
|
428
|
+
quota windows keep showing until their reset times pass instead of hiding the widget.
|
|
429
|
+
Once a window passes its reset time it stays visible with unknown usage (`—`) until a
|
|
430
|
+
refresh reports the new period, so a quota rollover neither hides the gauge nor claims
|
|
431
|
+
unconfirmed headroom. The widget is hidden when the active model selection is not
|
|
432
|
+
backed by the status provider.
|
|
433
|
+
- `provider-status` also refreshes from `x-codex-*` provider response headers when pi
|
|
434
|
+
exposes them, avoiding a separate Codex status request after provider calls. Claude
|
|
435
|
+
status refreshes from the Claude.ai usage endpoint, not provider response headers.
|
|
436
|
+
- `iconFamily` lets you choose between `nerd`, `emoji`, `unicode`, and `ascii` palettes.
|
|
437
|
+
- `nerd` keeps the original Nerd Font look. `emoji`, `unicode`, and `ascii` work better
|
|
438
|
+
in terminals that don't use a Nerd Font.
|
|
439
|
+
- Per-widget icon overrides only let you hide the icon. The selected `iconFamily`
|
|
440
|
+
controls which icon each widget uses.
|
|
441
|
+
- Pull request widgets live in [pi-prs](https://github.com/mavam/pi-prs), which owns all
|
|
442
|
+
GitHub polling and publishes them through the data-widget protocol.
|
|
443
|
+
|
|
444
|
+
## 🧱 Gauge styles
|
|
445
|
+
|
|
446
|
+
The `gaugeStyle` setting controls the characters used by the `context-bar` and
|
|
447
|
+
`provider-status` gauges. All gauges fill from the left with what is already consumed,
|
|
448
|
+
so a nearly full gauge means a nearly exhausted resource. Each style defines symbols for
|
|
449
|
+
filled and empty cells:
|
|
450
|
+
|
|
451
|
+
<!-- markdownlint-disable MD013 MD060 -->
|
|
452
|
+
|
|
453
|
+
| Style | Filled | Empty |
|
|
454
|
+
| ------------------ | ------ | ----- |
|
|
455
|
+
| `blocks` (default) | `■` | `□` |
|
|
456
|
+
| `lines` | `━` | `─` |
|
|
457
|
+
| `circles` | `●` | `○` |
|
|
458
|
+
| `parallelograms` | `▰` | `▱` |
|
|
459
|
+
| `diamonds` | `◆` | `◇` |
|
|
460
|
+
| `bars` | `█` | `░` |
|
|
461
|
+
| `stars` | `★` | `☆` |
|
|
462
|
+
| `specks` | `•` | `◦` |
|
|
463
|
+
|
|
464
|
+
<!-- markdownlint-enable MD013 MD060 -->
|
|
465
|
+
|
|
466
|
+
## 📄 License
|
|
467
|
+
|
|
468
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-fancy-footer-full-palette",
|
|
3
|
+
"version": "3.0.1-local.8",
|
|
4
|
+
"description": "A fancy footer extension for pi",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"@earendil-works/pi-ai": "*",
|
|
8
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
9
|
+
"@earendil-works/pi-tui": "*",
|
|
10
|
+
"typebox": "*"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"pi-package",
|
|
19
|
+
"pi",
|
|
20
|
+
"pi-extension",
|
|
21
|
+
"footer",
|
|
22
|
+
"statusline"
|
|
23
|
+
],
|
|
24
|
+
"author": "Matthias Vallentin",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/mavam/pi-fancy-footer.git"
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./src/index.ts",
|
|
32
|
+
"./api": "./src/api.ts"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=24.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"format": "prettier --write src tsconfig.json",
|
|
39
|
+
"format:check": "prettier --check src tsconfig.json",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "node --test --experimental-strip-types src/*.test.ts",
|
|
42
|
+
"check": "npm run format:check && npm run typecheck && npm test"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"pi": {
|
|
48
|
+
"extensions": [
|
|
49
|
+
"./src/index.ts"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|