@dmsdc-ai/aigentry-telepty 0.1.79 → 0.1.81

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 (4) hide show
  1. package/README.md +113 -54
  2. package/cli.js +60 -37
  3. package/daemon.js +43 -4
  4. package/package.json +25 -4
package/README.md CHANGED
@@ -1,92 +1,151 @@
1
- # @dmsdc-ai/aigentry-telepty
1
+ # telepty
2
2
 
3
- **Cross-machine PTY-based remote prompt injection daemon for AI CLIs.**
3
+ **Connect any terminal to any terminal, any machine.**
4
4
 
5
- `telepty` (Tele-Prompt) is a lightweight background daemon that bridges the gap between the network and interactive AI command-line interfaces. It allows you to seamlessly share, attach to, and inject commands into terminal sessions across different machines.
5
+ telepty is a lightweight PTY multiplexer and session bridge. It lets you spawn, attach to, and inject commands into terminal sessions — locally or across machines via Tailscale.
6
6
 
7
- Its primary user experience is prompt-driven operation inside LLM CLIs and the built-in TUI. Raw `telepty ...` commands are the lower-level control surface.
7
+ Built for AI CLI workflows (Claude Code, Codex, Gemini CLI), but works with any interactive terminal program.
8
8
 
9
- ## One-Click Installation & Update
9
+ ## Install
10
10
 
11
- To install or update `telepty` on any machine (macOS, Linux, or Windows), just run the command for your OS. (Node.js will be automatically installed if you don't have it).
12
-
13
- ### For macOS and Linux (Ubuntu, CentOS, etc.)
14
- Open your terminal and run:
15
11
  ```bash
12
+ # macOS / Linux
16
13
  curl -fsSL https://raw.githubusercontent.com/dmsdc-ai/aigentry-telepty/main/install.sh | bash
17
- ```
18
14
 
19
- ### For Windows (PowerShell)
20
- Open PowerShell as Administrator and run:
21
- ```powershell
15
+ # Windows (PowerShell as Admin)
22
16
  iwr -useb https://raw.githubusercontent.com/dmsdc-ai/aigentry-telepty/main/install.ps1 | iex
17
+
18
+ # Or via npm
19
+ npm install -g @dmsdc-ai/aigentry-telepty
23
20
  ```
24
21
 
25
- You can also launch the installer through npm without downloading the script first:
22
+ The installer sets up telepty as a background service (`launchd` on macOS, `systemd` on Linux, detached process on Windows).
23
+
24
+ ## Quick Start
26
25
 
27
26
  ```bash
28
- npx --yes @dmsdc-ai/aigentry-telepty@latest
29
- ```
27
+ # 1. Start the daemon
28
+ telepty daemon
29
+
30
+ # 2. Wrap an existing CLI session for remote control
31
+ telepty allow --id my-session claude
32
+
33
+ # 3. List active sessions (local + Tailnet)
34
+ telepty list
30
35
 
31
- *These single commands will install the package globally and automatically configure it to run as a background service specific to your OS (`systemd` for Linux, `launchd` for macOS, or a detached background process for Windows).*
32
- The installer now stops older local telepty daemons before starting the new one, so updates do not leave duplicate background processes behind.
36
+ # 4. Inject a prompt into a session
37
+ telepty inject my-session "explain this codebase"
33
38
 
34
- ## Seamless Usage
39
+ # 5. Attach to a session interactively
40
+ telepty attach my-session
35
41
 
36
- 1. **Start a background session:**
37
- ```bash
38
- telepty spawn --id "my-session" bash
39
- ```
42
+ # 6. Broadcast to all sessions
43
+ telepty broadcast "status report"
44
+ ```
40
45
 
41
- 2. **Attach to a session (Local or Remote):**
42
- ```bash
43
- telepty attach
44
- ```
45
- *telepty will automatically discover active sessions on your local machine and across your Tailscale network!*
46
+ ## Core Commands
47
+
48
+ | Command | Description |
49
+ |---------|-------------|
50
+ | `telepty daemon` | Start the background daemon (port 3848) |
51
+ | `telepty allow --id <name> <cmd>` | Wrap a CLI for inject control |
52
+ | `telepty spawn --id <name> <cmd>` | Spawn a new background session |
53
+ | `telepty list [--json]` | List sessions across all discovered hosts |
54
+ | `telepty attach [id[@host]]` | Attach to a session (interactive picker if no ID) |
55
+ | `telepty inject <id[@host]> "text"` | Inject text into a session |
56
+ | `telepty enter <id[@host]>` | Send Enter/Return to a session |
57
+ | `telepty multicast <id1,id2> "text"` | Inject into multiple sessions |
58
+ | `telepty broadcast "text"` | Inject into ALL sessions |
59
+ | `telepty rename <old> <new>` | Rename a session |
60
+ | `telepty read-screen <id> [--lines N]` | Read session screen buffer |
61
+ | `telepty reply "text"` | Reply to the last injector |
62
+ | `telepty monitor` | Real-time event billboard |
63
+ | `telepty listen` | Stream event bus as JSON |
64
+ | `telepty tui` | Full TUI dashboard |
65
+ | `telepty layout [grid\|tall\|stack]` | Arrange kitty windows |
66
+ | `telepty update` | Update to latest version |
67
+
68
+ ## Cross-Machine Sessions
69
+
70
+ telepty auto-discovers sessions across your Tailnet. All commands (`list`, `attach`, `inject`, `rename`, `multicast`, `broadcast`) work seamlessly across machines.
71
+
72
+ When the same session ID exists on multiple hosts, disambiguate with `session_id@host`:
46
73
 
47
- 3. **Inject commands remotely:**
48
- ```bash
49
- telepty inject my-session "echo 'Hello from nowhere!'"
50
- ```
74
+ ```bash
75
+ telepty inject my-session@macbook "hello"
76
+ telepty attach worker@server-01
77
+ ```
51
78
 
52
- 4. **Universal CLI submit (split_cr):**
79
+ ## How It Works
53
80
 
54
- All AI CLIs (Claude, Codex, Gemini) submit reliably via the `split_cr` strategy — text is injected first, then `\r` is sent separately after a 300ms delay. This works universally across all CLIs without any per-CLI workarounds.
81
+ ```
82
+ CLI (telepty) ──> HTTP/WS ──> Daemon (:3848)
83
+ ├── Session WebSocket (/api/sessions/:id)
84
+ ├── Event Bus WebSocket (/api/bus)
85
+ └── REST API (/api/sessions/*)
86
+ ```
55
87
 
56
- ```bash
57
- # The inject API handles split_cr automatically
58
- curl -X POST http://127.0.0.1:3848/api/sessions/my-session/inject \
59
- -H "Content-Type: application/json" \
60
- -d '{"prompt": "your command here"}'
61
- ```
88
+ - **`allow`** wraps a CLI process in a PTY bridge, enabling remote inject
89
+ - **`inject`** delivers text via the fastest available path: kitty terminal API, WebSocket, or UDS (Unix Domain Socket for embedded integrations)
90
+ - **`submit`** is handled separately from text injection for reliability across all AI CLIs
62
91
 
63
- CLI commands such as `list`, `attach`, `inject`, `rename`, `multicast`, and `broadcast` now auto-discover sessions across your Tailnet by default. If the same session ID exists on multiple hosts, disambiguate with `session_id@host`.
92
+ ## Inject Delivery Paths
64
93
 
65
- ## Testing
94
+ | Priority | Method | When |
95
+ |----------|--------|------|
96
+ | 1 | `kitty @ send-text` | Terminal supports kitty protocol |
97
+ | 2 | UDS (Unix Domain Socket) | Embedded IPC sessions (e.g. aterm) |
98
+ | 3 | WebSocket PTY write | Wrapped sessions via allow-bridge |
99
+
100
+ ## AI CLI Integration
66
101
 
67
- Run the full regression suite locally:
102
+ telepty works as a session bridge for AI CLIs. Use `allow` to wrap any CLI:
68
103
 
69
104
  ```bash
70
- npm test
105
+ # Claude Code
106
+ telepty allow --id claude-main claude
107
+
108
+ # Codex
109
+ telepty allow --id codex-main codex
110
+
111
+ # Gemini CLI
112
+ telepty allow --id gemini-main gemini
71
113
  ```
72
114
 
73
- Keep the suite running while you work:
115
+ Then inject prompts, read output, or attach from anywhere:
74
116
 
75
117
  ```bash
76
- npm run test:watch
118
+ telepty inject claude-main "refactor the auth module"
119
+ telepty read-screen claude-main --lines 50
120
+ telepty attach claude-main
77
121
  ```
78
122
 
79
- The automated suite covers config generation, daemon HTTP APIs, WebSocket attach/output flow, bus events, session deletion regressions, and CLI smoke tests against a real daemon process.
123
+ ## Deliberation (Multi-Session Discussion)
80
124
 
81
- If the local daemon ever gets stuck or duplicated, open `telepty` and choose `Repair local daemon`.
125
+ Coordinate structured discussions across multiple AI sessions:
126
+
127
+ ```bash
128
+ telepty deliberate --topic "API design for v2" --sessions claude-1,claude-2,codex-1
129
+ telepty deliberate status
130
+ telepty deliberate end <thread_id>
131
+ ```
82
132
 
83
133
  ## Skill Installation
84
134
 
85
- The package installer opens the telepty skill TUI automatically when you run it in a terminal.
135
+ telepty ships with packaged skills for Claude Code, Codex, and Gemini CLI. Run the interactive installer:
136
+
137
+ ```bash
138
+ telepty
139
+ # Choose "Install telepty skills"
140
+ ```
141
+
142
+ ## Testing
143
+
144
+ ```bash
145
+ npm test # 70 tests (node:test)
146
+ npm run test:watch # Watch mode
147
+ ```
86
148
 
87
- To reopen it later, run `telepty` and choose `Install telepty skills`.
149
+ ## License
88
150
 
89
- The TUI lets you choose:
90
- - which packaged skills to install
91
- - which target clients to install into (`Claude Code`, `Codex`, `Gemini`)
92
- - whether each target uses a global path, the current project path, or a custom path
151
+ ISC
package/cli.js CHANGED
@@ -2711,43 +2711,66 @@ Discuss the following topic from your project's perspective. Engage with other s
2711
2711
  }
2712
2712
 
2713
2713
  console.log(`
2714
- \x1b[1maigentry-telepty\x1b[0m - Remote PTY Control
2715
-
2716
- Usage:
2717
- telepty daemon Start the background daemon
2718
- telepty spawn --id <id> <command> [args...] Spawn a new background CLI
2719
- telepty allow [--id <id>] [--auto-restart] <command> [args...] Allow inject on a CLI (auto-restart on crash)
2720
- telepty list [--json] List all active sessions across discovered hosts
2721
- telepty attach [id[@host]] Attach to a session (Interactive picker if no ID)
2722
- telepty inject [--ref [file]] [--from <id>] [--reply-to <id>] <id[@host]> "<prompt>" Inject text into a single session
2723
- telepty enter <id[@host]> Send only Enter/Return to a single session
2724
- telepty read-screen <id[@host]> [--lines N] [--raw] Read session screen buffer
2725
- telepty reply "<text>" Reply to the session that last injected into $TELEPTY_SESSION_ID
2726
- telepty status-report [--id <id>] --phase <phase> [--task <text>] [--blocker <text>] [--needs-input] [--thread-id <id>] [--seq N]
2727
- telepty multicast <id1[@host],id2[@host]> "<prompt>" Inject text into multiple specific sessions
2728
- telepty broadcast [--ref [file]] "<prompt>" Inject text into ALL active sessions
2729
- telepty rename <old_id[@host]> <new_id> Rename a session (updates terminal title too)
2730
- telepty session info <id[@host]> [--json] Show detailed session metadata
2731
- telepty connect <user@host> [--name N] [--port P] Connect to a remote machine via SSH tunnel
2732
- telepty disconnect <name> | --all Disconnect from a remote machine
2733
- telepty peers [--remove <name>] List connected and known peers
2734
- telepty listen Listen to the event bus and print JSON to stdout
2735
- telepty monitor Human-readable real-time billboard of bus events
2736
- telepty update Update telepty to the latest version
2737
- telepty layout [grid|tall|stack] Arrange kitty windows on screen (default: grid)
2738
-
2739
- Handoff Commands:
2740
- handoff list [--status=S] List handoffs (filter: pending/claimed/executing/completed)
2741
- handoff drop [options] Create handoff from synthesis (pipe JSON or use --summary/--tasks)
2742
- handoff claim <id> [--agent=S] Claim a pending handoff
2743
- handoff status <id> [status] Get or update handoff status
2744
- handoff get <id> Get full synthesis JSON (for piping)
2745
-
2746
- Deliberation Commands:
2747
- deliberate --topic "..." [--sessions s1,s2] [--context file]
2748
- Start multi-session deliberation
2749
- deliberate status [thread_id] List threads or show thread details
2750
- deliberate end <thread_id> Close a deliberation thread
2714
+ \x1b[1mtelepty\x1b[0m Connect any terminal to any terminal, any machine.
2715
+
2716
+ \x1b[1mSession Management:\x1b[0m
2717
+ telepty daemon Start the background daemon (port 3848)
2718
+ telepty spawn --id <id> <command> [args...] Spawn a new background session
2719
+ telepty allow [--id <id>] [--auto-restart] <command> [args...] Wrap a CLI for remote control
2720
+ telepty list [--json] List sessions (local + Tailnet)
2721
+ telepty attach [id[@host]] Attach interactively (picker if no ID)
2722
+ telepty rename <old_id[@host]> <new_id> Rename a session
2723
+ telepty session info <id[@host]> [--json] Show session metadata
2724
+
2725
+ \x1b[1mInject & Communicate:\x1b[0m
2726
+ telepty inject [--ref [file]] [--from <id>] <id[@host]> "<prompt>" Inject text
2727
+ telepty enter <id[@host]> Send Enter/Return
2728
+ telepty reply "<text>" Reply to last injector
2729
+ telepty multicast <id1,id2> "<prompt>" Inject into multiple sessions
2730
+ telepty broadcast [--ref [file]] "<prompt>" Inject into ALL sessions
2731
+ telepty read-screen <id[@host]> [--lines N] Read session screen buffer
2732
+
2733
+ \x1b[1mCross-Machine:\x1b[0m
2734
+ telepty connect <user@host> [--name N] [--port P] SSH tunnel to remote host
2735
+ telepty disconnect <name> | --all Disconnect remote host
2736
+ telepty peers [--remove <name>] List connected peers
2737
+
2738
+ \x1b[1mMonitoring:\x1b[0m
2739
+ telepty tui Full TUI dashboard
2740
+ telepty monitor Real-time event billboard
2741
+ telepty listen Stream event bus as JSON
2742
+
2743
+ \x1b[1mHandoff:\x1b[0m
2744
+ telepty handoff list [--status=S] List handoffs
2745
+ telepty handoff drop [options] Create handoff from synthesis
2746
+ telepty handoff claim <id> [--agent=S] Claim a pending handoff
2747
+ telepty handoff status <id> [status] Get/update handoff status
2748
+
2749
+ \x1b[1mDeliberation:\x1b[0m
2750
+ telepty deliberate --topic "..." [--sessions s1,s2] Start multi-session discussion
2751
+ telepty deliberate status [thread_id] Show thread details
2752
+ telepty deliberate end <thread_id> Close a thread
2753
+
2754
+ \x1b[1mOther:\x1b[0m
2755
+ telepty update Update to latest version
2756
+ telepty layout [grid|tall|stack] Arrange terminal windows
2757
+ telepty status-report --phase <p> [options] Emit structured status event
2758
+
2759
+ \x1b[1mExamples:\x1b[0m
2760
+ \x1b[2m# Wrap Claude Code for remote control\x1b[0m
2761
+ telepty allow --id my-claude claude
2762
+
2763
+ \x1b[2m# Send a prompt to a session\x1b[0m
2764
+ telepty inject my-claude "explain the auth module"
2765
+
2766
+ \x1b[2m# Read what a session is showing\x1b[0m
2767
+ telepty read-screen my-claude --lines 30
2768
+
2769
+ \x1b[2m# Broadcast to all sessions\x1b[0m
2770
+ telepty broadcast "status report please"
2771
+
2772
+ \x1b[2m# Inject into a session on another machine\x1b[0m
2773
+ telepty inject my-claude@server-01 "run the tests"
2751
2774
  `);
2752
2775
  }
2753
2776
 
package/daemon.js CHANGED
@@ -13,6 +13,7 @@ const terminalBackend = require('./terminal-backend');
13
13
  const config = getConfig();
14
14
  const EXPECTED_TOKEN = config.authToken;
15
15
  const MACHINE_ID = process.env.TELEPTY_MACHINE_ID || os.hostname();
16
+ const net = require('net');
16
17
  const fs = require('fs');
17
18
  const SESSION_PERSIST_PATH = require('path').join(os.homedir(), '.config', 'aigentry-telepty', 'sessions.json');
18
19
  const SESSION_STALE_SECONDS = Math.max(1, Number(process.env.TELEPTY_SESSION_STALE_SECONDS || 60));
@@ -34,6 +35,8 @@ function persistSessions() {
34
35
  cmuxSurfaceId: s.cmuxSurfaceId || null,
35
36
  termProgram: s.termProgram || null,
36
37
  term: s.term || null,
38
+ delivery: s.delivery || null,
39
+ deliveryEndpoint: s.deliveryEndpoint || null,
37
40
  createdAt: s.createdAt,
38
41
  lastActivityAt: s.lastActivityAt || null,
39
42
  lastConnectedAt: s.lastConnectedAt || null,
@@ -219,7 +222,7 @@ function getSessionHealthStatus(session, options = {}) {
219
222
  }
220
223
 
221
224
  if (session.type === 'aterm') {
222
- if (session.deliveryEndpoint) {
225
+ if (session.deliveryEndpoint || (session.delivery && session.delivery.address)) {
223
226
  return 'CONNECTED';
224
227
  }
225
228
  if (disconnectedMs !== null && disconnectedMs >= staleMs) {
@@ -426,6 +429,33 @@ function emitInjectFailureEvent(sessionId, code, error, extra = {}, session = nu
426
429
 
427
430
  async function writeDataToSession(id, session, data) {
428
431
  if (session.type === 'aterm') {
432
+ // UDS delivery via net.connect()
433
+ if (session.delivery && session.delivery.transport === 'unix_socket' && session.delivery.address) {
434
+ return new Promise((resolve) => {
435
+ const payload = JSON.stringify({ text: data, session_id: id }) + '\n';
436
+ const timeout = setTimeout(() => {
437
+ sock.destroy();
438
+ resolve(buildErrorBody('TIMEOUT', 'UDS delivery timed out.', { httpStatus: 504 }));
439
+ }, DELIVERY_TIMEOUT_MS);
440
+ const sock = net.connect(session.delivery.address, () => {
441
+ sock.end(payload);
442
+ });
443
+ sock.on('data', () => {}); // drain
444
+ sock.on('end', () => {
445
+ clearTimeout(timeout);
446
+ resolve({ success: true });
447
+ });
448
+ sock.on('error', (err) => {
449
+ clearTimeout(timeout);
450
+ resolve(buildErrorBody('DISCONNECTED', 'UDS endpoint is unreachable.', {
451
+ httpStatus: 503,
452
+ detail: err.message
453
+ }));
454
+ });
455
+ });
456
+ }
457
+
458
+ // HTTP delivery (backward compat)
429
459
  if (!session.deliveryEndpoint) {
430
460
  return buildErrorBody('DISCONNECTED', 'Delivery endpoint is missing.', { httpStatus: 503 });
431
461
  }
@@ -504,7 +534,7 @@ async function deliverInjectionToSession(id, session, prompt, options = {}) {
504
534
  strategy: session.type === 'wrapped'
505
535
  ? 'ws_split_cr'
506
536
  : session.type === 'aterm'
507
- ? 'aterm_endpoint'
537
+ ? (session.delivery && session.delivery.transport === 'unix_socket' ? 'aterm_uds' : 'aterm_endpoint')
508
538
  : 'pty_split_cr',
509
539
  submit: options.noEnter ? 'skipped' : 'deferred'
510
540
  };
@@ -568,6 +598,7 @@ function serializeSession(id, session, options = {}) {
568
598
  idleSeconds,
569
599
  active_clients: session.clients ? session.clients.size : 0,
570
600
  ready: session.ready || false,
601
+ delivery: session.delivery || null,
571
602
  deliveryEndpoint: session.deliveryEndpoint || null,
572
603
  healthStatus,
573
604
  healthReason,
@@ -787,6 +818,12 @@ app.post('/api/sessions/register', (req, res) => {
787
818
  if (Object.prototype.hasOwnProperty.call(req.body, 'term')) existing.term = term || null;
788
819
  if (req.body.delivery_type) existing.type = req.body.delivery_type;
789
820
  if (req.body.delivery_endpoint) existing.deliveryEndpoint = req.body.delivery_endpoint;
821
+ if (req.body.delivery) {
822
+ existing.delivery = req.body.delivery;
823
+ if (!existing.deliveryEndpoint && req.body.delivery.address) {
824
+ existing.deliveryEndpoint = req.body.delivery.address;
825
+ }
826
+ }
790
827
  if (req.body.delivery_type === 'aterm') {
791
828
  existing.ready = true;
792
829
  markSessionConnected(existing);
@@ -795,7 +832,8 @@ app.post('/api/sessions/register', (req, res) => {
795
832
  return res.status(200).json({ session_id, type: existing.type, command: existing.command, cwd: existing.cwd, reregistered: true });
796
833
  }
797
834
 
798
- const { delivery_type, delivery_endpoint } = req.body;
835
+ const { delivery_type, delivery_endpoint, delivery } = req.body;
836
+ const resolvedEndpoint = delivery_endpoint || (delivery && delivery.address) || null;
799
837
  const sessionRecord = {
800
838
  id: session_id,
801
839
  type: delivery_type || 'wrapped',
@@ -808,7 +846,8 @@ app.post('/api/sessions/register', (req, res) => {
808
846
  cmuxSurfaceId: cmux_surface_id || null,
809
847
  termProgram: term_program || null,
810
848
  term: term || null,
811
- deliveryEndpoint: delivery_endpoint || null,
849
+ delivery: delivery || null,
850
+ deliveryEndpoint: resolvedEndpoint,
812
851
  createdAt: new Date().toISOString(),
813
852
  lastActivityAt: new Date().toISOString(),
814
853
  lastConnectedAt: delivery_type === 'aterm' ? new Date().toISOString() : null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.79",
3
+ "version": "0.1.81",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",
@@ -12,10 +12,31 @@
12
12
  "test:watch": "node --test --watch test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js",
13
13
  "test:ci": "node --test --test-reporter=spec test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js"
14
14
  },
15
- "keywords": [],
16
- "author": "",
15
+ "keywords": [
16
+ "pty",
17
+ "terminal",
18
+ "session",
19
+ "multiplexer",
20
+ "remote",
21
+ "inject",
22
+ "ai-cli",
23
+ "claude",
24
+ "codex",
25
+ "gemini",
26
+ "cross-machine",
27
+ "tailscale"
28
+ ],
29
+ "author": "dmsdc-ai",
17
30
  "license": "ISC",
18
- "description": "",
31
+ "description": "Universal terminal session bridge — connect any terminal to any terminal, any machine",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/dmsdc-ai/aigentry-telepty.git"
35
+ },
36
+ "homepage": "https://github.com/dmsdc-ai/aigentry-telepty#readme",
37
+ "bugs": {
38
+ "url": "https://github.com/dmsdc-ai/aigentry-telepty/issues"
39
+ },
19
40
  "dependencies": {
20
41
  "blessed": "^0.1.81",
21
42
  "cors": "^2.8.6",