Summary
detectCapabilities() in packages/tui/src/terminal-image.ts detects terminal image support exclusively from environment variables. Over SSH / EternalTerminal (ET) those variables never reach the remote host, so senpi misjudges a fully kitty-graphics-capable terminal as "unknown" and silently degrades images to a one-line text fallback — even though the transport passes the graphics escapes through transparently.
This issue proposes two complementary fixes:
- Active capability probing via the kitty graphics protocol query (transport- and env-agnostic).
SENPI_IMAGES env override as an explicit escape hatch.
Problem
Detection currently keys off client-side terminal identity envs:
| Variable |
Detects |
KITTY_WINDOW_ID, TERM_PROGRAM=kitty |
kitty |
GHOSTTY_RESOURCES_DIR, TERM contains ghostty |
ghostty |
WEZTERM_PANE, TERM_PROGRAM=wezterm |
wezterm |
WARP_SESSION_ID, TERM_PROGRAM=warpterminal |
warp |
ITERM_SESSION_ID, TERM_PROGRAM=iterm.app |
iTerm2 |
None of these survive a remote session:
- SSH forwards only
TERM by default. Anything else needs SendEnv on every client and AcceptEnv on every server's sshd_config.
- ET parses ssh config itself and ignores some directives (e.g.
SetEnv — see MisterTea/EternalTerminal#679), so ssh-style env forwarding is not reliable there.
TERM is frequently sanitized to xterm-256color for terminfo compatibility (a common Ghostty setup — see ghostty-org/ghostty#3161). Even when TERM does survive, the TERM-substring check only covers ghostty; xterm-kitty is not matched at all.
- No active probe exists. The only terminal query in use is the cell-metrics query (
CSI 16 t, packages/tui/src/tui.ts:866), which is used for sizing, not protocol detection.
Result: fallback to { images: null, ... } → imageFallback() text in packages/tui/src/components/image.ts.
Reproduced live
Session over ET 7.0.0, local terminal Ghostty, remote shell env:
TERM=xterm-256color
TERM_PROGRAM=<unset>
ET_VERSION=7.0.0
detectCapabilities() → {"images":null,"trueColor":false,"hyperlinks":false}
After adding export TERM_PROGRAM=ghostty to the remote shell rc:
detectCapabilities() → {"images":"kitty","trueColor":true,"hyperlinks":true}
… and kitty-protocol images render correctly through the ET pipe. Detection is the only blocker; transport is fine.
(Note: mosh is a different story — its state-synchronization design drops all non-grid sequences, so no client-side fix is possible. Out of scope here; see mobile-shell/mosh#1248.)
Proposal
1. Active probe: kitty graphics query
Probe the terminal directly at TUI startup, next to the existing CSI 16 t cell-size query infrastructure in packages/tui/src/tui.ts:
→ \x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\
← \x1b_Gi=31;OK\x1b\\ (kitty-protocol terminals)
- Any
\x1b_Gi=31;... APC response means images: "kitty".
- Terminals without kitty support silently ignore APC sequences → bounded timeout (~100–300 ms) → keep the env-based result.
- Must not block startup: run alongside the existing query round-trip, or resolve lazily before the first image render; the env-based result remains the pre-timeout default.
- Response parsing must live in the stdin sequence parser without consuming unrelated query responses (e.g. the
CSI 16 t reply).
Spec: kitty graphics protocol — query support.
2. Escape hatch: SENPI_IMAGES
| Value |
Effect |
kitty |
force kitty protocol |
iterm2 |
force iTerm2 protocol |
off |
force disable images |
Checked before all other detection. Covers cases a probe cannot (non-TTY output, exotic multiplexers, CI).
Detection precedence
SENPI_IMAGES (explicit user override, including off)
- tmux/screen branch →
images: null (unchanged; probe never runs there)
- Kitty query probe →
"kitty"
- Existing env-based detection (unchanged fallback)
Acceptance criteria
Non-goals (follow-up issues)
- tmux passthrough: DCS
passthrough wrapping + allow-passthrough probing. Currently hard-disabled by design ("Image protocols are unreliable under tmux").
- sixel support.
- iTerm2 active probing (no standard query; env detection is adequate for local iTerm2).
References
Summary
detectCapabilities()inpackages/tui/src/terminal-image.tsdetects terminal image support exclusively from environment variables. Over SSH / EternalTerminal (ET) those variables never reach the remote host, so senpi misjudges a fully kitty-graphics-capable terminal as "unknown" and silently degrades images to a one-line text fallback — even though the transport passes the graphics escapes through transparently.This issue proposes two complementary fixes:
SENPI_IMAGESenv override as an explicit escape hatch.Problem
Detection currently keys off client-side terminal identity envs:
KITTY_WINDOW_ID,TERM_PROGRAM=kittyGHOSTTY_RESOURCES_DIR,TERMcontainsghosttyWEZTERM_PANE,TERM_PROGRAM=weztermWARP_SESSION_ID,TERM_PROGRAM=warpterminalITERM_SESSION_ID,TERM_PROGRAM=iterm.appNone of these survive a remote session:
TERMby default. Anything else needsSendEnvon every client andAcceptEnvon every server'ssshd_config.SetEnv— see MisterTea/EternalTerminal#679), so ssh-style env forwarding is not reliable there.TERMis frequently sanitized toxterm-256colorfor terminfo compatibility (a common Ghostty setup — see ghostty-org/ghostty#3161). Even whenTERMdoes survive, theTERM-substring check only covers ghostty;xterm-kittyis not matched at all.CSI 16 t,packages/tui/src/tui.ts:866), which is used for sizing, not protocol detection.Result: fallback to
{ images: null, ... }→imageFallback()text inpackages/tui/src/components/image.ts.Reproduced live
Session over ET 7.0.0, local terminal Ghostty, remote shell env:
After adding
export TERM_PROGRAM=ghosttyto the remote shell rc:… and kitty-protocol images render correctly through the ET pipe. Detection is the only blocker; transport is fine.
(Note: mosh is a different story — its state-synchronization design drops all non-grid sequences, so no client-side fix is possible. Out of scope here; see mobile-shell/mosh#1248.)
Proposal
1. Active probe: kitty graphics query
Probe the terminal directly at TUI startup, next to the existing
CSI 16 tcell-size query infrastructure inpackages/tui/src/tui.ts:\x1b_Gi=31;...APC response meansimages: "kitty".CSI 16 treply).Spec: kitty graphics protocol — query support.
2. Escape hatch:
SENPI_IMAGESkittyiterm2offChecked before all other detection. Covers cases a probe cannot (non-TTY output, exotic multiplexers, CI).
Detection precedence
SENPI_IMAGES(explicit user override, includingoff)images: null(unchanged; probe never runs there)"kitty"Acceptance criteria
TERM=xterm-256color, senpi on a kitty-protocol terminal renders images with zero shell-rc workarounds.SENPI_IMAGES=kitty|iterm2|offwins over env detection and probe result in all combinations.images: null).packages/tui/test/virtual-terminal.ts, pattern afterpackages/tui/test/terminal-image.test.ts).Non-goals (follow-up issues)
passthroughwrapping +allow-passthroughprobing. Currently hard-disabled by design ("Image protocols are unreliable under tmux").References
packages/tui/src/terminal-image.ts—detectCapabilities(),encodeKitty(),renderImage()packages/tui/src/components/image.ts— render path and fallbackpackages/tui/src/tui.ts:866— existingCSI 16 tquery site