Two directories documented one concept (pi npm packages): top-level packages/ (browser-use, permission-system) and pi/packages/ (peon, web-access, subagents). Merged into pi/packages/ — one row per package in a single index, detail pages kept as archive records. The root index's 'OMP packages and skills' section was doubly wrong (packages/ was pi, not OMP); it becomes a Skills tooling section pointing at the consolidated /skills/ tree. Moved the two LIVE omp pages out of the retired-harness banner: pi/statusline.md -> core/statusline.md, pi/subagent-limits.md -> core/subagent-limits.md (both document omp behavior against agent/config.yml + the installed bundle; pi/index.md itself admitted the mis-shelf). Retargeted every inbound link (core/index, system/ dankserver, log x2, pi/index, settings x2, moved files' own xrefs). Refs #29
200 lines
12 KiB
Markdown
200 lines
12 KiB
Markdown
---
|
||
type: reference
|
||
title: OMP Status Line Customization
|
||
description: How the omp status line actually works — presets vs custom arrays, composer-shape segment routing, the context meter (box/band only), the 27-segment catalog, and the fleet's tuned layout.
|
||
tags:
|
||
- omp
|
||
- tui
|
||
- statusline
|
||
- config
|
||
timestamp: 2026-10-18
|
||
---
|
||
|
||
# OMP Status Line Customization
|
||
|
||
Ground truth from the installed bundle (`@oh-my-pi/pi-coding-agent/dist/cli.js`),
|
||
verified live on blankpad 2026-10-18 and re-audited line-by-line against the
|
||
minified source the same day (see [Audit log](#audit-log)). Status knobs live
|
||
in `agent/config.yml` under `statusLine:`; the two composer knobs
|
||
(`composer.shape`, `composer.tokenRate`) under `composer:`. The file is
|
||
file-watched — edits repaint a running session without restart (verify
|
||
visually: niri screenshot, see [niri capture recipe](../system/index.md)).
|
||
`/settings` (Appearance tab) previews these live with sample data.
|
||
|
||
## Preset vs custom — the trap
|
||
|
||
`statusLine.preset` ships whole configurations. **`leftSegments` /
|
||
`rightSegments` are ignored unless `preset: custom`** (`s ? user ?? preset
|
||
: preset` in the resolver). **`segmentOptions` is NOT gated** — user options
|
||
merge over the preset's own options under *every* preset.
|
||
|
||
| preset | leftSegments | rightSegments | separator |
|
||
|---|---|---|---|
|
||
| `default` | pi vim model mode collab stream path git pr context_pct cost | session_name | powerline-thin |
|
||
| `minimal` | vim path git | session_name mode context_pct | slash |
|
||
| `compact` | vim model mode git pr | session_name cost context_pct | powerline-thin |
|
||
| `full` | pi vim hostname model mode path git pr subagents | session_name cache_hit token_in token_out token_rate cache_read cost context_pct time_spent time | powerline |
|
||
| `nerd` | pi vim hostname model mode path git pr session subagents | session_name token_in token_out cache_read cache_write token_rate cost context_pct context_total time_spent time | powerline |
|
||
| `ascii` | vim model mode path git pr | session_name token_total cost context_pct | ascii |
|
||
| `custom` | your `statusLine.leftSegments` | your `statusLine.rightSegments` | powerline-thin |
|
||
|
||
The built-in default for `statusLine.leftSegments`/`rightSegments` themselves
|
||
is the `ascii`-era pair (`vim model mode path git pr` / `session_name
|
||
token_total cost context_pct`) — a config carrying those arrays with a named
|
||
preset is running the preset, not the arrays. The fleet runs `preset: custom`.
|
||
|
||
## Composer shape routes the segments
|
||
|
||
`composer.shape` decides *where* the two arrays render. Eight shapes ship in
|
||
the bundle; the plugin API can register more (`zG()` = built-ins + a live
|
||
registration map). Internally each shape carries `statusAttachment`,
|
||
`bottomBar`, `bottomBarGap`:
|
||
|
||
| shape | attach | bottom bar | leftSegments | rightSegments |
|
||
|---|---|---|---|---|
|
||
| `band` (fleet, default) | top-band | none | merged single row above editor, left | same row, right (`session_name` skipped — title rides the working row) |
|
||
| `box` | top-border | none | box top border | box top border |
|
||
| `claude` | top-rule-chip | left | bottom strip, left | top rule chip, right-aligned |
|
||
| `rule` | top-rule-chip | left + gap | like claude, gap above strip | like claude |
|
||
| `pi` | none | full | single bottom bar | single bottom bar |
|
||
| `borderless` | none | full | single bottom bar | single bottom bar |
|
||
| `field` / `rail` | none | full + gap (filled surface) | single bottom bar | single bottom bar |
|
||
|
||
The chip/strip layouts paint their rules as **session-accent fillers, not
|
||
meters** — the context meter exists only in `box` and `band`
|
||
(see next section). The top-rule chip renders right segments only, the bottom
|
||
strip left segments only.
|
||
|
||
The working row is composer chrome, not segment-addressable: session title +
|
||
shimmer, plus the tok/s readout when `composer.tokenRate: true` (default
|
||
false). The readout is estimated from streamed deltas, corrected by billed
|
||
output counts; the bundle also mentions a native-composer-bar placement in
|
||
filled styles.
|
||
|
||
## The context meter
|
||
|
||
`statusLine.contextLine` (enum `off` / `percentage` / `annotated` /
|
||
`embedded`, default `embedded`) tunes the rule between the segment groups.
|
||
The knob description says "box composer only" — **that is wrong**: the meter
|
||
renders in `box` **and** `band`; in every other shape the rule is a constant
|
||
session-accent filler regardless of this setting (live-verified claude with
|
||
`off` vs `embedded`: identical, and not context-proportional).
|
||
|
||
In box/band:
|
||
|
||
- `off` → plain accent rule (single color, full width).
|
||
- `embedded` → the inline labeled meter: `60% ——— × 200K`. Requires
|
||
`contextWindow > 0`, a `context_pct`/`context_total` segment present in
|
||
either array, and at least one non-context segment. The context segment(s)
|
||
are **hoisted out of the arrays** — they never render as text in this mode.
|
||
- `percentage` / `annotated` → unlabeled proportional meter painted into the
|
||
rule (live-verified in band: colored fill ∝ usage at the rule's right end);
|
||
`context_pct` stays a normal text segment.
|
||
|
||
Fill length ∝ `min(100, contextPercent)`; >100% paints the raw percent and
|
||
shortens the meter. Meter/segment color thresholds scale with window size —
|
||
warning at ≥ min(50 %, 150K tokens), purple ≥ min(70 %, 270K), error ≥
|
||
min(90 %, 500K); below that the theme's `statusLineContext` color.
|
||
|
||
## Segment universe (27)
|
||
|
||
`pi`, `status`, `model`, `mode`, `path`, `git`, `pr`, `subagents`,
|
||
`token_in`, `token_out`, `token_total`, `token_rate`, `cost`, `context_pct`,
|
||
`context_total`, `time_spent`, `time`, `session`, `hostname`, `cache_read`,
|
||
`cache_write`, `cache_hit`, `session_name`, `usage`, `collab`, `stream`, `vim`.
|
||
|
||
Zero/inactive segments render nothing — dead weight costs no width.
|
||
|
||
| segment | renders | trigger / notes |
|
||
|---|---|---|
|
||
| `pi` | brand glyph + turn-elapsed | while streaming; focused-agent ghost id (warning color) when a subagent is focused |
|
||
| `status` | hook/extension statuses, dot-joined | extension pushes via `ctx.ui.setStatus()` (ordered by key); suppressed in footer when `statusLine.showHookStatus: false` |
|
||
| `model` | display `name` + thinking level | suffix hidden by `segmentOptions.model.showThinkingLevel: false`; `statusLine.compactThinkingLevel: true` (default) swaps in a compact glyph; fast-mode, advisor/tariff badges attach here |
|
||
| `mode` | `Plan` / `Prewalk` / `Goal` / `Vibe` / `Loop …` | only in those modes (paused variants add a pause glyph); there is **no yolo/auto-approval display** in this segment |
|
||
| `path` | cwd or `project/worktree` label | see [Path segment](#path-segment-and-stripworkprefix) |
|
||
| `git` | branch + dirty badges | `+N` staged, `*N` unstaged, `?N` untracked (`segmentOptions.git.showStaged/showUnstaged/showUntracked`, default true); `showBranch: false` hides branch text and keeps badges (git icon carries them); color flips on dirty |
|
||
| `pr` | `#<number>` hyperlink | only once the session has an associated PR |
|
||
| `subagents` | count | hidden at 0; when subagents are actively running the composer paints its own live badge and this segment self-suppresses |
|
||
| `token_in` / `token_out` | cumulative session input/output tokens | hidden at 0; `cache_read` / `cache_write` likewise cumulative |
|
||
| `token_total` | `input + output + cache_write + orchestration_in + orchestration_out` | hidden at 0 |
|
||
| `token_rate` | `N.N tok/s` | hidden when not streaming (the working-row readout is a separate, `composer.tokenRate`-gated thing) |
|
||
| `cost` | session cost | includes subagent-tree + advisor cost; `↑`/`↓` peak/off-peak arrow for time-priced providers |
|
||
| `context_pct` | `NN.N%/200K` (or window alone when percent is unknown) | hoisted into the meter in `embedded` box/band; auto-compact speculation icon rides along |
|
||
| `context_total` | absolute context tokens | also hoisted by the embedded meter |
|
||
| `time_spent` | active session time | hidden under 1 s; this is *active* time, not wall clock |
|
||
| `time` | wall clock | `segmentOptions.time.format: "12h"/"24h"`, `showSeconds` |
|
||
| `session` | first 8 chars of session id (`new` before resume exists) | resume-by-id handle |
|
||
| `session_name` | session title, accent | in `band` it renders on the working row instead |
|
||
| `hostname` | short hostname | accent-tinted with session color |
|
||
| `usage` | rate-limit windows | 5h / daily / 7d / monthly bars + tier badge, when the provider reports them |
|
||
| `collab` | `collab:N` / `collab guest:N` | only in collab sessions |
|
||
| `stream` | `● LIVE <viewers>` / `● REC` | only while streaming/recording |
|
||
| `vim` | mode glyph/word | only while vim keybindings are active; selected-line count appended (` 3L`); `display` icon vs text comes from the vim runtime, not config |
|
||
|
||
## Path segment and `stripWorkPrefix`
|
||
|
||
`segmentOptions.path.stripWorkPrefix` (default **true** — the code tests
|
||
`!== false`) gates two behaviors in the `path` renderer:
|
||
|
||
1. **Worktree sessions:** `true` never shows the disk path — it renders the
|
||
logical `projectName/worktreeName` label (bare `projectName` when the
|
||
current branch is named after the worktree) with the worktree glyph.
|
||
`false` opts out and shows the real cwd with a folder glyph, plus a
|
||
` ↳ relativeRepoRoot` suffix inside a repo. Base-directory-only is not
|
||
renderable in either mode.
|
||
2. **All sessions:** `true` uses the pretty display path (scratch sessions
|
||
get the scratch-folder icon); `false` uses the raw absolute cwd.
|
||
|
||
`abbreviate` (default **true**: home → `~`; `false` keeps the full path —
|
||
also affects tool labels/errors per upstream docs) and `maxLength` (clip,
|
||
default 40) apply afterwards in both modes. The fleet runs `true` — logical
|
||
label, worktree glyph (verified live 2026-10-18; an earlier trial of `false`
|
||
for the full cwd was rejected by eye).
|
||
|
||
## Status knobs (registry-verified)
|
||
|
||
`statusLine.preset` · `.leftSegments` · `.rightSegments` · `.segmentOptions` ·
|
||
`.separator` (`powerline` `powerline-thin` `slash` `pipe` `block` `none`
|
||
`ascii`; default `powerline-thin`) · `.contextLine` · `.sessionAccent`
|
||
(default true; hashes the session name into the editor border *and* the
|
||
accent-tinted segments/meter color) · `.transparent` (terminal background,
|
||
no status bg fill) · `.showHookStatus` · `.compactThinkingLevel` (default
|
||
true). Plus `symbolPreset` at top level (`unicode` `nerd` `ascii` — segment
|
||
glyphs follow it).
|
||
|
||
## Fleet configuration (2026-10-18)
|
||
|
||
- Shape `band`: one merged status row. Left group: `session_name`→working
|
||
row (skipped), `vim`, `mode`, `hostname`, `path`, `git` (no branch text —
|
||
worktree branches are autogenerated names), `pr`. Right group (array order
|
||
= paint order, so the last entry hugs the screen edge): `model`, `cost`,
|
||
`context_pct`, `time_spent` — `context_pct` hoists out of the run to paint
|
||
the labeled inline meter (`60% ——— × 200K`) between the groups under
|
||
`contextLine: embedded`.
|
||
- Deliberately cut: `token_rate` (working-row readout covers it), `time`
|
||
(panel clock), `cache_*` (noise), `context_total` (implied by the meter),
|
||
`subagents` (live badge supersedes it), `session`, `token_in`/`token_out`
|
||
(cumulative totals are in `/session` anyway).
|
||
- `models.yml`: frank-archon's display `name` slimmed to `"frank-archon"`
|
||
(endpoint suffix dropped); frank-dragoon/frank-zealot keep their endpoint
|
||
suffixes. `name:` is free-form display, `id:` is the wiring — renaming ids
|
||
is the cutover procedure, see [Pi Models](/pi/models.md).
|
||
|
||
## Audit log
|
||
|
||
2026-10-18 full-source re-audit corrected the first pass of this page:
|
||
|
||
- ~~claude shape's top rule is the context meter~~ — **false**. The
|
||
meter code path runs only for `box`/`band`; claude/rule/pi/borderless/
|
||
field/rail rules are constant session-accent fillers. (Earlier reading
|
||
mistook the accent rule for a meter.)
|
||
- ~~working-row composer-bar variant is "Tern-only"~~ — **false**; the
|
||
tok/s readout is the `composer.tokenRate` option (default false), title
|
||
placement is composer chrome.
|
||
- ~~`segmentOptions` gated to `preset: custom`~~ — **false**; options merge
|
||
under every preset, only the segment *arrays* are gated.
|
||
- ~~`mode` covers yolo~~ — **false**; states are Plan/Prewalk/Goal/Vibe/Loop.
|
||
- `contextLine` "(box composer only)" — the *bundle description* is wrong;
|
||
band honors it too (verified live: `off` plain rule, `percentage` rule
|
||
meter, `embedded` inline meter, screenshots + code).
|