@dmsdc-ai/aigentry-telepty 0.0.6 → 0.0.8
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 +18 -0
- package/README.md +12 -5
- package/cli.js +146 -1
- package/daemon.js +1 -1
- package/install.ps1 +33 -0
- package/install.sh +118 -0
- package/package.json +2 -1
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
- Ensure you know the target session ID. If not specified, ask the user or run `telepty list` to find it.
|
|
18
|
+
- Run `telepty inject <target_session_id> "<message or command>"`.
|
package/README.md
CHANGED
|
@@ -6,14 +6,21 @@
|
|
|
6
6
|
|
|
7
7
|
## One-Click Installation
|
|
8
8
|
|
|
9
|
-
To install and set up `telepty` on any machine (macOS, Linux, or Windows)
|
|
9
|
+
To install and set up `telepty` on any machine (macOS, Linux, or Windows). These scripts will automatically install Node.js if it is missing from your system.
|
|
10
10
|
|
|
11
|
-
###
|
|
12
|
-
Open your terminal
|
|
11
|
+
### For macOS and Linux (Ubuntu, CentOS, etc.)
|
|
12
|
+
Open your terminal and run:
|
|
13
13
|
```bash
|
|
14
|
-
|
|
14
|
+
curl -fsSL https://raw.githubusercontent.com/dmsdc-ai/aigentry-telepty/main/install.sh | bash
|
|
15
15
|
```
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
### For Windows (PowerShell)
|
|
18
|
+
Open PowerShell and run:
|
|
19
|
+
```powershell
|
|
20
|
+
iwr -useb https://raw.githubusercontent.com/dmsdc-ai/aigentry-telepty/main/install.ps1 | iex
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
*These single commands will install the package globally and automatically configure it to run as a background service specific to your OS (`systemd` for Linux, `launchd` for macOS, or a detached background process for Windows).*
|
|
17
24
|
|
|
18
25
|
## Seamless Usage
|
|
19
26
|
|
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;
|
package/daemon.js
CHANGED
|
@@ -53,7 +53,7 @@ app.post('/api/sessions/spawn', (req, res) => {
|
|
|
53
53
|
cols: parseInt(cols),
|
|
54
54
|
rows: parseInt(rows),
|
|
55
55
|
cwd,
|
|
56
|
-
env: { ...process.env, TERM: isWin ? undefined : 'xterm-256color' }
|
|
56
|
+
env: { ...process.env, TERM: isWin ? undefined : 'xterm-256color', TELEPTY_SESSION_ID: session_id }
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
sessions[session_id] = {
|
package/install.ps1
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Write-Host "🚀 Installing @dmsdc-ai/aigentry-telepty..." -ForegroundColor Cyan
|
|
2
|
+
|
|
3
|
+
# 1. Check for Node.js/npm and install if missing
|
|
4
|
+
if (!(Get-Command npm -ErrorAction SilentlyContinue)) {
|
|
5
|
+
Write-Host "⚠️ Node.js/npm not found. Attempting to install via winget..." -ForegroundColor Yellow
|
|
6
|
+
if (Get-Command winget -ErrorAction SilentlyContinue) {
|
|
7
|
+
winget install OpenJS.NodeJS --accept-package-agreements --accept-source-agreements
|
|
8
|
+
Write-Host "🔄 Refreshing environment variables..." -ForegroundColor Cyan
|
|
9
|
+
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
|
10
|
+
} else {
|
|
11
|
+
Write-Host "❌ winget not found. Please install Node.js manually: https://nodejs.org/" -ForegroundColor Red
|
|
12
|
+
exit 1
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# 2. Install telepty via npm
|
|
17
|
+
Write-Host "📦 Installing telepty globally..." -ForegroundColor Cyan
|
|
18
|
+
npm install -g @dmsdc-ai/aigentry-telepty
|
|
19
|
+
|
|
20
|
+
# 3. Setup Daemon
|
|
21
|
+
Write-Host "⚙️ Setting up Windows background process..." -ForegroundColor Cyan
|
|
22
|
+
$teleptyCmd = Get-Command telepty -ErrorAction SilentlyContinue
|
|
23
|
+
if (!$teleptyCmd) {
|
|
24
|
+
Write-Host "❌ Failed to locate telepty executable after installation." -ForegroundColor Red
|
|
25
|
+
exit 1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
$teleptyPath = $teleptyCmd.Source
|
|
29
|
+
Start-Process -NoNewWindow -FilePath node -ArgumentList "$teleptyPath daemon" -WindowStyle Hidden
|
|
30
|
+
Write-Host "✅ Windows daemon started in background." -ForegroundColor Green
|
|
31
|
+
|
|
32
|
+
Write-Host "`n🎉 Installation complete! Telepty daemon is running." -ForegroundColor Cyan
|
|
33
|
+
Write-Host "👉 Try running: telepty attach" -ForegroundColor Yellow
|
package/install.sh
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
echo "🚀 Installing @dmsdc-ai/aigentry-telepty..."
|
|
5
|
+
|
|
6
|
+
# 1. Check for Node.js and install if missing
|
|
7
|
+
if ! command -v npm &> /dev/null; then
|
|
8
|
+
echo "⚠️ Node.js/npm not found. Attempting to install Node.js..."
|
|
9
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
10
|
+
if ! command -v brew &> /dev/null; then
|
|
11
|
+
echo "❌ Homebrew not found. Please install Node.js manually: https://nodejs.org/"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
brew install node
|
|
15
|
+
elif command -v apt-get &> /dev/null; then
|
|
16
|
+
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
|
17
|
+
sudo apt-get install -y nodejs
|
|
18
|
+
elif command -v yum &> /dev/null; then
|
|
19
|
+
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
|
|
20
|
+
sudo yum install -y nodejs
|
|
21
|
+
elif command -v dnf &> /dev/null; then
|
|
22
|
+
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
|
|
23
|
+
sudo dnf install -y nodejs
|
|
24
|
+
elif command -v pacman &> /dev/null; then
|
|
25
|
+
sudo pacman -Sy nodejs npm --noconfirm
|
|
26
|
+
elif command -v pkg &> /dev/null; then
|
|
27
|
+
# Termux environment
|
|
28
|
+
pkg install -y nodejs
|
|
29
|
+
else
|
|
30
|
+
echo "❌ Could not determine package manager. Please install Node.js manually: https://nodejs.org/"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# 2. Install telepty via npm
|
|
36
|
+
echo "📦 Installing telepty globally..."
|
|
37
|
+
if [[ "$OSTYPE" == "darwin"* ]] || command -v pkg &> /dev/null || [ "$EUID" -eq 0 ]; then
|
|
38
|
+
npm install -g @dmsdc-ai/aigentry-telepty
|
|
39
|
+
else
|
|
40
|
+
# Linux non-root might need sudo for global install depending on npm setup
|
|
41
|
+
sudo npm install -g @dmsdc-ai/aigentry-telepty || npm install -g @dmsdc-ai/aigentry-telepty
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
TELEPTY_PATH=$(which telepty || true)
|
|
45
|
+
if [ -z "$TELEPTY_PATH" ]; then
|
|
46
|
+
TELEPTY_PATH="$(npm prefix -g)/bin/telepty"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# 3. Setup Daemon
|
|
50
|
+
echo "⚙️ Setting up daemon..."
|
|
51
|
+
if command -v systemctl &> /dev/null && [ -d "/etc/systemd/system" ]; then
|
|
52
|
+
if [ "$EUID" -ne 0 ]; then
|
|
53
|
+
echo "⚠️ systemd requires root to install service. Prompting for sudo..."
|
|
54
|
+
SUDO_CMD="sudo"
|
|
55
|
+
else
|
|
56
|
+
SUDO_CMD=""
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
$SUDO_CMD bash -c "cat <<EOF > /etc/systemd/system/telepty.service
|
|
60
|
+
[Unit]
|
|
61
|
+
Description=Telepty Daemon
|
|
62
|
+
After=network.target
|
|
63
|
+
|
|
64
|
+
[Service]
|
|
65
|
+
ExecStart=$TELEPTY_PATH daemon
|
|
66
|
+
Restart=always
|
|
67
|
+
User=${SUDO_USER:-$USER}
|
|
68
|
+
Environment=PATH=/usr/bin:/usr/local/bin:\$PATH
|
|
69
|
+
Environment=NODE_ENV=production
|
|
70
|
+
|
|
71
|
+
[Install]
|
|
72
|
+
WantedBy=multi-user.target
|
|
73
|
+
EOF"
|
|
74
|
+
|
|
75
|
+
$SUDO_CMD systemctl daemon-reload
|
|
76
|
+
$SUDO_CMD systemctl enable telepty
|
|
77
|
+
$SUDO_CMD systemctl start telepty
|
|
78
|
+
echo "✅ Linux systemd service installed and started. (Auto-starts on boot)"
|
|
79
|
+
|
|
80
|
+
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
|
81
|
+
PLIST_PATH="$HOME/Library/LaunchAgents/com.aigentry.telepty.plist"
|
|
82
|
+
mkdir -p "$HOME/Library/LaunchAgents"
|
|
83
|
+
cat <<EOF > "$PLIST_PATH"
|
|
84
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
85
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
86
|
+
<plist version="1.0">
|
|
87
|
+
<dict>
|
|
88
|
+
<key>Label</key>
|
|
89
|
+
<string>com.aigentry.telepty</string>
|
|
90
|
+
<key>ProgramArguments</key>
|
|
91
|
+
<array>
|
|
92
|
+
<string>$TELEPTY_PATH</string>
|
|
93
|
+
<string>daemon</string>
|
|
94
|
+
</array>
|
|
95
|
+
<key>RunAtLoad</key>
|
|
96
|
+
<true/>
|
|
97
|
+
<key>KeepAlive</key>
|
|
98
|
+
<true/>
|
|
99
|
+
<key>EnvironmentVariables</key>
|
|
100
|
+
<dict>
|
|
101
|
+
<key>PATH</key>
|
|
102
|
+
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
103
|
+
</dict>
|
|
104
|
+
</dict>
|
|
105
|
+
</plist>
|
|
106
|
+
EOF
|
|
107
|
+
launchctl unload "$PLIST_PATH" 2>/dev/null || true
|
|
108
|
+
launchctl load "$PLIST_PATH"
|
|
109
|
+
echo "✅ macOS LaunchAgent installed and started. (Auto-starts on boot)"
|
|
110
|
+
else
|
|
111
|
+
echo "⚠️ Skipping OS-level service setup (Termux or missing systemd). Starting in background..."
|
|
112
|
+
nohup $TELEPTY_PATH daemon > /dev/null 2>&1 &
|
|
113
|
+
echo "✅ Daemon started in background. (Note: Will not auto-start on device reboot)"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
echo ""
|
|
117
|
+
echo "🎉 Installation complete! Telepty daemon is running in the background."
|
|
118
|
+
echo "👉 Try running: telepty attach"
|
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.8",
|
|
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"
|