pi-fancy-footer
A fancy footer extension for pi
Package details
Install pi-fancy-footer from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-fancy-footer- Package
pi-fancy-footer- Version
3.0.1- Published
- Aug 22, 2026
- Downloads
- 754/mo · 83/wk
- Author
- mavam
- License
- MIT
- Types
- extension
- Size
- 313.2 KB
- Dependencies
- 0 dependencies · 4 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
✨ pi-fancy-footer
A Pi extension that replaces the default footer with a compact, two-line fancy status footer.
🚀 Installation
pi install npm:pi-fancy-footer
📊 What it shows
- Active model + thinking level, plus the model's provider (hidden by default)
- Provider quota status for OpenAI Codex and Claude models
- A mini gauge of used context, which can optionally grow into a full-width bar, plus an optional context-capacity widget (hidden by default)
- Total session cost
- Prompt-cache statistics: cumulative cache-read/write tokens and the latest turn's cache hit rate
- Current path, branch, and optional commit SHA (hidden by default)
- Git diff stats and ahead/behind status
Pull request widgets come from pi-prs, which owns GitHub polling and publishes them into this footer.
📸 Configuration editor
🎮 Commands
/fancy-footer- open interactive footer config editor (small TUI)- widgets appear as a micro-view of the footer: same rows, alignment groups, and ordering as the real footer, which updates live below
- use
←→↑↓to select a widget (shown inverted), then:l/r- move it left/right; at a group edge it flows into the adjacent alignment group (left ↔ middle ↔ right)u/d- move it up/down a row;don the bottom row hides it into thehiddenstrip,ufrom there brings it backa- cycle alignment (left → middle → right)f- toggle fill (none↔grow)xor Space - toggle visibility- Enter - open widget-specific settings (visibility, icon, icon color, text color, min width, and the provider-status reset threshold)
- arrow down past the widgets to reach the General settings (refresh, icon family, gauge style/width/colors, default colors); Enter/Space cycles values
⚙️ Configuration
Create ~/.pi/agent/fancy-footer.json:
{
"refreshMs": 3000,
"iconFamily": "unicode",
"gaugeStyle": "blocks",
"gaugeWidth": 5,
"gaugeColors": {
"ok": "accent",
"warning": "warning",
"error": "error"
},
"defaultTextColor": "dim",
"defaultIconColor": "text",
"providerStatus": {
"refreshMs": 60000,
"cacheTtlMs": 60000,
"providers": ["openai-codex", "anthropic"],
"display": "gauge",
"showCredits": false,
"showReset": "all",
"resetMinUsedPercent": 75
},
"widgets": {
"context-bar": {
"align": "left",
"row": 0,
"position": 0,
"fill": "grow"
},
"total-cost": {
"enabled": false
},
"commit": {
"enabled": true
},
"branch": {
"icon": "hide",
"textColor": "muted"
}
},
"extensionWidgets": {
"acme.build-status": {
"row": 1,
"position": 8,
"align": "right"
}
}
}
Top-level settings:
[!NOTE]
fancy-footer.jsonis validated strictly. Use only the documented keys and values. Invalid configuration falls back to defaults and logs a warning.
[!WARNING]
providerStatus.showResetno longer accepts booleans. Before upgrading, replacetruewith"primary"andfalsewith"off". An invalid value causes the entire file to fall back to defaults.
refreshMs(number) - interval for background Git refreshes. Model, thinking-level, provider-response, and extension-widget changes update from events without waiting for this intervaliconFamily(nerd|emoji|unicode|ascii)gaugeStyle(blocks|lines|circles|parallelograms|diamonds|bars|stars|specks)gaugeWidth- cells spanned by the provider status gauges and the compact context gauge (3-40, default 5); a context bar withfillset togrowspans the row insteadgaugeColors- fill colors per gauge severity; each ofok,warning, anderroraccepts a widget color. Defaults toaccent/warning/error, so healthy gauges blend into the theme and only stand out when running lowdefaultTextColor(text|accent|muted|dim|success|error|warning)defaultIconColor(text|accent|muted|dim|success|error|warning)providerStatus:refreshMs- provider status refresh interval in millisecondscacheTtlMs- cache freshness window in millisecondsproviders- supported provider adapters (openai-codex,anthropic)display- render quota windows as a minigauge(default) or plaintextshowCredits- include a provider-specific credit balance when availableshowReset- control which relative reset countdowns are eligible:"off"- hide all countdowns"primary"- show the primary window countdown"all"- show every reported window countdown (default)
resetMinUsedPercent- show an eligible countdown once its window reaches this used quota percentage (0-100, default 75). The comparison is inclusive and uses the displayed percentage. A window without a reported usage value counts as 0%. Set this to0to always show eligible countdowns.
Supported per-widget overrides for both widgets and extensionWidgets:
enabled(boolean)row(number)position(number, ordering within an aligned row group)align(left|middle|right)fill(none|grow)minWidth(number)icon(default|hide)iconColor(text|accent|muted|dim|success|error|warning)textColor(text|accent|muted|dim|success|error|warning)
Built-in widget IDs:
providermodelthinkingcontext-capacitycontext-bartotal-costcache-readcache-writecache-hit-ratelocationbranchcommitprovider-statusdiff-addeddiff-removedgit-status
3rd-party widget IDs are extension-defined and live under extensionWidgets.
🧩 Extension widgets
Other pi extensions can contribute fancy-footer widgets.
For users
- Contributed widgets appear alongside built-in widgets in the
/fancy-footermicro-view. - Their overrides are stored in
extensionWidgetsinside~/.pi/agent/fancy-footer.json. - They use the same layout controls as built-in widgets, so you can mix and match them on any footer row.
For extension developers
Publish complete widget snapshots over pi's in-process event bus. Producers do
not need to depend on pi-fancy-footer:
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
const protocol = 1;
const widgetChannel = "pi-fancy-footer:widget";
const readyChannel = "pi-fancy-footer:ready";
export default function (pi: ExtensionAPI) {
let status = "passing";
const publish = () => {
pi.events.emit(widgetChannel, {
protocol,
type: "upsert",
widget: {
id: "acme.build-status",
label: "Build status",
description: "Current build result",
content: {
type: "text",
text: status,
href: "https://ci.example.com/builds/latest",
},
icon: {
glyphs: {
nerd: "",
emoji: "🧪",
unicode: "◈",
ascii: "B",
},
color: "success",
},
style: { textColor: "success" },
layout: { row: 1, position: 8, align: "right" },
},
});
};
const stopReady = pi.events.on(readyChannel, (message) => {
if (
typeof message === "object" &&
message !== null &&
"protocol" in message &&
message.protocol === protocol
) {
publish();
}
});
// Publish once for a footer that is already listening. Publish again when
// state changes; the footer does not poll producers.
publish();
pi.on("session_shutdown", () => {
stopReady();
pi.events.emit(widgetChannel, {
protocol,
type: "remove",
id: "acme.build-status",
});
});
}
The protocol uses these channels:
pi-fancy-footer:widgetaccepts protocol-1upsertandremovemessages.pi-fancy-footer:readyannounces that the footer is listening. Producers should republish their current snapshot when they receive it.
Each upsert replaces the complete snapshot for its id; the latest command
wins if multiple producers use the same ID. IDs must be at most 128 ASCII
characters and contain two or more dot-separated segments. Each segment starts
with a letter or digit and may also contain letters, digits, underscores, and
hyphens, for example acme.build-status. An empty content.text creates an
icon-only widget when the selected icon family has a glyph. Widgets with
layout.enabled: false stay hidden while their text is empty, even when a user
enables them. This keeps opt-in activity widgets out of the footer while they
have nothing to report. Without a glyph, an empty widget also stays hidden. Set
content.href to an HTTP or HTTPS URL to make the complete widget, including
its icon, clickable. Invalid links are omitted without dropping the widget
update. A remove message drops the live widget definition. Widget text is
limited to 512 Unicode code points and terminal control characters are
stripped.
The structured snapshot can provide label, description, an icon glyph or
per-family glyph map, icon and text colors, and layout defaults (enabled,
row, position, align, fill, and minWidth). Saved user settings override
event-provided defaults. Use layout.enabled: false for an opt-in widget.
Extensions that already depend on this package may use
createFancyFooterClient from pi-fancy-footer/api for typed upsert,
remove, and onReady helpers. It speaks the same event protocol.
🔣 Icon families
The following table shows the symbol used by each widget for each icon family.
For git-status, the table shows the rendered status symbols rather than a
leading widget icon.
[!NOTE] Some glyphs, especially in the
nerdfamily, may not render in your browser. If a cell looks blank or shows a replacement box, check the table in a terminal with the relevant font installed.
| Widget | nerd | emoji | unicode | ascii |
|---|---|---|---|---|
context-bar |
|
🔋 |
◧ |
| |
context-capacity |
|
💾 |
□ |
[] |
provider-status |
|
📊 |
% |
% |
cache-read |
|
📥 |
↧ |
R |
cache-write |
|
📤 |
↥ |
W |
cache-hit-rate |
|
🎯 |
◎ |
H |
total-cost |
|
💲 |
$ |
$ |
location |
|
📁 |
⌂ |
/ |
branch |
|
🌿 |
⎇ |
* |
commit |
|
🔖 |
# |
# |
diff-added |
↗ |
➕ |
+ |
+ |
diff-removed |
↘ |
➖ |
− |
- |
git-status |
// |
🔼/🔽/🔀 |
↑/↓/↕ |
^/_/<> |
provider |
|
☁️ |
☁ |
& |
model |
|
🤖 |
◉ |
% |
thinking |
|
🧠 |
✦ |
? |
Notes:
- Most widgets use a leading icon.
context-barrenders a mini gauge of used context, e.g.■■□□□ 40%, spanninggaugeWidthcells with the glyphs fromgaugeStyle(noticonFamily). Filled cells and the percentage show the consumed share and fill up from the left, colored viagaugeColorsby how close the context is to exhaustion; empty cells stay dim. It sits on the left of the top row by default, with provider quota gauges beside it. Set the widget'sfilltogrow(via/fancy-footeror the configuration file) to expand it into a full-width bar with the used tokens in front, e.g.246k ██████████░░░. Immediately after compaction, the gauge resets to empty (0%) while Pi waits for the next model response to report post-compaction usage.context-capacityshows the total context window in compact SI form (200k,1M). It is hidden by default since the context bar already conveys usage; enable it via/fancy-footer(it starts in thehiddenstrip) or with"context-capacity": { "enabled": true }. It then sits between the context bar and the provider quota gauges.commitshows the short Git commit SHA. It is hidden by default; enable it via/fancy-footeror with"commit": { "enabled": true }.providershows the display name of the active model's provider (Anthropic,OpenAI Codex, …), which the model name alone does not reveal. Providers defined only inmodels.jsonfall back to their id. The widget is hidden by default; enable it via/fancy-footeror with"provider": { "enabled": true }. It then sits left of the model on the bottom row.cache-readandcache-writeshow cumulative prompt-cache tokens for the session in compact form (e.g.246k,1.2M).cache-hit-rateshows the latest turn's cache hit rate, computed ascacheRead / (input + cacheRead + cacheWrite), matching theR/W/CHstats in pi's built-in footer. All three sit on the right of the top row by default, beforetotal-cost(which stays rightmost), and hide when the session has no cache activity or the terminal is narrower than 60 columns.git-statususes symbols for ahead / behind / diverged status.provider-statusshows provider quota windows for OpenAI Codex and Claude models as mini gauges per window, for example5h ▰▰▰▰▱ 80% ~1h12m 7d ▰▰▱▱▱ 38%. Filled cells show the used quota, growing from the left like the context bar, and each window is colored by how close it is to exhaustion. In gauge mode, a dim reset countdown belongs to the window immediately before it. The defaultresetMinUsedPercent: 75shows a countdown only when that window is at least 75% used. The defaultshowReset: "all"makes every reported window eligible; use"primary"to allow only the primary window or"off"to hide countdowns. You can change the threshold in theprovider-statuswidget's interactive settings. Set it to0to restore the previous always-show behavior. The gauge spansgaugeWidthcells and reuses the configuredgaugeStyleglyphs. SetproviderStatus.displaytotextfor the compact5h:80% ~1h12m 7d:38%form. Text mode keeps the existing provider severity color for the whole widget. Countdown placement, thresholds, and modes apply to Claude and Codex in both display styles. The widget renders only the windows that the provider reports. If Codex omits its 5-hour window and promotes the weekly window to primary, the footer removes the stale 5-hour value and shows only7d. Because that weekly window is primary, it receives a countdown under the default mode. In an output such as▱▱▱▱▱ 0% 7d ▰▰▰▰▱ 84% ~1d7h,0%is the share of pi's context window in use and84%is the used weekly Codex quota. Codex uses existing pi OpenAI Codex credentials from~/.pi/agent/auth.json, falling back to Codex CLI credentials in~/.codex/auth.json. Claude uses pi Anthropic OAuth credentials from~/.pi/agent/auth.jsonand reads Claude.ai usage for the 5-hour and weekly windows. When Claude reports a limit that applies only to the active model, such as the weekly Fable cap, that window replaces the account-wide window with the same label instead of adding a gauge, so7dshows the quota that actually limits the current model. The stricter of the two always wins, because whichever runs out first ends the session, and the value follows/modelswitches without another usage request. Status is cached under~/.cache/pi-fancy-footer/provider-status/; when a refresh fails, cached quota windows keep showing until their reset times pass instead of hiding the widget. Once a window passes its reset time it stays visible with unknown usage (—) until a refresh reports the new period, so a quota rollover neither hides the gauge nor claims unconfirmed headroom. The widget is hidden when the active model selection is not backed by the status provider.provider-statusalso refreshes fromx-codex-*provider response headers when pi exposes them, avoiding a separate Codex status request after provider calls. Claude status refreshes from the Claude.ai usage endpoint, not provider response headers.iconFamilylets you choose betweennerd,emoji,unicode, andasciipalettes.nerdkeeps the original Nerd Font look.emoji,unicode, andasciiwork better in terminals that don't use a Nerd Font.- Per-widget icon overrides only let you hide the icon. The selected
iconFamilycontrols which icon each widget uses. - Pull request widgets live in pi-prs, which owns all GitHub polling and publishes them through the data-widget protocol.
🧱 Gauge styles
The gaugeStyle setting controls the characters used by the context-bar
and provider-status gauges. All gauges fill from the left with what is
already consumed, so a nearly full gauge means a nearly exhausted resource.
Each style defines symbols for filled and empty cells:
| Style | Filled | Empty |
|---|---|---|
blocks (default) |
■ |
□ |
lines |
━ |
─ |
circles |
● |
○ |
parallelograms |
▰ |
▱ |
diamonds |
◆ |
◇ |
bars |
█ |
░ |
stars |
★ |
☆ |
specks |
• |
◦ |