@dmsdc-ai/aigentry-telepty 0.1.5 → 0.1.7
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/README.md +27 -0
- package/cli.js +249 -46
- package/daemon.js +261 -27
- package/install.js +96 -64
- package/install.ps1 +1 -0
- package/install.sh +1 -0
- package/package.json +4 -2
- package/skill-installer.js +269 -0
- package/skills/telepty/SKILL.md +86 -0
- package/.cross_session_deliberation.json +0 -7
- package/.deliberation_request.json +0 -1
- package/.deliberation_request2.json +0 -1
- package/.deliberation_request3.json +0 -1
- package/.deliberation_request_bridge.json +0 -1
- package/.deliberation_request_bridge_retry.json +0 -1
- package/.gemini/skills/telepty/SKILL.md +0 -48
- package/.github/workflows/test-install.yml +0 -32
- package/aigentry-telepty-0.0.4.tgz +0 -0
- package/clipboard_image.png +0 -0
- package/test-pty.js +0 -14
package/daemon.js
CHANGED
|
@@ -35,9 +35,34 @@ const PORT = process.env.PORT || 3848;
|
|
|
35
35
|
const HOST = process.env.HOST || '0.0.0.0';
|
|
36
36
|
|
|
37
37
|
const sessions = {};
|
|
38
|
+
const STRIPPED_SESSION_ENV_KEYS = [
|
|
39
|
+
'CLAUDECODE',
|
|
40
|
+
'CODEX_CI',
|
|
41
|
+
'CODEX_THREAD_ID'
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function buildSessionEnv(sessionId) {
|
|
45
|
+
const env = {
|
|
46
|
+
...process.env,
|
|
47
|
+
TERM: os.platform() === 'win32' ? undefined : 'xterm-256color',
|
|
48
|
+
TELEPTY_SESSION_ID: sessionId
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
for (const key of STRIPPED_SESSION_ENV_KEYS) {
|
|
52
|
+
delete env[key];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const key of Object.keys(env)) {
|
|
56
|
+
if (key.startsWith('CLAUDECODE_')) {
|
|
57
|
+
delete env[key];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return env;
|
|
62
|
+
}
|
|
38
63
|
|
|
39
64
|
app.post('/api/sessions/spawn', (req, res) => {
|
|
40
|
-
const { session_id, command, args = [], cwd = process.cwd(), cols = 80, rows = 30 } = req.body;
|
|
65
|
+
const { session_id, command, args = [], cwd = process.cwd(), cols = 80, rows = 30, type = 'AGENT' } = req.body;
|
|
41
66
|
if (!session_id) return res.status(400).json({ error: 'session_id is strictly required.' });
|
|
42
67
|
if (sessions[session_id]) return res.status(409).json({ error: `Session ID '${session_id}' is already active.` });
|
|
43
68
|
if (!command) return res.status(400).json({ error: 'command is required' });
|
|
@@ -49,15 +74,18 @@ app.post('/api/sessions/spawn', (req, res) => {
|
|
|
49
74
|
try {
|
|
50
75
|
console.log(`[SPAWN] Spawning ${shell} with args:`, shellArgs, "in cwd:", cwd);
|
|
51
76
|
|
|
52
|
-
|
|
53
|
-
// For bash/zsh, we can inject a custom PS1 variable.
|
|
54
|
-
let customEnv = { ...process.env, TERM: isWin ? undefined : 'xterm-256color', TELEPTY_SESSION_ID: session_id };
|
|
77
|
+
const customEnv = buildSessionEnv(session_id);
|
|
55
78
|
|
|
56
79
|
if (!isWin) {
|
|
80
|
+
const label = type.toUpperCase();
|
|
81
|
+
const colorCode = label === 'USER' ? '32' : '35'; // USER: Green (32), AGENT: Magenta (35)
|
|
82
|
+
const zshColor = label === 'USER' ? 'green' : 'magenta';
|
|
83
|
+
|
|
57
84
|
if (command.includes('bash')) {
|
|
58
|
-
customEnv.PS1 = `\\[\\e[
|
|
85
|
+
customEnv.PS1 = `\\[\\e[${colorCode}m\\][${label}: ${session_id}]\\[\\e[0m\\] \\w \\$ `;
|
|
59
86
|
} else if (command.includes('zsh')) {
|
|
60
|
-
customEnv.
|
|
87
|
+
customEnv.DISABLE_AUTO_TITLE = 'true';
|
|
88
|
+
customEnv.PROMPT = `%F{${zshColor}}[${label}: ${session_id}]%f %~ %# `;
|
|
61
89
|
}
|
|
62
90
|
}
|
|
63
91
|
|
|
@@ -69,24 +97,51 @@ app.post('/api/sessions/spawn', (req, res) => {
|
|
|
69
97
|
env: customEnv
|
|
70
98
|
});
|
|
71
99
|
|
|
72
|
-
|
|
100
|
+
const sessionRecord = {
|
|
101
|
+
id: session_id,
|
|
102
|
+
type: 'spawned',
|
|
73
103
|
ptyProcess,
|
|
74
104
|
command,
|
|
75
105
|
cwd,
|
|
76
106
|
createdAt: new Date().toISOString(),
|
|
77
|
-
clients: new Set()
|
|
107
|
+
clients: new Set(),
|
|
108
|
+
isClosing: false
|
|
78
109
|
};
|
|
110
|
+
sessions[session_id] = sessionRecord;
|
|
111
|
+
|
|
112
|
+
// Broadcast session creation to bus
|
|
113
|
+
const spawnMsg = JSON.stringify({
|
|
114
|
+
type: 'session_spawn',
|
|
115
|
+
sender: 'daemon',
|
|
116
|
+
session_id,
|
|
117
|
+
command,
|
|
118
|
+
cwd,
|
|
119
|
+
timestamp: new Date().toISOString()
|
|
120
|
+
});
|
|
121
|
+
busClients.forEach(client => {
|
|
122
|
+
if (client.readyState === 1) client.send(spawnMsg);
|
|
123
|
+
});
|
|
79
124
|
|
|
80
125
|
ptyProcess.onData((data) => {
|
|
81
|
-
sessions[
|
|
126
|
+
const currentSession = sessions[sessionRecord.id];
|
|
127
|
+
if (!currentSession || currentSession !== sessionRecord) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Send to direct WS clients
|
|
132
|
+
currentSession.clients.forEach(ws => {
|
|
82
133
|
if (ws.readyState === 1) ws.send(JSON.stringify({ type: 'output', data }));
|
|
83
134
|
});
|
|
84
135
|
});
|
|
85
136
|
|
|
86
137
|
ptyProcess.onExit(({ exitCode, signal }) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
138
|
+
const currentId = sessionRecord.id;
|
|
139
|
+
console.log(`[EXIT] Session ${currentId} exited with code ${exitCode}`);
|
|
140
|
+
sessionRecord.isClosing = true;
|
|
141
|
+
sessionRecord.clients.forEach(ws => ws.close(1000, 'Session exited'));
|
|
142
|
+
if (sessions[currentId] === sessionRecord) {
|
|
143
|
+
delete sessions[currentId];
|
|
144
|
+
}
|
|
90
145
|
});
|
|
91
146
|
|
|
92
147
|
console.log(`[SPAWN] Created session ${session_id} (${command})`);
|
|
@@ -96,9 +151,44 @@ app.post('/api/sessions/spawn', (req, res) => {
|
|
|
96
151
|
}
|
|
97
152
|
});
|
|
98
153
|
|
|
154
|
+
app.post('/api/sessions/register', (req, res) => {
|
|
155
|
+
const { session_id, command, cwd = process.cwd() } = req.body;
|
|
156
|
+
if (!session_id) return res.status(400).json({ error: 'session_id is required' });
|
|
157
|
+
if (sessions[session_id]) return res.status(409).json({ error: `Session ID '${session_id}' is already active.` });
|
|
158
|
+
|
|
159
|
+
const sessionRecord = {
|
|
160
|
+
id: session_id,
|
|
161
|
+
type: 'wrapped',
|
|
162
|
+
ptyProcess: null,
|
|
163
|
+
ownerWs: null,
|
|
164
|
+
command: command || 'wrapped',
|
|
165
|
+
cwd,
|
|
166
|
+
createdAt: new Date().toISOString(),
|
|
167
|
+
clients: new Set(),
|
|
168
|
+
isClosing: false
|
|
169
|
+
};
|
|
170
|
+
sessions[session_id] = sessionRecord;
|
|
171
|
+
|
|
172
|
+
const busMsg = JSON.stringify({
|
|
173
|
+
type: 'session_register',
|
|
174
|
+
sender: 'daemon',
|
|
175
|
+
session_id,
|
|
176
|
+
command: sessionRecord.command,
|
|
177
|
+
cwd,
|
|
178
|
+
timestamp: new Date().toISOString()
|
|
179
|
+
});
|
|
180
|
+
busClients.forEach(client => {
|
|
181
|
+
if (client.readyState === 1) client.send(busMsg);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
console.log(`[REGISTER] Registered wrapped session ${session_id}`);
|
|
185
|
+
res.status(201).json({ session_id, type: 'wrapped', command: sessionRecord.command, cwd });
|
|
186
|
+
});
|
|
187
|
+
|
|
99
188
|
app.get('/api/sessions', (req, res) => {
|
|
100
189
|
const list = Object.entries(sessions).map(([id, session]) => ({
|
|
101
190
|
id,
|
|
191
|
+
type: session.type || 'spawned',
|
|
102
192
|
command: session.command,
|
|
103
193
|
cwd: session.cwd,
|
|
104
194
|
createdAt: session.createdAt,
|
|
@@ -118,8 +208,30 @@ app.post('/api/sessions/multicast/inject', (req, res) => {
|
|
|
118
208
|
const session = sessions[id];
|
|
119
209
|
if (session) {
|
|
120
210
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
211
|
+
const injectData = `${prompt}\r`;
|
|
212
|
+
if (session.type === 'wrapped') {
|
|
213
|
+
if (session.ownerWs && session.ownerWs.readyState === 1) {
|
|
214
|
+
session.ownerWs.send(JSON.stringify({ type: 'inject', data: injectData }));
|
|
215
|
+
results.successful.push(id);
|
|
216
|
+
} else {
|
|
217
|
+
results.failed.push({ id, error: 'Wrap process not connected' });
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
session.ptyProcess.write(injectData);
|
|
221
|
+
results.successful.push(id);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Broadcast injection to bus
|
|
225
|
+
const busMsg = JSON.stringify({
|
|
226
|
+
type: 'injection',
|
|
227
|
+
sender: 'cli',
|
|
228
|
+
target_agent: id,
|
|
229
|
+
content: prompt,
|
|
230
|
+
timestamp: new Date().toISOString()
|
|
231
|
+
});
|
|
232
|
+
busClients.forEach(client => {
|
|
233
|
+
if (client.readyState === 1) client.send(busMsg);
|
|
234
|
+
});
|
|
123
235
|
} catch (err) {
|
|
124
236
|
results.failed.push({ id, error: err.message });
|
|
125
237
|
}
|
|
@@ -138,14 +250,40 @@ app.post('/api/sessions/broadcast/inject', (req, res) => {
|
|
|
138
250
|
const results = { successful: [], failed: [] };
|
|
139
251
|
|
|
140
252
|
Object.keys(sessions).forEach(id => {
|
|
253
|
+
const session = sessions[id];
|
|
141
254
|
try {
|
|
142
|
-
|
|
143
|
-
|
|
255
|
+
const injectData = `${prompt}\r`;
|
|
256
|
+
if (session.type === 'wrapped') {
|
|
257
|
+
if (session.ownerWs && session.ownerWs.readyState === 1) {
|
|
258
|
+
session.ownerWs.send(JSON.stringify({ type: 'inject', data: injectData }));
|
|
259
|
+
results.successful.push(id);
|
|
260
|
+
} else {
|
|
261
|
+
results.failed.push({ id, error: 'Wrap process not connected' });
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
session.ptyProcess.write(injectData);
|
|
265
|
+
results.successful.push(id);
|
|
266
|
+
}
|
|
144
267
|
} catch (err) {
|
|
145
268
|
results.failed.push({ id, error: err.message });
|
|
146
269
|
}
|
|
147
270
|
});
|
|
148
271
|
|
|
272
|
+
// Send a single bus event for the entire broadcast (not per-session)
|
|
273
|
+
if (results.successful.length > 0) {
|
|
274
|
+
const busMsg = JSON.stringify({
|
|
275
|
+
type: 'injection',
|
|
276
|
+
sender: 'cli',
|
|
277
|
+
target_agent: 'all',
|
|
278
|
+
content: prompt,
|
|
279
|
+
session_ids: results.successful,
|
|
280
|
+
timestamp: new Date().toISOString()
|
|
281
|
+
});
|
|
282
|
+
busClients.forEach(client => {
|
|
283
|
+
if (client.readyState === 1) client.send(busMsg);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
149
287
|
res.json({ success: true, results });
|
|
150
288
|
});
|
|
151
289
|
|
|
@@ -156,23 +294,80 @@ app.post('/api/sessions/:id/inject', (req, res) => {
|
|
|
156
294
|
if (!session) return res.status(404).json({ error: 'Session not found' });
|
|
157
295
|
if (!prompt) return res.status(400).json({ error: 'prompt is required' });
|
|
158
296
|
try {
|
|
159
|
-
|
|
297
|
+
const injectData = no_enter ? prompt : `${prompt}\r`;
|
|
298
|
+
if (session.type === 'wrapped') {
|
|
299
|
+
if (session.ownerWs && session.ownerWs.readyState === 1) {
|
|
300
|
+
session.ownerWs.send(JSON.stringify({ type: 'inject', data: injectData }));
|
|
301
|
+
} else {
|
|
302
|
+
return res.status(503).json({ error: 'Wrap process is not connected' });
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
session.ptyProcess.write(injectData);
|
|
306
|
+
}
|
|
160
307
|
console.log(`[INJECT] Wrote to session ${id}`);
|
|
308
|
+
|
|
309
|
+
const busMsg = JSON.stringify({
|
|
310
|
+
type: 'injection',
|
|
311
|
+
sender: 'cli',
|
|
312
|
+
target_agent: id,
|
|
313
|
+
content: prompt,
|
|
314
|
+
timestamp: new Date().toISOString()
|
|
315
|
+
});
|
|
316
|
+
busClients.forEach(client => {
|
|
317
|
+
if (client.readyState === 1) client.send(busMsg);
|
|
318
|
+
});
|
|
319
|
+
|
|
161
320
|
res.json({ success: true });
|
|
162
321
|
} catch (err) {
|
|
163
322
|
res.status(500).json({ error: err.message });
|
|
164
323
|
}
|
|
165
324
|
});
|
|
166
325
|
|
|
326
|
+
app.patch('/api/sessions/:id', (req, res) => {
|
|
327
|
+
const { id } = req.params;
|
|
328
|
+
const { new_id } = req.body;
|
|
329
|
+
const session = sessions[id];
|
|
330
|
+
if (!session) return res.status(404).json({ error: 'Session not found' });
|
|
331
|
+
if (!new_id) return res.status(400).json({ error: 'new_id is required' });
|
|
332
|
+
if (sessions[new_id]) return res.status(409).json({ error: `Session ID '${new_id}' is already in use.` });
|
|
333
|
+
|
|
334
|
+
// Move session to new key
|
|
335
|
+
sessions[new_id] = session;
|
|
336
|
+
delete sessions[id];
|
|
337
|
+
session.id = new_id;
|
|
338
|
+
|
|
339
|
+
// Broadcast rename to bus
|
|
340
|
+
const busMsg = JSON.stringify({
|
|
341
|
+
type: 'session_rename',
|
|
342
|
+
sender: 'daemon',
|
|
343
|
+
old_id: id,
|
|
344
|
+
new_id,
|
|
345
|
+
timestamp: new Date().toISOString()
|
|
346
|
+
});
|
|
347
|
+
busClients.forEach(client => {
|
|
348
|
+
if (client.readyState === 1) client.send(busMsg);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
console.log(`[RENAME] Session '${id}' renamed to '${new_id}'`);
|
|
352
|
+
res.json({ success: true, old_id: id, new_id });
|
|
353
|
+
});
|
|
354
|
+
|
|
167
355
|
app.delete('/api/sessions/:id', (req, res) => {
|
|
168
356
|
const { id } = req.params;
|
|
169
357
|
const session = sessions[id];
|
|
170
358
|
if (!session) return res.status(404).json({ error: 'Session not found' });
|
|
359
|
+
if (session.isClosing) return res.json({ success: true, status: 'closing' });
|
|
171
360
|
try {
|
|
172
|
-
session.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
361
|
+
session.isClosing = true;
|
|
362
|
+
if (session.type === 'wrapped') {
|
|
363
|
+
session.clients.forEach(ws => ws.close(1000, 'Session destroyed'));
|
|
364
|
+
delete sessions[id];
|
|
365
|
+
console.log(`[KILL] Wrapped session ${id} removed`);
|
|
366
|
+
} else {
|
|
367
|
+
session.ptyProcess.kill();
|
|
368
|
+
console.log(`[KILL] Session ${id} forcefully closed`);
|
|
369
|
+
}
|
|
370
|
+
res.json({ success: true, status: 'closing' });
|
|
176
371
|
} catch (err) {
|
|
177
372
|
res.status(500).json({ error: err.message });
|
|
178
373
|
}
|
|
@@ -215,15 +410,44 @@ wss.on('connection', (ws, req) => {
|
|
|
215
410
|
}
|
|
216
411
|
|
|
217
412
|
session.clients.add(ws);
|
|
218
|
-
|
|
413
|
+
|
|
414
|
+
// For wrapped sessions, first connector becomes the owner
|
|
415
|
+
if (session.type === 'wrapped' && !session.ownerWs) {
|
|
416
|
+
session.ownerWs = ws;
|
|
417
|
+
console.log(`[WS] Wrap owner connected for session ${sessionId} (Total: ${session.clients.size})`);
|
|
418
|
+
} else {
|
|
419
|
+
console.log(`[WS] Client attached to session ${sessionId} (Total: ${session.clients.size})`);
|
|
420
|
+
}
|
|
219
421
|
|
|
220
422
|
ws.on('message', (message) => {
|
|
221
423
|
try {
|
|
222
424
|
const { type, data, cols, rows } = JSON.parse(message);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
425
|
+
|
|
426
|
+
if (session.type === 'wrapped') {
|
|
427
|
+
if (ws === session.ownerWs) {
|
|
428
|
+
// Owner sending output -> broadcast to other clients
|
|
429
|
+
if (type === 'output') {
|
|
430
|
+
session.clients.forEach(client => {
|
|
431
|
+
if (client !== ws && client.readyState === 1) {
|
|
432
|
+
client.send(JSON.stringify({ type: 'output', data }));
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
} else {
|
|
437
|
+
// Non-owner client input -> forward to owner as inject
|
|
438
|
+
if (type === 'input' && session.ownerWs && session.ownerWs.readyState === 1) {
|
|
439
|
+
session.ownerWs.send(JSON.stringify({ type: 'inject', data }));
|
|
440
|
+
} else if (type === 'resize' && session.ownerWs && session.ownerWs.readyState === 1) {
|
|
441
|
+
session.ownerWs.send(JSON.stringify({ type: 'resize', cols, rows }));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
// Existing spawned session logic
|
|
446
|
+
if (type === 'input') {
|
|
447
|
+
session.ptyProcess.write(data);
|
|
448
|
+
} else if (type === 'resize') {
|
|
449
|
+
session.ptyProcess.resize(cols, rows);
|
|
450
|
+
}
|
|
227
451
|
}
|
|
228
452
|
} catch (e) {
|
|
229
453
|
console.error('[WS] Invalid message format', e);
|
|
@@ -232,7 +456,17 @@ wss.on('connection', (ws, req) => {
|
|
|
232
456
|
|
|
233
457
|
ws.on('close', () => {
|
|
234
458
|
session.clients.delete(ws);
|
|
235
|
-
|
|
459
|
+
if (session.type === 'wrapped' && ws === session.ownerWs) {
|
|
460
|
+
session.ownerWs = null;
|
|
461
|
+
console.log(`[WS] Wrap owner disconnected from session ${sessionId} (Total: ${session.clients.size})`);
|
|
462
|
+
// Clean up wrapped session when owner disconnects and no other clients
|
|
463
|
+
if (session.clients.size === 0 && !session.isClosing) {
|
|
464
|
+
delete sessions[sessionId];
|
|
465
|
+
console.log(`[CLEANUP] Wrapped session ${sessionId} removed (owner disconnected)`);
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
console.log(`[WS] Client detached from session ${sessionId} (Total: ${session.clients.size})`);
|
|
469
|
+
}
|
|
236
470
|
});
|
|
237
471
|
});
|
|
238
472
|
|
package/install.js
CHANGED
|
@@ -4,6 +4,7 @@ const { execSync, spawn } = require('child_process');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
+
const { runInteractiveSkillInstaller } = require('./skill-installer');
|
|
7
8
|
|
|
8
9
|
console.log("🚀 Installing @dmsdc-ai/aigentry-telepty...");
|
|
9
10
|
|
|
@@ -16,38 +17,69 @@ function run(cmd) {
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
function resolveInstalledPackageRoot() {
|
|
21
|
+
try {
|
|
22
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
|
|
23
|
+
return path.join(globalRoot, '@dmsdc-ai', 'aigentry-telepty');
|
|
24
|
+
} catch (e) {
|
|
25
|
+
return __dirname;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function installSkills() {
|
|
30
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
31
|
+
console.log('⏭️ Skipping interactive skill installation (no TTY).');
|
|
32
|
+
console.log(' Run `telepty` later and choose "Install telepty skills".');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log('\n📋 Telepty skill installation');
|
|
22
37
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
38
|
+
try {
|
|
39
|
+
await runInteractiveSkillInstaller({
|
|
40
|
+
packageRoot: resolveInstalledPackageRoot(),
|
|
41
|
+
cwd: process.cwd()
|
|
42
|
+
});
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.warn('⚠️ Could not install telepty skills:', e.message);
|
|
45
|
+
}
|
|
29
46
|
}
|
|
30
47
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
(async () => {
|
|
49
|
+
// 1. Install globally via npm
|
|
50
|
+
console.log("📦 Installing package globally...");
|
|
51
|
+
run("npm install -g @dmsdc-ai/aigentry-telepty");
|
|
52
|
+
|
|
53
|
+
// 2. Install telepty skills for supported clients
|
|
54
|
+
await installSkills();
|
|
55
|
+
|
|
56
|
+
// 3. Find executable
|
|
57
|
+
let teleptyPath = '';
|
|
58
|
+
try {
|
|
59
|
+
teleptyPath = execSync(os.platform() === 'win32' ? 'where telepty' : 'which telepty', { encoding: 'utf8' }).split('\n')[0].trim();
|
|
60
|
+
} catch (e) {
|
|
61
|
+
teleptyPath = 'telepty'; // fallback
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 4. Setup OS-specific autostart or background daemon
|
|
65
|
+
const platform = os.platform();
|
|
66
|
+
|
|
67
|
+
if (platform === 'win32') {
|
|
68
|
+
console.log("⚙️ Setting up Windows background process...");
|
|
69
|
+
const subprocess = spawn(teleptyPath, ['daemon'], {
|
|
70
|
+
detached: true,
|
|
71
|
+
stdio: 'ignore',
|
|
72
|
+
windowsHide: true
|
|
73
|
+
});
|
|
74
|
+
subprocess.unref();
|
|
75
|
+
console.log("✅ Windows daemon started in background.");
|
|
76
|
+
|
|
77
|
+
} else if (platform === 'darwin') {
|
|
78
|
+
console.log("⚙️ Setting up macOS launchd service...");
|
|
79
|
+
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.aigentry.telepty.plist');
|
|
80
|
+
fs.mkdirSync(path.dirname(plistPath), { recursive: true });
|
|
81
|
+
|
|
82
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
51
83
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
52
84
|
<plist version="1.0">
|
|
53
85
|
<dict>
|
|
@@ -64,20 +96,19 @@ if (platform === 'win32') {
|
|
|
64
96
|
<true/>
|
|
65
97
|
</dict>
|
|
66
98
|
</plist>`;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
} else {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const serviceContent = `[Unit]
|
|
99
|
+
|
|
100
|
+
fs.writeFileSync(plistPath, plistContent);
|
|
101
|
+
try { execSync(`launchctl unload "${plistPath}" 2>/dev/null`); } catch(e){}
|
|
102
|
+
run(`launchctl load "${plistPath}"`);
|
|
103
|
+
console.log("✅ macOS LaunchAgent installed and started.");
|
|
104
|
+
|
|
105
|
+
} else {
|
|
106
|
+
// Linux
|
|
107
|
+
try {
|
|
108
|
+
execSync('systemctl --version', { stdio: 'ignore' });
|
|
109
|
+
if (process.getuid && process.getuid() === 0) {
|
|
110
|
+
console.log("⚙️ Setting up systemd service for Linux...");
|
|
111
|
+
const serviceContent = `[Unit]
|
|
81
112
|
Description=Telepty Daemon
|
|
82
113
|
After=network.target
|
|
83
114
|
|
|
@@ -90,25 +121,26 @@ Environment=NODE_ENV=production
|
|
|
90
121
|
|
|
91
122
|
[Install]
|
|
92
123
|
WantedBy=multi-user.target`;
|
|
93
|
-
|
|
94
|
-
fs.writeFileSync('/etc/systemd/system/telepty.service', serviceContent);
|
|
95
|
-
run('systemctl daemon-reload');
|
|
96
|
-
run('systemctl enable telepty');
|
|
97
|
-
run('systemctl start telepty');
|
|
98
|
-
console.log("✅ Systemd service installed and started.");
|
|
99
|
-
process.exit(0);
|
|
100
|
-
}
|
|
101
|
-
} catch(e) {}
|
|
102
|
-
|
|
103
|
-
// Fallback for Linux without systemd or non-root
|
|
104
|
-
console.log("⚠️ Skipping systemd (no root or no systemd). Starting in background...");
|
|
105
|
-
const subprocess = spawn(teleptyPath, ['daemon'], {
|
|
106
|
-
detached: true,
|
|
107
|
-
stdio: 'ignore'
|
|
108
|
-
});
|
|
109
|
-
subprocess.unref();
|
|
110
|
-
console.log("✅ Linux daemon started in background using nohup equivalent.");
|
|
111
|
-
}
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
fs.writeFileSync('/etc/systemd/system/telepty.service', serviceContent);
|
|
126
|
+
run('systemctl daemon-reload');
|
|
127
|
+
run('systemctl enable telepty');
|
|
128
|
+
run('systemctl start telepty');
|
|
129
|
+
console.log("✅ Systemd service installed and started.");
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
} catch(e) {}
|
|
133
|
+
|
|
134
|
+
// Fallback for Linux without systemd or non-root
|
|
135
|
+
console.log("⚠️ Skipping systemd (no root or no systemd). Starting in background...");
|
|
136
|
+
const subprocess = spawn(teleptyPath, ['daemon'], {
|
|
137
|
+
detached: true,
|
|
138
|
+
stdio: 'ignore'
|
|
139
|
+
});
|
|
140
|
+
subprocess.unref();
|
|
141
|
+
console.log("✅ Linux daemon started in background using nohup equivalent.");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log("\n🎉 Installation complete! Telepty daemon is running.");
|
|
145
|
+
console.log("👉 Try running: telepty attach\n");
|
|
146
|
+
})();
|
package/install.ps1
CHANGED
|
@@ -31,3 +31,4 @@ Write-Host "Success: Windows daemon started in background." -ForegroundColor Gre
|
|
|
31
31
|
|
|
32
32
|
Write-Host "`nInstallation complete! Telepty daemon is running." -ForegroundColor Cyan
|
|
33
33
|
Write-Host "Next step: Try running: telepty attach" -ForegroundColor Yellow
|
|
34
|
+
Write-Host "Optional: Run telepty and choose 'Install telepty skills' to add skills for Claude Code, Codex, or Gemini" -ForegroundColor Yellow
|
package/install.sh
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmsdc-ai/aigentry-telepty",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"main": "daemon.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"telepty": "cli.js",
|
|
7
7
|
"telepty-install": "install.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "
|
|
10
|
+
"test": "node --test test/auth.test.js test/daemon.test.js test/cli.test.js test/skill-installer.test.js",
|
|
11
|
+
"test:watch": "node --test --watch test/auth.test.js test/daemon.test.js test/cli.test.js test/skill-installer.test.js",
|
|
12
|
+
"test:ci": "node --test --test-reporter=spec test/auth.test.js test/daemon.test.js test/cli.test.js test/skill-installer.test.js"
|
|
11
13
|
},
|
|
12
14
|
"keywords": [],
|
|
13
15
|
"author": "",
|