@moguw/pi-interactive-shell

Run AI coding agents in pi TUI overlays with interactive, hands-free, and dispatch supervision

Packages

Package details

extensionskill

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
  • zigpty support 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 old literal/regex matchers. 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 null values before validation.
  • On Responses APIs, this extension makes omitted/null strict explicitly false only on its own function declarations, including dynamically added tools. Explicit strict: true, other tools, and compat.supportsStrictMode: false are 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 /reload to 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).
  • /reload survival for background sessions and monitors (62c047a + prototype re-attach from 1b7a029) and the compact background widget.
  • Callbacks of sessions that outlive /reload follow the active API, bound at session_start (3572073, dacfd39), and unknown inputKeys/inputHex values 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, whose key-encoding.ts half is ported); this fork keeps skills/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.