A pi coding-agent extension that customizes the editor cursor and makes it focus-aware across terminal/multiplexer stacks.
pi draws its own fake cursor (a reverse-video cell) and hides the real hardware cursor. That fake cursor has no "inactive" state and no configurability. @getpipher/cursor replaces the editor with a CustomEditor that render-transforms the cursor cell per your config and restyles it when the pane loses focus.
pi install npm:@getpipher/cursorThen /reload (or restart pi) and run /cursor to open the settings panel.
- Focused styles:
block(pi native, default),bar(▎),underline. - Unfocused styles:
hollow(□ sharp hollow block, default),outline(▢ rounded),dim(faint block),underline,hide,highlight(char-preserving colored undercurl). - Cursor color (v0.2.0):
accent(follows the pi theme, truecolor) or an explicit#RRGGBB. - Cursor mode (v0.2.0):
fake(pi's fake cursor, default) orhardware(native terminal cursor via DECSCUSR + OSC 12 — Ghostty-targeted). - Blink (opt-in, default off) — pauses when the pane is unfocused. In
hardwaremode, native DECSCUSR blink replaces the fake blink. - Focus detection via a pluggable
FocusProvider:- tmux — pane-focus-in/out hooks +
fs.watch(push). - cmux — cmux v2 socket API
debug.terminal.is_focusedRPC (poll). - herdr — herdr socket API events (push).
- static — always-focused fallback (bare terminal / unknown).
- tmux — pane-focus-in/out hooks +
/cursorpanel — the sameSettingsListengine pi's native/settingsuses, plus power-user subcommands. Pressrin the panel to reset to defaults.
/cursor # open the settings panel (TUI) or print status
/cursor on | off # master switch
/cursor focused block|bar|underline
/cursor unfocused dim|hollow|outline|underline|hide|highlight
/cursor blink on [ms] | off # ms ∈ {400,500,600,800,1000}
/cursor provider auto|tmux|cmux|herdr|static
/cursor color accent|#RRGGBB
/cursor mode fake|hardware
/cursor status # print config + active provider + detected env
/cursor reset # reset to defaults (also: `r` in the panel)
Config persists to ~/.pi/agent/cursor.json and survives restarts. It's live-global: a change in one pi session writes the file and propagates to all other running pi sessions immediately (via a file watcher) — set it once in any pane, every running pane updates its cursor live.
Uses tmux set-hook -p -t $TMUX_PANE pane-focus-in/out to write focus state to a per-pane file, watched via fs.watch. Requires focus-events enabled in your tmux config:
# ~/.tmux.conf
set -g focus-events onIf focus-events is off, the extension degrades gracefully to static mode and notifies you once.
Uses cmux's v2 socket API to call the purpose-built debug.terminal.is_focused RPC with our surface id (CMUX_SURFACE_ID), polled every ~300 ms. The server authoritatively resolves the full window→workspace→pane→surface focus hierarchy, so the client never reconstructs it. Auto-detected when CMUX_SURFACE_ID is present and the cmux control socket is reachable (CMUX_SOCKET_PATH, or ~/Library/Application Support/cmux/last-socket-path, or /tmp/cmux-debug.sock, or ~/Library/Application Support/cmux/cmux.sock, or /tmp/cmux.sock). Precedence: tmux > cmux > herdr > static.
⚠️ v0.1.1 cmux adapter is built frommanaflow-ai/cmuxsource (tests_v2/cmux.py,docs/events.md,docs/cli-contract.md) and unit-tested against a mocked RPC, but not verified against a live cmux session. The wire envelope, thedebug.terminal.is_focusedmethod + params + result shape, and the socket-path resolution order are confirmed against the Python client source (not just prose docs). The one assumption: thatCMUX_SURFACE_IDis always injected into terminal surfaces (cli-contract.md states it is the "Default surface context inside cmux terminals"). Debug-build glob socket discovery (/tmp/cmux-debug-*.sock,cmux*.sock) is not implemented in v0.1.1. Seelib/focus/cmux.ts.
Uses herdr's local socket API (session.snapshot + events.subscribe); auto-detected when the herdr socket is present (HERDR_SOCKET_PATH, HERDR_SESSION, or ~/.config/herdr/herdr.sock).
⚠️ v0.1 herdr adapter is built fromherdr.dev/docs/socket-apiand unit-tested against a mocked socket, but not verified against a live herdr session. Three constants (the our-own-pane-id env var, theevents.subscribeevent names, and the focus-event field name) are documented assumptions — confirm/adjust them in a real herdr pane withenv | grep -i herdrandherdr api schema --json. Seelib/focus/herdr.ts.
No multiplexer detected (bare Ghostty/Kitty/iTerm2/Alacritty, or unknown). The cursor styles still work; only the focus-aware behavior is inactive.
Bare-terminal focus detection (DEC 1004) is not in v0.1 — pi 0.80.x doesn't enable
?1004hor parse\x1b[I/\x1b[O. Tracked for a later release.
/cursor color accent|#RRGGBB. accent (default) resolves the cursor color from pi's loaded theme via theme.getFgAnsi("accent") — truecolor end-to-end through tmux (Tc) on capable stacks (Ghostty + tmux). An explicit #RRGGBB override emits \x1b[38;2;R;G;Bm directly. The unfocused color is derived automatically: accent → the theme's dim color; a hex → the same hex dimmed 50%.
/cursor mode fake|hardware (default fake).
fake— pi's render-transformed fake cursor (the v0.1 path). All focused + unfocused styles apply.hardware— drives the terminal's native cursor for the focused state: DECSCUSR (\x1b[<n> q) sets the shape (block/underline/bar), OSC 12 (\x1b]12;<color>\x07) sets the color, and pi positions the real cursor viatui.setShowHardwareCursor(true). The focused cell renders as a bare char so only the native cursor shows. On focus loss the hardware cursor is hidden and the unfocused fake-cursor transform takes over (with the cursor color + any unfocused style incl.highlight). Onsession_shutdownthe terminal cursor is restored to its default shape/color (no state leak).
⚠️ v0.2.0 hardware mode + truecolor are built from the pi-tui source + spec, unit-tested against mocks, but not verified against a live Ghostty+tmux pane in this release. Hardware mode is Ghostty-targeted (DECSCUSR + OSC 12 are standard but only verified on Ghostty here); other terminals get thefakedefault. In 256-color theme mode, OSC 12 is skipped (no exact hex) and the terminal uses its configured cursor color. Seelib/editor.ts+lib/render.ts.
The char-hidden constraint
A fake cursor is the cell — ANSI has no partial-cell overlay. So the bar (focused) and hollow/outline (unfocused) styles render a glyph that hides the character at the cursor position while the cursor sits on it; the character reappears when the cursor moves. This is a terminal limitation, not a bug. block, underline, dim, hide, and highlight (char-preserving colored undercurl) preserve the character. In hardware mode the focused state uses the native terminal cursor (no fake cell), sidestepping this entirely.
- pi
@earendil-works/pi-coding-agentand@earendil-works/pi-tui^0.80.x(peer deps*). - Node
>=20. - macOS / Linux (Unix domain sockets). Windows herdr uses named pipes (untested).
pnpm install
pnpm typecheck
pnpm test:run # node:test via tsxMIT © RECTOR