terminal-pilot 0.0.1 → 0.0.3
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/README.md +9 -138
- package/dist/keys.js +17 -12
- package/dist/terminal-session.js +21 -12
- package/package.json +4 -13
- package/dist/mcp-server.d.ts +0 -11
- package/dist/mcp-server.js +0 -40
- package/dist/mcp-tools.d.ts +0 -46
- package/dist/mcp-tools.js +0 -169
- package/dist/testing/menu-cli.d.ts +0 -2
- package/dist/testing/menu-cli.js +0 -63
- package/dist/testing/test-cli.d.ts +0 -2
- package/dist/testing/test-cli.js +0 -22
package/README.md
CHANGED
|
@@ -1,38 +1,23 @@
|
|
|
1
|
-
#
|
|
1
|
+
# terminal-pilot
|
|
2
2
|
|
|
3
|
-
`terminal-pilot` is a Playwright-like SDK
|
|
3
|
+
`terminal-pilot` is a Playwright-like SDK for automating interactive CLI apps through a real pseudoterminal (PTY).
|
|
4
4
|
|
|
5
5
|
Use it when plain stdio is not enough: menus, prompts, arrow-key navigation, confirmations, and terminal redraws such as `poe-code configure`.
|
|
6
6
|
|
|
7
7
|
For design rationale and scope, see `docs/plans/terminal-pilot.md`.
|
|
8
8
|
|
|
9
|
+
For the MCP server, see [terminal-pilot-mcp](../terminal-pilot-mcp).
|
|
10
|
+
|
|
9
11
|
## What it includes
|
|
10
12
|
|
|
11
13
|
- **SDK:** `TerminalPilot` → `TerminalSession` → `TerminalScreen`
|
|
12
|
-
- **MCP server:** 10 tools for AI-driven CLI automation
|
|
13
14
|
- **Real PTY execution:** works with interactive CLIs that expect a terminal
|
|
14
15
|
- **Headless by default:** optional `observe: true` mirrors PTY output for debugging
|
|
15
16
|
|
|
16
17
|
## Entry points
|
|
17
18
|
|
|
18
19
|
```ts
|
|
19
|
-
import { TerminalPilot } from "
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
import { createTerminalPilotMcpServer, main } from "@poe-code/terminal-pilot/mcp";
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
CLI bin:
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
terminal-pilot-mcp
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Development entry point in this repo:
|
|
33
|
-
|
|
34
|
-
```sh
|
|
35
|
-
npx tsx packages/terminal-pilot/src/mcp-server.ts
|
|
20
|
+
import { TerminalPilot } from "terminal-pilot";
|
|
36
21
|
```
|
|
37
22
|
|
|
38
23
|
## SDK API
|
|
@@ -42,7 +27,7 @@ npx tsx packages/terminal-pilot/src/mcp-server.ts
|
|
|
42
27
|
Creates and tracks active terminal sessions.
|
|
43
28
|
|
|
44
29
|
```ts
|
|
45
|
-
import { TerminalPilot } from "
|
|
30
|
+
import { TerminalPilot } from "terminal-pilot";
|
|
46
31
|
|
|
47
32
|
const pilot = await TerminalPilot.launch();
|
|
48
33
|
|
|
@@ -188,125 +173,11 @@ class TerminalScreen {
|
|
|
188
173
|
}
|
|
189
174
|
```
|
|
190
175
|
|
|
191
|
-
## MCP server
|
|
192
|
-
|
|
193
|
-
The MCP server holds one in-memory `TerminalPilot` instance and exposes terminal automation over stdio.
|
|
194
|
-
|
|
195
|
-
### Run it
|
|
196
|
-
|
|
197
|
-
Development:
|
|
198
|
-
|
|
199
|
-
```sh
|
|
200
|
-
npx tsx packages/terminal-pilot/src/mcp-server.ts
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Built / installed package:
|
|
204
|
-
|
|
205
|
-
```sh
|
|
206
|
-
terminal-pilot-mcp
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
Programmatic:
|
|
210
|
-
|
|
211
|
-
```ts
|
|
212
|
-
import { main } from "@poe-code/terminal-pilot/mcp";
|
|
213
|
-
|
|
214
|
-
await main();
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### Connect to an MCP client
|
|
218
|
-
|
|
219
|
-
Install the package globally from a local build:
|
|
220
|
-
|
|
221
|
-
```sh
|
|
222
|
-
cd packages/terminal-pilot
|
|
223
|
-
npm pack --pack-destination /tmp && npm install -g /tmp/poe-code-terminal-pilot-*.tgz && rm /tmp/poe-code-terminal-pilot-*.tgz
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
This makes the `terminal-pilot-mcp` bin available globally.
|
|
227
|
-
|
|
228
|
-
**Claude Code** (`~/.claude.json` or project `.mcp.json`):
|
|
229
|
-
|
|
230
|
-
```json
|
|
231
|
-
{
|
|
232
|
-
"mcpServers": {
|
|
233
|
-
"terminal-pilot": {
|
|
234
|
-
"command": "terminal-pilot-mcp"
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
**Claude Desktop** (`claude_desktop_config.json`):
|
|
241
|
-
|
|
242
|
-
```json
|
|
243
|
-
{
|
|
244
|
-
"mcpServers": {
|
|
245
|
-
"terminal-pilot": {
|
|
246
|
-
"command": "terminal-pilot-mcp"
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
**Cursor** (`.cursor/mcp.json`):
|
|
253
|
-
|
|
254
|
-
```json
|
|
255
|
-
{
|
|
256
|
-
"mcpServers": {
|
|
257
|
-
"terminal-pilot": {
|
|
258
|
-
"command": "terminal-pilot-mcp"
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### Tools
|
|
265
|
-
|
|
266
|
-
`void` means the tool returns no payload.
|
|
267
|
-
|
|
268
|
-
| Tool | Input | Output |
|
|
269
|
-
| --- | --- | --- |
|
|
270
|
-
| `terminal_create_session` | `{ command: string, args?: string[], cwd?: string, cols?: number, rows?: number, observe?: boolean }` | `{ sessionId: string, pid: number }` |
|
|
271
|
-
| `terminal_type` | `{ sessionId: string, text: string }` | `void` |
|
|
272
|
-
| `terminal_press_key` | `{ sessionId: string, key: TerminalKey }` | `void` |
|
|
273
|
-
| `terminal_send_signal` | `{ sessionId: string, signal: string }` | `void` |
|
|
274
|
-
| `terminal_wait_for` | `{ sessionId: string, pattern: string, timeout?: number }` | `{ matched: true, output: string }` |
|
|
275
|
-
| `terminal_read_screen` | `{ sessionId: string }` | `{ lines: string[], cursor: { row: number, col: number }, size: { rows: number, cols: number } }` |
|
|
276
|
-
| `terminal_read_history` | `{ sessionId: string, last?: number }` | `{ lines: string[] }` |
|
|
277
|
-
| `terminal_resize` | `{ sessionId: string, cols: number, rows: number }` | `void` |
|
|
278
|
-
| `terminal_close_session` | `{ sessionId: string }` | `{ exitCode: number }` |
|
|
279
|
-
| `terminal_list_sessions` | `{}` | `{ sessions: Array<{ id: string, command: string, pid: number }> }` |
|
|
280
|
-
|
|
281
|
-
Practical notes:
|
|
282
|
-
|
|
283
|
-
- `terminal_wait_for.pattern` is compiled as a JavaScript `RegExp` on the server.
|
|
284
|
-
- `terminal_type` maps to `session.fill(...)` for bulk text entry.
|
|
285
|
-
- `terminal_read_screen` returns the **current visible screen**, not scrollback.
|
|
286
|
-
- `terminal_read_history` returns ANSI-stripped output since session start.
|
|
287
|
-
- `terminal_list_sessions` returns **active** sessions only.
|
|
288
|
-
- `observe: true` mirrors PTY output to `stderr`, which is useful when debugging MCP-driven runs.
|
|
289
|
-
|
|
290
|
-
Minimal MCP flow:
|
|
291
|
-
|
|
292
|
-
```json
|
|
293
|
-
{"tool":"terminal_create_session","arguments":{"command":"poe-code","args":["configure"]}}
|
|
294
|
-
{"tool":"terminal_wait_for","arguments":{"sessionId":"<id>","pattern":"Pick an agent to configure:"}}
|
|
295
|
-
{"tool":"terminal_press_key","arguments":{"sessionId":"<id>","key":"Enter"}}
|
|
296
|
-
{"tool":"terminal_read_screen","arguments":{"sessionId":"<id>"}}
|
|
297
|
-
{"tool":"terminal_close_session","arguments":{"sessionId":"<id>"}}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
176
|
## Environment variables
|
|
301
177
|
|
|
302
178
|
There are **no terminal-pilot-specific environment variables**.
|
|
303
179
|
|
|
304
|
-
Runtime environment is controlled per session
|
|
305
|
-
|
|
306
|
-
- SDK: `newSession({ env })`
|
|
307
|
-
- MCP server: spawned commands inherit the MCP server process environment unless you override it in your command wrapper
|
|
308
|
-
|
|
309
|
-
There are also **no package-level config files or config options** beyond the per-session options above.
|
|
180
|
+
Runtime environment is controlled per session via `newSession({ env })`. There are no package-level config files or config options beyond the per-session options.
|
|
310
181
|
|
|
311
182
|
## Testing
|
|
312
183
|
|
|
@@ -340,7 +211,7 @@ Example fixture-based test:
|
|
|
340
211
|
|
|
341
212
|
```ts
|
|
342
213
|
import path from "node:path";
|
|
343
|
-
import { TerminalPilot } from "
|
|
214
|
+
import { TerminalPilot } from "terminal-pilot";
|
|
344
215
|
|
|
345
216
|
const pilot = await TerminalPilot.launch();
|
|
346
217
|
const tsxPath = path.join(process.cwd(), "node_modules", ".bin", "tsx");
|
|
@@ -371,7 +242,7 @@ Use a temporary home directory so you do not touch your real config while testin
|
|
|
371
242
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
372
243
|
import os from "node:os";
|
|
373
244
|
import path from "node:path";
|
|
374
|
-
import { TerminalPilot } from "
|
|
245
|
+
import { TerminalPilot } from "terminal-pilot";
|
|
375
246
|
|
|
376
247
|
const tmpHome = mkdtempSync(path.join(os.tmpdir(), "poe-configure-test-"));
|
|
377
248
|
const pilot = await TerminalPilot.launch();
|
package/dist/keys.js
CHANGED
|
@@ -14,18 +14,24 @@ const NAMED_KEY_SEQUENCES = {
|
|
|
14
14
|
PageDown: "\x1b[6~",
|
|
15
15
|
Space: " "
|
|
16
16
|
};
|
|
17
|
+
const NAMED_KEY_LOWER = new Map(Object.entries(NAMED_KEY_SEQUENCES).map(([k, v]) => [k.toLowerCase(), v]));
|
|
18
|
+
const VALID_KEYS_HINT = `Valid keys: ${Object.keys(NAMED_KEY_SEQUENCES).join(", ")}, Control+<letter>, Alt+<key>`;
|
|
19
|
+
function unknownKeyError(key) {
|
|
20
|
+
return new Error(`Unknown terminal key: ${key}. ${VALID_KEYS_HINT}`);
|
|
21
|
+
}
|
|
17
22
|
export function keyToSequence(key) {
|
|
18
|
-
const
|
|
23
|
+
const lowerKey = key.toLowerCase();
|
|
24
|
+
const namedSequence = NAMED_KEY_LOWER.get(lowerKey);
|
|
19
25
|
if (namedSequence !== undefined) {
|
|
20
26
|
return namedSequence;
|
|
21
27
|
}
|
|
22
|
-
if (
|
|
23
|
-
return controlKeyToSequence(key);
|
|
28
|
+
if (lowerKey.startsWith("control+")) {
|
|
29
|
+
return controlKeyToSequence(key.slice("control+".length));
|
|
24
30
|
}
|
|
25
|
-
if (
|
|
26
|
-
const nestedKey = key.slice("
|
|
31
|
+
if (lowerKey.startsWith("alt+")) {
|
|
32
|
+
const nestedKey = key.slice("alt+".length);
|
|
27
33
|
if (nestedKey.length === 0) {
|
|
28
|
-
throw
|
|
34
|
+
throw unknownKeyError(key);
|
|
29
35
|
}
|
|
30
36
|
if (nestedKey.length === 1) {
|
|
31
37
|
return "\x1b" + nestedKey;
|
|
@@ -34,20 +40,19 @@ export function keyToSequence(key) {
|
|
|
34
40
|
return "\x1b" + keyToSequence(nestedKey);
|
|
35
41
|
}
|
|
36
42
|
catch {
|
|
37
|
-
throw
|
|
43
|
+
throw unknownKeyError(key);
|
|
38
44
|
}
|
|
39
45
|
}
|
|
40
|
-
throw
|
|
46
|
+
throw unknownKeyError(key);
|
|
41
47
|
}
|
|
42
|
-
function controlKeyToSequence(
|
|
43
|
-
const controlKey = key.slice("Control+".length);
|
|
48
|
+
function controlKeyToSequence(controlKey) {
|
|
44
49
|
if (controlKey.length !== 1) {
|
|
45
|
-
throw
|
|
50
|
+
throw unknownKeyError(`Control+${controlKey}`);
|
|
46
51
|
}
|
|
47
52
|
const uppercaseLetter = controlKey.toUpperCase();
|
|
48
53
|
const charCode = uppercaseLetter.charCodeAt(0);
|
|
49
54
|
if (charCode < 65 || charCode > 90) {
|
|
50
|
-
throw
|
|
55
|
+
throw unknownKeyError(`Control+${controlKey}`);
|
|
51
56
|
}
|
|
52
57
|
return String.fromCharCode(charCode - 64);
|
|
53
58
|
}
|
package/dist/terminal-session.js
CHANGED
|
@@ -66,7 +66,7 @@ export class TerminalSession {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
async fill(text) {
|
|
69
|
-
await this.send(text);
|
|
69
|
+
await this.send(text.replace(/\r?\n/g, "\r"));
|
|
70
70
|
}
|
|
71
71
|
async press(key) {
|
|
72
72
|
await this.send(keyToSequence(key));
|
|
@@ -140,7 +140,9 @@ export class TerminalSession {
|
|
|
140
140
|
async resize(cols, rows) {
|
|
141
141
|
this.currentCols = cols;
|
|
142
142
|
this.currentRows = rows;
|
|
143
|
-
this.
|
|
143
|
+
if (this.exitCode === null) {
|
|
144
|
+
this.pty.resize(cols, rows);
|
|
145
|
+
}
|
|
144
146
|
this.terminal.resize(cols, rows);
|
|
145
147
|
}
|
|
146
148
|
async close() {
|
|
@@ -149,11 +151,12 @@ export class TerminalSession {
|
|
|
149
151
|
}
|
|
150
152
|
if (!this.closeRequested) {
|
|
151
153
|
this.closeRequested = true;
|
|
154
|
+
const gracefulExitCode = await waitForExit(this.exitPromise, CLOSE_AFTER_SIGNAL_GRACE_MS);
|
|
155
|
+
if (gracefulExitCode !== null) {
|
|
156
|
+
return gracefulExitCode;
|
|
157
|
+
}
|
|
152
158
|
if (this.signalRequested) {
|
|
153
|
-
|
|
154
|
-
if (exitCode !== null) {
|
|
155
|
-
return exitCode;
|
|
156
|
-
}
|
|
159
|
+
return this.exitPromise;
|
|
157
160
|
}
|
|
158
161
|
if (this.exitCode === null) {
|
|
159
162
|
this.pty.kill("SIGTERM");
|
|
@@ -252,13 +255,19 @@ function signalToExitCode(signal) {
|
|
|
252
255
|
return 128 + signalNumber;
|
|
253
256
|
}
|
|
254
257
|
function matchPattern(buffer, pattern) {
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
const clean = normalizeHistoryBuffer(stripAnsi(buffer));
|
|
259
|
+
for (const line of clean.split("\n")) {
|
|
260
|
+
if (typeof pattern === "string") {
|
|
261
|
+
if (line.includes(pattern))
|
|
262
|
+
return line;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
const flags = removeCharacter(pattern.flags, "g");
|
|
266
|
+
if (new RegExp(pattern.source, flags).test(line))
|
|
267
|
+
return line;
|
|
268
|
+
}
|
|
257
269
|
}
|
|
258
|
-
|
|
259
|
-
const localPattern = new RegExp(pattern.source, flags);
|
|
260
|
-
const match = localPattern.exec(buffer);
|
|
261
|
-
return match?.[0] ?? null;
|
|
270
|
+
return null;
|
|
262
271
|
}
|
|
263
272
|
function removeCharacter(input, charToRemove) {
|
|
264
273
|
let output = "";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminal-pilot",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Playwright-like SDK for automating interactive CLI apps through a real PTY",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -9,19 +9,12 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./mcp": {
|
|
14
|
-
"types": "./dist/mcp-server.d.ts",
|
|
15
|
-
"import": "./dist/mcp-server.js"
|
|
16
12
|
}
|
|
17
13
|
},
|
|
18
|
-
"bin": {
|
|
19
|
-
"terminal-pilot-mcp": "dist/mcp-server.js"
|
|
20
|
-
},
|
|
21
14
|
"scripts": {
|
|
22
15
|
"build": "tsc",
|
|
23
16
|
"prepublishOnly": "tsc",
|
|
24
|
-
"install-local-package": "TMPD=$(mktemp -d) &&
|
|
17
|
+
"install-local-package": "TMPD=$(mktemp -d) && npm pack --pack-destination $TMPD && npm install -g $TMPD/terminal-pilot-*.tgz && rm -rf $TMPD"
|
|
25
18
|
},
|
|
26
19
|
"files": [
|
|
27
20
|
"dist"
|
|
@@ -37,14 +30,12 @@
|
|
|
37
30
|
"license": "MIT",
|
|
38
31
|
"keywords": [
|
|
39
32
|
"terminal",
|
|
40
|
-
"mcp",
|
|
41
33
|
"pty",
|
|
42
34
|
"headless"
|
|
43
35
|
],
|
|
44
36
|
"dependencies": {
|
|
45
37
|
"headless-terminal": "^0.4.0",
|
|
46
|
-
"node-pty": "^1.1.0"
|
|
47
|
-
"tiny-stdio-mcp-server": "^0.1.0"
|
|
38
|
+
"node-pty": "^1.1.0"
|
|
48
39
|
},
|
|
49
40
|
"devDependencies": {}
|
|
50
41
|
}
|
package/dist/mcp-server.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { type Server } from "tiny-stdio-mcp-server";
|
|
3
|
-
import { TerminalPilot } from "./terminal-pilot.js";
|
|
4
|
-
type RuntimeProcess = Pick<typeof process, "on" | "off">;
|
|
5
|
-
export declare function createTerminalPilotMcpServer(agent: TerminalPilot): Server;
|
|
6
|
-
export declare function main({ launchAgent, createMcpServer, runtimeProcess }?: {
|
|
7
|
-
launchAgent?: typeof TerminalPilot.launch;
|
|
8
|
-
createMcpServer?: (agent: TerminalPilot) => Server;
|
|
9
|
-
runtimeProcess?: RuntimeProcess;
|
|
10
|
-
}): Promise<void>;
|
|
11
|
-
export {};
|
package/dist/mcp-server.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import packageJson from "../package.json" with { type: "json" };
|
|
3
|
-
import { createServer } from "tiny-stdio-mcp-server";
|
|
4
|
-
import { terminalPilotMcpTools } from "./mcp-tools.js";
|
|
5
|
-
import { TerminalPilot } from "./terminal-pilot.js";
|
|
6
|
-
export function createTerminalPilotMcpServer(agent) {
|
|
7
|
-
const server = createServer({
|
|
8
|
-
name: "terminal-pilot",
|
|
9
|
-
version: packageJson.version
|
|
10
|
-
});
|
|
11
|
-
for (const tool of terminalPilotMcpTools(agent)) {
|
|
12
|
-
server.tool(tool.name, tool.description, tool.inputSchema, tool.handler);
|
|
13
|
-
}
|
|
14
|
-
return server;
|
|
15
|
-
}
|
|
16
|
-
export async function main({ launchAgent = TerminalPilot.launch, createMcpServer = createTerminalPilotMcpServer, runtimeProcess = process } = {}) {
|
|
17
|
-
const agent = await launchAgent();
|
|
18
|
-
let closed = false;
|
|
19
|
-
const closeAgent = async () => {
|
|
20
|
-
if (closed) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
closed = true;
|
|
24
|
-
await agent.close();
|
|
25
|
-
};
|
|
26
|
-
const handleExit = () => {
|
|
27
|
-
void closeAgent();
|
|
28
|
-
};
|
|
29
|
-
runtimeProcess.on("exit", handleExit);
|
|
30
|
-
try {
|
|
31
|
-
await createMcpServer(agent).listen();
|
|
32
|
-
}
|
|
33
|
-
finally {
|
|
34
|
-
runtimeProcess.off("exit", handleExit);
|
|
35
|
-
await closeAgent();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
39
|
-
void main();
|
|
40
|
-
}
|
package/dist/mcp-tools.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { type ToolDefinition } from "tiny-stdio-mcp-server";
|
|
2
|
-
import type { TerminalKey } from "./keys.js";
|
|
3
|
-
import type { TerminalPilot } from "./terminal-pilot.js";
|
|
4
|
-
export type TerminalPilotMcpTool<T = Record<string, unknown>> = ToolDefinition<T>;
|
|
5
|
-
export declare function terminalCreateSessionTool(agent: TerminalPilot): TerminalPilotMcpTool<{
|
|
6
|
-
command: string;
|
|
7
|
-
args?: string[];
|
|
8
|
-
cwd?: string;
|
|
9
|
-
cols?: number;
|
|
10
|
-
rows?: number;
|
|
11
|
-
observe?: boolean;
|
|
12
|
-
}>;
|
|
13
|
-
export declare function terminalTypeTool(agent: TerminalPilot): TerminalPilotMcpTool<{
|
|
14
|
-
sessionId: string;
|
|
15
|
-
text: string;
|
|
16
|
-
}>;
|
|
17
|
-
export declare function terminalPressKeyTool(agent: TerminalPilot): TerminalPilotMcpTool<{
|
|
18
|
-
sessionId: string;
|
|
19
|
-
key: TerminalKey;
|
|
20
|
-
}>;
|
|
21
|
-
export declare function terminalSendSignalTool(agent: TerminalPilot): TerminalPilotMcpTool<{
|
|
22
|
-
sessionId: string;
|
|
23
|
-
signal: string;
|
|
24
|
-
}>;
|
|
25
|
-
export declare function terminalWaitForTool(agent: TerminalPilot): TerminalPilotMcpTool<{
|
|
26
|
-
sessionId: string;
|
|
27
|
-
pattern: string;
|
|
28
|
-
timeout?: number;
|
|
29
|
-
}>;
|
|
30
|
-
export declare function terminalReadScreenTool(agent: TerminalPilot): TerminalPilotMcpTool<{
|
|
31
|
-
sessionId: string;
|
|
32
|
-
}>;
|
|
33
|
-
export declare function terminalReadHistoryTool(agent: TerminalPilot): TerminalPilotMcpTool<{
|
|
34
|
-
sessionId: string;
|
|
35
|
-
last?: number;
|
|
36
|
-
}>;
|
|
37
|
-
export declare function terminalResizeTool(agent: TerminalPilot): TerminalPilotMcpTool<{
|
|
38
|
-
sessionId: string;
|
|
39
|
-
cols: number;
|
|
40
|
-
rows: number;
|
|
41
|
-
}>;
|
|
42
|
-
export declare function terminalCloseSessionTool(agent: TerminalPilot): TerminalPilotMcpTool<{
|
|
43
|
-
sessionId: string;
|
|
44
|
-
}>;
|
|
45
|
-
export declare function terminalPilotMcpTools(agent: TerminalPilot): Array<TerminalPilotMcpTool<any>>;
|
|
46
|
-
export declare function terminalListSessionsTool(agent: TerminalPilot): TerminalPilotMcpTool<Record<string, never>>;
|
package/dist/mcp-tools.js
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { defineSchema } from "tiny-stdio-mcp-server";
|
|
2
|
-
export function terminalCreateSessionTool(agent) {
|
|
3
|
-
return {
|
|
4
|
-
name: "terminal_create_session",
|
|
5
|
-
description: "Spawn an interactive CLI in a PTY",
|
|
6
|
-
inputSchema: defineSchema({
|
|
7
|
-
command: { type: "string", description: "Command to execute" },
|
|
8
|
-
args: { type: "array", description: "Command arguments", optional: true },
|
|
9
|
-
cwd: { type: "string", description: "Working directory", optional: true },
|
|
10
|
-
cols: { type: "number", description: "Terminal width in columns", optional: true },
|
|
11
|
-
rows: { type: "number", description: "Terminal height in rows", optional: true },
|
|
12
|
-
observe: { type: "boolean", description: "Mirror PTY output to stderr", optional: true }
|
|
13
|
-
}),
|
|
14
|
-
async handler(input) {
|
|
15
|
-
const session = await agent.newSession(input);
|
|
16
|
-
return { sessionId: session.id, pid: session.pid };
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
export function terminalTypeTool(agent) {
|
|
21
|
-
return {
|
|
22
|
-
name: "terminal_type",
|
|
23
|
-
description: "Write text to an active terminal session",
|
|
24
|
-
inputSchema: defineSchema({
|
|
25
|
-
sessionId: { type: "string", description: "Terminal session id" },
|
|
26
|
-
text: { type: "string", description: "Text to write to the session" }
|
|
27
|
-
}),
|
|
28
|
-
async handler(input) {
|
|
29
|
-
await agent.getSession(input.sessionId).fill(input.text);
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
export function terminalPressKeyTool(agent) {
|
|
35
|
-
return {
|
|
36
|
-
name: "terminal_press_key",
|
|
37
|
-
description: "Send a named key press to an active terminal session",
|
|
38
|
-
inputSchema: defineSchema({
|
|
39
|
-
sessionId: { type: "string", description: "Terminal session id" },
|
|
40
|
-
key: { type: "string", description: "Named key to press" }
|
|
41
|
-
}),
|
|
42
|
-
async handler(input) {
|
|
43
|
-
await agent.getSession(input.sessionId).press(input.key);
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
export function terminalSendSignalTool(agent) {
|
|
49
|
-
return {
|
|
50
|
-
name: "terminal_send_signal",
|
|
51
|
-
description: "Send a process signal to an active terminal session",
|
|
52
|
-
inputSchema: defineSchema({
|
|
53
|
-
sessionId: { type: "string", description: "Terminal session id" },
|
|
54
|
-
signal: { type: "string", description: "Signal to send to the session process" }
|
|
55
|
-
}),
|
|
56
|
-
async handler(input) {
|
|
57
|
-
await agent.getSession(input.sessionId).signal(input.signal);
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
export function terminalWaitForTool(agent) {
|
|
63
|
-
return {
|
|
64
|
-
name: "terminal_wait_for",
|
|
65
|
-
description: "Wait for terminal output to match a pattern",
|
|
66
|
-
inputSchema: defineSchema({
|
|
67
|
-
sessionId: { type: "string", description: "Terminal session id" },
|
|
68
|
-
pattern: { type: "string", description: "Regular expression pattern to wait for" },
|
|
69
|
-
timeout: { type: "number", description: "Maximum wait time in milliseconds", optional: true }
|
|
70
|
-
}),
|
|
71
|
-
async handler(input) {
|
|
72
|
-
const session = agent.getSession(input.sessionId);
|
|
73
|
-
const pattern = new RegExp(input.pattern);
|
|
74
|
-
const output = input.timeout === undefined
|
|
75
|
-
? await session.waitFor(pattern)
|
|
76
|
-
: await session.waitFor(pattern, { timeout: input.timeout });
|
|
77
|
-
return { matched: true, output };
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
export function terminalReadScreenTool(agent) {
|
|
82
|
-
return {
|
|
83
|
-
name: "terminal_read_screen",
|
|
84
|
-
description: "Read the current visible terminal screen",
|
|
85
|
-
inputSchema: defineSchema({
|
|
86
|
-
sessionId: { type: "string", description: "Terminal session id" }
|
|
87
|
-
}),
|
|
88
|
-
async handler(input) {
|
|
89
|
-
const screen = await agent.getSession(input.sessionId).screen();
|
|
90
|
-
return {
|
|
91
|
-
lines: [...screen.lines],
|
|
92
|
-
cursor: { ...screen.cursor },
|
|
93
|
-
size: { ...screen.size }
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
export function terminalReadHistoryTool(agent) {
|
|
99
|
-
return {
|
|
100
|
-
name: "terminal_read_history",
|
|
101
|
-
description: "Read terminal output history",
|
|
102
|
-
inputSchema: defineSchema({
|
|
103
|
-
sessionId: { type: "string", description: "Terminal session id" },
|
|
104
|
-
last: { type: "number", description: "Return only the last N lines", optional: true }
|
|
105
|
-
}),
|
|
106
|
-
async handler(input) {
|
|
107
|
-
const lines = await agent.getSession(input.sessionId).history({ last: input.last });
|
|
108
|
-
return { lines };
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
export function terminalResizeTool(agent) {
|
|
113
|
-
return {
|
|
114
|
-
name: "terminal_resize",
|
|
115
|
-
description: "Resize an active terminal session",
|
|
116
|
-
inputSchema: defineSchema({
|
|
117
|
-
sessionId: { type: "string", description: "Terminal session id" },
|
|
118
|
-
cols: { type: "number", description: "Terminal width in columns" },
|
|
119
|
-
rows: { type: "number", description: "Terminal height in rows" }
|
|
120
|
-
}),
|
|
121
|
-
async handler(input) {
|
|
122
|
-
await agent.getSession(input.sessionId).resize(input.cols, input.rows);
|
|
123
|
-
return undefined;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
export function terminalCloseSessionTool(agent) {
|
|
128
|
-
return {
|
|
129
|
-
name: "terminal_close_session",
|
|
130
|
-
description: "Close an active terminal session",
|
|
131
|
-
inputSchema: defineSchema({
|
|
132
|
-
sessionId: { type: "string", description: "Terminal session id" }
|
|
133
|
-
}),
|
|
134
|
-
async handler(input) {
|
|
135
|
-
const exitCode = await agent.getSession(input.sessionId).close();
|
|
136
|
-
return { exitCode };
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
export function terminalPilotMcpTools(agent) {
|
|
141
|
-
return [
|
|
142
|
-
terminalCreateSessionTool(agent),
|
|
143
|
-
terminalTypeTool(agent),
|
|
144
|
-
terminalPressKeyTool(agent),
|
|
145
|
-
terminalSendSignalTool(agent),
|
|
146
|
-
terminalWaitForTool(agent),
|
|
147
|
-
terminalReadScreenTool(agent),
|
|
148
|
-
terminalReadHistoryTool(agent),
|
|
149
|
-
terminalResizeTool(agent),
|
|
150
|
-
terminalCloseSessionTool(agent),
|
|
151
|
-
terminalListSessionsTool(agent)
|
|
152
|
-
];
|
|
153
|
-
}
|
|
154
|
-
export function terminalListSessionsTool(agent) {
|
|
155
|
-
return {
|
|
156
|
-
name: "terminal_list_sessions",
|
|
157
|
-
description: "List active terminal sessions",
|
|
158
|
-
inputSchema: defineSchema({}),
|
|
159
|
-
async handler() {
|
|
160
|
-
return {
|
|
161
|
-
sessions: agent.sessions().map((session) => ({
|
|
162
|
-
id: session.id,
|
|
163
|
-
command: session.command,
|
|
164
|
-
pid: session.pid
|
|
165
|
-
}))
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
}
|
package/dist/testing/menu-cli.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
import readline from "node:readline";
|
|
3
|
-
const options = ["Option 1", "Option 2", "Option 3"];
|
|
4
|
-
const { stdin, stdout } = process;
|
|
5
|
-
let selectedIndex = 0;
|
|
6
|
-
let renderedLineCount = 0;
|
|
7
|
-
let hasExited = false;
|
|
8
|
-
readline.emitKeypressEvents(stdin);
|
|
9
|
-
if (stdin.isTTY) {
|
|
10
|
-
stdin.setRawMode(true);
|
|
11
|
-
}
|
|
12
|
-
stdin.resume();
|
|
13
|
-
render();
|
|
14
|
-
stdin.on("keypress", (_, key) => {
|
|
15
|
-
if (key.ctrl && key.name === "c") {
|
|
16
|
-
exitWithCode(130);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
if (key.name === "up") {
|
|
20
|
-
selectedIndex = (selectedIndex + options.length - 1) % options.length;
|
|
21
|
-
render();
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
if (key.name === "down") {
|
|
25
|
-
selectedIndex = (selectedIndex + 1) % options.length;
|
|
26
|
-
render();
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
if (key.name === "return" || key.name === "enter") {
|
|
30
|
-
cleanup();
|
|
31
|
-
stdout.write(`You selected: ${options[selectedIndex]}\n`);
|
|
32
|
-
process.exit(0);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
process.on("SIGINT", () => {
|
|
36
|
-
exitWithCode(130);
|
|
37
|
-
});
|
|
38
|
-
function cleanup() {
|
|
39
|
-
if (stdin.isTTY) {
|
|
40
|
-
stdin.setRawMode(false);
|
|
41
|
-
}
|
|
42
|
-
stdin.pause();
|
|
43
|
-
}
|
|
44
|
-
function render() {
|
|
45
|
-
if (renderedLineCount > 0) {
|
|
46
|
-
readline.moveCursor(stdout, 0, -renderedLineCount);
|
|
47
|
-
readline.clearScreenDown(stdout);
|
|
48
|
-
}
|
|
49
|
-
const lines = [
|
|
50
|
-
"Select an option:",
|
|
51
|
-
...options.map((option, index) => `${index === selectedIndex ? ">" : " "} ${option}`)
|
|
52
|
-
];
|
|
53
|
-
stdout.write(`${lines.join("\n")}\n`);
|
|
54
|
-
renderedLineCount = lines.length;
|
|
55
|
-
}
|
|
56
|
-
function exitWithCode(code) {
|
|
57
|
-
if (hasExited) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
hasExited = true;
|
|
61
|
-
cleanup();
|
|
62
|
-
process.exit(code);
|
|
63
|
-
}
|
package/dist/testing/test-cli.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
import readline from "node:readline";
|
|
3
|
-
const terminal = readline.createInterface({
|
|
4
|
-
input: process.stdin,
|
|
5
|
-
output: process.stdout
|
|
6
|
-
});
|
|
7
|
-
let hasGreeted = false;
|
|
8
|
-
process.stdout.write("What is your name? ");
|
|
9
|
-
terminal.on("line", (name) => {
|
|
10
|
-
if (hasGreeted) {
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
hasGreeted = true;
|
|
14
|
-
console.log(`Hello, ${name}!`);
|
|
15
|
-
terminal.close();
|
|
16
|
-
});
|
|
17
|
-
terminal.on("close", () => {
|
|
18
|
-
if (!hasGreeted) {
|
|
19
|
-
console.log(`Hello, ${terminal.line}!`);
|
|
20
|
-
}
|
|
21
|
-
process.exit(0);
|
|
22
|
-
});
|