@moguw/pi-interactive-shell
Run AI coding agents in pi TUI overlays with interactive, hands-free, and dispatch supervision
Package details
Install @moguw/pi-interactive-shell from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@moguw/pi-interactive-shell- Package
@moguw/pi-interactive-shell- Version
0.17.1- Published
- Oct 31, 2026
- Downloads
- 555/mo · 166/wk
- Author
- moguw
- License
- MIT
- Types
- extension, skill
- Size
- 396 KB
- Dependencies
- 3 dependencies · 4 peers
Pi manifest JSON
{
"skills": [
"./skills"
],
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@moguw/pi-interactive-shell
Run interactive CLIs inside observable Pi TUI overlays. Pi can drive the subprocess while you watch, and you can take control whenever needed.
Requirements
- Node.js 22.19 or newer
- Pi 0.86.0 or newer (including pi-ai's optional-null normalization)
- Pi interactive TUI mode for overlays
zigptysupport for the current platform
Install
pi install npm:@moguw/pi-interactive-shell
Install this workspace package directly during local development:
pi install /path/to/pi-ext/extensions/pi-interactive-shell
Run /reload after changing the installed package.
Usage
Ask Pi to open or supervise a CLI, or use the slash commands directly:
/spawn [agent] open an agent in an interactive overlay
/spawn codex "review the diffs" --dispatch run a delegated task and report completion
/spawn cursor open Cursor in an interactive overlay
/spawn claude "review the diffs" --dispatch run a delegated Claude task
/attach [session-id] reattach a background session
/dismiss [session-id] dismiss one session, or choose from a list
/spawn supports pi, codex, cursor, claude, configured custom agents, Pi session forks, and isolated Git worktrees. Add --worktree to run the spawned agent in a separate worktree.
Modes
| Mode | Use it for | Completion behavior |
|---|---|---|
interactive |
Editors, REPLs, SSH, and manual CLI flows | Opens an overlay and returns a controllable sessionId |
hands-free |
Builds and long-running tasks that need progress updates | Opens an overlay and emits quiet or interval updates |
dispatch |
Fire-and-forget delegated work | Reports completion without polling |
monitor |
Logs, tests, polling, and file watchers | Wakes Pi only when a trigger matches |
Dispatch defaults autoExitOnQuiet: true — the session gets a 15s startup grace period before closing after output becomes quiet. The completion notification identifies this as a quiet auto-close, not a user kill.
A spawn of pi is different: in dispatch and hands-free the child gets a private session directory, so its own transcript is readable while it runs. When that child finishes a turn, the parent receives an interactive-shell-settled notice with the child's closing message, before any quiet timeout.
Whether the child then stays alive depends on the mode. hands-free leaves it running (its default autoExitOnQuiet is false), so action: "send" starts another turn and each finished turn is announced. dispatch is one finite task: the child stops shortly after its turn settles (agent-settled) and its transcript is reachable through action: "attach" for five minutes. A quiet interval is likewise checked against the transcript, and a turn still in progress keeps waiting up to piSettlementMaxWait (default 30 minutes). Other agents, plain commands, and pi runs whose session cannot be read keep the quiet heuristic and still report auto-close-quiet.
Turn notices apply to dispatch and hands-free spawns of pi. An interactive spawn keeps the user's normal session location and has no watcher: the overlay reports the turn directly.
Start A Session
Choose an explicit action. For start, supply command or spawn, never both. Omit unused fields; strict structured calls may use null instead. Empty strings, zeroes, and false are values, not absence.
interactive_shell({ action: "start", command: "vim package.json" })
interactive_shell({
action: "start",
spawn: { agent: "pi", prompt: "Review the current changes" },
mode: "dispatch",
})
interactive_shell({
action: "start",
spawn: { agent: "cursor", prompt: "Review the diffs" },
mode: "dispatch",
})
interactive_shell({
action: "start",
spawn: { agent: "claude", prompt: "Review the diffs" },
mode: "hands-free",
})
Raw command is suitable for arbitrary CLIs. Structured spawn applies the configured agent command, default arguments, prompt format, and worktree behavior.
Control A Session
Use the returned sessionId for every follow-up operation:
interactive_shell({ action: "query", sessionId: "calm-reef" })
interactive_shell({ action: "send", sessionId: "calm-reef", input: "/compact", submit: true })
interactive_shell({ action: "send", sessionId: "calm-reef", inputKeys: ["ctrl+c"] })
interactive_shell({ action: "configure", sessionId: "calm-reef", settings: { quietThreshold: 8000 } })
interactive_shell({ action: "background", sessionId: "calm-reef" })
interactive_shell({ action: "kill", sessionId: "calm-reef" })
For editor-based TUIs, raw input only types text. It does not submit the prompt. Add submit: true or inputKeys: ["enter"] to submit it.
Background sessions can be listed, reattached, or dismissed:
interactive_shell({ action: "list" })
interactive_shell({ action: "attach", sessionId: "calm-reef", mode: "hands-free" })
interactive_shell({ action: "dismiss", sessionId: "calm-reef" })
Monitor A Process
Match output from a long-running command:
Each trigger has one kind (literal, regex, or numeric) and a non-empty pattern.
Only numeric accepts and requires threshold, with captureGroup >= 1.
Omit unused options, or use null under strict sampling; do not invent placeholders.
Validation finishes before any process or worktree is created. Correct invalid parameters before retrying.
interactive_shell({
action: "start",
command: "pnpm test -- --watch",
mode: "monitor",
monitor: {
strategy: "stream",
triggers: [{ id: "failed", kind: "literal", pattern: "FAIL" }],
},
})
Watch files without starting a command:
interactive_shell({
action: "start",
mode: "monitor",
monitor: {
strategy: "file-watch",
fileWatch: {
path: "./uploads",
recursive: true,
events: ["rename", "change"],
},
triggers: [{ id: "pdf", kind: "regex", pattern: "/\\.pdf$/i" }],
},
})
Query monitor state and events with the monitor session id:
interactive_shell({ action: "monitor_status", sessionId: "calm-reef" })
interactive_shell({ action: "monitor_events", sessionId: "calm-reef" })
For polling, use strategy: "poll-diff" with command and optional poll: { intervalMs: 5000 }; do not include fileWatch. For numeric comparisons:
interactive_shell({
action: "start", command: "tail -f prices.log", mode: "monitor",
monitor: { triggers: [{ id: "price", kind: "numeric", pattern: "/price=(\\d+)/", threshold: { captureGroup: 1, op: "gte", value: 0 } }] }
})
Compatibility and errors
- Unambiguous pre-action calls are migrated by
prepareArguments, including oldliteral/regexmatchers. Conflicting selectors, two non-empty matchers, and dummy numeric comparisons are rejected rather than guessed away. The public schema exposes only the new contract. - Strict-capable providers use Pi's preferred JSON-schema constrained sampling. Unused fields become nullable and Pi normalizes their
nullvalues before validation. - On Responses APIs, this extension makes omitted/null
strictexplicitlyfalseonly on its own function declarations, including dynamically added tools. Explicitstrict: true, other tools, andcompat.supportsStrictMode: falseare left untouched. It does not change global provider configuration. Gateways must honor the resulting schema; verify the wire request if a gateway still fills non-null placeholders. - Tool errors are thrown so Pi records
isError: true. A launch failure cleans up its unretained session resources; any worktree left in place is reported. A process that started and then failed instead emits a lifecycle notification with its exit status. - After upgrading, run
/reloadto refresh both tool schemas and Skill instructions. Existing slash commands and session IDs are unchanged.
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Alt+Shift+F |
Toggle focus between overlay and main chat |
Alt+Shift+P |
Launch the configured default spawn agent |
Ctrl+G |
Return control to Pi only after taking over a monitored hands-free or dispatch session |
Ctrl+B |
Move the current shell session to the background |
Ctrl+T |
Transfer terminal output back to Pi |
Ctrl+Q |
Open the transfer/background/kill menu |
Configuration
Global and project configuration files are merged, with project values taking precedence:
- Global:
~/.pi/agent/interactive-shell.json - Project:
.pi/interactive-shell.json
{
"defer": false,
"overlayAnchor": "center",
"focusShortcut": "alt+shift+f",
"spawn": {
"defaultAgent": "pi",
"shortcut": "alt+shift+p",
"commands": { "cursor": "agent" },
"defaultArgs": { "cursor": ["--model", "composer-2-fast"] },
"worktree": false
},
"minQueryIntervalSeconds": 60,
"handsFreeQuietThreshold": 8000,
"autoExitGracePeriod": 15000,
"piSettlementMaxWait": 1800000
}
Set defer to true to initially expose only enable_interactive_shell. Calling that loader activates interactive_shell for the current session; reload or session replacement resets availability. See skills/pi-interactive-shell/SKILL.md for the concise agent workflow.
Launch policy (global only)
launchPolicy gates commands launched through interactive_shell and /spawn by exact string match. Rules are allow, ask, or deny; when several match, deny wins over ask, which wins over allow; unmatched commands ask. deny blocks before any PTY, session, or worktree is created; ask shows a Pi confirmation dialog. Define it only in the global config — a project config that sets it is rejected:
{
"launchPolicy": [
{ "command": "npm test", "decision": "allow" },
{ "command": "rm -rf /", "decision": "deny" }
]
}
Omitting the field keeps launches unchanged.
Limitations
- macOS is tested; Linux support is experimental (the upstream Linux PTY completion rework is not ported; see Upstream sync).
- Existing-session queries are rate-limited by default.
- Terminal applications may have rendering quirks in an overlay.
Upstream sync
Ported from nicobailon/pi-interactive-shell 77df9a8 (v0.17.0; fork point 87938ca, v0.15.0):
- Force PTY cleanup (
b5adeed), empty spawn placeholder tolerance (#44). - Shell resolution through Pi's Bash selection and
shellPath(3390524), with literal quoting for generated commands. - Stale background-widget cleanup (
6be4554), agent control on Ctrl+B (584452f). - Machine-readable
completionReason(1342ccf) and five-minute completed dispatch output (9a01bde). - Global
launchPolicy(846b02b+1fc6388). /reloadsurvival for background sessions and monitors (62c047a+ prototype re-attach from1b7a029) and the compact background widget.- Callbacks of sessions that outlive
/reloadfollow the active API, bound atsession_start(3572073,dacfd39), and unknowninputKeys/inputHexvalues error instead of reaching the PTY (129a839). Remaining 0.18.0 changes are not ported yet: completion output logs (70a72fe), the lean tool schema (a37250d), the semantic-schema provider fixes (e9237fe,04fb78e), and the bundled-skill restructuring (129a839, whosekey-encoding.tshalf is ported); this fork keepsskills/pi-interactive-shell/as a single file.
Deliberately not ported: Jev semantic supervision and recoverable output selection (external API, credentials, ~2000 lines; revisit if semantic monitoring is needed), and the Linux PTY completion rework (981ca32, 500 lines of Linux-specific plumbing) until Linux tail-output loss is actually observed.
The action-based tool contract (action: "start" | "query" | ...) is a fork-local redesign; upstream still uses flat parameters, so future upstream tool-schema changes must be merged by hand into tool-schema.ts and tool-contract.ts.
Acknowledgments
Thanks to Nico Bailon for creating the original pi-interactive-shell project that this package is based on.