@dmsdc-ai/aigentry-telepty 0.1.4 → 0.1.6
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/.claude/commands/telepty-allow.md +58 -0
- package/.claude/commands/telepty-attach.md +22 -0
- package/.claude/commands/telepty-inject.md +34 -0
- package/.claude/commands/telepty-list.md +22 -0
- package/.claude/commands/telepty-manual-test.md +73 -0
- package/.claude/commands/telepty-start.md +25 -0
- package/.claude/commands/telepty-test.md +25 -0
- package/.claude/commands/telepty.md +82 -0
- package/.gemini/skills/telepty/SKILL.md +19 -1
- package/.github/workflows/test-install.yml +18 -24
- package/README.md +16 -0
- package/cli.js +267 -46
- package/daemon.js +261 -27
- package/package.json +4 -2
- package/test/auth.test.js +56 -0
- package/test/cli.test.js +57 -0
- package/test/daemon.test.js +415 -0
- package/test-support/daemon-harness.js +313 -0
package/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
4
5
|
const WebSocket = require('ws');
|
|
5
6
|
const { execSync, spawn } = require('child_process');
|
|
6
7
|
const readline = require('readline');
|
|
@@ -10,12 +11,14 @@ const pkg = require('./package.json');
|
|
|
10
11
|
const { getConfig } = require('./auth');
|
|
11
12
|
const args = process.argv.slice(2);
|
|
12
13
|
|
|
13
|
-
// Check for updates
|
|
14
|
-
|
|
14
|
+
// Check for updates unless explicitly disabled for tests/CI.
|
|
15
|
+
if (!process.env.NO_UPDATE_NOTIFIER && !process.env.TELEPTY_DISABLE_UPDATE_NOTIFIER) {
|
|
16
|
+
updateNotifier({pkg}).notify({ isGlobal: true });
|
|
17
|
+
}
|
|
15
18
|
|
|
16
19
|
// Support remote host via environment variable or default to localhost
|
|
17
20
|
let REMOTE_HOST = process.env.TELEPTY_HOST || '127.0.0.1';
|
|
18
|
-
const PORT = 3848;
|
|
21
|
+
const PORT = Number(process.env.TELEPTY_PORT || 3848);
|
|
19
22
|
let DAEMON_URL = `http://${REMOTE_HOST}:${PORT}`;
|
|
20
23
|
let WS_URL = `ws://${REMOTE_HOST}:${PORT}`;
|
|
21
24
|
|
|
@@ -27,25 +30,6 @@ const fetchWithAuth = (url, options = {}) => {
|
|
|
27
30
|
return fetch(url, { ...options, headers });
|
|
28
31
|
};
|
|
29
32
|
|
|
30
|
-
async function ensureDaemonRunning() {
|
|
31
|
-
if (REMOTE_HOST !== '127.0.0.1') return; // Only auto-start local daemon
|
|
32
|
-
try {
|
|
33
|
-
const res = await fetchWithAuth(`${DAEMON_URL}/api/sessions`);
|
|
34
|
-
if (res.ok) return; // Already running
|
|
35
|
-
} catch (e) {
|
|
36
|
-
// Not running, let's start it
|
|
37
|
-
process.stdout.write('\x1b[33m⚙️ Auto-starting local telepty daemon...\x1b[0m\n');
|
|
38
|
-
const cp = spawn(process.argv[0], [process.argv[1], 'daemon'], {
|
|
39
|
-
detached: true,
|
|
40
|
-
stdio: 'ignore'
|
|
41
|
-
});
|
|
42
|
-
cp.unref();
|
|
43
|
-
|
|
44
|
-
// Wait a brief moment for the daemon to boot up
|
|
45
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
33
|
async function discoverSessions() {
|
|
50
34
|
await ensureDaemonRunning();
|
|
51
35
|
const hosts = ['127.0.0.1'];
|
|
@@ -107,13 +91,19 @@ async function ensureDaemonRunning() {
|
|
|
107
91
|
async function manageInteractiveAttach(sessionId, targetHost) {
|
|
108
92
|
const wsUrl = `ws://${targetHost}:${PORT}/api/sessions/${encodeURIComponent(sessionId)}?token=${encodeURIComponent(TOKEN)}`;
|
|
109
93
|
const ws = new WebSocket(wsUrl);
|
|
94
|
+
let inputHandler = null;
|
|
95
|
+
let resizeHandler = null;
|
|
110
96
|
return new Promise((resolve) => {
|
|
111
97
|
ws.on('open', () => {
|
|
112
|
-
|
|
98
|
+
// Set Ghostty tab title to show session ID
|
|
99
|
+
process.stdout.write(`\x1b]0;⚡ telepty :: ${sessionId}\x07`);
|
|
100
|
+
console.log(`\n\x1b[32mEntered room '${sessionId}'.\x1b[0m\n`);
|
|
113
101
|
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
process.
|
|
102
|
+
inputHandler = (d) => ws.send(JSON.stringify({ type: 'input', data: d.toString() }));
|
|
103
|
+
resizeHandler = () => ws.send(JSON.stringify({ type: 'resize', cols: process.stdout.columns, rows: process.stdout.rows }));
|
|
104
|
+
process.stdin.on('data', inputHandler);
|
|
105
|
+
process.stdout.on('resize', resizeHandler);
|
|
106
|
+
resizeHandler();
|
|
117
107
|
});
|
|
118
108
|
ws.on('message', m => {
|
|
119
109
|
const msg = JSON.parse(m);
|
|
@@ -121,14 +111,27 @@ async function manageInteractiveAttach(sessionId, targetHost) {
|
|
|
121
111
|
});
|
|
122
112
|
ws.on('close', async () => {
|
|
123
113
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
124
|
-
|
|
125
|
-
process.stdin.
|
|
126
|
-
|
|
127
|
-
|
|
114
|
+
process.stdout.write(`\x1b]0;\x07`); // Restore default terminal title
|
|
115
|
+
if (inputHandler) process.stdin.off('data', inputHandler);
|
|
116
|
+
if (resizeHandler) process.stdout.off('resize', resizeHandler);
|
|
117
|
+
|
|
118
|
+
// Check if other clients are still attached before destroying
|
|
128
119
|
try {
|
|
129
|
-
await fetchWithAuth(`http://${targetHost}:${PORT}/api/sessions
|
|
130
|
-
|
|
131
|
-
|
|
120
|
+
const res = await fetchWithAuth(`http://${targetHost}:${PORT}/api/sessions`);
|
|
121
|
+
if (res.ok) {
|
|
122
|
+
const sessions = await res.json();
|
|
123
|
+
const session = sessions.find(s => s.id === sessionId);
|
|
124
|
+
if (session && session.active_clients > 0) {
|
|
125
|
+
console.log(`\n\x1b[33mLeft room '${sessionId}'. Other clients still attached — session kept alive.\x1b[0m\n`);
|
|
126
|
+
} else {
|
|
127
|
+
console.log(`\n\x1b[33mLeft room '${sessionId}'. No other clients — destroying session.\x1b[0m\n`);
|
|
128
|
+
await fetchWithAuth(`http://${targetHost}:${PORT}/api/sessions/${encodeURIComponent(sessionId)}`, { method: 'DELETE' });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch(e) {
|
|
132
|
+
// Daemon unreachable, nothing to clean up
|
|
133
|
+
}
|
|
134
|
+
|
|
132
135
|
resolve();
|
|
133
136
|
});
|
|
134
137
|
});
|
|
@@ -146,6 +149,7 @@ async function manageInteractive() {
|
|
|
146
149
|
choices: [
|
|
147
150
|
{ title: '🖥️ Enter a room (Attach to session)', value: 'attach' },
|
|
148
151
|
{ title: '➕ Create a new room (Spawn session)', value: 'spawn' },
|
|
152
|
+
{ title: '🔌 Allow inject (Run CLI with inject)', value: 'allow' },
|
|
149
153
|
{ title: '💬 Send message to a room (Inject command)', value: 'inject' },
|
|
150
154
|
{ title: '📋 View all open rooms (List sessions)', value: 'list' },
|
|
151
155
|
{ title: '🔄 Update telepty to latest version', value: 'update' },
|
|
@@ -215,7 +219,7 @@ async function manageInteractive() {
|
|
|
215
219
|
try {
|
|
216
220
|
const res = await fetchWithAuth(`${DAEMON_URL}/api/sessions/spawn`, {
|
|
217
221
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
218
|
-
body: JSON.stringify({ session_id: id, command, args: [], cwd: process.cwd(), cols, rows })
|
|
222
|
+
body: JSON.stringify({ session_id: id, command, args: [], cwd: process.cwd(), cols, rows, type: 'USER' })
|
|
219
223
|
});
|
|
220
224
|
const data = await res.json();
|
|
221
225
|
if (!res.ok) console.error(`\n❌ Error: ${data.error}\n`);
|
|
@@ -231,6 +235,20 @@ async function manageInteractive() {
|
|
|
231
235
|
continue;
|
|
232
236
|
}
|
|
233
237
|
|
|
238
|
+
if (response.action === 'allow') {
|
|
239
|
+
const { id, command } = await prompts([
|
|
240
|
+
{ type: 'text', name: 'id', message: 'Enter session ID (e.g. my-claude):', validate: v => v ? true : 'Required' },
|
|
241
|
+
{ type: 'text', name: 'command', message: 'Enter command to run (e.g. claude, codex, gemini, bash):', initial: 'bash' }
|
|
242
|
+
]);
|
|
243
|
+
if (!id || !command) continue;
|
|
244
|
+
|
|
245
|
+
// Delegate to the allow command handler by setting up args and calling main flow
|
|
246
|
+
process.argv.splice(2, process.argv.length - 2, 'allow', '--id', id, command);
|
|
247
|
+
args.length = 0;
|
|
248
|
+
args.push('allow', '--id', id, command);
|
|
249
|
+
return main();
|
|
250
|
+
}
|
|
251
|
+
|
|
234
252
|
if (response.action === 'attach' || response.action === 'inject') {
|
|
235
253
|
const sessions = await discoverSessions();
|
|
236
254
|
if (sessions.length === 0) {
|
|
@@ -346,7 +364,7 @@ async function main() {
|
|
|
346
364
|
try {
|
|
347
365
|
const res = await fetchWithAuth(`${DAEMON_URL}/api/sessions/spawn`, {
|
|
348
366
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
349
|
-
body: JSON.stringify({ session_id: sessionId, command: command, args: cmdArgs, cwd: process.cwd(), cols, rows })
|
|
367
|
+
body: JSON.stringify({ session_id: sessionId, command: command, args: cmdArgs, cwd: process.cwd(), cols, rows, type: 'USER' })
|
|
350
368
|
});
|
|
351
369
|
const data = await res.json();
|
|
352
370
|
if (!res.ok) { console.error(`❌ Error: ${data.error}`); return; }
|
|
@@ -356,6 +374,139 @@ async function main() {
|
|
|
356
374
|
return;
|
|
357
375
|
}
|
|
358
376
|
|
|
377
|
+
if (cmd === 'allow' || cmd === 'enable' || cmd === 'wrap') {
|
|
378
|
+
// Parse arguments: telepty allow [--id <session_id>] <command> [args...]
|
|
379
|
+
// Also supports legacy: telepty allow [--id <session_id>] -- <command> [args...]
|
|
380
|
+
const allowArgs = args.slice(1);
|
|
381
|
+
|
|
382
|
+
// Extract --id flag
|
|
383
|
+
let sessionId;
|
|
384
|
+
const idIndex = allowArgs.indexOf('--id');
|
|
385
|
+
if (idIndex !== -1 && allowArgs[idIndex + 1]) {
|
|
386
|
+
sessionId = allowArgs[idIndex + 1];
|
|
387
|
+
allowArgs.splice(idIndex, 2);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Strip optional -- separator for backward compat
|
|
391
|
+
const sepIndex = allowArgs.indexOf('--');
|
|
392
|
+
if (sepIndex !== -1) allowArgs.splice(sepIndex, 1);
|
|
393
|
+
|
|
394
|
+
const command = allowArgs[0];
|
|
395
|
+
const cmdArgs = allowArgs.slice(1);
|
|
396
|
+
|
|
397
|
+
if (!command) {
|
|
398
|
+
console.error('❌ Usage: telepty allow [--id <session_id>] <command> [args...]');
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Default session ID = command name
|
|
403
|
+
if (!sessionId) {
|
|
404
|
+
sessionId = path.basename(command);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
await ensureDaemonRunning();
|
|
408
|
+
|
|
409
|
+
// Register session with daemon
|
|
410
|
+
try {
|
|
411
|
+
const res = await fetchWithAuth(`${DAEMON_URL}/api/sessions/register`, {
|
|
412
|
+
method: 'POST',
|
|
413
|
+
headers: { 'Content-Type': 'application/json' },
|
|
414
|
+
body: JSON.stringify({ session_id: sessionId, command, cwd: process.cwd() })
|
|
415
|
+
});
|
|
416
|
+
const data = await res.json();
|
|
417
|
+
if (!res.ok) {
|
|
418
|
+
console.error(`❌ Error: ${data.error}`);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
} catch (e) {
|
|
422
|
+
console.error('❌ Failed to register with daemon:', e.message);
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Spawn local PTY (preserves isTTY, env, shell config)
|
|
427
|
+
const pty = require('node-pty');
|
|
428
|
+
const child = pty.spawn(command, cmdArgs, {
|
|
429
|
+
name: 'xterm-256color',
|
|
430
|
+
cols: process.stdout.columns || 80,
|
|
431
|
+
rows: process.stdout.rows || 30,
|
|
432
|
+
cwd: process.cwd(),
|
|
433
|
+
env: { ...process.env, TELEPTY_SESSION_ID: sessionId }
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Connect to daemon WebSocket for inject reception and output relay
|
|
437
|
+
const wsUrl = `ws://${REMOTE_HOST}:${PORT}/api/sessions/${encodeURIComponent(sessionId)}?token=${encodeURIComponent(TOKEN)}`;
|
|
438
|
+
const daemonWs = new WebSocket(wsUrl);
|
|
439
|
+
let wsReady = false;
|
|
440
|
+
|
|
441
|
+
daemonWs.on('open', () => {
|
|
442
|
+
wsReady = true;
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Receive inject messages from daemon
|
|
446
|
+
daemonWs.on('message', (message) => {
|
|
447
|
+
try {
|
|
448
|
+
const msg = JSON.parse(message);
|
|
449
|
+
if (msg.type === 'inject') {
|
|
450
|
+
child.write(msg.data);
|
|
451
|
+
} else if (msg.type === 'resize') {
|
|
452
|
+
child.resize(msg.cols, msg.rows);
|
|
453
|
+
}
|
|
454
|
+
} catch (e) {
|
|
455
|
+
// ignore malformed messages
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
daemonWs.on('close', () => {
|
|
460
|
+
wsReady = false;
|
|
461
|
+
console.error(`\n\x1b[33m⚠️ Disconnected from daemon. Inject unavailable. Session continues locally.\x1b[0m`);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
daemonWs.on('error', () => {
|
|
465
|
+
// silently handle
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Set terminal title
|
|
469
|
+
process.stdout.write(`\x1b]0;⚡ telepty :: ${sessionId}\x07`);
|
|
470
|
+
console.log(`\x1b[32m⚡ '${command}' is now session '\x1b[36m${sessionId}\x1b[32m'. Inject allowed.\x1b[0m\n`);
|
|
471
|
+
|
|
472
|
+
// Enter raw mode and relay stdin to local PTY
|
|
473
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
474
|
+
|
|
475
|
+
process.stdin.on('data', (data) => {
|
|
476
|
+
child.write(data.toString());
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// Relay PTY output to current terminal + send to daemon for attach clients
|
|
480
|
+
child.onData((data) => {
|
|
481
|
+
process.stdout.write(data);
|
|
482
|
+
if (wsReady && daemonWs.readyState === 1) {
|
|
483
|
+
daemonWs.send(JSON.stringify({ type: 'output', data }));
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Handle terminal resize
|
|
488
|
+
process.stdout.on('resize', () => {
|
|
489
|
+
child.resize(process.stdout.columns, process.stdout.rows);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Handle child exit
|
|
493
|
+
child.onExit(({ exitCode }) => {
|
|
494
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
495
|
+
process.stdout.write(`\x1b]0;\x07`);
|
|
496
|
+
console.log(`\n\x1b[33mSession '${sessionId}' exited (code ${exitCode}).\x1b[0m`);
|
|
497
|
+
|
|
498
|
+
// Deregister from daemon
|
|
499
|
+
fetchWithAuth(`${DAEMON_URL}/api/sessions/${encodeURIComponent(sessionId)}`, { method: 'DELETE' }).catch(() => {});
|
|
500
|
+
daemonWs.close();
|
|
501
|
+
process.exit(exitCode || 0);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Graceful shutdown on SIGINT (let child handle it via PTY)
|
|
505
|
+
process.on('SIGINT', () => {});
|
|
506
|
+
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
359
510
|
if (cmd === 'attach') {
|
|
360
511
|
let sessionId = args[1];
|
|
361
512
|
let targetHost = REMOTE_HOST;
|
|
@@ -389,19 +540,25 @@ async function main() {
|
|
|
389
540
|
|
|
390
541
|
const wsUrl = `ws://${targetHost}:${PORT}/api/sessions/${encodeURIComponent(sessionId)}?token=${encodeURIComponent(TOKEN)}`;
|
|
391
542
|
const ws = new WebSocket(wsUrl);
|
|
543
|
+
let inputHandler = null;
|
|
544
|
+
let resizeHandler = null;
|
|
392
545
|
|
|
393
546
|
ws.on('open', () => {
|
|
394
|
-
|
|
395
|
-
|
|
547
|
+
// Set Ghostty tab title to show session ID
|
|
548
|
+
const hostSuffix = targetHost === '127.0.0.1' ? '' : ` @ ${targetHost}`;
|
|
549
|
+
process.stdout.write(`\x1b]0;⚡ telepty :: ${sessionId}${hostSuffix}\x07`);
|
|
550
|
+
console.log(`\x1b[32mEntered room '${sessionId}'${hostSuffix ? ` (${targetHost})` : ''}.\x1b[0m\n`);
|
|
551
|
+
|
|
396
552
|
if (process.stdin.isTTY) {
|
|
397
553
|
process.stdin.setRawMode(true);
|
|
398
554
|
}
|
|
399
555
|
|
|
400
|
-
|
|
556
|
+
inputHandler = (data) => {
|
|
401
557
|
ws.send(JSON.stringify({ type: 'input', data: data.toString() }));
|
|
402
|
-
}
|
|
558
|
+
};
|
|
559
|
+
process.stdin.on('data', inputHandler);
|
|
403
560
|
|
|
404
|
-
|
|
561
|
+
resizeHandler = () => {
|
|
405
562
|
ws.send(JSON.stringify({
|
|
406
563
|
type: 'resize',
|
|
407
564
|
cols: process.stdout.columns,
|
|
@@ -424,9 +581,23 @@ async function main() {
|
|
|
424
581
|
if (process.stdin.isTTY) {
|
|
425
582
|
process.stdin.setRawMode(false);
|
|
426
583
|
}
|
|
427
|
-
|
|
584
|
+
process.stdout.write(`\x1b]0;\x07`); // Restore default terminal title
|
|
585
|
+
if (inputHandler) process.stdin.off('data', inputHandler);
|
|
586
|
+
if (resizeHandler) process.stdout.off('resize', resizeHandler);
|
|
587
|
+
|
|
588
|
+
// Check if other clients are still attached before destroying
|
|
428
589
|
try {
|
|
429
|
-
await fetchWithAuth(`http://${targetHost}:${PORT}/api/sessions
|
|
590
|
+
const res = await fetchWithAuth(`http://${targetHost}:${PORT}/api/sessions`);
|
|
591
|
+
if (res.ok) {
|
|
592
|
+
const allSessions = await res.json();
|
|
593
|
+
const session = allSessions.find(s => s.id === sessionId);
|
|
594
|
+
if (session && session.active_clients > 0) {
|
|
595
|
+
console.log(`\n\x1b[33mLeft room '${sessionId}'. Other clients still attached — session kept alive.\x1b[0m`);
|
|
596
|
+
} else {
|
|
597
|
+
console.log(`\n\x1b[33mLeft room '${sessionId}'. No other clients — destroying session.\x1b[0m`);
|
|
598
|
+
await fetchWithAuth(`http://${targetHost}:${PORT}/api/sessions/${encodeURIComponent(sessionId)}`, { method: 'DELETE' });
|
|
599
|
+
}
|
|
600
|
+
}
|
|
430
601
|
} catch(e) {}
|
|
431
602
|
process.exit(0);
|
|
432
603
|
});
|
|
@@ -490,9 +661,30 @@ async function main() {
|
|
|
490
661
|
return;
|
|
491
662
|
}
|
|
492
663
|
|
|
493
|
-
if (cmd === '
|
|
664
|
+
if (cmd === 'rename') {
|
|
665
|
+
const oldId = args[1]; const newId = args[2];
|
|
666
|
+
if (!oldId || !newId) { console.error('❌ Usage: telepty rename <old_id> <new_id>'); process.exit(1); }
|
|
667
|
+
try {
|
|
668
|
+
const res = await fetchWithAuth(`${DAEMON_URL}/api/sessions/${encodeURIComponent(oldId)}`, {
|
|
669
|
+
method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ new_id: newId })
|
|
670
|
+
});
|
|
671
|
+
const data = await res.json();
|
|
672
|
+
if (!res.ok) { console.error(`❌ Error: ${data.error}`); return; }
|
|
673
|
+
console.log(`✅ Session renamed: '\x1b[36m${oldId}\x1b[0m' → '\x1b[36m${newId}\x1b[0m'`);
|
|
674
|
+
} catch (e) { console.error('❌ Failed to connect to daemon. Is it running?'); }
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (cmd === 'listen' || cmd === 'monitor') {
|
|
494
679
|
await ensureDaemonRunning();
|
|
495
|
-
|
|
680
|
+
|
|
681
|
+
if (cmd === 'monitor') {
|
|
682
|
+
console.log('\x1b[36m\x1b[1m📺 Telepty Event Billboard\x1b[0m');
|
|
683
|
+
console.log('Listening for background agent communications...\n');
|
|
684
|
+
} else {
|
|
685
|
+
console.log('\x1b[36m👂 Listening to the telepty event bus...\x1b[0m');
|
|
686
|
+
}
|
|
687
|
+
|
|
496
688
|
const wsUrl = `ws://${REMOTE_HOST}:${PORT}/api/bus?token=${encodeURIComponent(TOKEN)}`;
|
|
497
689
|
const ws = new WebSocket(wsUrl);
|
|
498
690
|
|
|
@@ -501,8 +693,34 @@ async function main() {
|
|
|
501
693
|
});
|
|
502
694
|
|
|
503
695
|
ws.on('message', (message) => {
|
|
504
|
-
|
|
505
|
-
|
|
696
|
+
const raw = message.toString();
|
|
697
|
+
if (cmd === 'listen') {
|
|
698
|
+
// Raw JSON output for machines
|
|
699
|
+
console.log(raw);
|
|
700
|
+
} else {
|
|
701
|
+
// Human readable billboard output
|
|
702
|
+
try {
|
|
703
|
+
const msg = JSON.parse(raw);
|
|
704
|
+
const time = new Date().toLocaleTimeString();
|
|
705
|
+
const sender = msg.sender || msg.from || 'Unknown';
|
|
706
|
+
const target = msg.target_agent || msg.to || 'Bus';
|
|
707
|
+
|
|
708
|
+
let preview = msg.content || msg.message || msg.payload || msg.data;
|
|
709
|
+
if (msg.type === 'session_spawn') {
|
|
710
|
+
console.log(`\x1b[90m[${time}]\x1b[0m 🚀 \x1b[32m\x1b[1mNew Session\x1b[0m: \x1b[36m${msg.session_id}\x1b[0m (${msg.command})`);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (typeof preview === 'object') preview = JSON.stringify(preview);
|
|
715
|
+
if (preview && preview.length > 200) preview = preview.substring(0, 197) + '...';
|
|
716
|
+
|
|
717
|
+
console.log(`\x1b[90m[${time}]\x1b[0m \x1b[32m\x1b[1m${sender}\x1b[0m ➔ \x1b[33m\x1b[1m${target}\x1b[0m`);
|
|
718
|
+
if (preview) console.log(` \x1b[37m${preview}\x1b[0m\n`);
|
|
719
|
+
} catch (e) {
|
|
720
|
+
// Fallback if not valid JSON
|
|
721
|
+
console.log(`\x1b[90m[${new Date().toLocaleTimeString()}]\x1b[0m 📦 \x1b[37m${raw}\x1b[0m\n`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
506
724
|
});
|
|
507
725
|
|
|
508
726
|
ws.on('close', () => {
|
|
@@ -522,12 +740,15 @@ async function main() {
|
|
|
522
740
|
Usage:
|
|
523
741
|
telepty daemon Start the background daemon
|
|
524
742
|
telepty spawn --id <id> <command> [args...] Spawn a new background CLI
|
|
743
|
+
telepty allow [--id <id>] <command> [args...] Allow inject on a CLI
|
|
525
744
|
telepty list List all active sessions
|
|
526
745
|
telepty attach [id] Attach to a session (Interactive picker if no ID)
|
|
527
746
|
telepty inject [--no-enter] <id> "<prompt>" Inject text into a single session
|
|
528
747
|
telepty multicast <id1,id2> "<prompt>" Inject text into multiple specific sessions
|
|
529
748
|
telepty broadcast "<prompt>" Inject text into ALL active sessions
|
|
749
|
+
telepty rename <old_id> <new_id> Rename a session (updates terminal title too)
|
|
530
750
|
telepty listen Listen to the event bus and print JSON to stdout
|
|
751
|
+
telepty monitor Human-readable real-time billboard of bus events
|
|
531
752
|
telepty update Update telepty to the latest version
|
|
532
753
|
`);
|
|
533
754
|
}
|