pi-interactive-shell 0.4.7 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +108 -102
- package/SKILL.md +49 -16
- package/config.ts +0 -2
- package/index.ts +47 -537
- package/overlay-component.ts +75 -567
- package/package.json +2 -2
- package/pty-session.ts +32 -14
- package/session-manager.ts +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `pi-interactive-shell` extension will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.4.8] - 2026-01-19
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **node-pty ^1.1.0** - Updated minimum version to 1.1.0 which includes prebuilt binaries for macOS (arm64, x64) and Windows (x64, arm64). No more Xcode or Visual Studio required for installation on these platforms. Linux still requires build tools (`build-essential`, `python3`).
|
|
9
|
+
|
|
10
|
+
## [0.4.7] - 2026-01-18
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Incremental mode** - New `incremental: true` parameter for server-tracked pagination. Agent calls repeatedly and server tracks position automatically. Returns `hasMore` to indicate when more output is available.
|
|
14
|
+
- **hasMore in offset mode** - Offset pagination now returns `hasMore` field so agents can know when they've finished reading all output.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **Session ID leak on user takeover** - In streaming mode, session ID was unregistered but never released when user took over. Now properly releases ID since agent was notified and won't query.
|
|
18
|
+
- **Session ID leak in dispose()** - When overlay was disposed without going through finishWith* methods (error cases), session ID was never released. Now releases ID in all cleanup paths.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- **autoExitOnQuiet now defaults to false** - Sessions stay alive for multi-turn interaction by default. Enable with `handsFree: { autoExitOnQuiet: true }` for fire-and-forget single-task delegations.
|
|
22
|
+
- **Config documentation** - Fixed incorrect config path in README. Config files are `~/.pi/agent/interactive-shell.json` (global) and `.pi/interactive-shell.json` (project), not under `settings.json`. Added full settings table with all options documented.
|
|
23
|
+
- **Detach key** - Changed from double-Escape to Ctrl+Q for more reliable detection.
|
|
24
|
+
|
|
5
25
|
## [0.4.6] - 2026-01-18
|
|
6
26
|
|
|
7
27
|
### Added
|
package/README.md
CHANGED
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
# Pi Interactive Shell
|
|
2
2
|
|
|
3
|
-
An extension for [Pi coding agent](https://github.com/badlogic/pi-mono/) that lets Pi run
|
|
3
|
+
An extension for [Pi coding agent](https://github.com/badlogic/pi-mono/) that lets Pi autonomously run interactive CLIs in an observable TUI overlay. Pi controls the subprocess while you watch - take over anytime.
|
|
4
|
+
|
|
5
|
+
https://github.com/user-attachments/assets/76f56ecd-fc12-4d92-a01e-e6ae9ba65ff4
|
|
4
6
|
|
|
5
7
|
```typescript
|
|
6
|
-
interactive_shell({ command: '
|
|
7
|
-
// Returns immediately with sessionId
|
|
8
|
-
// User watches in overlay, you query for status
|
|
9
|
-
// Session auto-closes when agent finishes
|
|
8
|
+
interactive_shell({ command: 'vim config.yaml' })
|
|
10
9
|
```
|
|
11
10
|
|
|
12
11
|
## Why
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **Visibility** - What's the subagent doing? Is it stuck?
|
|
17
|
-
- **Control** - User needs to intervene. How?
|
|
18
|
-
- **Integration** - When does the parent agent check in?
|
|
19
|
-
|
|
20
|
-
Interactive Shell solves all three:
|
|
21
|
-
|
|
22
|
-
**Real-Time Overlay** - User sees the subprocess in a TUI overlay. Full terminal emulation via xterm-headless. ANSI colors, cursor movement, everything.
|
|
23
|
-
|
|
24
|
-
**Seamless Takeover** - Type anything to take control. Scroll with Shift+Up/Down. Double-Escape to detach.
|
|
13
|
+
Some tasks need interactive CLIs - editors, REPLs, database shells, long-running processes. Pi can launch them in an overlay where:
|
|
25
14
|
|
|
26
|
-
|
|
15
|
+
- **User watches** - See exactly what's happening in real-time
|
|
16
|
+
- **User takes over** - Type anything to gain control
|
|
17
|
+
- **Agent monitors** - Query status, send input, decide when done
|
|
27
18
|
|
|
28
|
-
|
|
19
|
+
Works with any CLI: `vim`, `htop`, `psql`, `ssh`, `docker logs -f`, `npm run dev`, `git rebase -i`, etc.
|
|
29
20
|
|
|
30
21
|
## Install
|
|
31
22
|
|
|
@@ -35,17 +26,32 @@ npx pi-interactive-shell
|
|
|
35
26
|
|
|
36
27
|
Installs to `~/.pi/agent/extensions/interactive-shell/`.
|
|
37
28
|
|
|
29
|
+
The `interactive-shell` skill is automatically symlinked to `~/.pi/agent/skills/interactive-shell/`.
|
|
30
|
+
|
|
38
31
|
**Requires:** Node.js, build tools for `node-pty` (Xcode CLI tools on macOS).
|
|
39
32
|
|
|
40
33
|
## Quick Start
|
|
41
34
|
|
|
42
|
-
###
|
|
35
|
+
### Interactive Mode
|
|
36
|
+
|
|
37
|
+
User controls the session directly:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
interactive_shell({ command: 'vim package.json' })
|
|
41
|
+
interactive_shell({ command: 'psql -d mydb' })
|
|
42
|
+
interactive_shell({ command: 'ssh user@server' })
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Hands-Free Mode
|
|
46
|
+
|
|
47
|
+
Agent monitors while user watches. Returns immediately with sessionId:
|
|
43
48
|
|
|
44
49
|
```typescript
|
|
45
|
-
// Start -
|
|
50
|
+
// Start a long-running process
|
|
46
51
|
interactive_shell({
|
|
47
|
-
command: '
|
|
48
|
-
mode: "hands-free"
|
|
52
|
+
command: 'npm run dev',
|
|
53
|
+
mode: "hands-free",
|
|
54
|
+
reason: "Dev server"
|
|
49
55
|
})
|
|
50
56
|
// → { sessionId: "calm-reef", status: "running" }
|
|
51
57
|
|
|
@@ -53,53 +59,57 @@ interactive_shell({
|
|
|
53
59
|
interactive_shell({ sessionId: "calm-reef" })
|
|
54
60
|
// → { status: "running", output: "...", runtime: 45000 }
|
|
55
61
|
|
|
56
|
-
//
|
|
57
|
-
interactive_shell({ sessionId: "calm-reef",
|
|
62
|
+
// Send input if needed
|
|
63
|
+
interactive_shell({ sessionId: "calm-reef", input: { keys: ["ctrl+c"] } })
|
|
58
64
|
|
|
59
|
-
// Kill when done
|
|
65
|
+
// Kill when done
|
|
60
66
|
interactive_shell({ sessionId: "calm-reef", kill: true })
|
|
61
67
|
```
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
User sees the overlay in real-time. Type anything to take over control.
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
interactive_shell({ command: 'vim package.json' })
|
|
67
|
-
```
|
|
71
|
+
### Timeout Mode
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
Capture output from TUI apps that don't exit cleanly:
|
|
70
74
|
|
|
71
75
|
```typescript
|
|
72
|
-
interactive_shell({
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
interactive_shell({
|
|
77
|
+
command: "htop",
|
|
78
|
+
mode: "hands-free",
|
|
79
|
+
timeout: 3000 // Kill after 3s, return captured output
|
|
80
|
+
})
|
|
75
81
|
```
|
|
76
82
|
|
|
77
|
-
## CLI Reference
|
|
78
|
-
|
|
79
|
-
| Agent | Interactive | With Prompt | Headless (use bash) |
|
|
80
|
-
|-------|-------------|-------------|---------------------|
|
|
81
|
-
| `claude` | `claude` | `claude "prompt"` | `claude -p "prompt"` |
|
|
82
|
-
| `gemini` | `gemini` | `gemini -i "prompt"` | `gemini "prompt"` |
|
|
83
|
-
| `codex` | `codex` | `codex "prompt"` | `codex exec "prompt"` |
|
|
84
|
-
| `agent` | `agent` | `agent "prompt"` | `agent -p "prompt"` |
|
|
85
|
-
| `pi` | `pi` | `pi "prompt"` | `pi -p "prompt"` |
|
|
86
|
-
|
|
87
83
|
## Features
|
|
88
84
|
|
|
89
85
|
### Auto-Exit on Quiet
|
|
90
86
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
### Timeout for TUI Capture
|
|
87
|
+
For fire-and-forget single-task delegations, enable auto-exit to kill the session after 5s of output silence:
|
|
94
88
|
|
|
95
89
|
```typescript
|
|
96
90
|
interactive_shell({
|
|
97
|
-
command: "
|
|
91
|
+
command: 'cursor-agent -f "Fix the bug in auth.ts"',
|
|
98
92
|
mode: "hands-free",
|
|
99
|
-
|
|
93
|
+
handsFree: { autoExitOnQuiet: true }
|
|
100
94
|
})
|
|
101
95
|
```
|
|
102
96
|
|
|
97
|
+
For multi-turn sessions where you need back-and-forth interaction, leave it disabled (default) and use `kill: true` when done.
|
|
98
|
+
|
|
99
|
+
### Send Input
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Text
|
|
103
|
+
interactive_shell({ sessionId: "calm-reef", input: "SELECT * FROM users;\n" })
|
|
104
|
+
|
|
105
|
+
// Named keys
|
|
106
|
+
interactive_shell({ sessionId: "calm-reef", input: { keys: ["ctrl+c"] } })
|
|
107
|
+
interactive_shell({ sessionId: "calm-reef", input: { keys: ["down", "down", "enter"] } })
|
|
108
|
+
|
|
109
|
+
// Bracketed paste (multiline without execution)
|
|
110
|
+
interactive_shell({ sessionId: "calm-reef", input: { paste: "line1\nline2\nline3" } })
|
|
111
|
+
```
|
|
112
|
+
|
|
103
113
|
### Configurable Output
|
|
104
114
|
|
|
105
115
|
```typescript
|
|
@@ -109,77 +119,67 @@ interactive_shell({ sessionId: "calm-reef" })
|
|
|
109
119
|
// More lines (max: 200)
|
|
110
120
|
interactive_shell({ sessionId: "calm-reef", outputLines: 100 })
|
|
111
121
|
|
|
112
|
-
//
|
|
113
|
-
interactive_shell({ sessionId: "calm-reef",
|
|
114
|
-
```
|
|
122
|
+
// Incremental pagination (server tracks position)
|
|
123
|
+
interactive_shell({ sessionId: "calm-reef", outputLines: 50, incremental: true })
|
|
115
124
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|--------|---------|
|
|
120
|
-
| Text | `input: "/model\n"` |
|
|
121
|
-
| Keys | `input: { keys: ["enter", "ctrl+c"] }` |
|
|
122
|
-
| Hex | `input: { hex: ["0x1b", "0x5b", "0x41"] }` |
|
|
123
|
-
| Paste | `input: { paste: "multi\nline" }` |
|
|
125
|
+
// Drain mode (raw stream since last query)
|
|
126
|
+
interactive_shell({ sessionId: "calm-reef", drain: true })
|
|
127
|
+
```
|
|
124
128
|
|
|
125
129
|
### Background Sessions
|
|
126
130
|
|
|
127
|
-
1.
|
|
131
|
+
1. Ctrl+Q → "Run in background"
|
|
128
132
|
2. `/attach` or `/attach <id>` to reattach
|
|
129
133
|
|
|
130
|
-
## Config
|
|
131
|
-
|
|
132
|
-
`~/.pi/agent/interactive-shell.json`
|
|
133
|
-
|
|
134
|
-
```json
|
|
135
|
-
{
|
|
136
|
-
"overlayHeightPercent": 45,
|
|
137
|
-
"overlayWidthPercent": 95,
|
|
138
|
-
"scrollbackLines": 5000,
|
|
139
|
-
"minQueryIntervalSeconds": 60,
|
|
140
|
-
"handsFreeQuietThreshold": 5000
|
|
141
|
-
}
|
|
142
|
-
```
|
|
143
|
-
|
|
144
134
|
## Keys
|
|
145
135
|
|
|
146
136
|
| Key | Action |
|
|
147
137
|
|-----|--------|
|
|
148
|
-
|
|
|
138
|
+
| Ctrl+Q | Detach dialog |
|
|
149
139
|
| Shift+Up/Down | Scroll history |
|
|
150
140
|
| Any key (hands-free) | Take over control |
|
|
151
141
|
|
|
152
|
-
##
|
|
153
|
-
|
|
154
|
-
Unlike the standard tmux workflow where you `capture-pane` the entire terminal on every poll, Interactive Shell minimizes token waste:
|
|
155
|
-
|
|
156
|
-
**Incremental Aggregation** - Output is accumulated as it arrives, not re-captured on each query.
|
|
157
|
-
|
|
158
|
-
**Tail by Default** - Status queries return only the last 20 lines (configurable), not the full history.
|
|
159
|
-
|
|
160
|
-
**ANSI Stripping** - All escape codes are stripped before sending output to the agent. Clean text only.
|
|
142
|
+
## Config
|
|
161
143
|
|
|
162
|
-
|
|
144
|
+
Configuration files (project overrides global):
|
|
145
|
+
- **Global:** `~/.pi/agent/interactive-shell.json`
|
|
146
|
+
- **Project:** `.pi/interactive-shell.json`
|
|
163
147
|
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"overlayWidthPercent": 95,
|
|
151
|
+
"overlayHeightPercent": 45,
|
|
152
|
+
"scrollbackLines": 5000,
|
|
153
|
+
"exitAutoCloseDelay": 10,
|
|
154
|
+
"minQueryIntervalSeconds": 60,
|
|
155
|
+
"handsFreeUpdateMode": "on-quiet",
|
|
156
|
+
"handsFreeUpdateInterval": 60000,
|
|
157
|
+
"handsFreeQuietThreshold": 5000,
|
|
158
|
+
"handsFreeUpdateMaxChars": 1500,
|
|
159
|
+
"handsFreeMaxTotalChars": 100000,
|
|
160
|
+
"handoffPreviewEnabled": true,
|
|
161
|
+
"handoffPreviewLines": 30,
|
|
162
|
+
"handoffPreviewMaxChars": 2000,
|
|
163
|
+
"handoffSnapshotEnabled": false,
|
|
164
|
+
"ansiReemit": true
|
|
165
|
+
}
|
|
172
166
|
```
|
|
173
167
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
168
|
+
| Setting | Default | Description |
|
|
169
|
+
|---------|---------|-------------|
|
|
170
|
+
| `overlayWidthPercent` | 95 | Overlay width (10-100%) |
|
|
171
|
+
| `overlayHeightPercent` | 45 | Overlay height (20-90%) |
|
|
172
|
+
| `scrollbackLines` | 5000 | Terminal scrollback buffer |
|
|
173
|
+
| `exitAutoCloseDelay` | 10 | Seconds before auto-close after exit |
|
|
174
|
+
| `minQueryIntervalSeconds` | 60 | Rate limit between agent queries |
|
|
175
|
+
| `handsFreeUpdateMode` | "on-quiet" | "on-quiet" or "interval" |
|
|
176
|
+
| `handsFreeQuietThreshold` | 5000 | Silence duration before update (ms) |
|
|
177
|
+
| `handsFreeUpdateInterval` | 60000 | Max interval between updates (ms) |
|
|
178
|
+
| `handsFreeUpdateMaxChars` | 1500 | Max chars per update |
|
|
179
|
+
| `handsFreeMaxTotalChars` | 100000 | Total char budget for updates |
|
|
180
|
+
| `handoffPreviewEnabled` | true | Include tail in tool result |
|
|
181
|
+
| `handoffSnapshotEnabled` | false | Write transcript on detach/exit |
|
|
182
|
+
| `ansiReemit` | true | Preserve ANSI colors in output |
|
|
183
183
|
|
|
184
184
|
## How It Works
|
|
185
185
|
|
|
@@ -193,6 +193,12 @@ interactive_shell → node-pty → subprocess
|
|
|
193
193
|
|
|
194
194
|
Full PTY. The subprocess thinks it's in a real terminal.
|
|
195
195
|
|
|
196
|
+
## Advanced: Multi-Agent Workflows
|
|
197
|
+
|
|
198
|
+
For orchestrating multi-agent chains (scout → planner → worker → reviewer) with file-based handoff and auto-continue support, see:
|
|
199
|
+
|
|
200
|
+
**[pi-foreground-chains](https://github.com/nicobailon/pi-foreground-chains)** - A separate skill that builds on interactive-shell for complex agent workflows.
|
|
201
|
+
|
|
196
202
|
## Limitations
|
|
197
203
|
|
|
198
204
|
- macOS tested, Linux experimental
|
package/SKILL.md
CHANGED
|
@@ -97,8 +97,8 @@ const result = interactive_shell({
|
|
|
97
97
|
command: 'codex "Review this codebase"',
|
|
98
98
|
mode: "hands-free"
|
|
99
99
|
})
|
|
100
|
-
// result.sessionId = "calm-reef"
|
|
101
|
-
// result.status = "running"
|
|
100
|
+
// result.details.sessionId = "calm-reef"
|
|
101
|
+
// result.details.status = "running"
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
The user sees the overlay immediately. You get control back to continue working.
|
|
@@ -122,21 +122,41 @@ interactive_shell({ sessionId: "calm-reef", kill: true })
|
|
|
122
122
|
|
|
123
123
|
Kill when you see the task is complete in the output. Returns final status and output.
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
### Fire-and-Forget Tasks
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
For single-task delegations where you don't need multi-turn interaction, enable auto-exit so the session kills itself when the agent goes quiet:
|
|
128
128
|
|
|
129
129
|
```typescript
|
|
130
130
|
interactive_shell({
|
|
131
131
|
command: 'pi "Review this codebase for security issues. Save your findings to /tmp/security-review.md"',
|
|
132
132
|
mode: "hands-free",
|
|
133
|
-
reason: "Security review"
|
|
133
|
+
reason: "Security review",
|
|
134
|
+
handsFree: { autoExitOnQuiet: true }
|
|
134
135
|
})
|
|
135
|
-
//
|
|
136
|
+
// Session auto-kills after ~5s of quiet
|
|
137
|
+
// Read results from file:
|
|
136
138
|
// read("/tmp/security-review.md")
|
|
137
139
|
```
|
|
138
140
|
|
|
139
|
-
|
|
141
|
+
**Instruct subagent to save results to a file** since the session closes automatically.
|
|
142
|
+
|
|
143
|
+
### Multi-Turn Sessions (default)
|
|
144
|
+
|
|
145
|
+
For back-and-forth interaction, leave auto-exit disabled (the default). Query status and kill manually when done:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
interactive_shell({
|
|
149
|
+
command: 'cursor-agent -f',
|
|
150
|
+
mode: "hands-free",
|
|
151
|
+
reason: "Interactive refactoring"
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Send follow-up prompts
|
|
155
|
+
interactive_shell({ sessionId: "calm-reef", input: "Now fix the tests\n" })
|
|
156
|
+
|
|
157
|
+
// Kill when done
|
|
158
|
+
interactive_shell({ sessionId: "calm-reef", kill: true })
|
|
159
|
+
```
|
|
140
160
|
|
|
141
161
|
### Sending Input
|
|
142
162
|
```typescript
|
|
@@ -159,19 +179,32 @@ interactive_shell({ sessionId: "calm-reef", outputLines: 50 })
|
|
|
159
179
|
interactive_shell({ sessionId: "calm-reef", outputLines: 100, outputMaxChars: 30000 })
|
|
160
180
|
```
|
|
161
181
|
|
|
162
|
-
###
|
|
182
|
+
### Incremental Reading
|
|
163
183
|
|
|
164
|
-
|
|
184
|
+
Use `incremental: true` to paginate through output without re-reading:
|
|
165
185
|
|
|
166
186
|
```typescript
|
|
167
|
-
//
|
|
168
|
-
interactive_shell({
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
})
|
|
187
|
+
// First call: get first 50 lines
|
|
188
|
+
interactive_shell({ sessionId: "calm-reef", outputLines: 50, incremental: true })
|
|
189
|
+
// → { output: "...", hasMore: true }
|
|
190
|
+
|
|
191
|
+
// Next call: get next 50 lines (server tracks position)
|
|
192
|
+
interactive_shell({ sessionId: "calm-reef", outputLines: 50, incremental: true })
|
|
193
|
+
// → { output: "...", hasMore: true }
|
|
194
|
+
|
|
195
|
+
// Keep calling until hasMore: false
|
|
196
|
+
interactive_shell({ sessionId: "calm-reef", outputLines: 50, incremental: true })
|
|
197
|
+
// → { output: "...", hasMore: false }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The server tracks your read position - just keep calling with `incremental: true` to get the next chunk.
|
|
201
|
+
|
|
202
|
+
### Reviewing Output
|
|
173
203
|
|
|
174
|
-
|
|
204
|
+
Query sessions to see progress. Increase limits when you need more context:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// Default: last 20 lines
|
|
175
208
|
interactive_shell({ sessionId: "calm-reef" })
|
|
176
209
|
|
|
177
210
|
// Get more lines when you need more context
|
package/config.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
5
|
export interface InteractiveShellConfig {
|
|
6
|
-
doubleEscapeThreshold: number;
|
|
7
6
|
exitAutoCloseDelay: number;
|
|
8
7
|
overlayWidthPercent: number;
|
|
9
8
|
overlayHeightPercent: number;
|
|
@@ -26,7 +25,6 @@ export interface InteractiveShellConfig {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
const DEFAULT_CONFIG: InteractiveShellConfig = {
|
|
29
|
-
doubleEscapeThreshold: 300,
|
|
30
28
|
exitAutoCloseDelay: 10,
|
|
31
29
|
overlayWidthPercent: 95,
|
|
32
30
|
overlayHeightPercent: 45,
|