<p>
  <img src="banner.png" alt="pi-statusbar" width="1100">
</p>

# pi-statusbar

Customizes the default [pi](https://github.com/badlogic/pi-mono) editor with a powerline-style status bar, welcome overlay, and built-in or AI-generated "vibes" for loading messages. Inspired by [Powerlevel10k](https://github.com/romkatv/powerlevel10k) and [oh-my-pi](https://github.com/can1357/oh-my-pi).

<img width="1261" height="817" alt="Image" src="https://github.com/user-attachments/assets/4cc43320-3fb8-4503-b857-69dffa7028f2" />

## Features

**Editor stash** — Press `Alt+S` to save your editor content and clear the editor, type a quick prompt, and your stashed text auto-restores when the agent finishes. Toggles between stash, pop, and update-existing-stash. A `stash` indicator appears in the statusbar while text is stashed.

**Working Vibes** — Themed loading messages from built-in packs (whimsical: star-wars, star-trek, etc.) or AI-generated. Set the vibe preset with `/vibe preset whimsical` for instant themed messages, or `/vibe source generated` for contextual AI vibes. Safe mode on by default, shimmer animation included.

**Welcome overlay** — Branded splash screen shown as centered overlay on startup. Shows gradient logo, model info, keyboard tips, loaded AGENTS.md/extensions/skills/templates counts, and recent sessions. Auto-dismisses after 30 seconds or on any key press.

**Rounded box design** — Status renders directly in the editor's top border, not as a separate footer.

**Fixed editor cluster** — In interactive TUI sessions, chat/feed content scrolls above the fixed Pi working/status line, statusbar rows, editor, ghost suggestions, bash transcript, and last-prompt/status rows. Scroll chat with the mouse wheel, PageUp/PageDown, Command+PageUp/PageDown, Ctrl+Shift+Up/Down, or message-jump shortcuts; the editor stays put. Drag text to copy it, drag selection to the viewport edge to scroll, double-click a line to select it, and right-click to open the terminal menu. Use `/statusbar fixed-editor off` for Pi’s regular scrolling layout, or `/statusbar mouse-scroll off` for native terminal selection.

**Live thinking level indicator** — Shows current thinking level (`think:off`, `think:med`, etc.) with per-level colors. High and xhigh levels use a rainbow effect inspired by Claude Code's ultrathink.

**Smart defaults** — Nerd Font auto-detection for iTerm, WezTerm, Kitty, Ghostty, and Alacritty with ASCII fallbacks. Colors matched to oh-my-pi's dark theme.

**Git integration** — Async status fetching with 1s cache TTL. Automatically invalidates on file writes/edits. Shows branch, staged (+), unstaged (\*), and untracked (?) counts.

**Context awareness** — Color-coded warnings at 70% (yellow) and 90% (red) context usage. During streaming, the context segment refreshes from live assistant usage instead of waiting for the next turn. Auto-compact indicator when enabled. If `pi-custom-compaction` is installed and enabled, the statusbar automatically hides native context segments so the footer does not show stale post-summary usage.

**Token intelligence** — Smart formatting (1.2k, 45M), subscription detection (shows "(sub)" vs dollar cost).

**Sticky bash mode** — Toggle bash mode with `ctrl+shift+b` or `/bash-mode`. It keeps a managed shell session alive for the current pi session, shows a dedicated `shell_mode` segment, streams command output into an embedded transcript below the editor, and lets `cd` or exported state persist across commands.

**Shell ghost suggestions** — Bash mode is now ghost-first. Successful per-project shell history is the primary source, while deterministic path and git continuations can still extend an existing command. Shell-native completion probes are disabled so `!command` predictions never spawn interactive shell completion subprocesses. At command position, short stems first resolve from the newest successful local command, can use guarded global shell history for high-confidence heads like `git`, and finally fall back to a tiny curated default set when history is absent. Right now that curated set is `g` → `git status` and `c` → `cd ..`. If the bash prompt is empty, bash mode shows the newest successful project-history ghost suggestion when one exists, otherwise it stays empty. The same inline predictions now also kick in for one-off `!command` and `!!command` prompts. Right Arrow or Tab accepts ghost text into the editor, and Enter runs the current shell command.

## Installation

```bash
pi install npm:@bumpyclock/pi-statusbar
```

Restart pi to activate.

## Usage

Activates automatically. Toggle with `/statusbar`, switch presets with `/statusbar <name>`, fixed-editor mode with `/statusbar fixed-editor on|off|toggle`, and wheel mode with `/statusbar mouse-scroll on|off|toggle`.

Fixed editor and mouse-scroll handling are both on by default.

- `/statusbar fixed-editor off` — return to Pi’s regular scrolling layout
- `/statusbar fixed-editor on` — re-enable the fixed editor
- `/statusbar fixed-editor toggle` — switch between the two
- `/statusbar mouse-scroll off` — keep fixed editor on, but let the terminal handle native selection

You can also set the defaults explicitly in `~/.pi/agent/settings.json` or project-local `.pi/settings.json`:

```json
{
  "statusbar": {
    "preset": "default",
    "fixedEditor": true,
    "mouseScroll": true
  }
}
```

Set `"fixedEditor": false` to return to Pi’s regular scrolling layout. Set `"mouseScroll": false` if you want native terminal selection instead of fixed-editor mouse handling.

| Preset    | Description                                                                                                                                                               |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `default` | Model, thinking, path (basename), git, context, tokens, cost                                                                                                              |
| `minimal` | Just path (basename), git, context                                                                                                                                        |
| `compact` | Model, git, cost, context                                                                                                                                                 |
| `full`    | Everything including hostname, time, abbreviated path                                                                                                                     |
| `nerd`    | Maximum detail for Nerd Font users                                                                                                                                        |
| `ascii`   | Safe for any terminal                                                                                                                                                     |
| `custom`  | Reserved built-in preset with selectable baseline segments (`model`, `shell_mode`, `path`, `git`, tokens/cost/context); this name cannot be used for user-defined presets |

**Environment:** `STATUSBAR_NERD_FONTS=1` to force Nerd Fonts, `=0` for ASCII.

Preset selection is saved to `~/.pi/agent/settings.json` under `statusbar` and restored on startup.
Run `/statusbar default` to switch back to the default preset.

### User-defined presets

Define named presets in `statusbar.presets` when you want custom layouts without editing `presets.ts`. The selected `statusbar.preset` can be either a built-in preset or one of your user-defined preset names. Built-in names always win on conflicts, so use names like `daily`, `work`, or `debug` instead of redefining `default`.

```json
{
  "statusbar": {
    "preset": "daily",
    "presets": {
      "daily": {
        "extends": "default",
        "leftSegments": [
          "model",
          "thinking",
          "shell_mode",
          "path",
          "git",
          "custom:ci"
        ],
        "rightSegments": ["context_pct", "cache_read", "cost"],
        "secondarySegments": ["extension_statuses"],
        "separator": "powerline-thin",
        "segmentOptions": {
          "path": { "mode": "basename" },
          "git": {
            "showBranch": true,
            "showStaged": true,
            "showUnstaged": true,
            "showUntracked": true
          }
        }
      }
    },
    "customItems": [
      {
        "id": "ci",
        "statusKey": "ci-status",
        "prefix": "CI",
        "color": "warning"
      }
    ]
  }
}
```

User preset fields:

- `extends` (optional): built-in or user preset to inherit from; defaults to `default`
- `leftSegments`, `rightSegments`, `secondarySegments` (optional): segment order arrays; omitted arrays inherit from the parent, while `[]` clears that group
- `separator` (optional): `powerline`, `powerline-thin`, `slash`, `pipe`, `block`, `none`, `ascii`, `dot`, `chevron`, or `star`
- `segmentOptions` (optional): `model.showThinkingLevel`, `path.mode`, `path.maxLength`, `git.showBranch`, `git.showStaged`, `git.showUnstaged`, `git.showUntracked`, `time.format`, and `time.showSeconds`

Available segment ids: `model`, `thinking`, `shell_mode`, `path`, `git`, `subagents`, `token_in`, `token_out`, `token_total`, `cost`, `context_pct`, `context_total`, `time_spent`, `time`, `session`, `hostname`, `cache_read`, `cache_write`, `extension_statuses`, and `custom:<id>` for a configured custom item.

`leftSegments` and `rightSegments` are concatenated left-to-right into one primary responsive flow; they are not fixed screen-left and screen-right alignment zones. `secondarySegments` are lower-priority segments that can move between rows depending on terminal width.

Custom item placement can be controlled in two places. If a preset includes `custom:<id>` in `leftSegments`, `rightSegments`, or `secondarySegments`, that preset-level placement wins. If no preset segment array references the custom item, `customItems.position` is used as the fallback placement. `customItems.position` is still supported; preset arrays are the more explicit layout control.

When merging global `~/.pi/agent/settings.json` with project-local `.pi/settings.json`, the extension settings loader merges object fields (like `statusbar.presets` and `segmentOptions`) field-by-field, but segment arrays (`leftSegments`, `rightSegments`, `secondarySegments`) from project settings completely replace the global arrays. This settings file merge is separate from preset `extends` inheritance, where omitted segment arrays inherit from the parent preset. Restart pi or start a new session after editing settings, because settings changes are read during startup/session setup.

### Custom items from extension statuses

You can promote any extension status key into its own dedicated statusbar item. This gives you a general way to register your own status items without changing this extension.

1. Any extension can publish status text through `ctx.ui.setStatus("my-key", "...value...")`.
2. Configure `statusbar.customItems` to place those keys on the left, right, or secondary row.

```json
{
  "statusbar": {
    "preset": "default",
    "customItems": [
      {
        "id": "ci",
        "statusKey": "ci-status",
        "position": "right",
        "prefix": "CI",
        "color": "warning"
      },
      {
        "id": "review",
        "position": "secondary",
        "hideWhenMissing": false,
        "prefix": "review"
      }
    ]
  }
}
```

`customItems` fields:

- `id` (required): unique item id (`a-z`, `A-Z`, `0-9`, `_`, `-`)
- `statusKey` (optional): extension status key to read, defaults to `id`
- `position` (optional): `left`, `right`, or `secondary` (default `right`)
- `prefix` (optional): text shown before the live status value
- `color` (optional): any Pi theme color (`warning`, `accent`, etc.) or hex (`#RRGGBB`)
- `hideWhenMissing` (optional): hide item when no status is present (default `true`)
- `excludeFromExtensionStatuses` (optional): omit this key from the aggregate `extension_statuses` segment (default `true`)

For shorthand config, `"statusbar": "default"` selects the default preset.

## Bash mode

Toggle bash mode with either:

- `ctrl+shift+b`
- `/bash-mode on`
- `/bash-mode off`
- `/bash-mode toggle`

Reset the managed shell with `/bash-reset`.

While bash mode is active:

- Enter runs the current shell command
- Right Arrow accepts ghost text into the editor without running it
- Tab accepts the current ghost suggestion when one exists; otherwise it does nothing
- Up and Down browse matching shell history
- `escape` exits bash mode and returns to normal prompt mode
- `ctrl+c` interrupts the active shell job before falling back to normal pi behavior

The managed shell is persistent for the current pi session. Command output appears in a transcript below the editor, and shell cwd changes are reflected in the footer path and `shell_mode` segment. If the bash prompt is empty, bash mode shows the newest successful project-history ghost suggestion immediately when one exists, including right after mode entry or after the prompt is cleared again. One-off `!command` and `!!command` prompts reuse the same shell prediction pipeline, including ghost text. Mode entry stays quiet: there is no automatic or manual dropdown completion surface, and ghost suggestions do not run shell-native completion probes.

### Bash mode configuration

In `~/.pi/agent/settings.json`:

```json
{
  "bashMode": {
    "toggleShortcut": "ctrl+shift+b",
    "transcriptMaxLines": 2000,
    "transcriptMaxBytes": 524288
  }
}
```

## Editor Stash

Use `Alt+S` / `Option+S` as a quick stash toggle while drafting. It keeps one active stash and clears the editor when stashing.

| Editor   | Stash     | `Alt+S` result                               |
| -------- | --------- | -------------------------------------------- |
| Has text | Empty     | Stash current text, clear editor             |
| Empty    | Has stash | Restore stash into editor                    |
| Has text | Has stash | Update stash with current text, clear editor |
| Empty    | Empty     | Show "Nothing to stash"                      |

Auto-restore after an agent run only happens when the editor is still empty. If you typed meanwhile, the stash is preserved.

The `stash` indicator appears in the statusbar (on presets with `extension_statuses`). Active stash is still session-local and resets on session switch / disable, but stash history is persisted to `~/.pi/agent/pi-statusbar/stash-history.json` so it survives restarts.

### Stash history

Open prompt history with either:

- `ctrl+alt+h`
- `/stash-history`

Prompt history now has two sources:

- stashed prompts — up to 12 recent stashed prompts (newest first)
- recent project prompts — up to 50 recent user-submitted prompts pulled from pi sessions in the current project folder

Selecting an entry inserts it into the editor. If the editor already has text, you can choose `Replace`, `Append`, or `Cancel`.

### Editor clipboard and chat shortcuts

- `ctrl+alt+c` — copy full editor content
- `ctrl+alt+x` — cut full editor content (copy, then clear)
- `cmd+up` — scroll the fixed-editor chat viewport up
- `cmd+down` — scroll the fixed-editor chat viewport down
- `cmd+shift+up` — move the editor cursor to the start of the first line
- `cmd+shift+down` — move the editor cursor to the end of the last line
- `ctrl+shift+u` — jump the fixed-editor chat viewport to the previous user message
- `ctrl+shift+i` — jump the fixed-editor chat viewport to the next user message
- `ctrl+alt+,` — jump the fixed-editor chat viewport to the previous LLM message
- `ctrl+alt+.` — jump the fixed-editor chat viewport to the next LLM message
- `ctrl+shift+g` — jump the fixed-editor chat viewport to the bottom
  Copy/cut actions do not modify stash state or stash history. Dragging files, folders, images, or screenshots from Finder into the custom editor inserts their path strings. Chat jumps require fixed-editor mode because they use its app-owned scroll viewport. Submitting editor text also returns that viewport to the bottom so new output stays in view.

### Shortcut configuration

You can override shortcut keys in `~/.pi/agent/settings.json`:

```json
{
  "statusbarShortcuts": {
    "stashHistory": "ctrl+alt+h",
    "copyEditor": "ctrl+alt+c",
    "cutEditor": "ctrl+alt+x",
    "jumpPreviousUserMessage": "ctrl+shift+u",
    "jumpNextUserMessage": "ctrl+shift+i",
    "jumpPreviousLlmMessage": "ctrl+alt+,",
    "jumpNextLlmMessage": "ctrl+alt+.",
    "jumpChatBottom": "ctrl+shift+g",
    "scrollChatUp": "cmd+up",
    "scrollChatDown": "cmd+down",
    "editorStart": "cmd+shift+up",
    "editorEnd": "cmd+shift+down"
  }
}
```

After changing bindings, run `/reload`. Invalid bindings, reserved key conflicts (like `Alt+S`), or duplicate conflicts automatically fall back to safe defaults. `cmd` and `command` are accepted aliases for Pi's `super` modifier for the documented Command navigation keys; unsupported Command-letter bindings such as `cmd+c` are ignored instead of matching plain text input. Some terminals, including Ghostty, bind Command+Arrow themselves; remap those terminal keys to send `\x1b[1;9A` / `\x1b[1;9B` for chat scrolling and `\x1b[1;10A` / `\x1b[1;10B` for editor-boundary navigation if you want Pi to receive them.

## Working Vibes

Transform boring "Working..." messages into themed phrases that match your style. Vibes are configured entirely under `statusbar.vibe` in settings — if you previously used the standalone `pi-whimsical` extension, it can be retired or disabled after enabling vibes here.

### Commands

Vibe presets are named bundles of packs; `whimsical` is the only built-in vibe preset and enables every built-in pack. This is separate from statusbar layout presets such as `default`, `minimal`, and `compact`. Packs are individual themed message sets such as `star-wars` or `star-trek`. Themes are freeform strings used by the AI-generated prompt; `/vibe generate <theme>` stores that theme for future generated vibes.

```bash
/vibe preset whimsical       → Activate the built-in whimsical vibe preset
/vibe pack list              → Show available packs and disabled status
/vibe pack disable star-wars → Disable a specific pack
/vibe pack enable star-wars  → Re-enable a specific pack
/vibe pack reset             → Re-enable all packs
/vibe safe on|off            → Toggle safe mode (filters unsafe messages, default: on)
/vibe animation shimmer|none → Shimmer animation on working message (default: shimmer)
/vibe source generated       → Switch to AI-generated vibes
/vibe generate star trek     → Set AI-generated vibe theme/source to "star trek"
/vibe source packs           → Switch back to built-in packs
/vibe off                    → Disable vibes entirely (back to "Working...")
/vibe                        → Show current status
```

### Configuration

In `~/.pi/agent/settings.json` under `statusbar.vibe`:

```json
{
  "statusbar": {
    "vibe": {
      "source": "packs",
      "disabledPacks": ["star-wars"],
      "safeMode": true,
      "animation": "shimmer",
      "generated": {
        "model": "openai-codex/gpt-5.4-mini",
        "refreshInterval": 30,
        "prompt": "Generate a 2-4 word {theme} themed loading message for: {task}"
      }
    }
  }
}
```

### Sources

| Source      | Description                    | Pros                              | Cons                             |
| ----------- | ------------------------------ | --------------------------------- | -------------------------------- |
| `packs`     | Built-in curated message packs | Instant, zero cost, works offline | Not contextual                   |
| `generated` | On-demand AI generation        | Contextual, hints at actual task  | Model-dependent cost and latency |

**Pack source** (`source: "packs"`) uses curated message sets, each with a distinct theme (star-wars, star-trek, etc.). Messages tagged `unsafe` are filtered when safe mode is on (the default). A message is tagged or treated as unsafe when the shared profanity matcher catches obvious or obfuscated profanity (`fuck`, `f***ing`, `shit`, `bitch`, `bullshit`, `dick`, `goddamn`). Tags are applied automatically at pack definition time and checked again at runtime; safe mode removes those messages from the pick pool. Disable individual packs with `/vibe pack disable <id>`, re-enable one with `/vibe pack enable <id>`, or re-enable all with `/vibe pack reset`.

**Generated source** calls an AI model on each refresh. Prompt template variables:

- `{theme}` — the default generated theme placeholder; `/vibe generate <theme>` writes the chosen theme into the prompt (e.g., "star trek")
- `{task}` — context hint (user prompt, then agent's response text or tool info on refresh)
- `{exclude}` — recent vibes to avoid (auto-populated)

**How it works:**

1. When you send a message, a themed message appears
2. **If `source` is `"packs"`:** picks from pre-built messages; shimmer is applied when `animation` is `"shimmer"`
3. **If `source` is `"generated"`:** calls AI in the background with a fixed 3s timeout
4. **If `source` is `"generated"`:** during long tasks, refreshes on tool calls (rate-limited by `generated.refreshInterval`, default 30s)
5. **If `source` is `"generated"`:** cost and latency depend on the configured `generated.model`

## Thinking Level Display

The thinking segment shows live updates when you change thinking level:

| Level   | Display       | Color       |
| ------- | ------------- | ----------- |
| off     | `think:off`   | gray        |
| minimal | `think:min`   | purple-gray |
| low     | `think:low`   | blue        |
| medium  | `think:med`   | teal        |
| high    | `think:high`  | rainbow     |
| xhigh   | `think:xhigh` | rainbow     |

## Path Display

The path segment supports three modes:

| Mode          | Example                               | Description                                      |
| ------------- | ------------------------------------- | ------------------------------------------------ |
| `basename`    | `pi-statusbar`                        | Just the directory name (default)                |
| `abbreviated` | `…/extensions/pi-statusbar`           | Full path with home abbreviated and length limit |
| `full`        | `~/.pi/agent/extensions/pi-statusbar` | Complete path with home abbreviated              |

Configure via preset options: `path: { mode: "full" }`

## Segments

`model` · `thinking` · `shell_mode` · `path` · `git` · `subagents` · `token_in` · `token_out` · `token_total` · `cost` · `context_pct` · `context_total` · `time_spent` · `time` · `session` · `hostname` · `cache_read` · `cache_write` · `extension_statuses`

## Separators

`powerline` · `powerline-thin` · `slash` · `pipe` · `dot` · `chevron` · `star` · `block` · `none` · `ascii`

## Theming

Colors are configurable via pi's theme system. Each preset defines its own color scheme, and you can override individual colors and icons with a `theme.json` file in the extension directory.

### Default Colors

| Semantic          | Theme Color       | Description                |
| ----------------- | ----------------- | -------------------------- |
| `model`           | `#d787af`         | Model name                 |
| `shellMode`       | `accent`          | Bash mode segment          |
| `path`            | `#00afaf`         | Directory path             |
| `gitClean`        | `success`         | Git branch (clean)         |
| `gitDirty`        | `warning`         | Git branch (dirty)         |
| `thinking`        | `thinkingOff`     | Thinking level (`off`)     |
| `thinkingMinimal` | `thinkingMinimal` | Thinking level (`minimal`) |
| `thinkingLow`     | `thinkingLow`     | Thinking level (`low`)     |
| `thinkingMedium`  | `thinkingMedium`  | Thinking level (`medium`)  |
| `context`         | `dim`             | Context usage              |
| `contextWarn`     | `warning`         | Context usage >70%         |
| `contextError`    | `error`           | Context usage >90%         |
| `cost`            | `text`            | Cost display               |
| `tokens`          | `muted`           | Token counts               |

### Custom Theme Override

Create `~/.pi/agent/extensions/pi-statusbar/theme.json`:

```json
{
  "colors": {
    "pi": "#ff5500",
    "model": "accent",
    "shellMode": "accent",
    "path": "#00afaf",
    "gitClean": "success",
    "thinking": "thinkingOff",
    "thinkingMinimal": "thinkingMinimal",
    "thinkingLow": "thinkingLow",
    "thinkingMedium": "thinkingMedium"
  },
  "icons": {
    "auto": "↯",
    "warning": ""
  }
}
```

Colors can be:

- **Theme color names**: `accent`, `muted`, `dim`, `text`, `success`, `warning`, `error`, `border`, `borderAccent`, `borderMuted`
- **Hex colors**: `#ff5500`, `#d787af`

Icons can be any string, including `""` when you want to suppress a specific glyph entirely.

See `theme.example.json` for all available options.
