pty-serv 0.1.2 → 0.2.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 +1 -7
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +32 -66
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/request-handler.d.ts +3 -0
- package/dist/request-handler.js +49 -0
- package/dist/spawn.d.ts +20 -0
- package/dist/spawn.js +21 -0
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -2,19 +2,13 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install -g pty-serv
|
|
9
|
-
```
|
|
10
|
-
|
|
11
5
|
## Usage
|
|
12
6
|
|
|
13
7
|
Run a shell, then send a command from another process:
|
|
14
8
|
|
|
15
9
|
```bash
|
|
16
10
|
# Terminal 1 – start the PTY server
|
|
17
|
-
pty-serv -s /tmp/pty-serv-0.sock -- bash
|
|
11
|
+
npx pty-serv -s /tmp/pty-serv-0.sock -- bash
|
|
18
12
|
|
|
19
13
|
# Terminal 2 – interact with it
|
|
20
14
|
curl --unix-socket /tmp/pty-serv-0.sock 'http://[::]/write' --data $'echo hello\r'
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { unlinkSync } from 'node:fs';
|
|
1
2
|
import { createServer } from 'node:http';
|
|
2
3
|
import { program } from 'commander';
|
|
3
|
-
import
|
|
4
|
+
import { spawn } from 'node-pty';
|
|
5
|
+
import { handlePtyRequest } from './request-handler.js';
|
|
4
6
|
program
|
|
7
|
+
.enablePositionalOptions()
|
|
8
|
+
.passThroughOptions()
|
|
9
|
+
.name('pty-serv')
|
|
5
10
|
.option('-o, --options <string>', 'JSON options forwarded to `node-pty`')
|
|
6
11
|
.option('-s, --socket <string>', 'Unix socket path to listen on')
|
|
7
12
|
.option('-h, --hostname <string>', 'Hostname for TCP listener')
|
|
@@ -10,79 +15,40 @@ program
|
|
|
10
15
|
.parse();
|
|
11
16
|
const opts = program.opts();
|
|
12
17
|
const server = createServer();
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
ptyProcess.onData((chunk) => {
|
|
16
|
-
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
ptyProcess.kill(process.platform === 'win32' ? undefined : signal);
|
|
29
|
-
}
|
|
30
|
-
server.close();
|
|
31
|
-
process.exit(0);
|
|
18
|
+
server.on('listening', () => {
|
|
19
|
+
const ptyProcess = spawn(program.args[0], program.args.slice(1), opts.options && JSON.parse(opts.options));
|
|
20
|
+
ptyProcess.onData((chunk) => {
|
|
21
|
+
process.stdout.write(chunk);
|
|
22
|
+
});
|
|
23
|
+
ptyProcess.onExit((info) => {
|
|
24
|
+
server.close(() => {
|
|
25
|
+
process.exit(info.exitCode);
|
|
26
|
+
});
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
process.exit(info.exitCode);
|
|
29
|
+
}, 0).unref();
|
|
30
|
+
});
|
|
31
|
+
server.on('request', (req, res) => {
|
|
32
|
+
handlePtyRequest(req, res, ptyProcess);
|
|
32
33
|
});
|
|
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
34
|
});
|
|
45
35
|
if (opts.socket) {
|
|
36
|
+
tryUnlink(opts.socket);
|
|
46
37
|
server.listen(opts.socket);
|
|
47
38
|
}
|
|
48
|
-
else {
|
|
39
|
+
else if (opts.port != null) {
|
|
49
40
|
server.listen(opts.port, opts.hostname);
|
|
50
41
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
}
|
|
42
|
+
else {
|
|
43
|
+
program
|
|
44
|
+
.addHelpText('after', '\nEither --socket or --port must be specified')
|
|
45
|
+
.help({ error: true });
|
|
78
46
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
47
|
+
function tryUnlink(filename) {
|
|
48
|
+
try {
|
|
49
|
+
unlinkSync(filename);
|
|
82
50
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
chunks.push(chunk);
|
|
51
|
+
catch {
|
|
52
|
+
// noop
|
|
86
53
|
}
|
|
87
|
-
return JSON.parse(Buffer.concat(chunks).toString());
|
|
88
54
|
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export async function handlePtyRequest(req, res, ptyProcess) {
|
|
2
|
+
try {
|
|
3
|
+
await handleRequest(ptyProcess, req);
|
|
4
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
5
|
+
res.end(JSON.stringify({ code: 0 }));
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
9
|
+
res.end(JSON.stringify({ code: -1, message: String(error) }));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function handleRequest(ptyProcess, req) {
|
|
13
|
+
switch (req.url) {
|
|
14
|
+
case '/resize': {
|
|
15
|
+
const data = await parseBody(req);
|
|
16
|
+
ptyProcess.resize(data.cols, data.rows);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
case '/clear':
|
|
20
|
+
ptyProcess.clear();
|
|
21
|
+
return;
|
|
22
|
+
case '/write':
|
|
23
|
+
for await (const chunk of req) {
|
|
24
|
+
ptyProcess.write(chunk);
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
case '/kill': {
|
|
28
|
+
const data = await parseBody(req);
|
|
29
|
+
ptyProcess.kill(data.signal);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
case '/pause':
|
|
33
|
+
ptyProcess.pause();
|
|
34
|
+
return;
|
|
35
|
+
case '/resume':
|
|
36
|
+
ptyProcess.resume();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function parseBody(req) {
|
|
41
|
+
if (req.method !== 'POST') {
|
|
42
|
+
throw new Error(`Can't parse body for ${req.method} request`);
|
|
43
|
+
}
|
|
44
|
+
const chunks = [];
|
|
45
|
+
for await (const chunk of req) {
|
|
46
|
+
chunks.push(chunk);
|
|
47
|
+
}
|
|
48
|
+
return JSON.parse(Buffer.concat(chunks).toString());
|
|
49
|
+
}
|
package/dist/spawn.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IPtyForkOptions, IWindowsPtyForkOptions } from 'node-pty';
|
|
2
|
+
export interface SpawnPtyOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Options forwarded to `node-pty`
|
|
5
|
+
*/
|
|
6
|
+
ptyOptions?: IPtyForkOptions | IWindowsPtyForkOptions;
|
|
7
|
+
/**
|
|
8
|
+
* Unix socket path to listen on
|
|
9
|
+
*/
|
|
10
|
+
socket?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Hostname for TCP listener
|
|
13
|
+
*/
|
|
14
|
+
hostname?: string;
|
|
15
|
+
/**
|
|
16
|
+
* TCP port to listen on
|
|
17
|
+
*/
|
|
18
|
+
port?: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function getSpawnPtyArgs(command: string, args?: string[], options?: SpawnPtyOptions): string[];
|
package/dist/spawn.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
export function getSpawnPtyArgs(command, args, options) {
|
|
3
|
+
const nodeArgs = [path.resolve(import.meta.dirname, 'cli.js')];
|
|
4
|
+
if (options?.ptyOptions) {
|
|
5
|
+
nodeArgs.push('--options', JSON.stringify(options.ptyOptions));
|
|
6
|
+
}
|
|
7
|
+
if (options?.socket) {
|
|
8
|
+
nodeArgs.push('--socket', options.socket);
|
|
9
|
+
}
|
|
10
|
+
if (options?.hostname) {
|
|
11
|
+
nodeArgs.push('--hostname', options.hostname);
|
|
12
|
+
}
|
|
13
|
+
if (options?.port != null) {
|
|
14
|
+
nodeArgs.push('--port', String(options.port));
|
|
15
|
+
}
|
|
16
|
+
nodeArgs.push(command);
|
|
17
|
+
if (args) {
|
|
18
|
+
nodeArgs.push(...args);
|
|
19
|
+
}
|
|
20
|
+
return nodeArgs;
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pty-serv",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/gzzhanghao/node-pty-serv.git"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
9
18
|
"bin": {
|
|
10
|
-
"pty-serv": "./bin/
|
|
19
|
+
"pty-serv": "./bin/cli.js"
|
|
11
20
|
},
|
|
12
21
|
"files": [
|
|
13
22
|
"bin",
|