@geohar/pi-oh-my-posh
Render pi coding agent's footer with Oh My Posh — reuse your shell prompt's theme (powerline, palette, glyphs) plus pi context/model/tokens and other extensions' status chips.
Package details
Install @geohar/pi-oh-my-posh from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@geohar/pi-oh-my-posh- Package
@geohar/pi-oh-my-posh- Version
0.6.1- Published
- Aug 31, 2026
- Downloads
- 1,073/mo · 22/wk
- Author
- georgeharker
- License
- MIT
- Types
- extension
- Size
- 50.3 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./pi-oh-my-posh.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-oh-my-posh
Render pi's footer with Oh My Posh, so pi wears the same theme as your shell prompt — powerline separators, palette, Nerd Font glyphs — with pi's own context/model/token data mixed in.
How it works
Oh My Posh is just template data in → ANSI out. This extension does no styling of its own. On each relevant pi event it:
- reads pi session state off
ctx— model, context-window %, tokens, thinking level; - exposes them to Oh My Posh as environment variables (
.Env.PI_*) and precomputes the▰▱context gauge; - shells
oh-my-posh print primary --config <file> --shell generic --escape=false --pwd <cwd> -w <width>and caches the ANSI lines; - returns those lines from pi's footer
Component(raw ANSI is allowed per line).
Native path / git segments in the config render authentic Oh My Posh output for free
(OMP runs git itself against --pwd). The extension only supplies the pi-specific data.
Install
./install.sh # symlinks pi-oh-my-posh.ts into ~/.pi/agent/extensions/
Requires oh-my-posh on PATH (brew install oh-my-posh, etc.). Start pi — the footer
renders through Oh My Posh.
Commands
/oh-my-posh— open the settings editor: an interactive list to toggle the footer on/off, show/hide each live status chip (writesstatusToggles), and set the pi-lens label / icon mode. Changes persist tooh-my-posh.json; footer and chip toggles apply live, the rest note a reload. (With no interactive TUI it falls back to a plain footer on/off toggle.) Each live chip row's description also shows its.Env.PI_STATUS_<KEY>template var for theme authoring.
The .Env.PI_* variables it exports
| Variable | Meaning |
|---|---|
PI_MODEL |
active model name/id |
PI_PROVIDER |
provider id (when available) |
PI_THINKING |
thinking level (omitted when off) |
PI_CTX_PERCENT |
context-window used, integer percent |
PI_CTX_GAUGE |
precomputed ▰▱ bar (width = gaugeWidth setting) |
PI_TOKENS |
current context tokens, humanized (1.5K, 2.3M) |
PI_CTX_WINDOW |
context-window size, humanized |
PI_STATUS |
selected statuses from other extensions, joined |
PI_STATUS_COUNT |
number of selected statuses |
PI_STATUS_<KEY> |
one var per publisher (e.g. PI_STATUS_REMOTE_PI_SESSION) |
Surfacing other extensions' footer items
Extensions like remote-pi publish footer chips via ctx.ui.setStatus(key, text) — pi
collects them in footerData.getExtensionStatuses(). Because our footer replaces pi's, we
read that same map and re-expose it:
- Aggregated:
.Env.PI_STATUSis every selected status joined (the default theme puts it in the last segment). remote-pi's📡 backend (1) 🟢 relay 📱 iphoneshows up here. - Individual: each publisher also gets
.Env.PI_STATUS_<KEY>, so you can place them in separate segments — e.g.{{ .Env.PI_STATUS_REMOTE_PI_RELAY }}in its own pill. - Select which: the
statussetting ="all"(default) ·"none"· or a comma list / array of exact keys in the order you want, e.g."status": "remote-pi:relay,remote-pi:session".statusSeparatorsets the join separator (default two spaces). - Show/hide per key:
statusToggles, e.g.{ "*": true, "pi-lens": false }("*"is the default for unlisted keys) — fully drops a chip, mirroring zentui's toggles. - Discover keys: open
/oh-my-posh— each live chip row shows its.Env.PI_STATUS_<KEY>template var. (The var name is deterministic:PI_STATUS_+ the key uppercased with each run of non-alphanumerics collapsed to_.)
ANSI in status text is stripped so your theme controls the color. The footer refreshes automatically when a status changes.
pi-lens chips. Two different projects call themselves pi-lens; both are handled:
- Unscoped
pi-lens(v4+, ast-grep/LSP) publishes keypi-lens-lspwith human text (LSP Inactive,sym · sym). It's decorated into a recognizable magnifier chip —LSP Inactive— optionally prefixed with a label (lensLabel). @harms-haus/pi-lens(prettier/linters/tsc) publishes keypi-lensas JSON ({"prettier":"clean",...}). Raw that's an ugly blob; it's decoded intop l s t(each a state glyph: clean · issues · error · skipped · pending · running), hidden until a check has run.
Set the label with lensLabel in the config (true → lens, or a custom word). Any
other extension's status passes through unchanged.
Emoji → Nerd Font. Publishers embed emoji directly (remote-pi uses 📡 🟢 🟡 📱). By default those are remapped to monochrome Nerd Font glyphs so the footer stays consistent with a powerline theme — state that emoji encode via color (🟢 on / 🟡 waiting) is kept via glyph shape (filled vs hollow circle), since a themed segment paints one color.
icons setting |
Meaning |
|---|---|
"none" |
keep raw emoji, no remap |
{ "📡": "", "🟢": "" } |
override/add mappings (merged over the defaults) |
Defaults: 📡→ (wifi) 🟢→ 🔴→ (filled) 🟡→ ⚪→ (hollow) 📱→ (mobile)
🔌→ (plug) ⚡→ (bolt). Edit DEFAULT_ICONS in the extension to change the built-ins.
Configuration
Settings live in a JSON file in your pi config dir — no env vars needed. The first
file to define a key wins, checked in this order (the extensions/ subdir over the
legacy root path, and project-local over global):
./.pi/extensions/oh-my-posh.json(project-local; preferred),./.pi/oh-my-posh.json(legacy project-local),$PI_CODING_AGENT_DIR/extensions/oh-my-posh.json(global; preferred),$PI_CODING_AGENT_DIR/oh-my-posh.json(legacy global; default~/.pi/agent/…).
Every field is optional (see oh-my-posh.example.json):
| Field | Default | Purpose |
|---|---|---|
config |
auto (see below) | omp theme path (~ ok); omit to auto-detect |
bin |
oh-my-posh |
Oh My Posh binary |
prompt |
primary |
which OMP prompt to print |
gaugeWidth |
10 |
cells in the ▰▱ gauge |
gaugeMarked / gaugeUnmarked |
▰ / ▱ |
gauge cell glyphs |
status |
"all" |
"all" · "none" · "k1,k2" · ["k1","k2"] — which chips feed the joined PI_STATUS |
statusToggles |
{} |
per-key show/hide, e.g. { "*": true, "pi-lens": false }; "*" is the default for unlisted keys |
statusSeparator |
" " |
join between chips |
icons |
"default" |
"default" · "none" · { "📡": "" } — emoji→glyph remap |
lensLabel |
false |
false · true (→ lens) · "word" — pi-lens chip label |
Example (remote-pi remap on, pi-lens chip labeled):
{ "icons": "default", "lensLabel": true }
Using it with a host TUI (zentui, etc.)
Oh My Posh owns pi's footer and draws the whole thing — model, context, git, tokens, and the other-extension status chips (with the emoji→glyph remap). It grabs the single footer slot with a deferred double-assert, so it wins even against a full-TUI extension that installs its own footer on startup.
For that to be stable alongside zentui, set zentui's footer to native
(components.footer.style: "native"). In native mode zentui never touches the footer slot, so
Oh My Posh owns it uncontested while zentui keeps its editor, user-message, working-line and
selector styling. (zentui's starship footer would fight for the slot and re-assert on
/zentui changes.)
Status presence. Because native mode also hides zentui's own extension-status controls,
Oh My Posh keeps its own per-key toggles — statusToggles — mirroring what zentui offers:
{ "statusToggles": { "*": true, "pi-lens": false } }
A key's own value wins; "*" is the default for unlisted keys; omit it and every chip shows.
This governs which ctx.ui.setStatus(...) chips Oh My Posh renders whether or not zentui is
installed — so it works standalone too. (Restart to re-read after editing.)
Config resolution
The config is auto-detected, first match wins:
- the
configsetting if set (explicit override,~-expanded); - a
pi.toml/pi.omp.toml/pi.json/pi.omp.json/pi.yaml/pi.omp.yamlin your Oh My Posh config dir ($XDG_CONFIG_HOME/oh-my-posh,~/.config/oh-my-posh, or the dir of$POSH_THEMES_PATH) — drop api.tomlnext to yourtheme.tomland it's used; - the bundled
pi.omp.toml(present only if you generated a matched one locally); - the bundled
pi.omp.json(generic default).
Matching your existing theme (composition)
Oh My Posh's --config loads one whole theme — there is no include/overlay, so this
cannot auto-merge with the theme that drives your shell. Two modes instead:
Standalone (default). Edit
pi.omp.json— swap the hex colors and glyphs for your palette. Separate file, hand-matched.Compose into your own theme. Copy the three segments from
pi-block.snippet.jsoninto a block in your.omp.json(keep your colors/separators), then point the extension at it via theconfigsetting:{ "config": "~/mytheme.omp.json" }One file, your palette + pi data. Each pi segment self-hides when its data is absent.
Note: Oh My Posh has no include/extends — a config can't reference a base theme. So if you want a footer that shows only pi's segments (not your whole shell prompt), you need a separate, trimmed config; you can't overlay onto your live theme.
Bundled pi.omp.toml (matched to a Tokyo Night theme.toml)
pi.omp.toml is a standalone footer built to match an existing
~/.config/oh-my-posh/theme.toml: its [palette] and path/git segments are copied
verbatim (same colors, separators, folder hyperlink, upstream URL, branch status), then the
pi segments are added in the same palette (p:yellow model, p:blue context with
p:orange/p:red thresholds, p:black tokens, p:grey status).
Row 1 carries path/git + model/context/tokens; other-extension status chips (remote-pi) sit on a second row that collapses away when there are none. It's picked up automatically (step 3 above) — no env needed. To keep it with your dotfiles
instead, copy it to ~/.config/oh-my-posh/pi.toml and it wins (step 2). Regenerate it after
changing your theme's palette:
node scripts/build-matched-toml.js # re-copies palette + path/git from your theme.toml
Composition with pi's UI and other extensions
- pi's footer is a single, last-writer-wins slot (
ctx.ui.setFooter). This extension replaces the footer; it does not stack with another footer extension. - It works alongside pi-open-tui: that overhaul installs its footer once at
session_startand never re-asserts, so this extension defers its ownsetFooterto take the slot deterministically. You keep pi-open-tui's header, editor, and message boxes; only its Starship-style footer is replaced by the Oh My Posh one. (Colors of the header vs. footer will differ — they're different theming systems.) - It ignores pi's color theme by design — the footer's colors come from your Oh My Posh theme, not pi's palette.
- If you'd rather keep another extension's footer and just inject pi data, that would use
ctx.ui.setStatus(additive) instead ofsetFooter— not what this extension does, since it targets the full Oh My Posh look.
Notes / limits
- Cost isn't shown: pi's
getContextUsage()exposes tokens/percent/window but not a running USD cost, so there's no reliable number to render yet. - The footer refreshes on
session_start,model_select,before_agent_start,agent_settled, git-branch changes, and a ~1.2s poll while a turn streams (so the gauge grows live). Each refresh is oneoh-my-poshsubprocess.