@ebowwa/terminal 0.2.0
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/dist/client.d.ts +15 -0
- package/dist/client.js +45 -0
- package/dist/error.d.ts +8 -0
- package/dist/error.js +12 -0
- package/dist/exec.d.ts +47 -0
- package/dist/exec.js +107 -0
- package/dist/files.d.ts +124 -0
- package/dist/files.js +436 -0
- package/dist/fingerprint.d.ts +67 -0
- package/dist/index.d.ts +17 -0
- package/dist/pool.d.ts +143 -0
- package/dist/pool.js +554 -0
- package/dist/pty.d.ts +59 -0
- package/dist/scp.d.ts +30 -0
- package/dist/scp.js +74 -0
- package/dist/sessions.d.ts +98 -0
- package/dist/tmux-exec.d.ts +50 -0
- package/dist/tmux.d.ts +213 -0
- package/dist/tmux.js +528 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.js +5 -0
- package/ebowwa-terminal-0.2.0.tgz +0 -0
- package/mcp/README.md +181 -0
- package/mcp/package.json +34 -0
- package/mcp/test-fix.sh +273 -0
- package/package.json +118 -0
- package/src/api.ts +752 -0
- package/src/client.ts +55 -0
- package/src/config.ts +489 -0
- package/src/error.ts +13 -0
- package/src/exec.ts +128 -0
- package/src/files.ts +636 -0
- package/src/fingerprint.ts +263 -0
- package/src/index.ts +144 -0
- package/src/manager.ts +319 -0
- package/src/mcp/index.ts +467 -0
- package/src/mcp/stdio.ts +708 -0
- package/src/network-error-detector.ts +121 -0
- package/src/pool.ts +662 -0
- package/src/pty.ts +285 -0
- package/src/scp.ts +109 -0
- package/src/sessions.ts +861 -0
- package/src/tmux-exec.ts +96 -0
- package/src/tmux-local.ts +839 -0
- package/src/tmux-manager.ts +962 -0
- package/src/tmux.ts +711 -0
- package/src/types.ts +19 -0
package/dist/tmux.js
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tmux-based Terminal Sessions
|
|
3
|
+
* Provides persistent terminal sessions using tmux on remote servers
|
|
4
|
+
* Includes automatic tmux installation and session management
|
|
5
|
+
*/
|
|
6
|
+
import { getSSHPool } from "./pool.js";
|
|
7
|
+
import { SSHFlags, SSHPresets, buildSSHArgs } from "@codespaces/ssh";
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
sessionPrefix: "codespaces",
|
|
10
|
+
defaultShell: "/bin/bash",
|
|
11
|
+
term: "xterm-256color",
|
|
12
|
+
timeout: 30,
|
|
13
|
+
historyLimit: 10000, // 10k lines ~ 1-2MB per session
|
|
14
|
+
sessionAgeLimit: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Generate a tmux session name for a host
|
|
18
|
+
*/
|
|
19
|
+
export function generateSessionName(host, user = "root") {
|
|
20
|
+
const sanitizedHost = host.replace(/[.]/g, "-");
|
|
21
|
+
return `${DEFAULT_CONFIG.sessionPrefix}-${sanitizedHost}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if tmux is installed on the remote server
|
|
25
|
+
*/
|
|
26
|
+
export async function isTmuxInstalled(options) {
|
|
27
|
+
const pool = getSSHPool();
|
|
28
|
+
try {
|
|
29
|
+
const result = await pool.exec("type tmux", {
|
|
30
|
+
...options,
|
|
31
|
+
timeout: 5,
|
|
32
|
+
});
|
|
33
|
+
return result !== "0";
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Install tmux on the remote server
|
|
41
|
+
*
|
|
42
|
+
* FALLBACK MECHANISM: This should NOT be the primary installation method.
|
|
43
|
+
* tmux should be installed via cloud-init during initial node provisioning.
|
|
44
|
+
*
|
|
45
|
+
* This function exists for:
|
|
46
|
+
* - Legacy nodes provisioned before cloud-init included tmux
|
|
47
|
+
* - Manual node provisioning outside cheapspaces
|
|
48
|
+
* - Recovery scenarios where cloud-init failed
|
|
49
|
+
*
|
|
50
|
+
* @see workspace/docs/design/node-agent/TMUX-INSTALLATION.md
|
|
51
|
+
* @see workspace/src/lib/bootstrap/cloud-init.ts - where tmux should be added to packages
|
|
52
|
+
*
|
|
53
|
+
* Supports Debian/Ubuntu (apt) and CentOS/RHEL (yum/dnf)
|
|
54
|
+
*/
|
|
55
|
+
export async function installTmux(options) {
|
|
56
|
+
const pool = getSSHPool();
|
|
57
|
+
try {
|
|
58
|
+
// Detect package manager and install tmux
|
|
59
|
+
const installCmd = `
|
|
60
|
+
if command -v apt-get >/dev/null 2>&1; then
|
|
61
|
+
# Debian/Ubuntu
|
|
62
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
63
|
+
apt-get update -qq && apt-get install -y -qq tmux
|
|
64
|
+
elif command -v yum >/dev/null 2>&1; then
|
|
65
|
+
# CentOS/RHEL (older)
|
|
66
|
+
yum install -y -q tmux
|
|
67
|
+
elif command -v dnf >/dev/null 2>&1; then
|
|
68
|
+
# Fedora/RHEL (newer)
|
|
69
|
+
dnf install -y -q tmux
|
|
70
|
+
elif command -v apk >/dev/null 2>&1; then
|
|
71
|
+
# Alpine
|
|
72
|
+
apk add --no-cache tmux
|
|
73
|
+
else
|
|
74
|
+
echo "ERROR: No supported package manager found"
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
`;
|
|
78
|
+
await pool.exec(installCmd, {
|
|
79
|
+
...options,
|
|
80
|
+
timeout: 120, // Installation can take time
|
|
81
|
+
});
|
|
82
|
+
// Verify installation
|
|
83
|
+
const installed = await isTmuxInstalled(options);
|
|
84
|
+
if (installed) {
|
|
85
|
+
return { success: true, message: "tmux installed successfully" };
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
return { success: false, message: "tmux installation failed" };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
message: `tmux installation error: ${error instanceof Error ? error.message : String(error)}`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Ensure tmux is available, installing if necessary
|
|
100
|
+
*/
|
|
101
|
+
export async function ensureTmux(options) {
|
|
102
|
+
const installed = await isTmuxInstalled(options);
|
|
103
|
+
if (installed) {
|
|
104
|
+
return { success: true, message: "tmux already installed" };
|
|
105
|
+
}
|
|
106
|
+
console.log("[Tmux] Not installed, attempting installation...");
|
|
107
|
+
return await installTmux(options);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* List existing tmux sessions on the remote server
|
|
111
|
+
*/
|
|
112
|
+
export async function listTmuxSessions(options) {
|
|
113
|
+
const pool = getSSHPool();
|
|
114
|
+
try {
|
|
115
|
+
const result = await pool.exec("tmux list-sessions -F '#{session_name}' 2>/dev/null || echo ''", { ...options, timeout: 5 });
|
|
116
|
+
if (!result || result === "0" || result.trim() === "") {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
return result.trim().split("\n");
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if a specific tmux session exists
|
|
127
|
+
*/
|
|
128
|
+
export async function hasTmuxSession(sessionName, options) {
|
|
129
|
+
const sessions = await listTmuxSessions(options);
|
|
130
|
+
return sessions.includes(sessionName);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create or attach to a tmux session
|
|
134
|
+
* Returns the SSH command arguments to connect to the tmux session
|
|
135
|
+
*/
|
|
136
|
+
export async function createOrAttachTmuxSession(host, user = "root", keyPath, config = {}) {
|
|
137
|
+
const fullConfig = { ...DEFAULT_CONFIG, ...config };
|
|
138
|
+
const sessionName = generateSessionName(host, user);
|
|
139
|
+
const pool = getSSHPool();
|
|
140
|
+
const sshOptions = {
|
|
141
|
+
host,
|
|
142
|
+
user,
|
|
143
|
+
port: 22,
|
|
144
|
+
keyPath,
|
|
145
|
+
timeout: fullConfig.timeout,
|
|
146
|
+
};
|
|
147
|
+
// Ensure tmux is installed
|
|
148
|
+
const tmuxCheck = await ensureTmux(sshOptions);
|
|
149
|
+
if (!tmuxCheck.success) {
|
|
150
|
+
throw new Error(`Failed to setup tmux: ${tmuxCheck.message}`);
|
|
151
|
+
}
|
|
152
|
+
// Check if session already exists
|
|
153
|
+
const sessionExists = await hasTmuxSession(sessionName, sshOptions);
|
|
154
|
+
// Build SSH command with typed flags
|
|
155
|
+
const flags = [
|
|
156
|
+
...SSHPresets.default,
|
|
157
|
+
SSHFlags.port(22),
|
|
158
|
+
SSHFlags.forceTTY(2), // -tt for forced PTY
|
|
159
|
+
];
|
|
160
|
+
if (keyPath) {
|
|
161
|
+
flags.push(SSHFlags.identity(String(keyPath)));
|
|
162
|
+
}
|
|
163
|
+
const sshArgs = buildSSHArgs(flags, host, user);
|
|
164
|
+
// Tmux commands to create or attach session
|
|
165
|
+
// -A: Attach to existing session or create new
|
|
166
|
+
// -s: Session name
|
|
167
|
+
// new-window: Ensure we have a window (for new sessions)
|
|
168
|
+
const tmuxCmd = [
|
|
169
|
+
"tmux",
|
|
170
|
+
"new-session",
|
|
171
|
+
"-A",
|
|
172
|
+
"-s", sessionName,
|
|
173
|
+
"-n", "codespaces",
|
|
174
|
+
];
|
|
175
|
+
sshArgs.push(...tmuxCmd);
|
|
176
|
+
// If creating a new session, also configure it via separate command
|
|
177
|
+
if (!sessionExists) {
|
|
178
|
+
try {
|
|
179
|
+
await pool.exec(`tmux set-option -t "${sessionName}" history-limit ${fullConfig.historyLimit} 2>/dev/null`, {
|
|
180
|
+
...sshOptions,
|
|
181
|
+
timeout: 5,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Ignore config errors
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
sshArgs,
|
|
190
|
+
sessionName,
|
|
191
|
+
newlyCreated: !sessionExists,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Kill a tmux session on the remote server
|
|
196
|
+
*/
|
|
197
|
+
export async function killTmuxSession(sessionName, options) {
|
|
198
|
+
const pool = getSSHPool();
|
|
199
|
+
try {
|
|
200
|
+
await pool.exec(`tmux kill-session -t "${sessionName}" 2>/dev/null`, {
|
|
201
|
+
...options,
|
|
202
|
+
timeout: 5,
|
|
203
|
+
});
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get tmux session information
|
|
212
|
+
*/
|
|
213
|
+
export async function getTmuxSessionInfo(sessionName, options) {
|
|
214
|
+
const pool = getSSHPool();
|
|
215
|
+
try {
|
|
216
|
+
const result = await pool.exec(`tmux display-message -t "${sessionName}" -p '#{session_windows} #{window_panes}' 2>/dev/null || echo ""`, { ...options, timeout: 5 });
|
|
217
|
+
if (!result || result.trim() === "" || result === "0") {
|
|
218
|
+
return { exists: false };
|
|
219
|
+
}
|
|
220
|
+
const [windows, panes] = result.trim().split(" ").map(Number);
|
|
221
|
+
return { exists: true, windows, panes };
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return { exists: false };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Cleanup old tmux sessions on a remote server
|
|
229
|
+
* Kills sessions with matching prefix that are older than specified age limit
|
|
230
|
+
* @param options SSH connection options
|
|
231
|
+
* @param config Optional configuration (uses default age limit if not provided)
|
|
232
|
+
* @returns Object with cleaned count and errors
|
|
233
|
+
*/
|
|
234
|
+
export async function cleanupOldTmuxSessions(options, config = {}) {
|
|
235
|
+
const fullConfig = { ...DEFAULT_CONFIG, ...config };
|
|
236
|
+
const pool = getSSHPool();
|
|
237
|
+
const errors = [];
|
|
238
|
+
let cleaned = 0;
|
|
239
|
+
try {
|
|
240
|
+
// Get all tmux sessions
|
|
241
|
+
const sessions = await listTmuxSessions(options);
|
|
242
|
+
// Filter for codespaces sessions
|
|
243
|
+
const codespacesSessions = sessions.filter(s => s.startsWith(fullConfig.sessionPrefix));
|
|
244
|
+
// Check age of each session by looking at socket file modification time
|
|
245
|
+
for (const sessionName of codespacesSessions) {
|
|
246
|
+
try {
|
|
247
|
+
// Check session age by examining tmux socket file
|
|
248
|
+
// Tmux sockets are in /tmp/tmux-*/default or /tmp/tmux-*
|
|
249
|
+
const ageCheckCmd = `
|
|
250
|
+
find /tmp -type s -name "*${sessionName}*" 2>/dev/null | head -1 | while read socket; do
|
|
251
|
+
if [ -n "$socket" ]; then
|
|
252
|
+
# Get file modification time in seconds since epoch
|
|
253
|
+
mtime=$(stat -c %Y "$socket" 2>/dev/null || stat -f %m "$socket" 2>/dev/null)
|
|
254
|
+
if [ -n "$mtime" ]; then
|
|
255
|
+
now=$(date +%s)
|
|
256
|
+
age=$((now - mtime))
|
|
257
|
+
age_ms=$((age * 1000))
|
|
258
|
+
echo "$age_ms"
|
|
259
|
+
fi
|
|
260
|
+
fi
|
|
261
|
+
done
|
|
262
|
+
`;
|
|
263
|
+
const ageResult = await pool.exec(ageCheckCmd, { ...options, timeout: 10 });
|
|
264
|
+
const ageMs = parseInt(ageResult.trim());
|
|
265
|
+
if (!isNaN(ageMs) && ageMs > fullConfig.sessionAgeLimit) {
|
|
266
|
+
console.log(`[Tmux] Cleaning up old session "${sessionName}" (age: ${Math.round(ageMs / 86400000)} days)`);
|
|
267
|
+
await killTmuxSession(sessionName, options);
|
|
268
|
+
cleaned++;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
errors.push(`${sessionName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return { cleaned, errors };
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
errors.push(`Cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
279
|
+
return { cleaned, errors };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get resource usage information for tmux sessions on a remote server
|
|
284
|
+
* @param options SSH connection options
|
|
285
|
+
* @returns Resource usage summary
|
|
286
|
+
*/
|
|
287
|
+
export async function getTmuxResourceUsage(options) {
|
|
288
|
+
const pool = getSSHPool();
|
|
289
|
+
try {
|
|
290
|
+
// Get count of tmux sessions
|
|
291
|
+
const sessions = await listTmuxSessions(options);
|
|
292
|
+
const codespacesSessions = sessions.filter(s => s.startsWith(DEFAULT_CONFIG.sessionPrefix));
|
|
293
|
+
// Estimate memory: each session ~10MB base + scrollback buffer
|
|
294
|
+
// Scrollback: 10000 lines × ~100 bytes/line = ~1MB per session
|
|
295
|
+
const estimatedMemoryMB = codespacesSessions.length * 11;
|
|
296
|
+
return {
|
|
297
|
+
totalSessions: sessions.length,
|
|
298
|
+
codespacesSessions: codespacesSessions.length,
|
|
299
|
+
estimatedMemoryMB,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Send a command to a specific pane in a tmux session
|
|
308
|
+
* @param sessionName Target tmux session name
|
|
309
|
+
* @param paneIndex Pane index (default: 0 for first pane)
|
|
310
|
+
* @param command Command to execute (sent as keystrokes)
|
|
311
|
+
* @param options SSH connection options
|
|
312
|
+
*/
|
|
313
|
+
export async function sendCommandToPane(sessionName, command, paneIndex = "0", options) {
|
|
314
|
+
const pool = getSSHPool();
|
|
315
|
+
try {
|
|
316
|
+
// Use send-keys to send command as keystrokes, then Enter
|
|
317
|
+
const escapedCmd = command.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
318
|
+
await pool.exec(`tmux send-keys -t "${sessionName}:${paneIndex}" "${escapedCmd}" Enter`, { ...options, timeout: 5 });
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
console.error(`[Tmux] Failed to send command to ${sessionName}:${paneIndex}:`, error);
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Split a pane horizontally or vertically in a tmux session
|
|
328
|
+
* @param sessionName Target tmux session name
|
|
329
|
+
* @param windowIndex Window index (default: 0)
|
|
330
|
+
* @param direction Split direction: "h" (horizontal) or "v" (vertical)
|
|
331
|
+
* @param command Optional command to run in the new pane
|
|
332
|
+
* @param options SSH connection options
|
|
333
|
+
* @returns The new pane index
|
|
334
|
+
*/
|
|
335
|
+
export async function splitPane(sessionName, direction = "v", command = null, options) {
|
|
336
|
+
const pool = getSSHPool();
|
|
337
|
+
try {
|
|
338
|
+
// Split the pane and capture the new pane ID
|
|
339
|
+
const splitCmd = command
|
|
340
|
+
? `tmux split-window -${direction} -t "${sessionName}" -c "#{pane_current_path}" "${command}"`
|
|
341
|
+
: `tmux split-window -${direction} -t "${sessionName}"`;
|
|
342
|
+
const result = await pool.exec(splitCmd, { ...options, timeout: 10 });
|
|
343
|
+
// Return the result which contains the new pane ID
|
|
344
|
+
return result?.trim() || null;
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
console.error(`[Tmux] Failed to split pane in ${sessionName}:`, error);
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* List all windows in a tmux session
|
|
353
|
+
* @param sessionName Target tmux session name
|
|
354
|
+
* @param options SSH connection options
|
|
355
|
+
*/
|
|
356
|
+
export async function listSessionWindows(sessionName, options) {
|
|
357
|
+
const pool = getSSHPool();
|
|
358
|
+
try {
|
|
359
|
+
const result = await pool.exec(`tmux list-windows -t "${sessionName}" -F '#{window_index} #{window_name} #{window_active}' 2>/dev/null || echo ''`, { ...options, timeout: 5 });
|
|
360
|
+
if (!result || result.trim() === "" || result === "0") {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
return result.trim().split("\n").map(line => {
|
|
364
|
+
const [index, name, active] = line.split(" ");
|
|
365
|
+
return { index, name, active: active === "1" };
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* List all panes in a tmux session window
|
|
374
|
+
* @param sessionName Target tmux session name
|
|
375
|
+
* @param windowIndex Window index (default: 0)
|
|
376
|
+
* @param options SSH connection options
|
|
377
|
+
*/
|
|
378
|
+
export async function listWindowPanes(sessionName, windowIndex = "0", options) {
|
|
379
|
+
const pool = getSSHPool();
|
|
380
|
+
try {
|
|
381
|
+
const result = await pool.exec(`tmux list-panes -t "${sessionName}:${windowIndex}" -F '#{pane_index} #{pane_current_path} #{pane_pid} #{pane_active}' 2>/dev/null || echo ''`, { ...options, timeout: 5 });
|
|
382
|
+
if (!result || result.trim() === "" || result === "0") {
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
return result.trim().split("\n").map(line => {
|
|
386
|
+
const [index, currentPath, pid, active] = line.split(" ");
|
|
387
|
+
return { index, currentPath, pid, active: active === "1" };
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Capture the current output of a pane
|
|
396
|
+
* @param sessionName Target tmux session name
|
|
397
|
+
* @param paneIndex Pane index (default: 0)
|
|
398
|
+
* @param options SSH connection options
|
|
399
|
+
*/
|
|
400
|
+
export async function capturePane(sessionName, paneIndex = "0", options) {
|
|
401
|
+
const pool = getSSHPool();
|
|
402
|
+
try {
|
|
403
|
+
const result = await pool.exec(`tmux capture-pane -t "${sessionName}:${paneIndex}" -p`, { ...options, timeout: 5 });
|
|
404
|
+
return result || null;
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
console.error(`[Tmux] Failed to capture pane ${sessionName}:${paneIndex}:`, error);
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Get scrollback/history from a pane
|
|
413
|
+
* @param sessionName Target tmux session name
|
|
414
|
+
* @param paneIndex Pane index (default: 0)
|
|
415
|
+
* @param lines Number of lines to retrieve (default: all)
|
|
416
|
+
* @param options SSH connection options
|
|
417
|
+
*/
|
|
418
|
+
export async function getPaneHistory(sessionName, paneIndex = "0", lines = -1, options) {
|
|
419
|
+
const pool = getSSHPool();
|
|
420
|
+
try {
|
|
421
|
+
const linesArg = lines > 0 ? `-S -${lines}` : "-S -";
|
|
422
|
+
const result = await pool.exec(`tmux capture-pane ${linesArg} -t "${sessionName}:${paneIndex}" -p`, { ...options, timeout: 10 });
|
|
423
|
+
return result || null;
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
console.error(`[Tmux] Failed to get history for ${sessionName}:${paneIndex}:`, error);
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Switch to a specific window in a tmux session
|
|
432
|
+
* @param sessionName Target tmux session name
|
|
433
|
+
* @param windowIndex Target window index
|
|
434
|
+
* @param options SSH connection options
|
|
435
|
+
*/
|
|
436
|
+
export async function switchWindow(sessionName, windowIndex, options) {
|
|
437
|
+
const pool = getSSHPool();
|
|
438
|
+
try {
|
|
439
|
+
await pool.exec(`tmux select-window -t "${sessionName}:${windowIndex}"`, { ...options, timeout: 5 });
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Switch to a specific pane in a tmux session window
|
|
448
|
+
* @param sessionName Target tmux session name
|
|
449
|
+
* @param paneIndex Target pane index (e.g., "0", "1", "0.1" for window.pane)
|
|
450
|
+
* @param options SSH connection options
|
|
451
|
+
*/
|
|
452
|
+
export async function switchPane(sessionName, paneIndex, options) {
|
|
453
|
+
const pool = getSSHPool();
|
|
454
|
+
try {
|
|
455
|
+
await pool.exec(`tmux select-pane -t "${sessionName}:${paneIndex}"`, { ...options, timeout: 5 });
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Rename a window in a tmux session
|
|
464
|
+
* @param sessionName Target tmux session name
|
|
465
|
+
* @param windowIndex Window index (default: 0)
|
|
466
|
+
* @param newName New window name
|
|
467
|
+
* @param options SSH connection options
|
|
468
|
+
*/
|
|
469
|
+
export async function renameWindow(sessionName, windowIndex, newName, options) {
|
|
470
|
+
const pool = getSSHPool();
|
|
471
|
+
try {
|
|
472
|
+
await pool.exec(`tmux rename-window -t "${sessionName}:${windowIndex}" "${newName}"`, { ...options, timeout: 5 });
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Kill a specific pane in a tmux session
|
|
481
|
+
* @param sessionName Target tmux session name
|
|
482
|
+
* @param paneIndex Pane index to kill
|
|
483
|
+
* @param options SSH connection options
|
|
484
|
+
*/
|
|
485
|
+
export async function killPane(sessionName, paneIndex, options) {
|
|
486
|
+
const pool = getSSHPool();
|
|
487
|
+
try {
|
|
488
|
+
await pool.exec(`tmux kill-pane -t "${sessionName}:${paneIndex}"`, { ...options, timeout: 5 });
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Get detailed information about all panes in a session
|
|
497
|
+
* @param sessionName Target tmux session name
|
|
498
|
+
* @param options SSH connection options
|
|
499
|
+
*/
|
|
500
|
+
export async function getDetailedSessionInfo(sessionName, options) {
|
|
501
|
+
const pool = getSSHPool();
|
|
502
|
+
try {
|
|
503
|
+
// First check if session exists
|
|
504
|
+
const exists = await hasTmuxSession(sessionName, options);
|
|
505
|
+
if (!exists) {
|
|
506
|
+
return { exists: false, windows: [] };
|
|
507
|
+
}
|
|
508
|
+
// Get all windows
|
|
509
|
+
const windows = await listSessionWindows(sessionName, options);
|
|
510
|
+
// Get panes for each window
|
|
511
|
+
const windowsWithPanes = await Promise.all(windows.map(async (window) => {
|
|
512
|
+
const panes = await listWindowPanes(sessionName, window.index, options);
|
|
513
|
+
return {
|
|
514
|
+
...window,
|
|
515
|
+
panes,
|
|
516
|
+
};
|
|
517
|
+
}));
|
|
518
|
+
return {
|
|
519
|
+
exists: true,
|
|
520
|
+
windows: windowsWithPanes,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
console.error(`[Tmux] Failed to get detailed info for ${sessionName}:`, error);
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
//# sourceMappingURL=tmux.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH type definitions
|
|
3
|
+
*/
|
|
4
|
+
export interface SSHOptions {
|
|
5
|
+
host: string;
|
|
6
|
+
user?: string;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
port?: number;
|
|
9
|
+
keyPath?: string;
|
|
10
|
+
password?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SCPOptions extends SSHOptions {
|
|
13
|
+
source: string;
|
|
14
|
+
destination: string;
|
|
15
|
+
recursive?: boolean;
|
|
16
|
+
preserve?: boolean;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
|
Binary file
|