@ebowwa/terminal 0.3.8 → 0.3.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/dist/index.js +168 -8
- package/dist/mcp/index.js +4 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -443,7 +443,7 @@ var init_error = __esm(() => {
|
|
|
443
443
|
};
|
|
444
444
|
});
|
|
445
445
|
|
|
446
|
-
//
|
|
446
|
+
// node_modules/@ebowwa/codespaces-types/runtime/ssh.js
|
|
447
447
|
import { z } from "zod";
|
|
448
448
|
var SSHOptionsSchema, SSHCommandSchema, SCPOptionsSchema, FilesListOptionsSchema, FilePreviewOptionsSchema;
|
|
449
449
|
var init_ssh = __esm(() => {
|
|
@@ -513,7 +513,7 @@ var init_client = __esm(() => {
|
|
|
513
513
|
// src/tmux.ts
|
|
514
514
|
init_pool();
|
|
515
515
|
|
|
516
|
-
//
|
|
516
|
+
// node_modules/@ebowwa/ssh/flags.js
|
|
517
517
|
function sshConfig(key, value) {
|
|
518
518
|
return { type: "config", key, value };
|
|
519
519
|
}
|
|
@@ -2176,9 +2176,29 @@ async function validateSSHKeyForServer(host, keyPath, hetznerKeyId) {
|
|
|
2176
2176
|
}
|
|
2177
2177
|
// src/pty.ts
|
|
2178
2178
|
var activeSessions = new Map;
|
|
2179
|
+
var activeReplSessions = new Map;
|
|
2179
2180
|
function generateSessionId() {
|
|
2180
2181
|
return `pty-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
2181
2182
|
}
|
|
2183
|
+
function stripAnsi(str) {
|
|
2184
|
+
return str.replace(/\x1B\[[0-9;]*m/g, "").replace(/\x1B\][0-9;]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B[\][0-9;]*[A-Za-z]/g, "").replace(/\x1B[()][AB012]/g, "").replace(/\x1B[78]/g, "").replace(/\r\n|\r/g, `
|
|
2185
|
+
`).trim();
|
|
2186
|
+
}
|
|
2187
|
+
function detectPrompt(output) {
|
|
2188
|
+
const promptPatterns = [
|
|
2189
|
+
/[$#]\s*$/,
|
|
2190
|
+
/~\s*$/,
|
|
2191
|
+
/]\$\s*$/,
|
|
2192
|
+
/>\s*$/
|
|
2193
|
+
];
|
|
2194
|
+
const cleanOutput = stripAnsi(output);
|
|
2195
|
+
for (const pattern of promptPatterns) {
|
|
2196
|
+
if (pattern.test(cleanOutput)) {
|
|
2197
|
+
return true;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
return false;
|
|
2201
|
+
}
|
|
2182
2202
|
async function createPTYSession(host, user = "root", options = {}) {
|
|
2183
2203
|
const { rows = 24, cols = 80, port = 22, keyPath } = options;
|
|
2184
2204
|
const sessionId = generateSessionId();
|
|
@@ -2203,8 +2223,7 @@ async function createPTYSession(host, user = "root", options = {}) {
|
|
|
2203
2223
|
}
|
|
2204
2224
|
sshArgs.push("-t", "-t");
|
|
2205
2225
|
sshArgs.push(`${user}@${host}`);
|
|
2206
|
-
sshArgs.push("TERM=xterm-256color");
|
|
2207
|
-
sshArgs.push("script", "-q", "/dev/null", "/bin/bash");
|
|
2226
|
+
sshArgs.push("TERM=xterm-256color script -q -c 'bash --norc --noprofile' /dev/null");
|
|
2208
2227
|
const proc = Bun.spawn(sshArgs, {
|
|
2209
2228
|
stdin: "pipe",
|
|
2210
2229
|
stdout: "pipe",
|
|
@@ -2256,9 +2275,9 @@ async function writeToPTY(sessionId, data) {
|
|
|
2256
2275
|
return false;
|
|
2257
2276
|
}
|
|
2258
2277
|
try {
|
|
2259
|
-
const
|
|
2260
|
-
await
|
|
2261
|
-
|
|
2278
|
+
const stdin = session.stdin;
|
|
2279
|
+
await stdin.write(new TextEncoder().encode(data));
|
|
2280
|
+
await stdin.flush();
|
|
2262
2281
|
return true;
|
|
2263
2282
|
} catch {
|
|
2264
2283
|
return false;
|
|
@@ -2331,6 +2350,141 @@ function getPTYSession(sessionId) {
|
|
|
2331
2350
|
function getActivePTYSessions() {
|
|
2332
2351
|
return Array.from(activeSessions.values());
|
|
2333
2352
|
}
|
|
2353
|
+
async function createSSHReplSession(options) {
|
|
2354
|
+
const {
|
|
2355
|
+
host,
|
|
2356
|
+
user = "root",
|
|
2357
|
+
shell = "/bin/bash",
|
|
2358
|
+
initCommands = [],
|
|
2359
|
+
timeout = 30000,
|
|
2360
|
+
onConnect
|
|
2361
|
+
} = options;
|
|
2362
|
+
const sessionId = generateSessionId();
|
|
2363
|
+
const startTime = Date.now();
|
|
2364
|
+
const { sessionId: ptyId, initialOutput } = await createPTYSession(host, user, {
|
|
2365
|
+
port: options.port,
|
|
2366
|
+
keyPath: options.keyPath
|
|
2367
|
+
});
|
|
2368
|
+
const session = {
|
|
2369
|
+
id: sessionId,
|
|
2370
|
+
ptyId,
|
|
2371
|
+
host,
|
|
2372
|
+
user,
|
|
2373
|
+
cwd: "~",
|
|
2374
|
+
env: {},
|
|
2375
|
+
lastExitCode: 0,
|
|
2376
|
+
lastCommand: "",
|
|
2377
|
+
createdAt: startTime,
|
|
2378
|
+
connected: true
|
|
2379
|
+
};
|
|
2380
|
+
activeReplSessions.set(sessionId, session);
|
|
2381
|
+
for (const cmd of initCommands) {
|
|
2382
|
+
await execPTY(sessionId, cmd, { timeout });
|
|
2383
|
+
}
|
|
2384
|
+
onConnect?.(session);
|
|
2385
|
+
return session;
|
|
2386
|
+
}
|
|
2387
|
+
async function execPTY(sessionId, command, options = {}) {
|
|
2388
|
+
const replSession = activeReplSessions.get(sessionId);
|
|
2389
|
+
if (!replSession) {
|
|
2390
|
+
throw new Error(`REPL session not found: ${sessionId}`);
|
|
2391
|
+
}
|
|
2392
|
+
const session = activeSessions.get(replSession.ptyId);
|
|
2393
|
+
if (!session) {
|
|
2394
|
+
throw new Error(`PTY session not found: ${replSession.ptyId}`);
|
|
2395
|
+
}
|
|
2396
|
+
const startTime = Date.now();
|
|
2397
|
+
const timeout = options.timeout || 30000;
|
|
2398
|
+
const cmdWithNewline = command.endsWith(`
|
|
2399
|
+
`) ? command : `${command}
|
|
2400
|
+
`;
|
|
2401
|
+
const written = await writeToPTY(replSession.ptyId, cmdWithNewline);
|
|
2402
|
+
if (!written) {
|
|
2403
|
+
throw new Error("Failed to write to PTY");
|
|
2404
|
+
}
|
|
2405
|
+
let output = "";
|
|
2406
|
+
let stderrOutput = "";
|
|
2407
|
+
const deadline = startTime + timeout;
|
|
2408
|
+
while (Date.now() < deadline) {
|
|
2409
|
+
const chunk = await readFromPTY(replSession.ptyId, 200);
|
|
2410
|
+
if (chunk === null) {
|
|
2411
|
+
break;
|
|
2412
|
+
}
|
|
2413
|
+
output += chunk;
|
|
2414
|
+
if (detectPrompt(output)) {
|
|
2415
|
+
break;
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
const exitCodeResult = await writeToPTY(replSession.ptyId, `echo "$?"
|
|
2419
|
+
`);
|
|
2420
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2421
|
+
const exitChunk = await readFromPTY(replSession.ptyId, 200);
|
|
2422
|
+
const exitCodeMatch = (exitChunk || "").trim().match(/^\d+$/);
|
|
2423
|
+
const exitCode = exitCodeMatch ? parseInt(exitCodeMatch[0], 10) : 0;
|
|
2424
|
+
if (command.startsWith("cd ") || command === "cd") {
|
|
2425
|
+
const cwdMatch = output.match(/^(.+?)\n/);
|
|
2426
|
+
if (cwdMatch) {
|
|
2427
|
+
replSession.cwd = cwdMatch[1].trim();
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
const clean = stripAnsi(output).replace(/\r?\n/g, `
|
|
2431
|
+
`).trim();
|
|
2432
|
+
replSession.lastCommand = command;
|
|
2433
|
+
replSession.lastExitCode = exitCode;
|
|
2434
|
+
return {
|
|
2435
|
+
stdout: clean,
|
|
2436
|
+
stderr: stderrOutput,
|
|
2437
|
+
exitCode,
|
|
2438
|
+
cwd: replSession.cwd,
|
|
2439
|
+
duration: Date.now() - startTime,
|
|
2440
|
+
raw: output,
|
|
2441
|
+
clean
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
async function execPTYBatch(sessionId, commands, options = {}) {
|
|
2445
|
+
const results = [];
|
|
2446
|
+
const errors = [];
|
|
2447
|
+
const startTime = Date.now();
|
|
2448
|
+
for (const command of commands) {
|
|
2449
|
+
try {
|
|
2450
|
+
const result = await execPTY(sessionId, command, options);
|
|
2451
|
+
results.push(result);
|
|
2452
|
+
if (result.exitCode !== 0 && options.stopOnError) {
|
|
2453
|
+
errors.push({ command, error: new Error(`Command failed with exit code ${result.exitCode}`) });
|
|
2454
|
+
break;
|
|
2455
|
+
}
|
|
2456
|
+
} catch (error) {
|
|
2457
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2458
|
+
errors.push({ command, error: err });
|
|
2459
|
+
if (options.stopOnError) {
|
|
2460
|
+
break;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
return {
|
|
2465
|
+
results,
|
|
2466
|
+
errors,
|
|
2467
|
+
totalDuration: Date.now() - startTime
|
|
2468
|
+
};
|
|
2469
|
+
}
|
|
2470
|
+
function getSSHReplSession(sessionId) {
|
|
2471
|
+
return activeReplSessions.get(sessionId);
|
|
2472
|
+
}
|
|
2473
|
+
function getActiveSSHReplSessions() {
|
|
2474
|
+
return Array.from(activeReplSessions.values());
|
|
2475
|
+
}
|
|
2476
|
+
async function closeSSHReplSession(sessionId) {
|
|
2477
|
+
const session = activeReplSessions.get(sessionId);
|
|
2478
|
+
if (!session) {
|
|
2479
|
+
return false;
|
|
2480
|
+
}
|
|
2481
|
+
const result = await closePTYSession(session.ptyId);
|
|
2482
|
+
if (result) {
|
|
2483
|
+
session.connected = false;
|
|
2484
|
+
activeReplSessions.delete(sessionId);
|
|
2485
|
+
}
|
|
2486
|
+
return result;
|
|
2487
|
+
}
|
|
2334
2488
|
|
|
2335
2489
|
// src/index.ts
|
|
2336
2490
|
init_pool();
|
|
@@ -2338,7 +2492,7 @@ init_pool();
|
|
|
2338
2492
|
// src/sessions.ts
|
|
2339
2493
|
import path2 from "path";
|
|
2340
2494
|
|
|
2341
|
-
//
|
|
2495
|
+
// node_modules/@ebowwa/codespaces-types/compile/index.js
|
|
2342
2496
|
var WebSocketCloseCode = {
|
|
2343
2497
|
NORMAL_CLOSURE: 1000,
|
|
2344
2498
|
ENDPOINT_GOING_AWAY: 1001,
|
|
@@ -3754,6 +3908,7 @@ export {
|
|
|
3754
3908
|
getSessionCount,
|
|
3755
3909
|
getSession,
|
|
3756
3910
|
getSecurityEvents,
|
|
3911
|
+
getSSHReplSession,
|
|
3757
3912
|
getSSHPool,
|
|
3758
3913
|
getSSHFingerprint,
|
|
3759
3914
|
getPaneHistory,
|
|
@@ -3767,6 +3922,7 @@ export {
|
|
|
3767
3922
|
getDetailedLocalSessionInfo,
|
|
3768
3923
|
getAllSessions,
|
|
3769
3924
|
getAllSessionInfo,
|
|
3925
|
+
getActiveSSHReplSessions,
|
|
3770
3926
|
getActiveSSHConnections,
|
|
3771
3927
|
getActivePTYSessions,
|
|
3772
3928
|
generateSessionName,
|
|
@@ -3776,14 +3932,18 @@ export {
|
|
|
3776
3932
|
execViaTmux,
|
|
3777
3933
|
execSSHParallel,
|
|
3778
3934
|
execSSH,
|
|
3935
|
+
execPTYBatch,
|
|
3936
|
+
execPTY,
|
|
3779
3937
|
ensureTmux,
|
|
3780
3938
|
ensureCorrectSSHKey,
|
|
3781
3939
|
detectNetworkError,
|
|
3782
3940
|
detachWebSocket,
|
|
3941
|
+
createSSHReplSession,
|
|
3783
3942
|
createPTYSession,
|
|
3784
3943
|
createOrAttachTmuxSession,
|
|
3785
3944
|
createLocalTmuxSSHSession,
|
|
3786
3945
|
closeSession,
|
|
3946
|
+
closeSSHReplSession,
|
|
3787
3947
|
closePTYSession,
|
|
3788
3948
|
closeGlobalSSHPool,
|
|
3789
3949
|
clearSecurityEvents,
|
package/dist/mcp/index.js
CHANGED
|
@@ -444,7 +444,7 @@ var init_error = __esm(() => {
|
|
|
444
444
|
};
|
|
445
445
|
});
|
|
446
446
|
|
|
447
|
-
//
|
|
447
|
+
// node_modules/@ebowwa/codespaces-types/runtime/ssh.js
|
|
448
448
|
import { z } from "zod";
|
|
449
449
|
var SSHOptionsSchema, SSHCommandSchema, SCPOptionsSchema, FilesListOptionsSchema, FilePreviewOptionsSchema;
|
|
450
450
|
var init_ssh = __esm(() => {
|
|
@@ -514,7 +514,7 @@ var init_client = __esm(() => {
|
|
|
514
514
|
// src/tmux.ts
|
|
515
515
|
init_pool();
|
|
516
516
|
|
|
517
|
-
//
|
|
517
|
+
// node_modules/@ebowwa/ssh/flags.js
|
|
518
518
|
function sshConfig(key, value) {
|
|
519
519
|
return { type: "config", key, value };
|
|
520
520
|
}
|
|
@@ -1871,6 +1871,7 @@ import { promisify } from "util";
|
|
|
1871
1871
|
var execAsync = promisify(__require("child_process").exec);
|
|
1872
1872
|
// src/pty.ts
|
|
1873
1873
|
var activeSessions = new Map;
|
|
1874
|
+
var activeReplSessions = new Map;
|
|
1874
1875
|
|
|
1875
1876
|
// src/index.ts
|
|
1876
1877
|
init_pool();
|
|
@@ -1878,7 +1879,7 @@ init_pool();
|
|
|
1878
1879
|
// src/sessions.ts
|
|
1879
1880
|
import path2 from "path";
|
|
1880
1881
|
|
|
1881
|
-
//
|
|
1882
|
+
// node_modules/@ebowwa/codespaces-types/compile/index.js
|
|
1882
1883
|
var WebSocketCloseCode = {
|
|
1883
1884
|
NORMAL_CLOSURE: 1000,
|
|
1884
1885
|
ENDPOINT_GOING_AWAY: 1001,
|
package/package.json
CHANGED