An extension for Pi coding agent that lets Pi autonomously run interactive CLIs in an observable TUI overlay. Pi controls the subprocess while you watch - take over anytime.
pi-interactive-shell-extension.mp4
interactive_shell({ command: 'vim config.yaml' })Some tasks need interactive CLIs - editors, REPLs, database shells, long-running processes. Pi can launch them in an overlay where:
- User watches - See exactly what's happening in real-time
- User takes over - Type anything to gain control
- Agent monitors - Query status, send input, decide when done
Works with any CLI: vim, htop, psql, ssh, docker logs -f, npm run dev, git rebase -i, etc.
pi install npm:pi-interactive-shellThe interactive-shell skill is automatically symlinked to ~/.pi/agent/skills/interactive-shell/.
Requires: Node.js, build tools for node-pty (Xcode CLI tools on macOS).
Three modes control how the agent engages with a session:
| Interactive | Hands-Free | Dispatch | |
|---|---|---|---|
| Agent blocked? | Yes — tool call waits | No — returns immediately | No — returns immediately |
| How agent gets output | Tool return value | Polls with sessionId |
Notification via triggerTurn |
| Overlay visible? | Yes | Yes | Yes (or headless with background: true) |
| User can interact? | Always | Type to take over | Type to take over |
| Concurrent sessions? | No | One overlay + queries | Multiple headless, one overlay |
| Best for | Editors, REPLs, SSH | Dev servers, builds | Delegating to other agents |
Interactive is the default. The agent's tool call blocks until the session ends — use this when the agent needs the result right away, or when the user drives the session (editors, database shells).
Hands-free returns immediately so the agent can do other work, but the agent must poll periodically to discover output and completion. Good for processes the agent needs to monitor and react to mid-flight, like watching build output and sending follow-up commands.
Dispatch also returns immediately, but the agent doesn't poll at all. When the session completes — whether by natural exit, quiet detection, timeout, or user intervention — the agent gets woken up with a notification containing the tail output. This is the right mode for delegating a task to a subagent and moving on. Add background: true to skip the overlay entirely and run headless.
interactive_shell({ command: 'vim package.json' })
interactive_shell({ command: 'psql -d mydb' })
interactive_shell({ command: 'ssh user@server' })The agent's turn is blocked until the overlay closes. User controls the session directly.
// Start a long-running process
interactive_shell({
command: 'npm run dev',
mode: "hands-free",
reason: "Dev server"
})
// → { sessionId: "calm-reef", status: "running" }
// Poll for output (rate-limited to 60s between queries)
interactive_shell({ sessionId: "calm-reef" })
// → { status: "running", output: "Server ready on :3000", runtime: 45000 }
// Send input when needed
interactive_shell({ sessionId: "calm-reef", inputKeys: ["ctrl+c"] })
// Kill when done
interactive_shell({ sessionId: "calm-reef", kill: true })
// → { status: "killed", output: "..." }The overlay opens for the user to watch. The agent checks in periodically. User can type anything to take over control.
// Fire off a task
interactive_shell({
command: 'pi "Refactor the auth module"',
mode: "dispatch",
reason: "Auth refactor"
})
// → Returns immediately: { sessionId: "calm-reef" }
// → Agent ends turn or does other work.When the session completes, the agent receives a compact notification on a new turn:
Session calm-reef completed successfully (5m 23s). 847 lines of output.
Step 9 of 10
Step 10 of 10
All tasks completed.
Attach to review full output: interactive_shell({ attach: "calm-reef" })
The notification includes a brief tail (last 5 lines) and a reattach instruction. The PTY is preserved for 5 minutes so the agent can attach to review full scrollback.
Dispatch defaults autoExitOnQuiet: true — the session gets a 30s startup grace period, then is killed after output goes silent (5s by default), which signals completion for task-oriented subagents. Tune the grace period with handsFree: { gracePeriod: 60000 } or opt out entirely with handsFree: { autoExitOnQuiet: false }.
The overlay still shows for the user, who can Ctrl+T to transfer output, Ctrl+B to background, take over by typing, or Ctrl+Q for more options.
// No overlay — runs completely invisibly
interactive_shell({
command: 'pi "Fix all lint errors"',
mode: "dispatch",
background: true
})
// → { sessionId: "calm-reef" }
// → User can /attach calm-reef to peek
// → Agent notified on completion, same as regular dispatchMultiple headless dispatches can run concurrently alongside a single interactive overlay. This is how you parallelize subagent work — fire off three background dispatches and process results as each completion notification arrives.
Capture output from TUI apps that don't exit cleanly:
interactive_shell({
command: "htop",
mode: "hands-free",
timeout: 3000 // Kill after 3s, return captured output
})For fire-and-forget single-task delegations, enable auto-exit to kill the session after 5s of output silence:
interactive_shell({
command: 'cursor-agent -f "Fix the bug in auth.ts"',
mode: "hands-free",
handsFree: { autoExitOnQuiet: true }
})A 30s startup grace period prevents the session from being killed before the subprocess has time to produce output. Customize it per-call with gracePeriod:
interactive_shell({
command: 'pi "Run the full test suite"',
mode: "hands-free",
handsFree: { autoExitOnQuiet: true, gracePeriod: 60000 }
})The default grace period is also configurable globally via autoExitGracePeriod in the config file.
For multi-turn sessions where you need back-and-forth interaction, leave it disabled (default) and use kill: true when done.
// Text
interactive_shell({ sessionId: "calm-reef", input: "SELECT * FROM users;\n" })
// Named keys
interactive_shell({ sessionId: "calm-reef", inputKeys: ["ctrl+c"] })
interactive_shell({ sessionId: "calm-reef", inputKeys: ["down", "down", "enter"] })
// Bracketed paste (multiline without execution)
interactive_shell({ sessionId: "calm-reef", inputPaste: "line1\nline2\nline3" })
// Hex bytes (raw escape sequences)
interactive_shell({ sessionId: "calm-reef", inputHex: ["0x1b", "0x5b", "0x41"] })
// Combine text with keys
interactive_shell({ sessionId: "calm-reef", input: "y", inputKeys: ["enter"] })// Default: 20 lines, 5KB
interactive_shell({ sessionId: "calm-reef" })
// More lines (max: 200)
interactive_shell({ sessionId: "calm-reef", outputLines: 100 })
// Incremental pagination (server tracks position)
interactive_shell({ sessionId: "calm-reef", outputLines: 50, incremental: true })
// Drain mode (raw stream since last query)
interactive_shell({ sessionId: "calm-reef", drain: true })When a subagent finishes work, press Ctrl+T to capture its output and send it directly to the main agent:
[Subagent finishes work]
↓
[Press Ctrl+T]
↓
[Overlay closes, main agent receives full output]
The main agent then has the subagent's response in context and can continue working with that information.
Configuration:
transferLines: Max lines to capture (default: 200)transferMaxChars: Max characters (default: 20KB)
Sessions can be backgrounded by the user (Ctrl+B, or Ctrl+Q → "Run in background") or by the agent:
// Agent backgrounds an active session
interactive_shell({ sessionId: "calm-reef", background: true })
// → Overlay closes, process keeps running
// List background sessions
interactive_shell({ listBackground: true })
// Reattach with a specific mode
interactive_shell({ attach: "calm-reef" }) // interactive (blocking)
interactive_shell({ attach: "calm-reef", mode: "hands-free" }) // hands-free (poll)
interactive_shell({ attach: "calm-reef", mode: "dispatch" }) // dispatch (notified)
// Dismiss background sessions
interactive_shell({ dismissBackground: true }) // all sessions
interactive_shell({ dismissBackground: "calm-reef" }) // specific sessionUser can also /attach or /attach <id> to reattach, and /dismiss or /dismiss <id> to clean up from the chat.
| Key | Action |
|---|---|
| Ctrl+T | Transfer & close - capture output and send to main agent |
| Ctrl+B | Background session (dismiss overlay, keep running) |
| Ctrl+Q | Session menu (transfer/background/kill/cancel) |
| Shift+Up/Down | Scroll history |
| Any key (hands-free) | Take over control |
Configuration files (project overrides global):
- Global:
~/.pi/agent/interactive-shell.json - Project:
.pi/interactive-shell.json
{
"overlayWidthPercent": 95,
"overlayHeightPercent": 60,
"scrollbackLines": 5000,
"exitAutoCloseDelay": 10,
"minQueryIntervalSeconds": 60,
"transferLines": 200,
"transferMaxChars": 20000,
"completionNotifyLines": 50,
"completionNotifyMaxChars": 5000,
"handsFreeUpdateMode": "on-quiet",
"handsFreeUpdateInterval": 60000,
"handsFreeQuietThreshold": 5000,
"autoExitGracePeriod": 30000,
"handsFreeUpdateMaxChars": 1500,
"handsFreeMaxTotalChars": 100000,
"handoffPreviewEnabled": true,
"handoffPreviewLines": 30,
"handoffPreviewMaxChars": 2000,
"handoffSnapshotEnabled": false,
"ansiReemit": true
}| Setting | Default | Description |
|---|---|---|
overlayWidthPercent |
95 | Overlay width (10-100%) |
overlayHeightPercent |
60 | Overlay height (20-90%) |
scrollbackLines |
5000 | Terminal scrollback buffer |
exitAutoCloseDelay |
10 | Seconds before auto-close after exit |
minQueryIntervalSeconds |
60 | Rate limit between agent queries |
transferLines |
200 | Lines to capture on Ctrl+T transfer (10-1000) |
transferMaxChars |
20000 | Max chars for transfer (1KB-100KB) |
completionNotifyLines |
50 | Lines in dispatch completion notification (10-500) |
completionNotifyMaxChars |
5000 | Max chars in completion notification (1KB-50KB) |
handsFreeUpdateMode |
"on-quiet" | "on-quiet" or "interval" |
handsFreeQuietThreshold |
5000 | Silence duration before update (ms) |
autoExitGracePeriod |
30000 | Startup grace before autoExitOnQuiet kill (ms) |
handsFreeUpdateInterval |
60000 | Max interval between updates (ms) |
handsFreeUpdateMaxChars |
1500 | Max chars per update |
handsFreeMaxTotalChars |
100000 | Total char budget for updates |
handoffPreviewEnabled |
true | Include tail in tool result |
handoffPreviewLines |
30 | Lines in tail preview (0-500) |
handoffPreviewMaxChars |
2000 | Max chars in tail preview (0-50KB) |
handoffSnapshotEnabled |
false | Write transcript on detach/exit |
ansiReemit |
true | Preserve ANSI colors in output |
interactive_shell → node-pty → subprocess
↓
xterm-headless (terminal emulation)
↓
TUI overlay (pi rendering)
Full PTY. The subprocess thinks it's in a real terminal.
The examples/prompts/ directory includes three prompt templates that chain together into a complete development workflow using Codex CLI. Each template instructs pi to gather context, generate a tailored meta prompt based on the Codex prompting guide, and launch Codex in an interactive overlay.
Write a plan
↓
/codex-review-plan path/to/plan.md ← Codex verifies every assumption against the codebase
↓
/codex-implement-plan path/to/plan.md ← Codex implements the reviewed plan faithfully
↓
/codex-review-impl path/to/plan.md ← Codex reviews the diff against the plan, fixes issues
Copy the prompt templates and Codex CLI skill to your pi config:
# Prompt templates (slash commands)
cp ~/.pi/agent/extensions/interactive-shell/examples/prompts/*.md ~/.pi/agent/prompts/
# Codex CLI skill (teaches pi how to use codex flags, sandbox caveats, etc.)
cp -r ~/.pi/agent/extensions/interactive-shell/examples/skills/codex-cli ~/.pi/agent/skills/Say you have a plan at docs/auth-redesign-plan.md:
Step 1: Review the plan — Codex reads your plan, then verifies every file path, API shape, data flow, and integration point against the actual codebase. Fixes issues directly in the plan file.
/codex-review-plan docs/auth-redesign-plan.md
/codex-review-plan docs/auth-redesign-plan.md pay attention to the migration steps
Step 2: Implement the plan — Codex reads all relevant code first, then implements bottom-up: shared utilities first, then dependent modules, then integration code. No stubs, no TODOs.
/codex-implement-plan docs/auth-redesign-plan.md
/codex-implement-plan docs/auth-redesign-plan.md skip test files for now
Step 3: Review the implementation — Codex diffs the changes, reads every changed file in full (plus imports and dependents), traces code paths across file boundaries, and fixes every issue it finds. Pass the plan to verify completeness, or omit it to just review the diff.
/codex-review-impl docs/auth-redesign-plan.md # review diff against plan
/codex-review-impl docs/auth-redesign-plan.md check cleanup ordering
/codex-review-impl # just review the diff, no plan
/codex-review-impl focus on error handling and race conditions
These templates demonstrate a "meta-prompt generation" pattern:
- Pi gathers context — reads the plan, runs git diff, fetches the Codex prompting guide
- Pi generates a calibrated prompt — tailored to the specific plan/diff, following the guide's best practices
- Pi launches Codex in the overlay — with explicit flags (
-m gpt-5.3-codex -c model_reasoning_effort="high" -a never) and hands off control
The user watches Codex work in the overlay and can take over anytime (type to intervene, Ctrl+T to transfer output back to pi, Ctrl+Q for options).
These are starting points. Fork them and adjust:
- Model/flags — swap
gpt-5.3-codexfor another model, change reasoning effort - Review criteria — add project-specific checks (security policies, style rules)
- Implementation rules — change the 500-line file limit, add framework-specific patterns
- Other agents — adapt the pattern for Claude (
claude "prompt"), Gemini (gemini -i "prompt"), or any CLI
See the pi prompt templates docs for the full $1, $@ placeholder syntax.
For orchestrating multi-agent chains (scout → planner → worker → reviewer) with file-based handoff and auto-continue support, see:
pi-foreground-chains - A separate skill that builds on interactive-shell for complex agent workflows.
- macOS tested, Linux experimental
- 60s rate limit between queries (configurable)
- Some TUI apps may have rendering quirks
