skills/pi-agent/references/terminal-setup.md
Source: https://pi.dev/docs/latest/terminal-setup
Pi uses the Kitty keyboard protocol for reliable modifier detection. Most modern terminals support it; some need configuration.
Kitty and iTerm2 (regular TUI mode). Apple Terminal enables enhanced key reporting when available; if it still sends plain Return for Shift+Enter, Pi uses a local macOS modifier fallback — which only works when Pi runs on the same Mac, not over SSH. VS Code 1.109.5+ also works by default.
Pi owns the viewport, so iTerm2 sends mouse-wheel reports instead of scrolling its native scrollback. With iTerm2's default fast-trackpad behavior those reports can lose most of an accelerated wheel delta. If fast gestures move only ~one line at a time, open iTerm2 → Settings → Advanced, find Trackpad scrolls fast? and set it to No. This is an iTerm2-wide workaround (tracked in iTerm2 issue 9619). Inline images also render as text placeholders in fullscreen mode because iTerm2's inline-image protocol cannot delete or crop placements during application-owned scrolling.
Config at ~/Library/Application Support/com.mitchellh.ghostty/config (macOS) or ~/.config/ghostty/config (Linux):
keybind = alt+backspace=text:\x1b\x7f
Older Claude Code versions may have added keybind = shift+enter=text:\n. That sends a raw linefeed, which inside Pi is indistinguishable from Ctrl+J, so tmux and Pi no longer see a real shift+enter event. Remove it unless you still need it for Claude Code in tmux. Pi binds Ctrl+J as a default newline alias, so Shift+Enter keeps working through that remap without extra Pi configuration.
In fullscreen TUI mode links stay clickable, but Ghostty hides its hover underline and lower-left URL preview while Pi captures mouse input. Hold Shift+Command (macOS) or Shift+Ctrl (Linux) for Ghostty's native link handling.
Usually works via xterm modifyOtherKeys. To force the Kitty protocol, in ~/.wezterm.lua:
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
config.enable_kitty_keyboard = true
return config
On macOS Option+Enter is bound to fullscreen; to use it for follow-up queueing add to config.keys:
{ key = 'Enter', mods = 'ALT', action = wezterm.action.SendString('\x1b[13;3u') }
On WSL, WezTerm may need a visible hardware cursor for IME candidate positioning — set PI_HARDWARE_CURSOR=1 or showHardwareCursor: true.
Usually works for Shift+Enter. On macOS Option+Enter may arrive as plain Enter; add to ~/.config/alacritty/alacritty.toml and restart:
[[keyboard.bindings]]
key = "Enter"
mods = "Alt"
chars = "\u001b[13;3u"
1.109.5+ enables the Kitty protocol by default. Older versions need an explicit keybindings.json entry (macOS ~/Library/Application Support/Code/User/keybindings.json, Linux ~/.config/Code/User/keybindings.json, Windows %APPDATA%\Code\User\keybindings.json):
{
"key": "shift+enter",
"command": "workbench.action.terminal.sendSequence",
"args": { "text": "\u001b[13;2u" },
"when": "terminalFocus"
}
Add to settings.json (Ctrl+Shift+, then Open JSON file) so the modified Enter keys reach Pi:
{
"actions": [
{ "command": { "action": "sendInput", "input": "\u001b[13;2u" }, "keys": "shift+enter" },
{ "command": { "action": "sendInput", "input": "\u001b[13;3u" }, "keys": "alt+enter" }
]
}
Windows Terminal binds Alt+Enter to fullscreen by default, which blocks follow-up queueing; remapping it to sendInput forwards the real chord. Fully close and reopen Windows Terminal if the old fullscreen behavior persists.
xfce4-terminal, terminator, and IntelliJ IDEA's integrated terminal cannot distinguish modified Enter keys from plain Enter, so bindings such as submit: ["ctrl+enter"] will not work. Prefer a terminal with Kitty keyboard support: Kitty, Ghostty, WezTerm, iTerm2, or Alacritty compiled with Kitty protocol support. In IntelliJ, set PI_HARDWARE_CURSOR=1 if you want the hardware cursor visible (off by default for compatibility).