@dmsdc-ai/aigentry-telepty 0.0.7 → 0.0.9
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/.gemini/skills/telepty/SKILL.md +19 -0
- package/cli.js +181 -2
- package/daemon.js +42 -0
- package/install.ps1 +1 -1
- package/mcp.js +34 -5
- package/package.json +2 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# telepty
|
|
2
|
+
|
|
3
|
+
**Description:**
|
|
4
|
+
Help the user interact with the `telepty` daemon, check their current session ID, list active sessions, and inject commands into remote or local PTY sessions.
|
|
5
|
+
|
|
6
|
+
**Trigger:**
|
|
7
|
+
When the user asks about their current session ID (e.g. "내 세션 ID가 뭐야?"), wants to check active sessions ("세션 목록 보여줘"), or wants to inject a prompt/command into a specific session ("dustcraw한테 메시지 보내줘").
|
|
8
|
+
|
|
9
|
+
**Instructions:**
|
|
10
|
+
1. **To check the current session ID:**
|
|
11
|
+
- Execute `run_shell_command` with `echo $TELEPTY_SESSION_ID`.
|
|
12
|
+
- If the value is empty, inform the user that the current shell is *not* running inside a telepty spawned session (it is a normal native terminal).
|
|
13
|
+
- If it has a value, output it clearly: "현재 계신 터미널의 telepty 세션 ID는 `[ID]` 입니다."
|
|
14
|
+
2. **To list all sessions:**
|
|
15
|
+
- Run `telepty list`.
|
|
16
|
+
3. **To inject a command into another session:**
|
|
17
|
+
- For a single session: Run `telepty inject <target_session_id> "<message or command>"`.
|
|
18
|
+
- For broadcasting to ALL active sessions: Run `telepty broadcast "<message or command>"`.
|
|
19
|
+
- For multicasting to multiple specific sessions: Run `telepty multicast <id1>,<id2> "<message or command>"`.
|
package/cli.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const WebSocket = require('ws');
|
|
5
|
-
const { execSync } = require('child_process');
|
|
5
|
+
const { execSync, spawn } = require('child_process');
|
|
6
6
|
const readline = require('readline');
|
|
7
|
+
const prompts = require('prompts');
|
|
7
8
|
const { getConfig } = require('./auth');
|
|
8
9
|
const args = process.argv.slice(2);
|
|
9
10
|
|
|
@@ -59,8 +60,152 @@ async function discoverSessions() {
|
|
|
59
60
|
return allSessions;
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
async function manageInteractive() {
|
|
64
|
+
console.clear();
|
|
65
|
+
console.log('\x1b[36m\x1b[1m⚡ Telepty Agent Manager\x1b[0m\n');
|
|
66
|
+
|
|
67
|
+
while (true) {
|
|
68
|
+
const response = await prompts({
|
|
69
|
+
type: 'select',
|
|
70
|
+
name: 'action',
|
|
71
|
+
message: 'What would you like to do?',
|
|
72
|
+
choices: [
|
|
73
|
+
{ title: '📡 Attach to a session', value: 'attach' },
|
|
74
|
+
{ title: '🚀 Spawn a new session', value: 'spawn' },
|
|
75
|
+
{ title: '💉 Inject command into a session', value: 'inject' },
|
|
76
|
+
{ title: '📋 List active sessions', value: 'list' },
|
|
77
|
+
{ title: '⚙️ Start background daemon', value: 'daemon' },
|
|
78
|
+
{ title: '❌ Exit', value: 'exit' }
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!response.action || response.action === 'exit') {
|
|
83
|
+
console.log('Goodbye!');
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (response.action === 'daemon') {
|
|
88
|
+
console.log('\n\x1b[33mStarting daemon in background...\x1b[0m');
|
|
89
|
+
const cp = spawn(process.argv[0], [process.argv[1], 'daemon'], {
|
|
90
|
+
detached: true,
|
|
91
|
+
stdio: 'ignore'
|
|
92
|
+
});
|
|
93
|
+
cp.unref();
|
|
94
|
+
console.log('✅ Daemon started.\n');
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (response.action === 'list') {
|
|
99
|
+
console.log('\n');
|
|
100
|
+
const sessions = await discoverSessions();
|
|
101
|
+
if (sessions.length === 0) {
|
|
102
|
+
console.log('❌ No active sessions found.');
|
|
103
|
+
} else {
|
|
104
|
+
console.log('\x1b[1mAvailable Sessions:\x1b[0m');
|
|
105
|
+
sessions.forEach(s => {
|
|
106
|
+
const hostLabel = s.host === '127.0.0.1' ? 'Local' : s.host;
|
|
107
|
+
console.log(` - \x1b[36m${s.id}\x1b[0m (\x1b[33m${hostLabel}\x1b[0m) [${s.command}] - Clients: ${s.active_clients}`);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
console.log('\n');
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (response.action === 'spawn') {
|
|
115
|
+
const { id, command } = await prompts([
|
|
116
|
+
{ type: 'text', name: 'id', message: 'Enter new session ID (e.g. agent-1):', validate: v => v ? true : 'Required' },
|
|
117
|
+
{ type: 'text', name: 'command', message: 'Enter command to run (e.g. bash, zsh, python):', initial: 'bash' }
|
|
118
|
+
]);
|
|
119
|
+
if (!id || !command) continue;
|
|
120
|
+
|
|
121
|
+
const cols = process.stdout.columns || 80;
|
|
122
|
+
const rows = process.stdout.rows || 30;
|
|
123
|
+
try {
|
|
124
|
+
const res = await fetchWithAuth(`${DAEMON_URL}/api/sessions/spawn`, {
|
|
125
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
126
|
+
body: JSON.stringify({ session_id: id, command, args: [], cwd: process.cwd(), cols, rows })
|
|
127
|
+
});
|
|
128
|
+
const data = await res.json();
|
|
129
|
+
if (!res.ok) console.error(`\n❌ Error: ${data.error}\n`);
|
|
130
|
+
else console.log(`\n✅ Session '\x1b[36m${data.session_id}\x1b[0m' spawned successfully.\n`);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
console.error('\n❌ Failed to connect to local daemon. Is it running?\n');
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (response.action === 'attach' || response.action === 'inject') {
|
|
138
|
+
const sessions = await discoverSessions();
|
|
139
|
+
if (sessions.length === 0) {
|
|
140
|
+
console.log('\n❌ No active sessions found to ' + response.action + '.\n');
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const { target } = await prompts({
|
|
144
|
+
type: 'select',
|
|
145
|
+
name: 'target',
|
|
146
|
+
message: `Select a session to ${response.action}:`,
|
|
147
|
+
choices: sessions.map(s => ({
|
|
148
|
+
title: `${s.id} (${s.host === '127.0.0.1' ? 'Local' : s.host}) - ${s.command}`,
|
|
149
|
+
value: s
|
|
150
|
+
}))
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (!target) continue;
|
|
154
|
+
|
|
155
|
+
if (response.action === 'attach') {
|
|
156
|
+
const wsUrl = `ws://${target.host}:${PORT}/api/sessions/${encodeURIComponent(target.id)}?token=${encodeURIComponent(TOKEN)}`;
|
|
157
|
+
const ws = new WebSocket(wsUrl);
|
|
158
|
+
await new Promise((resolve) => {
|
|
159
|
+
ws.on('open', () => {
|
|
160
|
+
console.log(`\n\x1b[32mConnected to '${target.id}'. Press Ctrl+C to detach.\x1b[0m\n`);
|
|
161
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
162
|
+
process.stdin.on('data', d => ws.send(JSON.stringify({ type: 'input', data: d.toString() })));
|
|
163
|
+
const resizer = () => ws.send(JSON.stringify({ type: 'resize', cols: process.stdout.columns, rows: process.stdout.rows }));
|
|
164
|
+
process.stdout.on('resize', resizer); resizer();
|
|
165
|
+
});
|
|
166
|
+
ws.on('message', m => {
|
|
167
|
+
const msg = JSON.parse(m);
|
|
168
|
+
if (msg.type === 'output') process.stdout.write(msg.data);
|
|
169
|
+
});
|
|
170
|
+
ws.on('close', () => {
|
|
171
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
172
|
+
console.log(`\n\x1b[33mDisconnected from session.\x1b[0m\n`);
|
|
173
|
+
process.stdin.removeAllListeners('data');
|
|
174
|
+
resolve();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (response.action === 'inject') {
|
|
181
|
+
const { promptText } = await prompts({
|
|
182
|
+
type: 'text',
|
|
183
|
+
name: 'promptText',
|
|
184
|
+
message: 'Enter text to inject:',
|
|
185
|
+
validate: v => v ? true : 'Required'
|
|
186
|
+
});
|
|
187
|
+
if (!promptText) continue;
|
|
188
|
+
try {
|
|
189
|
+
const res = await fetchWithAuth(`http://${target.host}:${PORT}/api/sessions/${encodeURIComponent(target.id)}/inject`, {
|
|
190
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: promptText })
|
|
191
|
+
});
|
|
192
|
+
const data = await res.json();
|
|
193
|
+
if (!res.ok) console.error(`\n❌ Error: ${data.error}\n`);
|
|
194
|
+
else console.log(`\n✅ Injected successfully into '\x1b[36m${target.id}\x1b[0m'.\n`);
|
|
195
|
+
} catch (e) { console.error('\n❌ Failed to connect.\n'); }
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
62
202
|
async function main() {
|
|
63
203
|
const cmd = args[0];
|
|
204
|
+
|
|
205
|
+
if (!cmd) {
|
|
206
|
+
return manageInteractive();
|
|
207
|
+
}
|
|
208
|
+
|
|
64
209
|
if (cmd === 'mcp') {
|
|
65
210
|
require('./mcp.js');
|
|
66
211
|
return;
|
|
@@ -210,6 +355,38 @@ async function main() {
|
|
|
210
355
|
return;
|
|
211
356
|
}
|
|
212
357
|
|
|
358
|
+
if (cmd === 'multicast') {
|
|
359
|
+
const sessionIdsRaw = args[1]; const prompt = args.slice(2).join(' ');
|
|
360
|
+
if (!sessionIdsRaw || !prompt) { console.error('❌ Usage: telepty multicast <id1,id2,...> "<prompt text>"'); process.exit(1); }
|
|
361
|
+
const sessionIds = sessionIdsRaw.split(',').map(s => s.trim()).filter(s => s);
|
|
362
|
+
try {
|
|
363
|
+
const res = await fetchWithAuth(`${DAEMON_URL}/api/sessions/multicast/inject`, {
|
|
364
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_ids: sessionIds, prompt })
|
|
365
|
+
});
|
|
366
|
+
const data = await res.json();
|
|
367
|
+
if (!res.ok) { console.error(`❌ Error: ${data.error}`); return; }
|
|
368
|
+
console.log(`✅ Context multicasted successfully to ${data.results.successful.length} sessions.`);
|
|
369
|
+
if (data.results.failed.length > 0) {
|
|
370
|
+
console.warn(`⚠️ Failed to inject into ${data.results.failed.length} sessions:`, data.results.failed.map(f => f.id).join(', '));
|
|
371
|
+
}
|
|
372
|
+
} catch (e) { console.error('❌ Failed to connect to daemon. Is it running?'); }
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (cmd === 'broadcast') {
|
|
377
|
+
const prompt = args.slice(1).join(' ');
|
|
378
|
+
if (!prompt) { console.error('❌ Usage: telepty broadcast "<prompt text>"'); process.exit(1); }
|
|
379
|
+
try {
|
|
380
|
+
const res = await fetchWithAuth(`${DAEMON_URL}/api/sessions/broadcast/inject`, {
|
|
381
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt })
|
|
382
|
+
});
|
|
383
|
+
const data = await res.json();
|
|
384
|
+
if (!res.ok) { console.error(`❌ Error: ${data.error}`); return; }
|
|
385
|
+
console.log(`✅ Context broadcasted successfully to ${data.results.successful.length} active sessions.`);
|
|
386
|
+
} catch (e) { console.error('❌ Failed to connect to daemon. Is it running?'); }
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
213
390
|
console.log(`
|
|
214
391
|
\x1b[1maigentry-telepty\x1b[0m - Remote PTY Control
|
|
215
392
|
|
|
@@ -218,7 +395,9 @@ Usage:
|
|
|
218
395
|
telepty spawn --id <id> <command> [args...] Spawn a new background CLI
|
|
219
396
|
telepty list List all active sessions
|
|
220
397
|
telepty attach [id] Attach to a session (Interactive picker if no ID)
|
|
221
|
-
telepty inject <id> "<prompt>" Inject text into
|
|
398
|
+
telepty inject <id> "<prompt>" Inject text into a single session
|
|
399
|
+
telepty multicast <id1,id2> "<prompt>" Inject text into multiple specific sessions
|
|
400
|
+
telepty broadcast "<prompt>" Inject text into ALL active sessions
|
|
222
401
|
telepty mcp Start the MCP stdio server
|
|
223
402
|
`);
|
|
224
403
|
}
|
package/daemon.js
CHANGED
|
@@ -94,6 +94,48 @@ app.get('/api/sessions', (req, res) => {
|
|
|
94
94
|
res.json(list);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
app.post('/api/sessions/multicast/inject', (req, res) => {
|
|
98
|
+
const { session_ids, prompt } = req.body;
|
|
99
|
+
if (!prompt) return res.status(400).json({ error: 'prompt is required' });
|
|
100
|
+
if (!Array.isArray(session_ids)) return res.status(400).json({ error: 'session_ids must be an array' });
|
|
101
|
+
|
|
102
|
+
const results = { successful: [], failed: [] };
|
|
103
|
+
|
|
104
|
+
session_ids.forEach(id => {
|
|
105
|
+
const session = sessions[id];
|
|
106
|
+
if (session) {
|
|
107
|
+
try {
|
|
108
|
+
session.ptyProcess.write(`${prompt}\r`);
|
|
109
|
+
results.successful.push(id);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
results.failed.push({ id, error: err.message });
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
results.failed.push({ id, error: 'Session not found' });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
res.json({ success: true, results });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
app.post('/api/sessions/broadcast/inject', (req, res) => {
|
|
122
|
+
const { prompt } = req.body;
|
|
123
|
+
if (!prompt) return res.status(400).json({ error: 'prompt is required' });
|
|
124
|
+
|
|
125
|
+
const results = { successful: [], failed: [] };
|
|
126
|
+
|
|
127
|
+
Object.keys(sessions).forEach(id => {
|
|
128
|
+
try {
|
|
129
|
+
sessions[id].ptyProcess.write(`${prompt}\r`);
|
|
130
|
+
results.successful.push(id);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
results.failed.push({ id, error: err.message });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
res.json({ success: true, results });
|
|
137
|
+
});
|
|
138
|
+
|
|
97
139
|
app.post('/api/sessions/:id/inject', (req, res) => {
|
|
98
140
|
const { id } = req.params;
|
|
99
141
|
const { prompt } = req.body;
|
package/install.ps1
CHANGED
|
@@ -26,7 +26,7 @@ if (!$teleptyCmd) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
$teleptyPath = $teleptyCmd.Source
|
|
29
|
-
Start-Process -
|
|
29
|
+
Start-Process -FilePath node -ArgumentList "$teleptyPath daemon" -WindowStyle Hidden
|
|
30
30
|
Write-Host "✅ Windows daemon started in background." -ForegroundColor Green
|
|
31
31
|
|
|
32
32
|
Write-Host "`n🎉 Installation complete! Telepty daemon is running." -ForegroundColor Cyan
|
package/mcp.js
CHANGED
|
@@ -18,8 +18,14 @@ const tools = [
|
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
name: 'telepty_inject_context',
|
|
21
|
-
description: 'Inject a prompt or context into
|
|
22
|
-
schema: z.object({
|
|
21
|
+
description: 'Inject a prompt or context into specific active AI CLI sessions on a remote machine. You can specify a single session ID, multiple session IDs, or broadcast to all.',
|
|
22
|
+
schema: z.object({
|
|
23
|
+
remote_url: z.string(),
|
|
24
|
+
session_ids: z.array(z.string()).optional().describe('An array of exact session IDs to inject into. If not provided, it will inject into session_id.'),
|
|
25
|
+
session_id: z.string().optional().describe('Legacy fallback for a single session ID.'),
|
|
26
|
+
broadcast: z.boolean().optional().describe('If true, injects the prompt into ALL active sessions on the remote daemon. Overrides session_ids.'),
|
|
27
|
+
prompt: z.string().describe('Text to inject into stdin.')
|
|
28
|
+
})
|
|
23
29
|
}
|
|
24
30
|
];
|
|
25
31
|
|
|
@@ -47,12 +53,35 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
47
53
|
}
|
|
48
54
|
if (name === 'telepty_inject_context') {
|
|
49
55
|
const baseUrl = args.remote_url.startsWith('http') ? args.remote_url : `http://${args.remote_url}`;
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
|
|
57
|
+
let endpoint = '';
|
|
58
|
+
let body = {};
|
|
59
|
+
|
|
60
|
+
if (args.broadcast) {
|
|
61
|
+
endpoint = `${baseUrl}/api/sessions/broadcast/inject`;
|
|
62
|
+
body = { prompt: args.prompt };
|
|
63
|
+
} else if (args.session_ids && args.session_ids.length > 0) {
|
|
64
|
+
endpoint = `${baseUrl}/api/sessions/multicast/inject`;
|
|
65
|
+
body = { session_ids: args.session_ids, prompt: args.prompt };
|
|
66
|
+
} else if (args.session_id) {
|
|
67
|
+
endpoint = `${baseUrl}/api/sessions/${encodeURIComponent(args.session_id)}/inject`;
|
|
68
|
+
body = { prompt: args.prompt };
|
|
69
|
+
} else {
|
|
70
|
+
throw new Error('You must provide either broadcast: true, session_ids: [...], or session_id: "..."');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const res = await fetch(endpoint, {
|
|
74
|
+
method: 'POST', headers: { 'Content-Type': 'application/json', 'x-telepty-token': TOKEN }, body: JSON.stringify(body)
|
|
52
75
|
});
|
|
53
76
|
const data = await res.json();
|
|
54
77
|
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
|
55
|
-
|
|
78
|
+
|
|
79
|
+
let msg = `✅ Successfully injected context.`;
|
|
80
|
+
if (args.broadcast) msg += ` (Broadcasted to ${data.results.successful.length} sessions)`;
|
|
81
|
+
else if (args.session_ids) msg += ` (Multicasted to ${data.results.successful.length} sessions)`;
|
|
82
|
+
else msg += ` (Targeted session '${args.session_id}')`;
|
|
83
|
+
|
|
84
|
+
return { content: [{ type: 'text', text: msg }] };
|
|
56
85
|
}
|
|
57
86
|
throw new Error(`Unknown tool: ${name}`);
|
|
58
87
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmsdc-ai/aigentry-telepty",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"main": "daemon.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"telepty": "cli.js",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"cors": "^2.8.6",
|
|
19
19
|
"express": "^5.2.1",
|
|
20
20
|
"node-pty": "^1.2.0-beta.11",
|
|
21
|
+
"prompts": "^2.4.2",
|
|
21
22
|
"uuid": "^13.0.0",
|
|
22
23
|
"ws": "^8.19.0",
|
|
23
24
|
"zod": "^4.3.6"
|