@ellery/terminal-mcp 0.1.1

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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +207 -0
  3. package/dist/client.d.ts +5 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +185 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +192 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/prompts/index.d.ts +3 -0
  12. package/dist/prompts/index.d.ts.map +1 -0
  13. package/dist/prompts/index.js +79 -0
  14. package/dist/prompts/index.js.map +1 -0
  15. package/dist/server.d.ts +28 -0
  16. package/dist/server.d.ts.map +1 -0
  17. package/dist/server.js +58 -0
  18. package/dist/server.js.map +1 -0
  19. package/dist/terminal/index.d.ts +4 -0
  20. package/dist/terminal/index.d.ts.map +1 -0
  21. package/dist/terminal/index.js +3 -0
  22. package/dist/terminal/index.js.map +1 -0
  23. package/dist/terminal/manager.d.ts +54 -0
  24. package/dist/terminal/manager.d.ts.map +1 -0
  25. package/dist/terminal/manager.js +79 -0
  26. package/dist/terminal/manager.js.map +1 -0
  27. package/dist/terminal/session.d.ts +85 -0
  28. package/dist/terminal/session.d.ts.map +1 -0
  29. package/dist/terminal/session.js +246 -0
  30. package/dist/terminal/session.js.map +1 -0
  31. package/dist/tools/getContent.d.ts +32 -0
  32. package/dist/tools/getContent.d.ts.map +1 -0
  33. package/dist/tools/getContent.js +38 -0
  34. package/dist/tools/getContent.js.map +1 -0
  35. package/dist/tools/index.d.ts +4 -0
  36. package/dist/tools/index.d.ts.map +1 -0
  37. package/dist/tools/index.js +43 -0
  38. package/dist/tools/index.js.map +1 -0
  39. package/dist/tools/screenshot.d.ts +20 -0
  40. package/dist/tools/screenshot.d.ts.map +1 -0
  41. package/dist/tools/screenshot.js +28 -0
  42. package/dist/tools/screenshot.js.map +1 -0
  43. package/dist/tools/sendKey.d.ts +31 -0
  44. package/dist/tools/sendKey.d.ts.map +1 -0
  45. package/dist/tools/sendKey.js +38 -0
  46. package/dist/tools/sendKey.js.map +1 -0
  47. package/dist/tools/type.d.ts +31 -0
  48. package/dist/tools/type.d.ts.map +1 -0
  49. package/dist/tools/type.js +31 -0
  50. package/dist/tools/type.js.map +1 -0
  51. package/dist/transport/index.d.ts +2 -0
  52. package/dist/transport/index.d.ts.map +1 -0
  53. package/dist/transport/index.js +2 -0
  54. package/dist/transport/index.js.map +1 -0
  55. package/dist/transport/socket.d.ts +30 -0
  56. package/dist/transport/socket.d.ts.map +1 -0
  57. package/dist/transport/socket.js +168 -0
  58. package/dist/transport/socket.js.map +1 -0
  59. package/dist/ui/index.d.ts +24 -0
  60. package/dist/ui/index.d.ts.map +1 -0
  61. package/dist/ui/index.js +124 -0
  62. package/dist/ui/index.js.map +1 -0
  63. package/dist/utils/keys.d.ts +16 -0
  64. package/dist/utils/keys.d.ts.map +1 -0
  65. package/dist/utils/keys.js +98 -0
  66. package/dist/utils/keys.js.map +1 -0
  67. package/dist/utils/stats.d.ts +46 -0
  68. package/dist/utils/stats.d.ts.map +1 -0
  69. package/dist/utils/stats.js +89 -0
  70. package/dist/utils/stats.js.map +1 -0
  71. package/logo.png +0 -0
  72. package/package.json +47 -0
  73. package/tsconfig.json +18 -0
@@ -0,0 +1,79 @@
1
+ import { ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
2
+ const TOOL_USAGE_PROMPT = `# Terminal MCP Tool Usage Guide
3
+
4
+ ## Overview
5
+ This MCP server provides tools to interact with a terminal emulator. Use these tools to execute commands and read output.
6
+
7
+ ## Tools
8
+
9
+ ### type
10
+ Send text input to the terminal. Text is written exactly as provided - no Enter key is sent automatically.
11
+
12
+ To execute a command, use type() followed by sendKey('Enter').
13
+
14
+ **Example workflow:**
15
+ 1. type('ls -la') - types the command
16
+ 2. sendKey('Enter') - executes it
17
+ 3. getContent() - reads the output
18
+
19
+ ### sendKey
20
+ Send a special key or key combination to the terminal.
21
+
22
+ **Common keys:** Enter, Tab, Escape, Backspace, Delete, ArrowUp, ArrowDown, ArrowLeft, ArrowRight
23
+
24
+ **Navigation:** Home, End, PageUp, PageDown
25
+
26
+ **Control sequences:**
27
+ - Ctrl+C - interrupt current process
28
+ - Ctrl+D - EOF/exit
29
+ - Ctrl+Z - suspend process
30
+ - Ctrl+L - clear screen
31
+ - Ctrl+A - move to start of line
32
+ - Ctrl+E - move to end of line
33
+
34
+ **Function keys:** F1-F12
35
+
36
+ ### getContent
37
+ Get terminal content as plain text. Use after sending commands to see output.
38
+
39
+ Returns full scrollback buffer by default (up to 1000 lines). Set visibleOnly=true for just the current viewport (useful when output is very long).
40
+
41
+ Prefer this over takeScreenshot when you only need text content.
42
+
43
+ ### takeScreenshot
44
+ Capture terminal state as structured JSON with:
45
+ - content: visible text
46
+ - cursor: {x, y} position
47
+ - dimensions: {cols, rows}
48
+
49
+ Use when you need cursor position (e.g., for interactive apps, editors) or terminal dimensions. For simple command output, prefer getContent().
50
+ `;
51
+ const prompts = [
52
+ {
53
+ name: "tool-usage",
54
+ description: "Instructions for effectively using terminal-mcp tools",
55
+ },
56
+ ];
57
+ export function registerPrompts(server) {
58
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
59
+ prompts,
60
+ }));
61
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
62
+ const { name } = request.params;
63
+ if (name === "tool-usage") {
64
+ return {
65
+ messages: [
66
+ {
67
+ role: "user",
68
+ content: {
69
+ type: "text",
70
+ text: TOOL_USAGE_PROMPT,
71
+ },
72
+ },
73
+ ],
74
+ };
75
+ }
76
+ throw new Error(`Unknown prompt: ${name}`);
77
+ });
78
+ }
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/prompts/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAE5C,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgDzB,CAAC;AAEF,MAAM,OAAO,GAAG;IACd;QACE,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,uDAAuD;KACrE;CACF,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,MAAM,CAAC,iBAAiB,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC9D,OAAO;KACR,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QACjE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;QAEhC,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;YAC1B,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,IAAI,EAAE,MAAe;wBACrB,OAAO,EAAE;4BACP,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,iBAAiB;yBACxB;qBACF;iBACF;aACF,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
3
+ import { TerminalManager } from "./terminal/index.js";
4
+ export interface ServerOptions {
5
+ cols?: number;
6
+ rows?: number;
7
+ shell?: string;
8
+ }
9
+ /**
10
+ * Create and configure the MCP server with an existing terminal manager
11
+ */
12
+ export declare function createServerWithManager(manager: TerminalManager): Server;
13
+ /**
14
+ * Create and configure the MCP server with a new terminal manager
15
+ */
16
+ export declare function createServer(options?: ServerOptions): {
17
+ server: Server;
18
+ manager: TerminalManager;
19
+ };
20
+ /**
21
+ * Connect an MCP server to a transport
22
+ */
23
+ export declare function connectServer(server: Server, transport: Transport): Promise<void>;
24
+ /**
25
+ * Start the MCP server with stdio transport (legacy mode)
26
+ */
27
+ export declare function startServer(options?: ServerOptions): Promise<void>;
28
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAEnE,OAAO,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAItD,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAkBxE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,aAAkB,GAAG;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,eAAe,CAAC;CAC1B,CAUA;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvF;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5E"}
package/dist/server.js ADDED
@@ -0,0 +1,58 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { TerminalManager } from "./terminal/index.js";
4
+ import { registerTools } from "./tools/index.js";
5
+ import { registerPrompts } from "./prompts/index.js";
6
+ /**
7
+ * Create and configure the MCP server with an existing terminal manager
8
+ */
9
+ export function createServerWithManager(manager) {
10
+ const server = new Server({
11
+ name: "terminal-mcp",
12
+ version: "0.1.0",
13
+ }, {
14
+ capabilities: {
15
+ tools: {},
16
+ prompts: {},
17
+ },
18
+ });
19
+ registerTools(server, manager);
20
+ registerPrompts(server);
21
+ return server;
22
+ }
23
+ /**
24
+ * Create and configure the MCP server with a new terminal manager
25
+ */
26
+ export function createServer(options = {}) {
27
+ const manager = new TerminalManager({
28
+ cols: options.cols,
29
+ rows: options.rows,
30
+ shell: options.shell,
31
+ });
32
+ const server = createServerWithManager(manager);
33
+ return { server, manager };
34
+ }
35
+ /**
36
+ * Connect an MCP server to a transport
37
+ */
38
+ export async function connectServer(server, transport) {
39
+ await server.connect(transport);
40
+ }
41
+ /**
42
+ * Start the MCP server with stdio transport (legacy mode)
43
+ */
44
+ export async function startServer(options = {}) {
45
+ const { server, manager } = createServer(options);
46
+ const transport = new StdioServerTransport();
47
+ // Handle graceful shutdown
48
+ process.on("SIGINT", () => {
49
+ manager.dispose();
50
+ process.exit(0);
51
+ });
52
+ process.on("SIGTERM", () => {
53
+ manager.dispose();
54
+ process.exit(0);
55
+ });
56
+ await server.connect(transport);
57
+ }
58
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAQrD;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAwB;IAC9D,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;QACE,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,OAAO;KACjB,EACD;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;YACT,OAAO,EAAE,EAAE;SACZ;KACF,CACF,CAAC;IAEF,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,eAAe,CAAC,MAAM,CAAC,CAAC;IAExB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,UAAyB,EAAE;IAItD,MAAM,OAAO,GAAG,IAAI,eAAe,CAAC;QAClC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAEhD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc,EAAE,SAAoB;IACtE,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAAyB,EAAE;IAC3D,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAE7C,2BAA2B;IAC3B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,OAAO,EAAE,CAAC;QAClB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,OAAO,CAAC,OAAO,EAAE,CAAC;QAClB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { TerminalSession } from "./session.js";
2
+ export type { TerminalSessionOptions, ScreenshotResult } from "./session.js";
3
+ export { TerminalManager } from "./manager.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/terminal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { TerminalSession } from "./session.js";
2
+ export { TerminalManager } from "./manager.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/terminal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,54 @@
1
+ import { TerminalSession, TerminalSessionOptions, ScreenshotResult } from "./session.js";
2
+ /**
3
+ * Manages the terminal session lifecycle
4
+ * Currently supports a single session for simplicity
5
+ */
6
+ export declare class TerminalManager {
7
+ private session;
8
+ private options;
9
+ constructor(options?: TerminalSessionOptions);
10
+ /**
11
+ * Get or create the terminal session
12
+ */
13
+ getSession(): TerminalSession;
14
+ /**
15
+ * Check if a session exists and is active
16
+ */
17
+ hasActiveSession(): boolean;
18
+ /**
19
+ * Write data to the terminal
20
+ */
21
+ write(data: string): void;
22
+ /**
23
+ * Get terminal content
24
+ */
25
+ getContent(): string;
26
+ /**
27
+ * Get visible content only
28
+ */
29
+ getVisibleContent(): string;
30
+ /**
31
+ * Take a screenshot
32
+ */
33
+ takeScreenshot(): ScreenshotResult;
34
+ /**
35
+ * Clear the terminal
36
+ */
37
+ clear(): void;
38
+ /**
39
+ * Resize the terminal
40
+ */
41
+ resize(cols: number, rows: number): void;
42
+ /**
43
+ * Get terminal dimensions
44
+ */
45
+ getDimensions(): {
46
+ cols: number;
47
+ rows: number;
48
+ };
49
+ /**
50
+ * Dispose of the current session
51
+ */
52
+ dispose(): void;
53
+ }
54
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/terminal/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzF;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,OAAO,CAAyB;gBAE5B,OAAO,GAAE,sBAA2B;IAIhD;;OAEG;IACH,UAAU,IAAI,eAAe;IAO7B;;OAEG;IACH,gBAAgB,IAAI,OAAO;IAI3B;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIzB;;OAEG;IACH,UAAU,IAAI,MAAM;IAIpB;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,cAAc,IAAI,gBAAgB;IAIlC;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIxC;;OAEG;IACH,aAAa,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;IAI/C;;OAEG;IACH,OAAO,IAAI,IAAI;CAMhB"}
@@ -0,0 +1,79 @@
1
+ import { TerminalSession } from "./session.js";
2
+ /**
3
+ * Manages the terminal session lifecycle
4
+ * Currently supports a single session for simplicity
5
+ */
6
+ export class TerminalManager {
7
+ session = null;
8
+ options;
9
+ constructor(options = {}) {
10
+ this.options = options;
11
+ }
12
+ /**
13
+ * Get or create the terminal session
14
+ */
15
+ getSession() {
16
+ if (!this.session || !this.session.isActive()) {
17
+ this.session = new TerminalSession(this.options);
18
+ }
19
+ return this.session;
20
+ }
21
+ /**
22
+ * Check if a session exists and is active
23
+ */
24
+ hasActiveSession() {
25
+ return this.session !== null && this.session.isActive();
26
+ }
27
+ /**
28
+ * Write data to the terminal
29
+ */
30
+ write(data) {
31
+ this.getSession().write(data);
32
+ }
33
+ /**
34
+ * Get terminal content
35
+ */
36
+ getContent() {
37
+ return this.getSession().getContent();
38
+ }
39
+ /**
40
+ * Get visible content only
41
+ */
42
+ getVisibleContent() {
43
+ return this.getSession().getVisibleContent();
44
+ }
45
+ /**
46
+ * Take a screenshot
47
+ */
48
+ takeScreenshot() {
49
+ return this.getSession().takeScreenshot();
50
+ }
51
+ /**
52
+ * Clear the terminal
53
+ */
54
+ clear() {
55
+ this.getSession().clear();
56
+ }
57
+ /**
58
+ * Resize the terminal
59
+ */
60
+ resize(cols, rows) {
61
+ this.getSession().resize(cols, rows);
62
+ }
63
+ /**
64
+ * Get terminal dimensions
65
+ */
66
+ getDimensions() {
67
+ return this.getSession().getDimensions();
68
+ }
69
+ /**
70
+ * Dispose of the current session
71
+ */
72
+ dispose() {
73
+ if (this.session) {
74
+ this.session.dispose();
75
+ this.session = null;
76
+ }
77
+ }
78
+ }
79
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/terminal/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA4C,MAAM,cAAc,CAAC;AAEzF;;;GAGG;AACH,MAAM,OAAO,eAAe;IAClB,OAAO,GAA2B,IAAI,CAAC;IACvC,OAAO,CAAyB;IAExC,YAAY,UAAkC,EAAE;QAC9C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC9C,IAAI,CAAC,OAAO,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,OAAO,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC1D,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAY;QAChB,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,UAAU,EAAE,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,iBAAiB;QACf,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,iBAAiB,EAAE,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,cAAc;QACZ,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,cAAc,EAAE,CAAC;IAC5C,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,IAAY,EAAE,IAAY;QAC/B,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,aAAa;QACX,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,aAAa,EAAE,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,OAAO;QACL,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACvB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,85 @@
1
+ export interface TerminalSessionOptions {
2
+ cols?: number;
3
+ rows?: number;
4
+ shell?: string;
5
+ cwd?: string;
6
+ env?: Record<string, string>;
7
+ }
8
+ export interface ScreenshotResult {
9
+ content: string;
10
+ cursor: {
11
+ x: number;
12
+ y: number;
13
+ };
14
+ dimensions: {
15
+ cols: number;
16
+ rows: number;
17
+ };
18
+ }
19
+ /**
20
+ * Terminal session that combines node-pty with xterm.js headless
21
+ * for full terminal emulation
22
+ */
23
+ export declare class TerminalSession {
24
+ private ptyProcess;
25
+ private terminal;
26
+ private disposed;
27
+ private dataListeners;
28
+ private exitListeners;
29
+ private rcFile;
30
+ private zdotdir;
31
+ /**
32
+ * Set up shell-specific prompt customization
33
+ * Returns args to pass to shell and env modifications
34
+ */
35
+ private setupShellPrompt;
36
+ constructor(options?: TerminalSessionOptions);
37
+ /**
38
+ * Subscribe to PTY output data
39
+ */
40
+ onData(listener: (data: string) => void): void;
41
+ /**
42
+ * Subscribe to PTY exit
43
+ */
44
+ onExit(listener: (code: number) => void): void;
45
+ /**
46
+ * Write data to the terminal (simulates typing)
47
+ */
48
+ write(data: string): void;
49
+ /**
50
+ * Get the current terminal buffer content as plain text
51
+ */
52
+ getContent(): string;
53
+ /**
54
+ * Get only the visible viewport content
55
+ */
56
+ getVisibleContent(): string;
57
+ /**
58
+ * Take a screenshot of the terminal state
59
+ */
60
+ takeScreenshot(): ScreenshotResult;
61
+ /**
62
+ * Clear the terminal screen
63
+ */
64
+ clear(): void;
65
+ /**
66
+ * Resize the terminal
67
+ */
68
+ resize(cols: number, rows: number): void;
69
+ /**
70
+ * Check if the session is still active
71
+ */
72
+ isActive(): boolean;
73
+ /**
74
+ * Get terminal dimensions
75
+ */
76
+ getDimensions(): {
77
+ cols: number;
78
+ rows: number;
79
+ };
80
+ /**
81
+ * Dispose of the terminal session
82
+ */
83
+ dispose(): void;
84
+ }
85
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/terminal/session.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE;QACN,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;KACX,CAAC;IACF,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,UAAU,CAAW;IAC7B,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,aAAa,CAAqC;IAE1D,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,OAAO,CAAuB;IAEtC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;gBA+CZ,OAAO,GAAE,sBAA2B;IA6ChD;;OAEG;IACH,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAI9C;;OAEG;IACH,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAI9C;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAOzB;;OAEG;IACH,UAAU,IAAI,MAAM;IAwBpB;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAmB3B;;OAEG;IACH,cAAc,IAAI,gBAAgB;IAoBlC;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQxC;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,aAAa,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;IAO/C;;OAEG;IACH,OAAO,IAAI,IAAI;CAuBhB"}
@@ -0,0 +1,246 @@
1
+ import * as pty from "node-pty";
2
+ import * as fs from "fs";
3
+ import * as os from "os";
4
+ import * as path from "path";
5
+ import xtermHeadless from "@xterm/headless";
6
+ const { Terminal } = xtermHeadless;
7
+ // Custom prompt indicator for terminal-mcp
8
+ const PROMPT_INDICATOR = "⚡";
9
+ /**
10
+ * Terminal session that combines node-pty with xterm.js headless
11
+ * for full terminal emulation
12
+ */
13
+ export class TerminalSession {
14
+ ptyProcess;
15
+ terminal;
16
+ disposed = false;
17
+ dataListeners = [];
18
+ exitListeners = [];
19
+ rcFile = null;
20
+ zdotdir = null;
21
+ /**
22
+ * Set up shell-specific prompt customization
23
+ * Returns args to pass to shell and env modifications
24
+ */
25
+ setupShellPrompt(shellName, extraEnv) {
26
+ const env = {
27
+ TERMINAL_MCP: "1",
28
+ ...extraEnv,
29
+ };
30
+ if (shellName === "bash" || shellName === "sh") {
31
+ // Create temp rcfile that sources user's .bashrc then sets our prompt
32
+ const homeDir = os.homedir();
33
+ const bashrcContent = `
34
+ # Source user's bashrc if it exists
35
+ [ -f "${homeDir}/.bashrc" ] && source "${homeDir}/.bashrc"
36
+ # Set terminal-mcp prompt
37
+ PS1="${PROMPT_INDICATOR} \\$ "
38
+ `;
39
+ this.rcFile = path.join(os.tmpdir(), `terminal-mcp-bashrc-${process.pid}`);
40
+ fs.writeFileSync(this.rcFile, bashrcContent);
41
+ return { args: ["--rcfile", this.rcFile], env };
42
+ }
43
+ if (shellName === "zsh") {
44
+ // Create temp ZDOTDIR with .zshrc that sources user's config then sets prompt
45
+ const homeDir = os.homedir();
46
+ this.zdotdir = path.join(os.tmpdir(), `terminal-mcp-zsh-${process.pid}`);
47
+ fs.mkdirSync(this.zdotdir, { recursive: true });
48
+ const zshrcContent = `
49
+ # Reset ZDOTDIR so nested zsh uses normal config
50
+ export ZDOTDIR="${homeDir}"
51
+ # Source user's zshrc if it exists
52
+ [ -f "${homeDir}/.zshrc" ] && source "${homeDir}/.zshrc"
53
+ # Set terminal-mcp prompt
54
+ PROMPT="${PROMPT_INDICATOR} %# "
55
+ `;
56
+ fs.writeFileSync(path.join(this.zdotdir, ".zshrc"), zshrcContent);
57
+ env.ZDOTDIR = this.zdotdir;
58
+ return { args: [], env };
59
+ }
60
+ // For other shells, just set env vars and hope for the best
61
+ env.PS1 = `${PROMPT_INDICATOR} $ `;
62
+ return { args: [], env };
63
+ }
64
+ constructor(options = {}) {
65
+ const cols = options.cols ?? 120;
66
+ const rows = options.rows ?? 40;
67
+ const shell = options.shell ?? process.env.SHELL ?? "bash";
68
+ // Create headless terminal emulator
69
+ this.terminal = new Terminal({
70
+ cols,
71
+ rows,
72
+ scrollback: 1000,
73
+ allowProposedApi: true,
74
+ });
75
+ // Determine shell type and set up custom prompt
76
+ const shellName = path.basename(shell);
77
+ const { args, env } = this.setupShellPrompt(shellName, options.env);
78
+ // Spawn PTY process
79
+ this.ptyProcess = pty.spawn(shell, args, {
80
+ name: "xterm-256color",
81
+ cols,
82
+ rows,
83
+ cwd: options.cwd ?? process.cwd(),
84
+ env: { ...process.env, ...env },
85
+ });
86
+ // Pipe PTY output to terminal emulator and listeners
87
+ this.ptyProcess.onData((data) => {
88
+ if (!this.disposed) {
89
+ this.terminal.write(data);
90
+ // Notify all data listeners
91
+ for (const listener of this.dataListeners) {
92
+ listener(data);
93
+ }
94
+ }
95
+ });
96
+ this.ptyProcess.onExit(({ exitCode }) => {
97
+ this.disposed = true;
98
+ for (const listener of this.exitListeners) {
99
+ listener(exitCode);
100
+ }
101
+ });
102
+ }
103
+ /**
104
+ * Subscribe to PTY output data
105
+ */
106
+ onData(listener) {
107
+ this.dataListeners.push(listener);
108
+ }
109
+ /**
110
+ * Subscribe to PTY exit
111
+ */
112
+ onExit(listener) {
113
+ this.exitListeners.push(listener);
114
+ }
115
+ /**
116
+ * Write data to the terminal (simulates typing)
117
+ */
118
+ write(data) {
119
+ if (this.disposed) {
120
+ throw new Error("Terminal session has been disposed");
121
+ }
122
+ this.ptyProcess.write(data);
123
+ }
124
+ /**
125
+ * Get the current terminal buffer content as plain text
126
+ */
127
+ getContent() {
128
+ if (this.disposed) {
129
+ throw new Error("Terminal session has been disposed");
130
+ }
131
+ const buffer = this.terminal.buffer.active;
132
+ const lines = [];
133
+ // Get all lines from the buffer (including scrollback)
134
+ for (let i = 0; i < buffer.length; i++) {
135
+ const line = buffer.getLine(i);
136
+ if (line) {
137
+ lines.push(line.translateToString(true));
138
+ }
139
+ }
140
+ // Trim trailing empty lines
141
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
142
+ lines.pop();
143
+ }
144
+ return lines.join("\n");
145
+ }
146
+ /**
147
+ * Get only the visible viewport content
148
+ */
149
+ getVisibleContent() {
150
+ if (this.disposed) {
151
+ throw new Error("Terminal session has been disposed");
152
+ }
153
+ const buffer = this.terminal.buffer.active;
154
+ const lines = [];
155
+ const baseY = buffer.baseY;
156
+ for (let i = 0; i < this.terminal.rows; i++) {
157
+ const line = buffer.getLine(baseY + i);
158
+ if (line) {
159
+ lines.push(line.translateToString(true));
160
+ }
161
+ }
162
+ return lines.join("\n");
163
+ }
164
+ /**
165
+ * Take a screenshot of the terminal state
166
+ */
167
+ takeScreenshot() {
168
+ if (this.disposed) {
169
+ throw new Error("Terminal session has been disposed");
170
+ }
171
+ const buffer = this.terminal.buffer.active;
172
+ return {
173
+ content: this.getVisibleContent(),
174
+ cursor: {
175
+ x: buffer.cursorX,
176
+ y: buffer.cursorY,
177
+ },
178
+ dimensions: {
179
+ cols: this.terminal.cols,
180
+ rows: this.terminal.rows,
181
+ },
182
+ };
183
+ }
184
+ /**
185
+ * Clear the terminal screen
186
+ */
187
+ clear() {
188
+ if (this.disposed) {
189
+ throw new Error("Terminal session has been disposed");
190
+ }
191
+ this.terminal.clear();
192
+ }
193
+ /**
194
+ * Resize the terminal
195
+ */
196
+ resize(cols, rows) {
197
+ if (this.disposed) {
198
+ throw new Error("Terminal session has been disposed");
199
+ }
200
+ this.terminal.resize(cols, rows);
201
+ this.ptyProcess.resize(cols, rows);
202
+ }
203
+ /**
204
+ * Check if the session is still active
205
+ */
206
+ isActive() {
207
+ return !this.disposed;
208
+ }
209
+ /**
210
+ * Get terminal dimensions
211
+ */
212
+ getDimensions() {
213
+ return {
214
+ cols: this.terminal.cols,
215
+ rows: this.terminal.rows,
216
+ };
217
+ }
218
+ /**
219
+ * Dispose of the terminal session
220
+ */
221
+ dispose() {
222
+ if (!this.disposed) {
223
+ this.disposed = true;
224
+ this.ptyProcess.kill();
225
+ this.terminal.dispose();
226
+ // Clean up temp rc files
227
+ if (this.rcFile) {
228
+ try {
229
+ fs.unlinkSync(this.rcFile);
230
+ }
231
+ catch {
232
+ // Ignore cleanup errors
233
+ }
234
+ }
235
+ if (this.zdotdir) {
236
+ try {
237
+ fs.rmSync(this.zdotdir, { recursive: true });
238
+ }
239
+ catch {
240
+ // Ignore cleanup errors
241
+ }
242
+ }
243
+ }
244
+ }
245
+ }
246
+ //# sourceMappingURL=session.js.map