@dmsdc-ai/aigentry-telepty 0.1.13 → 0.1.15
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/cli.js +159 -29
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -15,6 +15,89 @@ const { getRuntimeInfo } = require('./runtime-info');
|
|
|
15
15
|
const { formatHostLabel, groupSessionsByHost, pickSessionTarget } = require('./session-routing');
|
|
16
16
|
const { runInteractiveSkillInstaller } = require('./skill-installer');
|
|
17
17
|
const args = process.argv.slice(2);
|
|
18
|
+
let pendingTerminalInputError = null;
|
|
19
|
+
let simulatedPromptErrorInjected = false;
|
|
20
|
+
|
|
21
|
+
function isRecoverableTerminalInputError(error) {
|
|
22
|
+
return Boolean(error && (error.code === 'EIO' || error.syscall === 'read'));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function rememberTerminalInputError(error) {
|
|
26
|
+
pendingTerminalInputError = error;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function consumeTerminalInputError() {
|
|
30
|
+
if (!pendingTerminalInputError) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const error = pendingTerminalInputError;
|
|
35
|
+
pendingTerminalInputError = null;
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resetInteractiveInput(stream = process.stdin) {
|
|
40
|
+
if (!stream) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (stream.isTTY && typeof stream.setRawMode === 'function') {
|
|
45
|
+
try {
|
|
46
|
+
stream.setRawMode(false);
|
|
47
|
+
} catch {
|
|
48
|
+
// Ignore raw-mode reset failures when the TTY is already gone.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof stream.pause === 'function') {
|
|
53
|
+
stream.pause();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof stream.resume === 'function') {
|
|
57
|
+
stream.resume();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handleTerminalInputError(error, options = {}) {
|
|
62
|
+
if (!isRecoverableTerminalInputError(error)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
rememberTerminalInputError(error);
|
|
67
|
+
resetInteractiveInput(options.stream);
|
|
68
|
+
|
|
69
|
+
if (!options.silent) {
|
|
70
|
+
process.stderr.write('\n\x1b[33m⚠️ Terminal input was interrupted. Returning to the telepty menu...\x1b[0m\n');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const originalCreateInterface = readline.createInterface.bind(readline);
|
|
77
|
+
readline.createInterface = function patchedCreateInterface(...interfaceArgs) {
|
|
78
|
+
const rl = originalCreateInterface(...interfaceArgs);
|
|
79
|
+
rl.on('error', (error) => {
|
|
80
|
+
if (handleTerminalInputError(error, { stream: rl.input, silent: true })) {
|
|
81
|
+
try {
|
|
82
|
+
rl.close();
|
|
83
|
+
} catch {
|
|
84
|
+
// Ignore close failures after a TTY read error.
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
process.stderr.write(`\n❌ Telepty terminal input error: ${error.message}\n`);
|
|
90
|
+
});
|
|
91
|
+
return rl;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
process.stdin.on('error', (error) => {
|
|
95
|
+
if (handleTerminalInputError(error, { stream: process.stdin, silent: true })) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
process.stderr.write(`\n❌ Telepty stdin error: ${error.message}\n`);
|
|
100
|
+
});
|
|
18
101
|
|
|
19
102
|
// Check for updates unless explicitly disabled for tests/CI.
|
|
20
103
|
if (!process.env.NO_UPDATE_NOTIFIER && !process.env.TELEPTY_DISABLE_UPDATE_NOTIFIER) {
|
|
@@ -57,23 +140,45 @@ function startDetachedDaemon() {
|
|
|
57
140
|
cp.unref();
|
|
58
141
|
}
|
|
59
142
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
NO_UPDATE_NOTIFIER: '1',
|
|
67
|
-
TELEPTY_DISABLE_UPDATE_NOTIFIER: '1'
|
|
68
|
-
}
|
|
69
|
-
});
|
|
143
|
+
function renderInteractiveHeader() {
|
|
144
|
+
const runtimeInfo = getRuntimeInfo(__dirname);
|
|
145
|
+
console.clear();
|
|
146
|
+
console.log('\x1b[36m\x1b[1m⚡ Telepty Agent Manager\x1b[0m\n');
|
|
147
|
+
console.log(`\x1b[90mVersion ${runtimeInfo.version} Updated ${runtimeInfo.updatedAtLabel}\x1b[0m\n`);
|
|
148
|
+
}
|
|
70
149
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
150
|
+
async function promptWithRecovery(promptConfig) {
|
|
151
|
+
if (process.env.TELEPTY_TEST_TRIGGER_PROMPT_EIO_ONCE === '1' && !simulatedPromptErrorInjected) {
|
|
152
|
+
simulatedPromptErrorInjected = true;
|
|
153
|
+
rememberTerminalInputError(Object.assign(new Error('simulated terminal EIO'), { code: 'EIO', syscall: 'read' }));
|
|
154
|
+
console.log('\n\x1b[33m⚠️ Terminal input was interrupted. Returning to the telepty menu...\x1b[0m\n');
|
|
155
|
+
return { __teleptyRetry: true };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const response = await prompts(promptConfig);
|
|
159
|
+
const terminalError = consumeTerminalInputError();
|
|
160
|
+
if (terminalError) {
|
|
161
|
+
console.log('\n\x1b[33m⚠️ Terminal input was interrupted. Returning to the telepty menu...\x1b[0m\n');
|
|
162
|
+
return { __teleptyRetry: true };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return response;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function runUpdateInstall() {
|
|
169
|
+
if (process.env.TELEPTY_SKIP_PACKAGE_UPDATE === '1') {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const updateCommand = process.env.TELEPTY_UPDATE_COMMAND || 'npm install -g @dmsdc-ai/aigentry-telepty@latest';
|
|
174
|
+
execSync(updateCommand, { stdio: 'inherit' });
|
|
74
175
|
}
|
|
75
176
|
|
|
76
177
|
async function repairLocalDaemon(options = {}) {
|
|
178
|
+
if (process.env.TELEPTY_SKIP_DAEMON_REPAIR === '1') {
|
|
179
|
+
return { stopped: 0, failed: 0, meta: null, skipped: true };
|
|
180
|
+
}
|
|
181
|
+
|
|
77
182
|
const restart = options.restart !== false;
|
|
78
183
|
const results = cleanupDaemonProcesses();
|
|
79
184
|
|
|
@@ -243,13 +348,10 @@ async function manageInteractiveAttach(sessionId, targetHost) {
|
|
|
243
348
|
}
|
|
244
349
|
|
|
245
350
|
async function manageInteractive() {
|
|
246
|
-
|
|
247
|
-
console.clear();
|
|
248
|
-
console.log('\x1b[36m\x1b[1m⚡ Telepty Agent Manager\x1b[0m\n');
|
|
249
|
-
console.log(`\x1b[90mVersion ${runtimeInfo.version} Updated ${runtimeInfo.updatedAtLabel}\x1b[0m\n`);
|
|
351
|
+
renderInteractiveHeader();
|
|
250
352
|
|
|
251
353
|
while (true) {
|
|
252
|
-
const response = await
|
|
354
|
+
const response = await promptWithRecovery({
|
|
253
355
|
type: 'select',
|
|
254
356
|
name: 'action',
|
|
255
357
|
message: 'What would you like to do?',
|
|
@@ -266,15 +368,23 @@ async function manageInteractive() {
|
|
|
266
368
|
]
|
|
267
369
|
});
|
|
268
370
|
|
|
371
|
+
if (response.__teleptyRetry) {
|
|
372
|
+
renderInteractiveHeader();
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
|
|
269
376
|
if (response.action === 'update') {
|
|
270
377
|
console.log('\n\x1b[36m🔄 Updating telepty to the latest version...\x1b[0m');
|
|
271
378
|
try {
|
|
272
|
-
|
|
379
|
+
runUpdateInstall();
|
|
273
380
|
console.log('\n\x1b[32m✅ Update complete! Restarting daemon...\x1b[0m');
|
|
274
|
-
await repairLocalDaemon({ restart: true });
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
381
|
+
const repairResult = await repairLocalDaemon({ restart: true });
|
|
382
|
+
if (repairResult.skipped) {
|
|
383
|
+
console.log('\x1b[36m↻ Refreshing telepty without daemon restart...\x1b[0m\n');
|
|
384
|
+
} else {
|
|
385
|
+
console.log('\x1b[36m↻ Returning to telepty...\x1b[0m\n');
|
|
386
|
+
}
|
|
387
|
+
renderInteractiveHeader();
|
|
278
388
|
} catch (e) {
|
|
279
389
|
console.error(`\n❌ Update failed: ${e.message}\n`);
|
|
280
390
|
}
|
|
@@ -331,10 +441,15 @@ async function manageInteractive() {
|
|
|
331
441
|
}
|
|
332
442
|
|
|
333
443
|
if (response.action === 'spawn') {
|
|
334
|
-
const
|
|
444
|
+
const spawnResponse = await promptWithRecovery([
|
|
335
445
|
{ type: 'text', name: 'id', message: 'Enter new session ID (e.g. agent-1):', validate: v => v ? true : 'Required' },
|
|
336
446
|
{ type: 'text', name: 'command', message: 'Enter command to run (e.g. bash, zsh, python):', initial: 'bash' }
|
|
337
447
|
]);
|
|
448
|
+
if (spawnResponse.__teleptyRetry) {
|
|
449
|
+
renderInteractiveHeader();
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const { id, command } = spawnResponse;
|
|
338
453
|
if (!id || !command) continue;
|
|
339
454
|
|
|
340
455
|
await ensureDaemonRunning();
|
|
@@ -361,10 +476,15 @@ async function manageInteractive() {
|
|
|
361
476
|
}
|
|
362
477
|
|
|
363
478
|
if (response.action === 'allow') {
|
|
364
|
-
const
|
|
479
|
+
const allowResponse = await promptWithRecovery([
|
|
365
480
|
{ type: 'text', name: 'id', message: 'Enter session ID (e.g. my-claude):', validate: v => v ? true : 'Required' },
|
|
366
481
|
{ type: 'text', name: 'command', message: 'Enter command to run (e.g. claude, codex, gemini, bash):', initial: 'bash' }
|
|
367
482
|
]);
|
|
483
|
+
if (allowResponse.__teleptyRetry) {
|
|
484
|
+
renderInteractiveHeader();
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const { id, command } = allowResponse;
|
|
368
488
|
if (!id || !command) continue;
|
|
369
489
|
|
|
370
490
|
// Delegate to the allow command handler by setting up args and calling main flow
|
|
@@ -380,7 +500,7 @@ async function manageInteractive() {
|
|
|
380
500
|
console.log('\n❌ No active sessions found to ' + response.action + '.\n');
|
|
381
501
|
continue;
|
|
382
502
|
}
|
|
383
|
-
const
|
|
503
|
+
const attachOrInjectResponse = await promptWithRecovery({
|
|
384
504
|
type: 'select',
|
|
385
505
|
name: 'target',
|
|
386
506
|
message: `Select a session to ${response.action}:`,
|
|
@@ -389,6 +509,11 @@ async function manageInteractive() {
|
|
|
389
509
|
value: s
|
|
390
510
|
}))
|
|
391
511
|
});
|
|
512
|
+
if (attachOrInjectResponse.__teleptyRetry) {
|
|
513
|
+
renderInteractiveHeader();
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
const { target } = attachOrInjectResponse;
|
|
392
517
|
|
|
393
518
|
if (!target) continue;
|
|
394
519
|
|
|
@@ -398,12 +523,17 @@ async function manageInteractive() {
|
|
|
398
523
|
}
|
|
399
524
|
|
|
400
525
|
if (response.action === 'inject') {
|
|
401
|
-
const
|
|
526
|
+
const injectPromptResponse = await promptWithRecovery({
|
|
402
527
|
type: 'text',
|
|
403
528
|
name: 'promptText',
|
|
404
529
|
message: 'Enter text to inject:',
|
|
405
530
|
validate: v => v ? true : 'Required'
|
|
406
531
|
});
|
|
532
|
+
if (injectPromptResponse.__teleptyRetry) {
|
|
533
|
+
renderInteractiveHeader();
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
const { promptText } = injectPromptResponse;
|
|
407
537
|
if (!promptText) continue;
|
|
408
538
|
try {
|
|
409
539
|
const res = await fetchWithAuth(`http://${target.host}:${PORT}/api/sessions/${encodeURIComponent(target.id)}/inject`, {
|
|
@@ -429,9 +559,9 @@ async function main() {
|
|
|
429
559
|
if (cmd === 'update') {
|
|
430
560
|
console.log('\x1b[36m🔄 Updating telepty to the latest version...\x1b[0m');
|
|
431
561
|
try {
|
|
432
|
-
|
|
562
|
+
runUpdateInstall();
|
|
433
563
|
console.log('\n\x1b[32m✅ Update complete! Restarting daemon...\x1b[0m');
|
|
434
|
-
|
|
564
|
+
await repairLocalDaemon({ restart: true });
|
|
435
565
|
console.log('🎉 You are now using the latest version.');
|
|
436
566
|
} catch (e) {
|
|
437
567
|
console.error('\n❌ Update failed. Please try running: npm install -g @dmsdc-ai/aigentry-telepty@latest');
|