Documentation
¶
Overview ¶
Package repl drives a child program (typically `claude`) through an in-process pseudo-terminal so callers can type into it and read its rendered output programmatically. The interactive runner ships its per-step prompts as PTY keystrokes (Write to the master end + Enter), and reads claude's rendered output through a VT-grid emulator. API surface: NewSession / KillSession / HasSession / CapturePane / SendText / SendEnter / SendCommand / WaitForReady, keyed by session name. PLAN-8 (2026-05-22) replaced the prior `internal/tmux` shim that shelled out to a `tmux` binary; the keystroke-delivery shape is the same (writing bytes to the PTY master is what `tmux send-keys -l` did under the hood), only the PTY ownership moved in-process.
The PTY backend is github.com/aymanbagabas/go-pty, which transparently uses Unix PTYs on Linux/macOS and ConPTY on Windows — so ape works natively under Git Bash on Windows 11 without WSL or a tmux binary on PATH.
PTY output is parsed through a github.com/hinshun/vt10x VT100/xterm emulator. CapturePane returns the rendered grid as plain text — no ANSI escape sequences, no cursor-positioning noise — matching tmux's `capture-pane -p` semantics rather than dumping raw bytes.
Known limitations:
- No external attach: a session lives and dies with the ape process. There is no equivalent of the old `tmux attach -t …` for an in-flight run. Pipeline runs that want live introspection should tail the per-step ndjson event log instead.
- Visible-grid scrollback only. vt10x's grid is sized to claude's perceived terminal (200×50), matching the ioctl winsize claude reads. Lines that scroll off the top via `\n` at the bottom are gone — tmux's history buffer (default 2000 lines) isn't reproduced. In practice the manifest's per-step `step-out` field and the debug stderr mirror are the consumers, and both look at the most recent screenful; the authoritative stream of model output flows through the bridge's hook events, not pane capture.
Index ¶
- Constants
- func CapturePane(_ context.Context, name string) (string, error)
- func HasSession(_ context.Context, name string) bool
- func KillSession(_ context.Context, name string) error
- func NewSession(_ context.Context, name, dir string, argv []string) error
- func ScrubClaudeCodeEnv(env []string) []string
- func SendCommand(ctx context.Context, name, text string) error
- func SendEnter(_ context.Context, name string) error
- func SendText(_ context.Context, name, text string) error
- func SessionDone(_ context.Context, name string) <-chan struct{}
- func WaitForReady(ctx context.Context, name string) error
- type NotReadyError
Constants ¶
const PromptSettle = 300 * time.Millisecond
PromptSettle is the wait between typing a command and pressing Enter. Long prompts otherwise submit before the REPL has finished loading them — confirmed by the community /pmux pattern and anthropics/claude-code#40168. 300ms is the well-known safe value.
const ReadyGlyph = "❯"
ReadyGlyph is the prompt glyph claude renders when ready.
const ReadyPollInterval = 250 * time.Millisecond
ReadyPollInterval is how often we capture pane output while waiting for the ❯ glyph that signals the claude REPL is accepting input.
Variables ¶
This section is empty.
Functions ¶
func CapturePane ¶
CapturePane returns the rendered VT grid as plain text. ANSI escape sequences are interpreted, not included in the output.
func HasSession ¶
HasSession reports whether the session is alive: registered AND the child process hasn't exited yet.
func KillSession ¶
KillSession terminates the child and tears down the PTY. Not-found is treated as success so callers can use it as a pre-check (`_ = KillSession(...)` before `NewSession`).
func NewSession ¶
NewSession spawns argv attached to a PTY, registers it under name, and starts background readers that accumulate pane output for CapturePane / WaitForReady. argv[0] is the program; argv[1:] its arguments. dir becomes the child's working directory.
func ScrubClaudeCodeEnv ¶ added in v0.0.33
ScrubClaudeCodeEnv returns env with the parent Claude Code session's nesting markers removed, so a spawned claude persists its own session transcript (the source ape scans for telemetry):
- CLAUDECODE — the top-level "running inside Claude Code" flag;
- CLAUDE_CODE_* — the whole parent-injected family (CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_SESSION_ID, CLAUDE_CODE_CHILD_SESSION, CLAUDE_CODE_SSE_PORT, …). The persistence-suppressing marker is in this set; stripping the family is robust across claude versions;
- CLAUDE_EFFORT — so the child's effort comes from ape's flags, not the parent session's inherited effort.
Everything else — ANTHROPIC_* auth included — passes through untouched. Exported so non-PTY interactive spawns (`ape chat`) can apply the same scrub.
func SendCommand ¶
SendCommand types text, settles for PromptSettle, then presses Enter. The canonical "send a slash command" helper.
func SendEnter ¶
SendEnter writes a carriage return to the PTY. The kernel's ICRNL termios setting on the slave converts CR into NL for the child, so the spawned program sees a real Enter keypress.
func SendText ¶
SendText writes text literally to the PTY input without submitting it. Successor to tmux's `send-keys -l`: every byte goes through verbatim so leading slashes and special chars survive.
func SessionDone ¶ added in v0.0.24
SessionDone returns a channel that is closed when the session's child process exits. Returns a nil channel if the session is not registered; callers should treat nil as "never closes".
func WaitForReady ¶
WaitForReady polls CapturePane until the claude REPL is genuinely accepting input or ctx cancels. Known blocking modals (see blockingModals) are dismissed along the way; after a dismissal the loop re-polls before testing readiness. On timeout the returned *NotReadyError includes the last pane snapshot.
Types ¶
type NotReadyError ¶ added in v0.0.27
NotReadyError is returned by WaitForReady when the REPL did not become ready before ctx expired. Pane carries the last captured snapshot so an unrecognized blocking modal is diagnosable from the error text instead of a silent stall (no-silent-caps principle).
func (*NotReadyError) Error ¶ added in v0.0.27
func (e *NotReadyError) Error() string
func (*NotReadyError) Unwrap ¶ added in v0.0.27
func (e *NotReadyError) Unwrap() error