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 CHANGED
@@ -1,38 +1,23 @@
1
- # @poe-code/terminal-pilot
1
+ # terminal-pilot
2
2
 
3
- `terminal-pilot` is a Playwright-like SDK and MCP server for automating interactive CLI apps through a real pseudoterminal (PTY).
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 "@poe-code/terminal-pilot";
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 "@poe-code/terminal-pilot";
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 "@poe-code/terminal-pilot";
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 "@poe-code/terminal-pilot";
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 namedSequence = NAMED_KEY_SEQUENCES[key];
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 (key.startsWith("Control+")) {
23
- return controlKeyToSequence(key);
28
+ if (lowerKey.startsWith("control+")) {
29
+ return controlKeyToSequence(key.slice("control+".length));
24
30
  }
25
- if (key.startsWith("Alt+")) {
26
- const nestedKey = key.slice("Alt+".length);
31
+ if (lowerKey.startsWith("alt+")) {
32
+ const nestedKey = key.slice("alt+".length);
27
33
  if (nestedKey.length === 0) {
28
- throw new Error(`Unknown terminal key: ${key}`);
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 new Error(`Unknown terminal key: ${key}`);
43
+ throw unknownKeyError(key);
38
44
  }
39
45
  }
40
- throw new Error(`Unknown terminal key: ${key}`);
46
+ throw unknownKeyError(key);
41
47
  }
42
- function controlKeyToSequence(key) {
43
- const controlKey = key.slice("Control+".length);
48
+ function controlKeyToSequence(controlKey) {
44
49
  if (controlKey.length !== 1) {
45
- throw new Error(`Unknown terminal key: ${key}`);
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 new Error(`Unknown terminal key: ${key}`);
55
+ throw unknownKeyError(`Control+${controlKey}`);
51
56
  }
52
57
  return String.fromCharCode(charCode - 64);
53
58
  }
@@ -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.pty.resize(cols, rows);
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
- const exitCode = await waitForExit(this.exitPromise, CLOSE_AFTER_SIGNAL_GRACE_MS);
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
- if (typeof pattern === "string") {
256
- return buffer.includes(pattern) ? pattern : null;
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
- const flags = removeCharacter(pattern.flags, "g");
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.1",
4
- "description": "Headless terminal session manager with MCP server support",
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) && cd ../tiny-stdio-mcp-server && npm pack --pack-destination $TMPD && cd ../terminal-pilot && npm pack --pack-destination $TMPD && npm install -g $TMPD/poe-code-terminal-pilot-*.tgz $TMPD/tiny-stdio-mcp-server-*.tgz && rm -rf $TMPD"
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
  }
@@ -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 {};
@@ -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
- }
@@ -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
- }
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env tsx
2
- export {};
@@ -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
- }
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env tsx
2
- export {};
@@ -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
- });