@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.16.1- Published
- Oct 8, 2026
- Downloads
- 280/mo · 205/wk
- Author
- moguw
- License
- MIT
- Types
- extension, skill
- Size
- 365.5 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.
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
}
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.
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.