@mentiko/pty-mgr 1.2.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 +206 -0
- package/bin/pty-mgr +71 -0
- package/lib/pty-manager.mjs +2105 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# pty-mgr
|
|
2
|
+
|
|
3
|
+
PTY session manager with terminal emulation for programmatic session control.
|
|
4
|
+
|
|
5
|
+
Spawn commands in real pseudo-terminals, capture rendered screen output (not raw
|
|
6
|
+
bytes), and manage sessions through a persistent daemon. Single binary, no
|
|
7
|
+
external dependencies.
|
|
8
|
+
|
|
9
|
+
## How It Works
|
|
10
|
+
|
|
11
|
+
`Bun.spawn({ terminal })` allocates a native PTY for each session. An
|
|
12
|
+
`@xterm/headless` terminal emulator parses escape codes, cursor movements, and
|
|
13
|
+
screen redraws so `capture()` returns exactly what you'd see on screen.
|
|
14
|
+
|
|
15
|
+
Compiles to a single self-contained binary via `bun build --compile`.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
### Binary (no dependencies)
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
curl -fsSL https://raw.githubusercontent.com/maarco/pty-mgr/main/install.sh | sh
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Installs to `~/.pty-mgr/bin/` and adds to PATH. Works on Linux and macOS (x64, arm64).
|
|
26
|
+
|
|
27
|
+
### Bun package (for library use)
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
bun add pty-mgr
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Requires [Bun](https://bun.sh) runtime.
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### As a library
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
import { PtyManager } from 'pty-mgr';
|
|
41
|
+
|
|
42
|
+
const mgr = new PtyManager();
|
|
43
|
+
mgr.spawn('my-session', 'zsh', [], { cols: 120, rows: 30 });
|
|
44
|
+
mgr.sendKeys('my-session', 'echo hello\r');
|
|
45
|
+
|
|
46
|
+
// rename a running session
|
|
47
|
+
mgr.rename('my-session', 'my-session-renamed');
|
|
48
|
+
|
|
49
|
+
// wait for output, then capture rendered screen
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
console.log(mgr.capture('my-session-renamed', 5));
|
|
52
|
+
mgr.kill('my-session-renamed');
|
|
53
|
+
}, 1000);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### As a CLI
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
# start the daemon (forks to background)
|
|
60
|
+
p daemon
|
|
61
|
+
|
|
62
|
+
# named daemon for isolated environments
|
|
63
|
+
p daemon @myproject
|
|
64
|
+
|
|
65
|
+
# spawn a session
|
|
66
|
+
# auto-incrementing session named after current directory
|
|
67
|
+
p wrap # spawns as pty-mgr-1
|
|
68
|
+
p wrap # spawns as pty-mgr-2
|
|
69
|
+
p wrap # spawns as pty-mgr-3
|
|
70
|
+
p spawn agent-1 claude --print
|
|
71
|
+
|
|
72
|
+
# send keystrokes
|
|
73
|
+
p send agent-1 "fix the login bug"
|
|
74
|
+
|
|
75
|
+
# capture rendered screen (last 20 lines)
|
|
76
|
+
p capture agent-1 20
|
|
77
|
+
|
|
78
|
+
# attach interactively (ctrl-] to detach)
|
|
79
|
+
p attach agent-1
|
|
80
|
+
|
|
81
|
+
# rename a session
|
|
82
|
+
p rename agent-1 agent-refactored
|
|
83
|
+
|
|
84
|
+
# bulk operations with globs
|
|
85
|
+
p capture all 50
|
|
86
|
+
p kill refa*
|
|
87
|
+
|
|
88
|
+
# stop daemon
|
|
89
|
+
p stop
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### CLI Aliases
|
|
93
|
+
|
|
94
|
+
| Short | Full | Short | Full |
|
|
95
|
+
|-------|---------|-------|---------|
|
|
96
|
+
| n/new | spawn | w/wrap | wrap |
|
|
97
|
+
| s | send | k | kill |
|
|
98
|
+
| c/cap | capture | l/ls | list |
|
|
99
|
+
| a | attach | r/rm | remove |
|
|
100
|
+
| st | status | mv/ren | rename |
|
|
101
|
+
| i | info | d | daemon |
|
|
102
|
+
| cfg | config | x | stop |
|
|
103
|
+
|
|
104
|
+
## Managed CLI Sessions
|
|
105
|
+
|
|
106
|
+
Wrap any CLI tool (claude, codex, gemini, etc.) in managed PTY sessions.
|
|
107
|
+
Run the interactive setup:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
pty-mgr setup
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
It asks which commands to wrap, then adds shell functions to your rc file.
|
|
114
|
+
After that, just type `claude` like normal. What you get:
|
|
115
|
+
|
|
116
|
+
- Claude runs inside a managed PTY session named `<folder>-1`
|
|
117
|
+
- If you open another claude in the same folder, it gets `<folder>-2`
|
|
118
|
+
- `ctrl-]` to detach -- Claude keeps running in the background
|
|
119
|
+
- `p attach <name>` to jump back in
|
|
120
|
+
- `p capture <name> 50` to check on it from another terminal
|
|
121
|
+
- `p list` to see all your Claude sessions across all projects
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
$ cd ~/dev/my-app
|
|
125
|
+
$ claude # spawns as my-app-1, attaches
|
|
126
|
+
ctrl-] # detach
|
|
127
|
+
$ claude # spawns as my-app-2
|
|
128
|
+
ctrl-]
|
|
129
|
+
$ p list
|
|
130
|
+
my-app-1 pid=1234 120x40 alive claude
|
|
131
|
+
my-app-2 pid=1235 120x40 alive claude
|
|
132
|
+
$ p capture my-app-1 20 # peek at what agent 1 is doing
|
|
133
|
+
$ p attach my-app-1 # jump back into agent 1
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Programmatic (parallel agents)
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
import { PtyManager } from 'pty-mgr';
|
|
140
|
+
|
|
141
|
+
const mgr = new PtyManager();
|
|
142
|
+
|
|
143
|
+
// launch 3 Claude agents in parallel
|
|
144
|
+
const agents = ['auth-fix', 'api-tests', 'docs-update'];
|
|
145
|
+
for (const name of agents) {
|
|
146
|
+
mgr.spawn(name, 'claude', ['--print'], { cols: 120, rows: 40 });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
mgr.sendKeys('auth-fix', 'fix the login bug in src/auth.ts\r');
|
|
150
|
+
mgr.sendKeys('api-tests', 'write tests for the /users endpoint\r');
|
|
151
|
+
mgr.sendKeys('docs-update', 'update the API docs in README.md\r');
|
|
152
|
+
|
|
153
|
+
// poll until all agents finish
|
|
154
|
+
for (const name of agents) {
|
|
155
|
+
await mgr.waitFor(name, /[✔✓]|completed|done/i, 120000);
|
|
156
|
+
console.log(`${name} done:\n${mgr.capture(name, 10)}\n`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
mgr.destroyAll();
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Daemon Protocol
|
|
163
|
+
|
|
164
|
+
The daemon listens on a Unix socket at `~/.pty-manager/<name>.sock`.
|
|
165
|
+
Communication is newline-delimited JSON:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{"cmd": "spawn", "name": "agent-1", "args": {"cmd": "zsh"}}
|
|
169
|
+
{"cmd": "wrap", "args": {"cmd": "zsh", "cwd": "/Users/you/dev/myproject"}}
|
|
170
|
+
{"cmd": "send", "name": "agent-1", "args": {"text": "echo hi\r"}}
|
|
171
|
+
{"cmd": "capture", "name": "agent-1", "args": {"lines": 20}}
|
|
172
|
+
{"cmd": "list"}
|
|
173
|
+
{"cmd": "kill", "name": "agent-1"}
|
|
174
|
+
{"cmd": "rename", "name": "agent-1", "args": {"newName": "agent-refactored"}}
|
|
175
|
+
{"cmd": "shutdown"}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The `attach` command switches the connection to raw streaming mode for
|
|
179
|
+
interactive use.
|
|
180
|
+
|
|
181
|
+
## Configuration
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
p config screen 120x40 # default terminal size for new sessions
|
|
185
|
+
p config cap-on-send on # return capture with every send command
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Logging
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
p spawn agent-1 --log claude # spawn with auto-logging (jsonl)
|
|
192
|
+
p log agent-1 on jsonl # start logging an existing session
|
|
193
|
+
p log agent-1 off # stop logging
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Formats: `jsonl` (timestamped events), `raw` (PTY bytes), `rendered` (screen snapshots).
|
|
197
|
+
|
|
198
|
+
## Build
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
bun run build # compiles to dist/pty-mgr (single binary, ~60MB)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT
|
package/bin/pty-mgr
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Binary launcher for pty-mgr.
|
|
4
|
+
// Finds the platform-specific binary installed via optionalDependencies
|
|
5
|
+
// and executes it with the same args.
|
|
6
|
+
|
|
7
|
+
import { execFileSync } from "child_process";
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
import { dirname, join } from "path";
|
|
10
|
+
import { createRequire } from "module";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
const PLATFORMS = {
|
|
16
|
+
"linux-x64": "@mentiko/pty-mgr-linux-x64",
|
|
17
|
+
"linux-arm64": "@mentiko/pty-mgr-linux-arm64",
|
|
18
|
+
"darwin-x64": "@mentiko/pty-mgr-darwin-x64",
|
|
19
|
+
"darwin-arm64": "@mentiko/pty-mgr-darwin-arm64",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function getBinaryPath() {
|
|
23
|
+
const key = `${process.platform}-${process.arch}`;
|
|
24
|
+
const pkg = PLATFORMS[key];
|
|
25
|
+
|
|
26
|
+
if (!pkg) {
|
|
27
|
+
console.error(
|
|
28
|
+
`pty-mgr: unsupported platform ${process.platform}-${process.arch}`
|
|
29
|
+
);
|
|
30
|
+
console.error("supported: linux-x64, linux-arm64, darwin-x64, darwin-arm64");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// try to find the binary from the installed optional dep
|
|
35
|
+
try {
|
|
36
|
+
const pkgDir = dirname(require.resolve(`${pkg}/package.json`));
|
|
37
|
+
const bin = join(pkgDir, "pty-mgr");
|
|
38
|
+
if (existsSync(bin)) return bin;
|
|
39
|
+
} catch {}
|
|
40
|
+
|
|
41
|
+
// fallback: check ~/.pty-mgr/bin/ (install.sh location)
|
|
42
|
+
const homeBin = join(
|
|
43
|
+
process.env.HOME || process.env.USERPROFILE || "",
|
|
44
|
+
".pty-mgr",
|
|
45
|
+
"bin",
|
|
46
|
+
"pty-mgr"
|
|
47
|
+
);
|
|
48
|
+
if (existsSync(homeBin)) return homeBin;
|
|
49
|
+
|
|
50
|
+
console.error("pty-mgr: binary not found");
|
|
51
|
+
console.error("");
|
|
52
|
+
console.error("install it:");
|
|
53
|
+
console.error(
|
|
54
|
+
" curl -fsSL https://raw.githubusercontent.com/kollaborai/pty-mgr/main/install.sh | sh"
|
|
55
|
+
);
|
|
56
|
+
console.error("");
|
|
57
|
+
console.error("or reinstall this package:");
|
|
58
|
+
console.error(" npm install -g @mentiko/pty-mgr");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const bin = getBinaryPath();
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
execFileSync(bin, process.argv.slice(2), {
|
|
66
|
+
stdio: "inherit",
|
|
67
|
+
env: process.env,
|
|
68
|
+
});
|
|
69
|
+
} catch (e) {
|
|
70
|
+
process.exit(e.status || 1);
|
|
71
|
+
}
|