@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/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
- // Create a custom prompt for Linux/Mac so the user sees the session ID.
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[36m\\][telepty: ${session_id}]\\[\\e[0m\\] \\w \\$ `;
85
+ customEnv.PS1 = `\\[\\e[${colorCode}m\\][${label}: ${session_id}]\\[\\e[0m\\] \\w \\$ `;
59
86
  } else if (command.includes('zsh')) {
60
- customEnv.PROMPT = `%F{cyan}[telepty: ${session_id}]%f %~ %# `;
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
- sessions[session_id] = {
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[session_id].clients.forEach(ws => {
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
- console.log(`[EXIT] Session ${session_id} exited with code ${exitCode}`);
88
- sessions[session_id].clients.forEach(ws => ws.close(1000, 'Session exited'));
89
- delete sessions[session_id];
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
- session.ptyProcess.write(`${prompt}\r`);
122
- results.successful.push(id);
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
- sessions[id].ptyProcess.write(`${prompt}\r`);
143
- results.successful.push(id);
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
- session.ptyProcess.write(no_enter ? prompt : `${prompt}\r`);
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.ptyProcess.kill();
173
- delete sessions[id];
174
- console.log(`[KILL] Session ${id} forcefully closed`);
175
- res.json({ success: true });
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
- console.log(`[WS] Client attached to session \${sessionId} (Total: \${session.clients.size})`);
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
- if (type === 'input') {
224
- session.ptyProcess.write(data);
225
- } else if (type === 'resize') {
226
- session.ptyProcess.resize(cols, rows);
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
- console.log(`[WS] Client detached from session \${sessionId} (Total: \${session.clients.size})`);
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
- // 1. Install globally via npm
20
- console.log("📦 Installing package globally...");
21
- run("npm install -g @dmsdc-ai/aigentry-telepty");
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
- // 2. Find executable
24
- let teleptyPath = '';
25
- try {
26
- teleptyPath = execSync(os.platform() === 'win32' ? 'where telepty' : 'which telepty', { encoding: 'utf8' }).split('\n')[0].trim();
27
- } catch (e) {
28
- teleptyPath = 'telepty'; // fallback
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
- // 3. Setup OS-specific autostart or background daemon
32
- const platform = os.platform();
33
-
34
- if (platform === 'win32') {
35
- console.log("⚙️ Setting up Windows background process...");
36
- // Launch daemon in background detaching from current console
37
- const subprocess = spawn(teleptyPath, ['daemon'], {
38
- detached: true,
39
- stdio: 'ignore',
40
- windowsHide: true
41
- });
42
- subprocess.unref();
43
- console.log("✅ Windows daemon started in background.");
44
-
45
- } else if (platform === 'darwin') {
46
- console.log("⚙️ Setting up macOS launchd service...");
47
- const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.aigentry.telepty.plist');
48
- fs.mkdirSync(path.dirname(plistPath), { recursive: true });
49
-
50
- const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
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
- fs.writeFileSync(plistPath, plistContent);
69
- try { execSync(`launchctl unload "${plistPath}" 2>/dev/null`); } catch(e){}
70
- run(`launchctl load "${plistPath}"`);
71
- console.log("✅ macOS LaunchAgent installed and started.");
72
-
73
- } else {
74
- // Linux
75
- try {
76
- // Check if systemd is available and running as root/sudo
77
- execSync('systemctl --version', { stdio: 'ignore' });
78
- if (process.getuid && process.getuid() === 0) {
79
- console.log("⚙️ Setting up systemd service for Linux...");
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
- console.log("\n🎉 Installation complete! Telepty daemon is running.");
114
- console.log("👉 Try running: telepty attach\n");
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
@@ -116,3 +116,4 @@ fi
116
116
  echo ""
117
117
  echo "🎉 Installation complete! Telepty daemon is running in the background."
118
118
  echo "👉 Try running: telepty attach"
119
+ echo "👉 Optional: run telepty and choose 'Install telepty skills' to add skills for Claude Code, Codex, or Gemini"
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.5",
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": "echo \"Error: no test specified\" && exit 1"
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": "",