Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

A pi coding-agent extension that customizes the editor cursor and makes it
focus-aware across terminal/multiplexer stacks (tmux + cmux + herdr in v0.1.1;
static fallback elsewhere; zellij/screen/wezterm + bare-terminal DEC 1004 in
later releases).
static fallback elsewhere; v0.2.0 adds truecolor + theme-aware cursor colors,
a char-preserving `highlight` unfocused style, and an opt-in `hardware` cursor
mode for Ghostty; zellij/screen/wezterm + bare-terminal DEC 1004 in later
releases).

## Build & test

Expand All @@ -21,6 +23,8 @@ pnpm test:run # node:test via tsx
- `/cursor unfocused dim|hollow|outline|underline|hide`
- `/cursor blink on [ms] | off`
- `/cursor provider auto|tmux|cmux|herdr|static`
- `/cursor color accent|#RRGGBB` (v0.2.0)
- `/cursor mode fake|hardware` (v0.2.0)
- `/cursor status`
- `/cursor reset` (also `r` in the panel)

Expand All @@ -31,8 +35,8 @@ pnpm test:run # node:test via tsx
| `extensions/cursor.ts` | entry: `/cursor` command + session lifecycle |
| `lib/defaults.ts` | style enums + default config |
| `lib/config.ts` | load/save/normalize config (`~/.pi/agent/cursor.json`) |
| `lib/render.ts` | ANSI cursor-cell transforms per style |
| `lib/editor.ts` | `CursorEditor` (CustomEditor subclass) + composition |
| `lib/render.ts` | ANSI cursor-cell transforms per style + truecolor color helpers + `highlight` undercurl + hardware bare-char |
| `lib/editor.ts` | `CursorEditor` (CustomEditor subclass) + composition + hardware-mode side effects (DECSCUSR/OSC12, `setShowHardwareCursor`, `restoreCursor`) |
| `lib/panel.ts` | `/cursor` SettingsList rows |
| `lib/state.ts` | blink timer |
| `lib/focus/index.ts` | `FocusProvider` interface + auto-detect |
Expand All @@ -56,6 +60,16 @@ pnpm test:run # node:test via tsx
`docs/events.md`, `docs/cli-contract.md`); uses the `debug.terminal.is_focused`
RPC polled every ~300 ms. Unverified without a live cmux session in v0.1.1;
debug-build glob socket discovery not implemented.
- v0.2.0 truecolor cursor colors resolve via pi's `Theme.getFgAnsi("accent"/"dim")`
(truecolor end-to-end through tmux `Tc`); `cursorColor` hex override emits
`\x1b[38;2;R;G;Bm`. `highlight` unfocused style = char-preserving colored
undercurl (`4:3` + `58:2::R:G:B`), 256-color fallback = plain underline.
- v0.2.0 `hardware` cursor mode emits DECSCUSR (`\x1b[<n> q`) + OSC 12
(`\x1b]12;<color>\x07`) for the focused state via `tui.terminal.write`, toggles
`tui.setShowHardwareCursor`, and `restoreCursor()` on `session_shutdown`
(resets shape/color — no terminal state leak). Native DECSCUSR blink replaces
the fake BlinkController in hardware+blink. 256-color theme → OSC 12 skipped.
⚠️ Unverified against a live Ghostty+tmux pane; Ghostty-targeted.
- herdr adapter is built from `herdr.dev/docs/socket-api` (verified against
`herdr api schema --json`); unverified without a live herdr session in v0.1.
- Bare-terminal (no multiplexer) = static mode in v0.1 (pi-tui upstream gap;
Expand Down
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ Then `/reload` (or restart pi) and run `/cursor` to open the settings panel.
## Features

- **Focused styles:** `block` (pi native, default), `bar` (▎), `underline`.
- **Unfocused styles:** `hollow` (□ sharp hollow block, default), `outline` (▢ rounded), `dim` (faint block), `underline`, `hide`.
- **Blink** (opt-in, default off) — pauses when the pane is unfocused.
- **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) or `hardware` (native terminal cursor via DECSCUSR + OSC 12 — Ghostty-targeted).
- **Blink** (opt-in, default off) — pauses when the pane is unfocused. In `hardware` mode, 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_focused` RPC (poll).
Expand All @@ -30,9 +32,11 @@ Then `/reload` (or restart pi) and run `/cursor` to open the settings panel.
/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
/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)
```
Expand Down Expand Up @@ -70,9 +74,22 @@ No multiplexer detected (bare Ghostty/Kitty/iTerm2/Alacritty, or unknown). The c

> **Bare-terminal focus detection (DEC 1004) is not in v0.1** — pi 0.80.x doesn't enable `?1004h` or parse `\x1b[I`/`\x1b[O`. Tracked for a later release.

## Cursor color (v0.2.0)

`/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 modes (v0.2.0)

`/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 via `tui.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`). On `session_shutdown` the 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 the `fake` default. In 256-color theme mode, OSC 12 is skipped (no exact hex) and the terminal uses its configured cursor color. See `lib/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`, and `hide` preserve the character.
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.

## Compatibility

Expand Down
Loading
Loading