pty-serv 0.1.0

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 ADDED
@@ -0,0 +1,73 @@
1
+ # pty-serve
2
+
3
+ Spawns a command in a [node-pty](https://github.com/microsoft/node-pty) pseudo-terminal and exposes an HTTP endpoint to interact with it at runtime.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g pty-serve
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Run a shell, then send a command from another process:
14
+
15
+ ```bash
16
+ # Terminal 1 – start the PTY server
17
+ pty-serve -s /tmp/pty-serve-0.sock -- bash
18
+
19
+ # Terminal 2 – interact with it
20
+ curl --unix-socket /tmp/pty-serve-0.sock 'http://[::]/write' --data $'echo hello\r'
21
+ ```
22
+
23
+ ### CLI Options
24
+
25
+ | Flag | Description |
26
+ |------|-------------|
27
+ | `-s, --socket <path>` | Unix socket path to listen on |
28
+ | `-p, --port <number>` | TCP port to listen on |
29
+ | `-h, --hostname <string>` | Hostname for TCP listener (default: all interfaces) |
30
+ | `-o, --options <json>` | JSON options forwarded to [node-pty](https://github.com/microsoft/node-pty) |
31
+
32
+ ### HTTP Endpoint
33
+
34
+ #### `POST /write`
35
+
36
+ Write raw data to the PTY's stdin. The request body is streamed directly to the PTY.
37
+
38
+ ```bash
39
+ curl 'http://[::]/write' -d $'ls\r'
40
+ ```
41
+
42
+ #### `POST /resize`
43
+
44
+ Resize the PTY window.
45
+
46
+ ```bash
47
+ curl 'http://[::]/resize' -d '{"cols":220,"rows":50}'
48
+ ```
49
+
50
+ #### `POST /kill`
51
+
52
+ Send a signal to the PTY process.
53
+
54
+ ```bash
55
+ curl 'http://[::]/kill' -d '{"signal":"SIGTERM"}'
56
+ ```
57
+
58
+ #### `POST /clear`
59
+
60
+ Clear the PTY screen buffer.
61
+
62
+ ```bash
63
+ curl 'http://[::]/clear' -X POST
64
+ ```
65
+
66
+ #### `POST /pause` / `POST /resume`
67
+
68
+ Pause or resume data flow from the PTY.
69
+
70
+ ```bash
71
+ curl 'http://[::]/pause' -X POST
72
+ curl 'http://[::]/resume' -X POST
73
+ ```
package/bin/cli.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import '../dist/cli.js';
package/dist/cli.js ADDED
@@ -0,0 +1,88 @@
1
+ import { createServer } from 'node:http';
2
+ import { program } from 'commander';
3
+ import * as pty from 'node-pty';
4
+ program
5
+ .option('-o, --options <string>', 'JSON options forwarded to `node-pty`')
6
+ .option('-s, --socket <string>', 'Unix socket path to listen on')
7
+ .option('-h, --hostname <string>', 'Hostname for TCP listener')
8
+ .option('-p, --port <number>', 'TCP port to listen on')
9
+ .allowExcessArguments()
10
+ .parse();
11
+ const opts = program.opts();
12
+ const server = createServer();
13
+ const ptyProcess = pty.spawn(program.args[0], program.args.slice(1), opts.options && JSON.parse(opts.options));
14
+ console.log('PTY spawned with PID:', ptyProcess.pid);
15
+ ptyProcess.onData((chunk) => {
16
+ process.stdout.write(chunk);
17
+ });
18
+ let hasExit = false;
19
+ ptyProcess.onExit((info) => {
20
+ hasExit = true;
21
+ server.close();
22
+ process.exit(info.exitCode);
23
+ });
24
+ const EXIT_SIGNALS = ['SIGINT', 'SIGTERM'];
25
+ EXIT_SIGNALS.forEach((signal) => {
26
+ process.on(signal, () => {
27
+ if (!hasExit) {
28
+ ptyProcess.kill(process.platform === 'win32' ? undefined : signal);
29
+ }
30
+ server.close();
31
+ process.exit(0);
32
+ });
33
+ });
34
+ server.on('request', async (req, res) => {
35
+ try {
36
+ await handleRequest(req);
37
+ res.writeHead(200, { 'Content-Type': 'application/json' });
38
+ res.end(JSON.stringify({ code: 0 }));
39
+ }
40
+ catch (error) {
41
+ res.writeHead(400, { 'Content-Type': 'application/json' });
42
+ res.end(JSON.stringify({ code: -1, message: String(error) }));
43
+ }
44
+ });
45
+ if (opts.socket) {
46
+ server.listen(opts.socket);
47
+ }
48
+ else {
49
+ server.listen(opts.port, opts.hostname);
50
+ }
51
+ async function handleRequest(req) {
52
+ switch (req.url) {
53
+ case '/resize': {
54
+ const data = await parseBody(req);
55
+ ptyProcess.resize(data.cols, data.rows);
56
+ return;
57
+ }
58
+ case '/clear':
59
+ ptyProcess.clear();
60
+ return;
61
+ case '/write':
62
+ for await (const chunk of req) {
63
+ ptyProcess.write(chunk);
64
+ }
65
+ return;
66
+ case '/kill': {
67
+ const data = await parseBody(req);
68
+ ptyProcess.kill(data.signal);
69
+ return;
70
+ }
71
+ case '/pause':
72
+ ptyProcess.pause();
73
+ return;
74
+ case '/resume':
75
+ ptyProcess.resume();
76
+ return;
77
+ }
78
+ }
79
+ async function parseBody(req) {
80
+ if (req.method !== 'POST') {
81
+ throw new Error(`Can't parse body for ${req.method} request`);
82
+ }
83
+ const chunks = [];
84
+ for await (const chunk of req) {
85
+ chunks.push(chunk);
86
+ }
87
+ return JSON.parse(Buffer.concat(chunks).toString());
88
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "pty-serv",
3
+ "version": "0.1.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/gzzhanghao/node-pty-serve.git"
7
+ },
8
+ "type": "module",
9
+ "bin": {
10
+ "pty-serve": "./dist/index.js"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "dist"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "dev": "tsc --watch",
19
+ "test": "node --import tsx/esm --experimental-test-coverage --test src/cli.test.ts"
20
+ },
21
+ "dependencies": {
22
+ "commander": "^14.0.3",
23
+ "node-pty": "^1.1.0"
24
+ },
25
+ "devDependencies": {
26
+ "@changesets/cli": "^2.29.8",
27
+ "@eslint/js": "^10.0.1",
28
+ "@types/node": "^25.3.0",
29
+ "eslint": "^10.0.2",
30
+ "eslint-config-prettier": "^10.1.8",
31
+ "eslint-plugin-prettier": "^5.5.5",
32
+ "prettier": "^3.8.1",
33
+ "rolldown": "1.0.0-rc.5",
34
+ "tsx": "^4.21.0",
35
+ "typescript": "^5.9.3",
36
+ "typescript-eslint": "^8.56.1"
37
+ },
38
+ "packageManager": "pnpm@10.30.3"
39
+ }