@akms/mcp-ssh 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/CHANGELOG.md +205 -0
- package/README.md +6 -1
- package/lib/cli.cjs +639 -503
- package/lib/cli.cjs.map +1 -1
- package/lib/cli.js +642 -506
- package/lib/cli.js.map +1 -1
- package/lib/index.cjs +18 -3
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +2 -2
- package/lib/index.d.ts +2 -2
- package/lib/index.js +18 -3
- package/lib/index.js.map +1 -1
- package/package.json +2 -1
package/lib/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { createHash } from 'crypto';
|
|
|
8
8
|
import { Duplex } from 'stream';
|
|
9
9
|
import { StringDecoder } from 'string_decoder';
|
|
10
10
|
import ssh2 from 'ssh2';
|
|
11
|
-
import { setInterval, clearInterval } from 'timers';
|
|
11
|
+
import { setInterval as setInterval$1, clearInterval as clearInterval$1 } from 'timers';
|
|
12
12
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
13
13
|
import { z } from 'zod';
|
|
14
14
|
|
|
@@ -22,11 +22,12 @@ var DEFAULT_MAX_READ_FILE_BYTES = 2e5;
|
|
|
22
22
|
var KEEPALIVE_INTERVAL_MS = 15e3;
|
|
23
23
|
var SESSION_IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
24
24
|
var SESSION_SWEEP_INTERVAL_MS = 6e4;
|
|
25
|
+
var SHUTDOWN_DEADLINE_MS = 5e3;
|
|
25
26
|
var CWD_MARKER = "__AKMS_SSH_CWD__";
|
|
26
27
|
var MAX_DIRECTORY_ENTRIES = 1e3;
|
|
27
28
|
var SERVER_NAME = "akms-mcp-ssh";
|
|
28
29
|
var PACKAGE_NAME = "@akms/mcp-ssh";
|
|
29
|
-
var SERVER_VERSION = "0.1.
|
|
30
|
+
var SERVER_VERSION = "0.1.7";
|
|
30
31
|
|
|
31
32
|
// src/_defs/command-rules.ts
|
|
32
33
|
var CRITICAL_PATH_PATTERN = new RegExp(
|
|
@@ -489,6 +490,10 @@ var LEVEL_WEIGHTS = {
|
|
|
489
490
|
error: 4
|
|
490
491
|
};
|
|
491
492
|
var SECRET_META_KEYS = ["password", "passphrase", "privatekey", "privatekeypath", "secret", "token"];
|
|
493
|
+
var stderrBroken = false;
|
|
494
|
+
process.stderr.on("error", () => {
|
|
495
|
+
stderrBroken = true;
|
|
496
|
+
});
|
|
492
497
|
function resolveThreshold() {
|
|
493
498
|
const configured = process.env[LOG_LEVEL_ENV];
|
|
494
499
|
if (configured == null) {
|
|
@@ -512,6 +517,9 @@ function maskMeta(meta) {
|
|
|
512
517
|
return masked;
|
|
513
518
|
}
|
|
514
519
|
function write(level, message, meta) {
|
|
520
|
+
if (stderrBroken == true) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
515
523
|
const threshold = resolveThreshold();
|
|
516
524
|
if (threshold === "silent") {
|
|
517
525
|
return;
|
|
@@ -525,8 +533,12 @@ function write(level, message, meta) {
|
|
|
525
533
|
if (Object.keys(masked).length > 0) {
|
|
526
534
|
line += ` ${JSON.stringify(masked)}`;
|
|
527
535
|
}
|
|
528
|
-
|
|
536
|
+
try {
|
|
537
|
+
process.stderr.write(`${line}
|
|
529
538
|
`);
|
|
539
|
+
} catch {
|
|
540
|
+
stderrBroken = true;
|
|
541
|
+
}
|
|
530
542
|
}
|
|
531
543
|
function createLogger(boundMeta) {
|
|
532
544
|
return {
|
|
@@ -1628,7 +1640,7 @@ var SshConnection = class {
|
|
|
1628
1640
|
* @throws {Error} If the connection was closed, authentication fails, the host is
|
|
1629
1641
|
* unreachable, the handshake times out, or the carrier cannot be opened.
|
|
1630
1642
|
*/
|
|
1631
|
-
async connect() {
|
|
1643
|
+
async connect(onProgress) {
|
|
1632
1644
|
if (this.disposed == true) {
|
|
1633
1645
|
throw new Error(`ssh connection for '${this.profile.name}' has been closed; open a new session`);
|
|
1634
1646
|
}
|
|
@@ -1650,6 +1662,9 @@ var SshConnection = class {
|
|
|
1650
1662
|
this.observedFingerprint = fingerprint;
|
|
1651
1663
|
});
|
|
1652
1664
|
this.connecting = (async () => {
|
|
1665
|
+
if (this.profile.jump != null || this.profile.proxyCommand != null) {
|
|
1666
|
+
onProgress?.("transport");
|
|
1667
|
+
}
|
|
1653
1668
|
const transport = await openTransport(this.profile, (fingerprint) => {
|
|
1654
1669
|
this.observedJumpFingerprint = fingerprint;
|
|
1655
1670
|
});
|
|
@@ -1660,6 +1675,7 @@ var SshConnection = class {
|
|
|
1660
1675
|
if (transport != null) {
|
|
1661
1676
|
connectConfig.sock = transport.sock;
|
|
1662
1677
|
}
|
|
1678
|
+
onProgress?.("host");
|
|
1663
1679
|
await new Promise((resolve, reject) => {
|
|
1664
1680
|
const client = new Client();
|
|
1665
1681
|
this.pendingClient = client;
|
|
@@ -2115,7 +2131,7 @@ var SshSessionManager = class {
|
|
|
2115
2131
|
if (this.sweepTimer != null) {
|
|
2116
2132
|
return;
|
|
2117
2133
|
}
|
|
2118
|
-
this.sweepTimer = setInterval(() => {
|
|
2134
|
+
this.sweepTimer = setInterval$1(() => {
|
|
2119
2135
|
const expiredAt = Date.now() - SESSION_IDLE_TIMEOUT_MS;
|
|
2120
2136
|
for (const session of Array.from(this.sessions.values())) {
|
|
2121
2137
|
if (session.isBusy == true) {
|
|
@@ -2139,7 +2155,7 @@ var SshSessionManager = class {
|
|
|
2139
2155
|
if (this.sweepTimer == null) {
|
|
2140
2156
|
return;
|
|
2141
2157
|
}
|
|
2142
|
-
clearInterval(this.sweepTimer);
|
|
2158
|
+
clearInterval$1(this.sweepTimer);
|
|
2143
2159
|
this.sweepTimer = null;
|
|
2144
2160
|
}
|
|
2145
2161
|
};
|
|
@@ -2358,386 +2374,109 @@ async function downloadFile(connection, remotePath, localPath) {
|
|
|
2358
2374
|
return localStats.size;
|
|
2359
2375
|
}
|
|
2360
2376
|
|
|
2361
|
-
// src/modules/manage/
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
output.write(" The entry has no host.\n");
|
|
2367
|
-
}
|
|
2368
|
-
return profile;
|
|
2369
|
-
} catch (ex) {
|
|
2370
|
-
let detail = String(ex);
|
|
2371
|
-
if (ex instanceof Error) {
|
|
2372
|
-
detail = ex.message;
|
|
2373
|
-
}
|
|
2374
|
-
output.write(` The server would refuse to start with this entry: ${detail}
|
|
2375
|
-
`);
|
|
2376
|
-
return null;
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
function describeAuth(profile) {
|
|
2380
|
-
if (profile.privateKeyPath != null) {
|
|
2381
|
-
return `key ${profile.privateKeyPath}`;
|
|
2382
|
-
}
|
|
2383
|
-
if (profile.password != null) {
|
|
2384
|
-
return "password";
|
|
2385
|
-
}
|
|
2386
|
-
return "ssh agent";
|
|
2387
|
-
}
|
|
2388
|
-
async function runConnectionCheck(profile, output) {
|
|
2389
|
-
const endpoint = `${profile.username}@${profile.host}:${profile.port}`;
|
|
2390
|
-
const auth = describeAuth(profile);
|
|
2391
|
-
output.write(`Checking ${profile.name} \u2192 ${endpoint}
|
|
2392
|
-
`);
|
|
2393
|
-
if (profile.proxyCommand != null) {
|
|
2394
|
-
output.write(` via proxy command: ${profile.proxyCommand}
|
|
2395
|
-
`);
|
|
2377
|
+
// src/modules/manage/prompter.ts
|
|
2378
|
+
var PromptClosedError = class extends Error {
|
|
2379
|
+
constructor() {
|
|
2380
|
+
super("input closed before the prompt was answered");
|
|
2381
|
+
this.name = "PromptClosedError";
|
|
2396
2382
|
}
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2383
|
+
};
|
|
2384
|
+
var PromptBackError = class extends Error {
|
|
2385
|
+
constructor() {
|
|
2386
|
+
super("prompt left with esc");
|
|
2387
|
+
this.name = "PromptBackError";
|
|
2400
2388
|
}
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2389
|
+
};
|
|
2390
|
+
var ESC = "\x1B";
|
|
2391
|
+
var KEY_UP = [`${ESC}[A`, `${ESC}OA`];
|
|
2392
|
+
var KEY_DOWN = [`${ESC}[B`, `${ESC}OB`];
|
|
2393
|
+
var KEY_RIGHT = [`${ESC}[C`, `${ESC}OC`];
|
|
2394
|
+
var KEY_LEFT = [`${ESC}[D`, `${ESC}OD`];
|
|
2395
|
+
var KEY_HOME = [`${ESC}[H`, `${ESC}OH`, `${ESC}[1~`, ""];
|
|
2396
|
+
var KEY_END = [`${ESC}[F`, `${ESC}OF`, `${ESC}[4~`, ""];
|
|
2397
|
+
var KEY_DELETE = [`${ESC}[3~`];
|
|
2398
|
+
var KEY_ENTER = ["\r", "\n"];
|
|
2399
|
+
var KEY_BACKSPACE = ["\x7F", "\b"];
|
|
2400
|
+
var KEY_CTRL_C = "";
|
|
2401
|
+
var KEY_CTRL_D = "";
|
|
2402
|
+
var KEY_CLEAR_LINE = "";
|
|
2403
|
+
function splitKeys(chunk) {
|
|
2404
|
+
const keys = [];
|
|
2405
|
+
let index = 0;
|
|
2406
|
+
while (index < chunk.length) {
|
|
2407
|
+
const isSequence = chunk[index] === ESC && (chunk[index + 1] === "[" || chunk[index + 1] === "O");
|
|
2408
|
+
if (isSequence == true) {
|
|
2409
|
+
let end = index + 2;
|
|
2410
|
+
while (end < chunk.length && /[0-9;]/.test(chunk[end]) == true) {
|
|
2411
|
+
end += 1;
|
|
2412
|
+
}
|
|
2413
|
+
keys.push(chunk.slice(index, end + 1));
|
|
2414
|
+
index = end + 1;
|
|
2415
|
+
continue;
|
|
2413
2416
|
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2417
|
+
const codePoint = chunk.codePointAt(index) ?? 0;
|
|
2418
|
+
const character = String.fromCodePoint(codePoint);
|
|
2419
|
+
keys.push(character);
|
|
2420
|
+
index += character.length;
|
|
2416
2421
|
}
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
output.write(` FAILED ${endpoint} (${auth})
|
|
2423
|
-
`);
|
|
2424
|
-
output.write(` ${detail}
|
|
2425
|
-
`);
|
|
2422
|
+
return keys;
|
|
2423
|
+
}
|
|
2424
|
+
function isPrintableKey(key) {
|
|
2425
|
+
if (key.startsWith(ESC) == true) {
|
|
2426
|
+
return false;
|
|
2426
2427
|
}
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
`);
|
|
2428
|
+
const codePoint = key.codePointAt(0) ?? 0;
|
|
2429
|
+
return codePoint >= 32 && codePoint !== 127;
|
|
2430
|
+
}
|
|
2431
|
+
function charWidth(codePoint) {
|
|
2432
|
+
if (codePoint < 4352) {
|
|
2433
|
+
return 1;
|
|
2434
2434
|
}
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
pinNote = "pinned";
|
|
2439
|
-
}
|
|
2440
|
-
output.write(` jump host key ${connection.jumpHostKeyFingerprint} (${pinNote})
|
|
2441
|
-
`);
|
|
2435
|
+
const isWide = codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 && codePoint !== 12351 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65072 && codePoint <= 65103 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127744 && codePoint <= 128591 || codePoint >= 129280 && codePoint <= 129535 || codePoint >= 131072 && codePoint <= 262141;
|
|
2436
|
+
if (isWide == true) {
|
|
2437
|
+
return 2;
|
|
2442
2438
|
}
|
|
2443
|
-
return
|
|
2444
|
-
connected,
|
|
2445
|
-
hostKeyFingerprint: connection.hostKeyFingerprint,
|
|
2446
|
-
jumpHostKeyFingerprint: connection.jumpHostKeyFingerprint
|
|
2447
|
-
};
|
|
2439
|
+
return 1;
|
|
2448
2440
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
SINGLE_HOST_ENV.PORT,
|
|
2454
|
-
SINGLE_HOST_ENV.USER,
|
|
2455
|
-
SINGLE_HOST_ENV.KEY,
|
|
2456
|
-
SINGLE_HOST_ENV.PASSPHRASE,
|
|
2457
|
-
SINGLE_HOST_ENV.PASSWORD,
|
|
2458
|
-
SINGLE_HOST_ENV.DESCRIPTION,
|
|
2459
|
-
SINGLE_HOST_ENV.JUMP_HOST,
|
|
2460
|
-
SINGLE_HOST_ENV.JUMP_PORT,
|
|
2461
|
-
SINGLE_HOST_ENV.JUMP_USER,
|
|
2462
|
-
SINGLE_HOST_ENV.PROXY_COMMAND,
|
|
2463
|
-
SINGLE_HOST_ENV.READONLY,
|
|
2464
|
-
SINGLE_HOST_ENV.ALLOWED_PATHS,
|
|
2465
|
-
SINGLE_HOST_ENV.HOST_KEY,
|
|
2466
|
-
SINGLE_HOST_ENV.JUMP_HOST_KEY
|
|
2467
|
-
];
|
|
2468
|
-
var JUMP_ENV_PREFIX = "SSH_JUMP_";
|
|
2469
|
-
var SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]{1,48}$/;
|
|
2470
|
-
function isOurEntry(entry) {
|
|
2471
|
-
if (entry.args.includes(PACKAGE_NAME) == true) {
|
|
2472
|
-
return true;
|
|
2441
|
+
function displayWidth(text) {
|
|
2442
|
+
let width = 0;
|
|
2443
|
+
for (const character of text) {
|
|
2444
|
+
width += charWidth(character.codePointAt(0) ?? 0);
|
|
2473
2445
|
}
|
|
2474
|
-
|
|
2475
|
-
return commandBase === SERVER_NAME || commandBase.startsWith(`${SERVER_NAME}.`) == true;
|
|
2446
|
+
return width;
|
|
2476
2447
|
}
|
|
2477
|
-
function
|
|
2478
|
-
|
|
2479
|
-
|
|
2448
|
+
function padToWidth(text, width) {
|
|
2449
|
+
const missing = width - displayWidth(text);
|
|
2450
|
+
if (missing <= 0) {
|
|
2451
|
+
return text;
|
|
2480
2452
|
}
|
|
2481
|
-
return {
|
|
2453
|
+
return `${text}${" ".repeat(missing)}`;
|
|
2482
2454
|
}
|
|
2483
|
-
function
|
|
2484
|
-
const
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
if (answers.auth === "key" && answers.keyPath != null) {
|
|
2491
|
-
env[SINGLE_HOST_ENV.KEY] = answers.keyPath;
|
|
2492
|
-
if (answers.passphrase != null && answers.passphrase !== "") {
|
|
2493
|
-
env[SINGLE_HOST_ENV.PASSPHRASE] = answers.passphrase;
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
if (answers.auth === "password" && answers.password != null) {
|
|
2497
|
-
env[SINGLE_HOST_ENV.PASSWORD] = answers.password;
|
|
2498
|
-
}
|
|
2499
|
-
if (answers.hostKeyFingerprint != null) {
|
|
2500
|
-
env[SINGLE_HOST_ENV.HOST_KEY] = answers.hostKeyFingerprint;
|
|
2501
|
-
}
|
|
2502
|
-
if (answers.description != null && answers.description !== "") {
|
|
2503
|
-
env[SINGLE_HOST_ENV.DESCRIPTION] = answers.description;
|
|
2504
|
-
}
|
|
2505
|
-
if (answers.jumpHost != null) {
|
|
2506
|
-
env[SINGLE_HOST_ENV.JUMP_HOST] = answers.jumpHost;
|
|
2507
|
-
if (answers.jumpPort != null && answers.jumpPort !== DEFAULT_SSH_PORT) {
|
|
2508
|
-
env[SINGLE_HOST_ENV.JUMP_PORT] = String(answers.jumpPort);
|
|
2455
|
+
function groupLabelWidths(items) {
|
|
2456
|
+
const widths = items.map(() => 0);
|
|
2457
|
+
let groupStart = 0;
|
|
2458
|
+
for (let index = 0; index <= items.length; index += 1) {
|
|
2459
|
+
const atBoundary = index === items.length || "value" in items[index] == false;
|
|
2460
|
+
if (atBoundary == false) {
|
|
2461
|
+
continue;
|
|
2509
2462
|
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2463
|
+
let groupWidth = 0;
|
|
2464
|
+
for (let member = groupStart; member < index; member += 1) {
|
|
2465
|
+
const item = items[member];
|
|
2466
|
+
groupWidth = Math.max(groupWidth, displayWidth(item.label));
|
|
2512
2467
|
}
|
|
2513
|
-
|
|
2514
|
-
|
|
2468
|
+
for (let member = groupStart; member < index; member += 1) {
|
|
2469
|
+
widths[member] = groupWidth;
|
|
2515
2470
|
}
|
|
2471
|
+
groupStart = index + 1;
|
|
2516
2472
|
}
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
if (
|
|
2521
|
-
|
|
2522
|
-
}
|
|
2523
|
-
if (answers.allowedPaths.length > 0) {
|
|
2524
|
-
env[SINGLE_HOST_ENV.ALLOWED_PATHS] = answers.allowedPaths.join(",");
|
|
2525
|
-
}
|
|
2526
|
-
for (const [name, value] of Object.entries(answers.extraEnv)) {
|
|
2527
|
-
if (MANAGED_ENV_NAMES.includes(name) == true) {
|
|
2528
|
-
continue;
|
|
2529
|
-
}
|
|
2530
|
-
if (answers.jumpHost == null && name.startsWith(JUMP_ENV_PREFIX) == true) {
|
|
2531
|
-
continue;
|
|
2532
|
-
}
|
|
2533
|
-
env[name] = value;
|
|
2473
|
+
return widths;
|
|
2474
|
+
}
|
|
2475
|
+
function supportsUnicode() {
|
|
2476
|
+
if (process.platform !== "win32") {
|
|
2477
|
+
return true;
|
|
2534
2478
|
}
|
|
2535
|
-
|
|
2536
|
-
return {
|
|
2537
|
-
command: launch.command,
|
|
2538
|
-
args: launch.args,
|
|
2539
|
-
env
|
|
2540
|
-
};
|
|
2541
|
-
}
|
|
2542
|
-
function readPort(value) {
|
|
2543
|
-
if (value == null) {
|
|
2544
|
-
return DEFAULT_SSH_PORT;
|
|
2545
|
-
}
|
|
2546
|
-
const parsed = Number(value);
|
|
2547
|
-
if (Number.isInteger(parsed) == false || parsed <= 0) {
|
|
2548
|
-
return DEFAULT_SSH_PORT;
|
|
2549
|
-
}
|
|
2550
|
-
return parsed;
|
|
2551
|
-
}
|
|
2552
|
-
function isTruthy(value) {
|
|
2553
|
-
if (value == null) {
|
|
2554
|
-
return false;
|
|
2555
|
-
}
|
|
2556
|
-
return TRUTHY_ENV_VALUES.includes(value.trim().toLowerCase());
|
|
2557
|
-
}
|
|
2558
|
-
function entryToAnswers(name, entry) {
|
|
2559
|
-
const env = entry.env;
|
|
2560
|
-
const host = env[SINGLE_HOST_ENV.HOST] ?? "";
|
|
2561
|
-
const username = env[SINGLE_HOST_ENV.USER] ?? "";
|
|
2562
|
-
let auth = "agent";
|
|
2563
|
-
if (env[SINGLE_HOST_ENV.KEY] != null) {
|
|
2564
|
-
auth = "key";
|
|
2565
|
-
} else if (env[SINGLE_HOST_ENV.PASSWORD] != null) {
|
|
2566
|
-
auth = "password";
|
|
2567
|
-
}
|
|
2568
|
-
let allowedPaths = [];
|
|
2569
|
-
const allowedPathsValue = env[SINGLE_HOST_ENV.ALLOWED_PATHS];
|
|
2570
|
-
if (allowedPathsValue != null) {
|
|
2571
|
-
allowedPaths = allowedPathsValue.split(",").map((item) => item.trim()).filter((item) => item !== "");
|
|
2572
|
-
}
|
|
2573
|
-
const extraEnv = {};
|
|
2574
|
-
for (const [variable, value] of Object.entries(env)) {
|
|
2575
|
-
if (MANAGED_ENV_NAMES.includes(variable) == false) {
|
|
2576
|
-
extraEnv[variable] = value;
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
const answers = {
|
|
2580
|
-
name,
|
|
2581
|
-
host,
|
|
2582
|
-
port: readPort(env[SINGLE_HOST_ENV.PORT]),
|
|
2583
|
-
username,
|
|
2584
|
-
auth,
|
|
2585
|
-
keyPath: env[SINGLE_HOST_ENV.KEY],
|
|
2586
|
-
passphrase: env[SINGLE_HOST_ENV.PASSPHRASE],
|
|
2587
|
-
password: env[SINGLE_HOST_ENV.PASSWORD],
|
|
2588
|
-
description: env[SINGLE_HOST_ENV.DESCRIPTION],
|
|
2589
|
-
proxyCommand: env[SINGLE_HOST_ENV.PROXY_COMMAND],
|
|
2590
|
-
readonly: isTruthy(env[SINGLE_HOST_ENV.READONLY]),
|
|
2591
|
-
allowedPaths,
|
|
2592
|
-
hostKeyFingerprint: env[SINGLE_HOST_ENV.HOST_KEY],
|
|
2593
|
-
extraEnv
|
|
2594
|
-
};
|
|
2595
|
-
const jumpHost = env[SINGLE_HOST_ENV.JUMP_HOST];
|
|
2596
|
-
if (jumpHost != null) {
|
|
2597
|
-
answers.jumpHost = jumpHost;
|
|
2598
|
-
answers.jumpPort = readPort(env[SINGLE_HOST_ENV.JUMP_PORT]);
|
|
2599
|
-
answers.jumpUser = env[SINGLE_HOST_ENV.JUMP_USER] ?? username;
|
|
2600
|
-
answers.jumpHostKeyFingerprint = env[SINGLE_HOST_ENV.JUMP_HOST_KEY];
|
|
2601
|
-
}
|
|
2602
|
-
return answers;
|
|
2603
|
-
}
|
|
2604
|
-
function summarizeEntry(entry) {
|
|
2605
|
-
const env = entry.env;
|
|
2606
|
-
const host = env[SINGLE_HOST_ENV.HOST] ?? "?";
|
|
2607
|
-
const port = env[SINGLE_HOST_ENV.PORT] ?? String(DEFAULT_SSH_PORT);
|
|
2608
|
-
const username = env[SINGLE_HOST_ENV.USER] ?? "?";
|
|
2609
|
-
let auth = "agent";
|
|
2610
|
-
if (env[SINGLE_HOST_ENV.KEY] != null) {
|
|
2611
|
-
auth = "key";
|
|
2612
|
-
} else if (env[SINGLE_HOST_ENV.PASSWORD] != null) {
|
|
2613
|
-
auth = "password";
|
|
2614
|
-
}
|
|
2615
|
-
const notes = [];
|
|
2616
|
-
if (env[SINGLE_HOST_ENV.JUMP_HOST] != null) {
|
|
2617
|
-
notes.push(`jump ${env[SINGLE_HOST_ENV.JUMP_HOST]}`);
|
|
2618
|
-
}
|
|
2619
|
-
if (env[SINGLE_HOST_ENV.PROXY_COMMAND] != null) {
|
|
2620
|
-
notes.push("proxy");
|
|
2621
|
-
}
|
|
2622
|
-
if (isTruthy(env[SINGLE_HOST_ENV.READONLY]) == true) {
|
|
2623
|
-
notes.push("readonly");
|
|
2624
|
-
}
|
|
2625
|
-
if (env[SINGLE_HOST_ENV.ALLOWED_PATHS] != null) {
|
|
2626
|
-
notes.push(`paths ${env[SINGLE_HOST_ENV.ALLOWED_PATHS]}`);
|
|
2627
|
-
}
|
|
2628
|
-
if (env[SINGLE_HOST_ENV.HOST_KEY] != null) {
|
|
2629
|
-
notes.push("pinned");
|
|
2630
|
-
}
|
|
2631
|
-
return {
|
|
2632
|
-
endpoint: `${username}@${host}:${port}`,
|
|
2633
|
-
auth,
|
|
2634
|
-
notes: notes.join(", ")
|
|
2635
|
-
};
|
|
2636
|
-
}
|
|
2637
|
-
|
|
2638
|
-
// src/modules/manage/prompter.ts
|
|
2639
|
-
var PromptClosedError = class extends Error {
|
|
2640
|
-
constructor() {
|
|
2641
|
-
super("input closed before the prompt was answered");
|
|
2642
|
-
this.name = "PromptClosedError";
|
|
2643
|
-
}
|
|
2644
|
-
};
|
|
2645
|
-
var PromptBackError = class extends Error {
|
|
2646
|
-
constructor() {
|
|
2647
|
-
super("prompt left with esc");
|
|
2648
|
-
this.name = "PromptBackError";
|
|
2649
|
-
}
|
|
2650
|
-
};
|
|
2651
|
-
var ESC = "\x1B";
|
|
2652
|
-
var KEY_UP = [`${ESC}[A`, `${ESC}OA`];
|
|
2653
|
-
var KEY_DOWN = [`${ESC}[B`, `${ESC}OB`];
|
|
2654
|
-
var KEY_RIGHT = [`${ESC}[C`, `${ESC}OC`];
|
|
2655
|
-
var KEY_LEFT = [`${ESC}[D`, `${ESC}OD`];
|
|
2656
|
-
var KEY_HOME = [`${ESC}[H`, `${ESC}OH`, `${ESC}[1~`, ""];
|
|
2657
|
-
var KEY_END = [`${ESC}[F`, `${ESC}OF`, `${ESC}[4~`, ""];
|
|
2658
|
-
var KEY_DELETE = [`${ESC}[3~`];
|
|
2659
|
-
var KEY_ENTER = ["\r", "\n"];
|
|
2660
|
-
var KEY_BACKSPACE = ["\x7F", "\b"];
|
|
2661
|
-
var KEY_CTRL_C = "";
|
|
2662
|
-
var KEY_CTRL_D = "";
|
|
2663
|
-
var KEY_CLEAR_LINE = "";
|
|
2664
|
-
function splitKeys(chunk) {
|
|
2665
|
-
const keys = [];
|
|
2666
|
-
let index = 0;
|
|
2667
|
-
while (index < chunk.length) {
|
|
2668
|
-
const isSequence = chunk[index] === ESC && (chunk[index + 1] === "[" || chunk[index + 1] === "O");
|
|
2669
|
-
if (isSequence == true) {
|
|
2670
|
-
let end = index + 2;
|
|
2671
|
-
while (end < chunk.length && /[0-9;]/.test(chunk[end]) == true) {
|
|
2672
|
-
end += 1;
|
|
2673
|
-
}
|
|
2674
|
-
keys.push(chunk.slice(index, end + 1));
|
|
2675
|
-
index = end + 1;
|
|
2676
|
-
continue;
|
|
2677
|
-
}
|
|
2678
|
-
const codePoint = chunk.codePointAt(index) ?? 0;
|
|
2679
|
-
const character = String.fromCodePoint(codePoint);
|
|
2680
|
-
keys.push(character);
|
|
2681
|
-
index += character.length;
|
|
2682
|
-
}
|
|
2683
|
-
return keys;
|
|
2684
|
-
}
|
|
2685
|
-
function isPrintableKey(key) {
|
|
2686
|
-
if (key.startsWith(ESC) == true) {
|
|
2687
|
-
return false;
|
|
2688
|
-
}
|
|
2689
|
-
const codePoint = key.codePointAt(0) ?? 0;
|
|
2690
|
-
return codePoint >= 32 && codePoint !== 127;
|
|
2691
|
-
}
|
|
2692
|
-
function charWidth(codePoint) {
|
|
2693
|
-
if (codePoint < 4352) {
|
|
2694
|
-
return 1;
|
|
2695
|
-
}
|
|
2696
|
-
const isWide = codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 && codePoint !== 12351 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65072 && codePoint <= 65103 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127744 && codePoint <= 128591 || codePoint >= 129280 && codePoint <= 129535 || codePoint >= 131072 && codePoint <= 262141;
|
|
2697
|
-
if (isWide == true) {
|
|
2698
|
-
return 2;
|
|
2699
|
-
}
|
|
2700
|
-
return 1;
|
|
2701
|
-
}
|
|
2702
|
-
function displayWidth(text) {
|
|
2703
|
-
let width = 0;
|
|
2704
|
-
for (const character of text) {
|
|
2705
|
-
width += charWidth(character.codePointAt(0) ?? 0);
|
|
2706
|
-
}
|
|
2707
|
-
return width;
|
|
2708
|
-
}
|
|
2709
|
-
function padToWidth(text, width) {
|
|
2710
|
-
const missing = width - displayWidth(text);
|
|
2711
|
-
if (missing <= 0) {
|
|
2712
|
-
return text;
|
|
2713
|
-
}
|
|
2714
|
-
return `${text}${" ".repeat(missing)}`;
|
|
2715
|
-
}
|
|
2716
|
-
function groupLabelWidths(items) {
|
|
2717
|
-
const widths = items.map(() => 0);
|
|
2718
|
-
let groupStart = 0;
|
|
2719
|
-
for (let index = 0; index <= items.length; index += 1) {
|
|
2720
|
-
const atBoundary = index === items.length || "value" in items[index] == false;
|
|
2721
|
-
if (atBoundary == false) {
|
|
2722
|
-
continue;
|
|
2723
|
-
}
|
|
2724
|
-
let groupWidth = 0;
|
|
2725
|
-
for (let member = groupStart; member < index; member += 1) {
|
|
2726
|
-
const item = items[member];
|
|
2727
|
-
groupWidth = Math.max(groupWidth, displayWidth(item.label));
|
|
2728
|
-
}
|
|
2729
|
-
for (let member = groupStart; member < index; member += 1) {
|
|
2730
|
-
widths[member] = groupWidth;
|
|
2731
|
-
}
|
|
2732
|
-
groupStart = index + 1;
|
|
2733
|
-
}
|
|
2734
|
-
return widths;
|
|
2735
|
-
}
|
|
2736
|
-
function supportsUnicode() {
|
|
2737
|
-
if (process.platform !== "win32") {
|
|
2738
|
-
return true;
|
|
2739
|
-
}
|
|
2740
|
-
return process.env.WT_SESSION != null || process.env.TERM_PROGRAM === "vscode" || process.env.TERMINAL_EMULATOR === "JetBrains-JediTerm" || process.env.ConEmuTask != null;
|
|
2479
|
+
return process.env.WT_SESSION != null || process.env.TERM_PROGRAM === "vscode" || process.env.TERMINAL_EMULATOR === "JetBrains-JediTerm" || process.env.ConEmuTask != null;
|
|
2741
2480
|
}
|
|
2742
2481
|
function createStyle(enabled) {
|
|
2743
2482
|
const paint = (open, close) => {
|
|
@@ -2811,6 +2550,17 @@ var Prompter = class {
|
|
|
2811
2550
|
get paint() {
|
|
2812
2551
|
return this.style;
|
|
2813
2552
|
}
|
|
2553
|
+
/** Keeps a report visible until Enter on a TTY; Esc goes back. Pipes need no extra answer. */
|
|
2554
|
+
async pause() {
|
|
2555
|
+
if (this.interactive == false) {
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
if (this.inputEnded == true) {
|
|
2559
|
+
throw new PromptClosedError();
|
|
2560
|
+
}
|
|
2561
|
+
const prompt = this.fitLine([{ text: "Press Enter to continue", paint: this.style.dim }], this.columns() - 1);
|
|
2562
|
+
await this.readText(prompt, false);
|
|
2563
|
+
}
|
|
2814
2564
|
/**
|
|
2815
2565
|
* Free-text prompt. Enter accepts `defaultValue` when one is shown.
|
|
2816
2566
|
*
|
|
@@ -3117,148 +2867,519 @@ var Prompter = class {
|
|
|
3117
2867
|
if (columns == null || columns < 20) {
|
|
3118
2868
|
return 80;
|
|
3119
2869
|
}
|
|
3120
|
-
return columns;
|
|
2870
|
+
return columns;
|
|
2871
|
+
}
|
|
2872
|
+
/** Joins styled parts, cutting the text (not the styles) once `width` columns are used. */
|
|
2873
|
+
fitLine(parts, width) {
|
|
2874
|
+
let used = 0;
|
|
2875
|
+
let line = "";
|
|
2876
|
+
for (const part of parts) {
|
|
2877
|
+
let text = "";
|
|
2878
|
+
for (const character of part.text) {
|
|
2879
|
+
const characterWidth = charWidth(character.codePointAt(0) ?? 0);
|
|
2880
|
+
if (used + characterWidth > width) {
|
|
2881
|
+
break;
|
|
2882
|
+
}
|
|
2883
|
+
used += characterWidth;
|
|
2884
|
+
text += character;
|
|
2885
|
+
}
|
|
2886
|
+
if (part.paint != null) {
|
|
2887
|
+
line += part.paint(text);
|
|
2888
|
+
} else {
|
|
2889
|
+
line += text;
|
|
2890
|
+
}
|
|
2891
|
+
if (used >= width) {
|
|
2892
|
+
break;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
return line;
|
|
2896
|
+
}
|
|
2897
|
+
/** The one-line record an answered prompt leaves behind on a TTY. */
|
|
2898
|
+
writeAnswered(question, answer) {
|
|
2899
|
+
if (this.interactive == false || this.transient == true) {
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
this.output.write(`${this.style.success(this.symbols.tick)} ${question} ${this.style.dim("\xB7")} ${this.style.accent(answer)}
|
|
2903
|
+
`);
|
|
2904
|
+
}
|
|
2905
|
+
/** @returns The picked value, or null on Esc. */
|
|
2906
|
+
selectWithKeys(question, items, startIndex) {
|
|
2907
|
+
const ttyInput = this.input;
|
|
2908
|
+
const selectableIndexes = items.flatMap((item, index) => {
|
|
2909
|
+
if ("value" in item) {
|
|
2910
|
+
return [index];
|
|
2911
|
+
}
|
|
2912
|
+
return [];
|
|
2913
|
+
});
|
|
2914
|
+
const labelWidths = groupLabelWidths(items);
|
|
2915
|
+
const ruleWidth = Math.min(Math.max(...labelWidths), 24);
|
|
2916
|
+
let active = startIndex;
|
|
2917
|
+
let renderedLines = 0;
|
|
2918
|
+
const draw = (lines) => {
|
|
2919
|
+
let text = "";
|
|
2920
|
+
if (renderedLines > 1) {
|
|
2921
|
+
text += `${ESC}[${renderedLines - 1}A`;
|
|
2922
|
+
}
|
|
2923
|
+
text += `\r${ESC}[J${lines.join("\n")}`;
|
|
2924
|
+
this.output.write(text);
|
|
2925
|
+
renderedLines = lines.length;
|
|
2926
|
+
};
|
|
2927
|
+
const buildLines = () => {
|
|
2928
|
+
const width = this.columns() - 1;
|
|
2929
|
+
const lines = [
|
|
2930
|
+
this.fitLine([
|
|
2931
|
+
{ text: this.symbols.question, paint: this.style.accent },
|
|
2932
|
+
{ text: " " },
|
|
2933
|
+
{ text: question, paint: this.style.bold }
|
|
2934
|
+
], width)
|
|
2935
|
+
];
|
|
2936
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
2937
|
+
const item = items[index];
|
|
2938
|
+
if ("value" in item == false) {
|
|
2939
|
+
lines.push(this.fitLine([{ text: ` ${this.symbols.rule.repeat(ruleWidth)}`, paint: this.style.dim }], width));
|
|
2940
|
+
continue;
|
|
2941
|
+
}
|
|
2942
|
+
const isActive = index === active;
|
|
2943
|
+
const parts = [];
|
|
2944
|
+
if (isActive == true) {
|
|
2945
|
+
parts.push({ text: `${this.symbols.pointer} `, paint: this.style.accent });
|
|
2946
|
+
parts.push({ text: padToWidth(item.label, labelWidths[index]), paint: this.style.accent });
|
|
2947
|
+
} else {
|
|
2948
|
+
parts.push({ text: " " });
|
|
2949
|
+
parts.push({ text: padToWidth(item.label, labelWidths[index]) });
|
|
2950
|
+
}
|
|
2951
|
+
if (item.hint != null && item.hint !== "") {
|
|
2952
|
+
parts.push({ text: ` ${item.hint}`, paint: this.style.dim });
|
|
2953
|
+
}
|
|
2954
|
+
lines.push(this.fitLine(parts, width));
|
|
2955
|
+
}
|
|
2956
|
+
lines.push(this.fitLine([{ text: "\u2191\u2193 move \xB7 enter select \xB7 esc back", paint: this.style.dim }], width));
|
|
2957
|
+
return lines;
|
|
2958
|
+
};
|
|
2959
|
+
const move = (step) => {
|
|
2960
|
+
const position = selectableIndexes.indexOf(active);
|
|
2961
|
+
const next = (position + step + selectableIndexes.length) % selectableIndexes.length;
|
|
2962
|
+
active = selectableIndexes[next];
|
|
2963
|
+
};
|
|
2964
|
+
return new Promise((resolve, reject) => {
|
|
2965
|
+
const finish = () => {
|
|
2966
|
+
this.keyHandler = null;
|
|
2967
|
+
ttyInput.setRawMode?.(false);
|
|
2968
|
+
draw([]);
|
|
2969
|
+
this.output.write(`${ESC}[?25h`);
|
|
2970
|
+
};
|
|
2971
|
+
this.keyHandler = (key) => {
|
|
2972
|
+
if (key === KEY_CTRL_C || key === KEY_CTRL_D) {
|
|
2973
|
+
finish();
|
|
2974
|
+
reject(new PromptClosedError());
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
if (KEY_ENTER.includes(key) == true) {
|
|
2978
|
+
const picked = items[active];
|
|
2979
|
+
finish();
|
|
2980
|
+
this.writeAnswered(question, picked.label);
|
|
2981
|
+
resolve(picked.value);
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
if (key === ESC) {
|
|
2985
|
+
finish();
|
|
2986
|
+
resolve(null);
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
if (KEY_UP.includes(key) == true) {
|
|
2990
|
+
move(-1);
|
|
2991
|
+
} else if (KEY_DOWN.includes(key) == true) {
|
|
2992
|
+
move(1);
|
|
2993
|
+
} else if (KEY_HOME.includes(key) == true) {
|
|
2994
|
+
active = selectableIndexes[0];
|
|
2995
|
+
} else if (KEY_END.includes(key) == true) {
|
|
2996
|
+
active = selectableIndexes[selectableIndexes.length - 1];
|
|
2997
|
+
} else if (/^[1-9]$/.test(key) == true && Number(key) <= selectableIndexes.length) {
|
|
2998
|
+
active = selectableIndexes[Number(key) - 1];
|
|
2999
|
+
} else {
|
|
3000
|
+
return;
|
|
3001
|
+
}
|
|
3002
|
+
draw(buildLines());
|
|
3003
|
+
};
|
|
3004
|
+
ttyInput.setRawMode?.(true);
|
|
3005
|
+
this.output.write(`${ESC}[?25l`);
|
|
3006
|
+
draw(buildLines());
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
};
|
|
3010
|
+
|
|
3011
|
+
// src/modules/manage/check-progress.ts
|
|
3012
|
+
var ESC2 = "\x1B";
|
|
3013
|
+
var CheckProgress = class {
|
|
3014
|
+
constructor(output) {
|
|
3015
|
+
this.output = output;
|
|
3016
|
+
this.unicode = supportsUnicode();
|
|
3017
|
+
this.frames = this.unicode ? ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] : ["-", "\\", "|", "/"];
|
|
3018
|
+
this.timer = null;
|
|
3019
|
+
this.label = null;
|
|
3020
|
+
this.startedAt = 0;
|
|
3021
|
+
this.frame = 0;
|
|
3022
|
+
this.enabled = output.isTTY === true && process.env.TERM !== "dumb";
|
|
3023
|
+
this.color = process.env.NO_COLOR == null;
|
|
3024
|
+
}
|
|
3025
|
+
/** Advancing means the preceding phase actually completed successfully. */
|
|
3026
|
+
start(label) {
|
|
3027
|
+
if (this.enabled == false) {
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
this.finish(true);
|
|
3031
|
+
this.label = label;
|
|
3032
|
+
this.startedAt = Date.now();
|
|
3033
|
+
this.frame = 0;
|
|
3034
|
+
this.draw(this.frames[0], 36);
|
|
3035
|
+
this.timer = setInterval(() => {
|
|
3036
|
+
this.frame = (this.frame + 1) % this.frames.length;
|
|
3037
|
+
this.draw(this.frames[this.frame], 36);
|
|
3038
|
+
}, 80);
|
|
3039
|
+
this.timer.unref();
|
|
3040
|
+
}
|
|
3041
|
+
/** Leaves the completed phase and its duration visible. */
|
|
3042
|
+
finish(ok) {
|
|
3043
|
+
if (this.label == null) {
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
this.clearTimer();
|
|
3047
|
+
const symbol = ok ? this.unicode ? "\u2714" : "OK" : this.unicode ? "\u2716" : "X";
|
|
3048
|
+
this.draw(symbol, ok ? 32 : 31);
|
|
3049
|
+
this.output.write("\n");
|
|
3050
|
+
this.label = null;
|
|
3051
|
+
}
|
|
3052
|
+
/** Clears an unfinished phase without recording a result. */
|
|
3053
|
+
stop() {
|
|
3054
|
+
this.clearTimer();
|
|
3055
|
+
if (this.label != null) {
|
|
3056
|
+
this.output.write(`\r${ESC2}[2K`);
|
|
3057
|
+
this.label = null;
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
clearTimer() {
|
|
3061
|
+
if (this.timer != null) {
|
|
3062
|
+
clearInterval(this.timer);
|
|
3063
|
+
this.timer = null;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
draw(symbol, color) {
|
|
3067
|
+
const columns = this.output.columns;
|
|
3068
|
+
const width = Math.max(0, (columns != null && columns > 0 ? columns : 80) - 1);
|
|
3069
|
+
const elapsed = ` ${((Date.now() - this.startedAt) / 1e3).toFixed(1)}s`;
|
|
3070
|
+
const prefix = ` ${symbol} `;
|
|
3071
|
+
const labelWidth = Math.min(48, Math.max(0, width - displayWidth(prefix) - displayWidth(elapsed)));
|
|
3072
|
+
const label = this.fit(this.label ?? "", labelWidth);
|
|
3073
|
+
const line = this.fit(`${prefix}${label}${" ".repeat(labelWidth - displayWidth(label))}${elapsed}`, width);
|
|
3074
|
+
const painted = this.color ? `${ESC2}[${color}m${line}${ESC2}[39m` : line;
|
|
3075
|
+
this.output.write(`\r${ESC2}[2K${painted}`);
|
|
3076
|
+
}
|
|
3077
|
+
fit(text, width) {
|
|
3078
|
+
if (displayWidth(text) <= width) {
|
|
3079
|
+
return text;
|
|
3080
|
+
}
|
|
3081
|
+
if (width < 1) {
|
|
3082
|
+
return "";
|
|
3083
|
+
}
|
|
3084
|
+
let result = "";
|
|
3085
|
+
let used = 0;
|
|
3086
|
+
for (const character of text) {
|
|
3087
|
+
const size = displayWidth(character);
|
|
3088
|
+
if (used + size > width - 1) {
|
|
3089
|
+
break;
|
|
3090
|
+
}
|
|
3091
|
+
result += character;
|
|
3092
|
+
used += size;
|
|
3093
|
+
}
|
|
3094
|
+
return `${result}${this.unicode ? "\u2026" : "~"}`;
|
|
3095
|
+
}
|
|
3096
|
+
};
|
|
3097
|
+
|
|
3098
|
+
// src/modules/manage/connection-check.ts
|
|
3099
|
+
function profileFromEnv(output, env) {
|
|
3100
|
+
try {
|
|
3101
|
+
const profile = loadHostProfile(env);
|
|
3102
|
+
if (profile == null) {
|
|
3103
|
+
output.write(" The entry has no host.\n");
|
|
3104
|
+
}
|
|
3105
|
+
return profile;
|
|
3106
|
+
} catch (ex) {
|
|
3107
|
+
let detail = String(ex);
|
|
3108
|
+
if (ex instanceof Error) {
|
|
3109
|
+
detail = ex.message;
|
|
3110
|
+
}
|
|
3111
|
+
output.write(` The server would refuse to start with this entry: ${detail}
|
|
3112
|
+
`);
|
|
3113
|
+
return null;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
function describeAuth(profile) {
|
|
3117
|
+
if (profile.privateKeyPath != null) {
|
|
3118
|
+
return `key ${profile.privateKeyPath}`;
|
|
3119
|
+
}
|
|
3120
|
+
if (profile.password != null) {
|
|
3121
|
+
return "password";
|
|
3122
|
+
}
|
|
3123
|
+
return "ssh agent";
|
|
3124
|
+
}
|
|
3125
|
+
async function runConnectionCheck(profile, output) {
|
|
3126
|
+
const endpoint = `${profile.username}@${profile.host}:${profile.port}`;
|
|
3127
|
+
const auth = describeAuth(profile);
|
|
3128
|
+
output.write(`Checking ${profile.name} \u2192 ${endpoint}
|
|
3129
|
+
`);
|
|
3130
|
+
if (profile.proxyCommand != null) {
|
|
3131
|
+
output.write(` via proxy command: ${profile.proxyCommand}
|
|
3132
|
+
`);
|
|
3133
|
+
}
|
|
3134
|
+
if (profile.jump != null) {
|
|
3135
|
+
output.write(` via jump host: ${profile.jump.username}@${profile.jump.host}:${profile.jump.port}
|
|
3136
|
+
`);
|
|
3137
|
+
}
|
|
3138
|
+
output.write("\n");
|
|
3139
|
+
const connection = new SshConnection(profile);
|
|
3140
|
+
const progress = new CheckProgress(output);
|
|
3141
|
+
const startedAt = Date.now();
|
|
3142
|
+
let connected = false;
|
|
3143
|
+
let detail = "";
|
|
3144
|
+
try {
|
|
3145
|
+
progress.start("Preparing SSH connection");
|
|
3146
|
+
await connection.connect((stage) => {
|
|
3147
|
+
if (stage === "transport") {
|
|
3148
|
+
progress.start(profile.jump != null ? "Jump host connection and forwarding" : "Starting proxy connection");
|
|
3149
|
+
} else {
|
|
3150
|
+
progress.start("SSH handshake and authentication");
|
|
3151
|
+
}
|
|
3152
|
+
});
|
|
3153
|
+
connected = true;
|
|
3154
|
+
} catch (ex) {
|
|
3155
|
+
detail = String(ex);
|
|
3156
|
+
if (ex instanceof Error) {
|
|
3157
|
+
detail = ex.message;
|
|
3158
|
+
}
|
|
3159
|
+
} finally {
|
|
3160
|
+
progress.finish(connected);
|
|
3161
|
+
connection.disconnect();
|
|
3162
|
+
}
|
|
3163
|
+
const durationMs = Date.now() - startedAt;
|
|
3164
|
+
if (connected == true) {
|
|
3165
|
+
output.write(` OK ${endpoint} (${auth}, ${durationMs}ms)
|
|
3166
|
+
`);
|
|
3167
|
+
} else {
|
|
3168
|
+
output.write(` FAILED ${endpoint} (${auth})
|
|
3169
|
+
`);
|
|
3170
|
+
output.write(` ${detail}
|
|
3171
|
+
`);
|
|
3172
|
+
}
|
|
3173
|
+
if (connection.hostKeyFingerprint != null) {
|
|
3174
|
+
let pinNote = `not pinned \u2014 set ${SINGLE_HOST_ENV.HOST_KEY} to it to detect a changed key`;
|
|
3175
|
+
if (profile.hostKeyFingerprint != null) {
|
|
3176
|
+
pinNote = "pinned";
|
|
3177
|
+
}
|
|
3178
|
+
output.write(` host key ${connection.hostKeyFingerprint} (${pinNote})
|
|
3179
|
+
`);
|
|
3180
|
+
}
|
|
3181
|
+
if (connection.jumpHostKeyFingerprint != null) {
|
|
3182
|
+
let pinNote = `not pinned \u2014 set ${SINGLE_HOST_ENV.JUMP_HOST_KEY} to it to detect a changed key`;
|
|
3183
|
+
if (profile.jump?.hostKeyFingerprint != null) {
|
|
3184
|
+
pinNote = "pinned";
|
|
3185
|
+
}
|
|
3186
|
+
output.write(` jump host key ${connection.jumpHostKeyFingerprint} (${pinNote})
|
|
3187
|
+
`);
|
|
3188
|
+
}
|
|
3189
|
+
return {
|
|
3190
|
+
connected,
|
|
3191
|
+
hostKeyFingerprint: connection.hostKeyFingerprint,
|
|
3192
|
+
jumpHostKeyFingerprint: connection.jumpHostKeyFingerprint
|
|
3193
|
+
};
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
// src/modules/manage/entry.ts
|
|
3197
|
+
var MANAGED_ENV_NAMES = [
|
|
3198
|
+
SINGLE_HOST_ENV.HOST,
|
|
3199
|
+
SINGLE_HOST_ENV.PORT,
|
|
3200
|
+
SINGLE_HOST_ENV.USER,
|
|
3201
|
+
SINGLE_HOST_ENV.KEY,
|
|
3202
|
+
SINGLE_HOST_ENV.PASSPHRASE,
|
|
3203
|
+
SINGLE_HOST_ENV.PASSWORD,
|
|
3204
|
+
SINGLE_HOST_ENV.DESCRIPTION,
|
|
3205
|
+
SINGLE_HOST_ENV.JUMP_HOST,
|
|
3206
|
+
SINGLE_HOST_ENV.JUMP_PORT,
|
|
3207
|
+
SINGLE_HOST_ENV.JUMP_USER,
|
|
3208
|
+
SINGLE_HOST_ENV.PROXY_COMMAND,
|
|
3209
|
+
SINGLE_HOST_ENV.READONLY,
|
|
3210
|
+
SINGLE_HOST_ENV.ALLOWED_PATHS,
|
|
3211
|
+
SINGLE_HOST_ENV.HOST_KEY,
|
|
3212
|
+
SINGLE_HOST_ENV.JUMP_HOST_KEY
|
|
3213
|
+
];
|
|
3214
|
+
var JUMP_ENV_PREFIX = "SSH_JUMP_";
|
|
3215
|
+
var SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]{1,48}$/;
|
|
3216
|
+
function isOurEntry(entry) {
|
|
3217
|
+
if (entry.args.includes(PACKAGE_NAME) == true) {
|
|
3218
|
+
return true;
|
|
3219
|
+
}
|
|
3220
|
+
const commandBase = entry.command.replace(/\\/g, "/").split("/").pop() ?? "";
|
|
3221
|
+
return commandBase === SERVER_NAME || commandBase.startsWith(`${SERVER_NAME}.`) == true;
|
|
3222
|
+
}
|
|
3223
|
+
function launchCommand() {
|
|
3224
|
+
if (process.platform === "win32") {
|
|
3225
|
+
return { command: "cmd", args: ["/c", "npx", "-y", PACKAGE_NAME] };
|
|
3226
|
+
}
|
|
3227
|
+
return { command: "npx", args: ["-y", PACKAGE_NAME] };
|
|
3228
|
+
}
|
|
3229
|
+
function answersToEntry(answers) {
|
|
3230
|
+
const env = {};
|
|
3231
|
+
env[SINGLE_HOST_ENV.HOST] = answers.host;
|
|
3232
|
+
if (answers.port !== DEFAULT_SSH_PORT) {
|
|
3233
|
+
env[SINGLE_HOST_ENV.PORT] = String(answers.port);
|
|
3234
|
+
}
|
|
3235
|
+
env[SINGLE_HOST_ENV.USER] = answers.username;
|
|
3236
|
+
if (answers.auth === "key" && answers.keyPath != null) {
|
|
3237
|
+
env[SINGLE_HOST_ENV.KEY] = answers.keyPath;
|
|
3238
|
+
if (answers.passphrase != null && answers.passphrase !== "") {
|
|
3239
|
+
env[SINGLE_HOST_ENV.PASSPHRASE] = answers.passphrase;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
if (answers.auth === "password" && answers.password != null) {
|
|
3243
|
+
env[SINGLE_HOST_ENV.PASSWORD] = answers.password;
|
|
3244
|
+
}
|
|
3245
|
+
if (answers.hostKeyFingerprint != null) {
|
|
3246
|
+
env[SINGLE_HOST_ENV.HOST_KEY] = answers.hostKeyFingerprint;
|
|
3247
|
+
}
|
|
3248
|
+
if (answers.description != null && answers.description !== "") {
|
|
3249
|
+
env[SINGLE_HOST_ENV.DESCRIPTION] = answers.description;
|
|
3250
|
+
}
|
|
3251
|
+
if (answers.jumpHost != null) {
|
|
3252
|
+
env[SINGLE_HOST_ENV.JUMP_HOST] = answers.jumpHost;
|
|
3253
|
+
if (answers.jumpPort != null && answers.jumpPort !== DEFAULT_SSH_PORT) {
|
|
3254
|
+
env[SINGLE_HOST_ENV.JUMP_PORT] = String(answers.jumpPort);
|
|
3255
|
+
}
|
|
3256
|
+
if (answers.jumpUser != null && answers.jumpUser !== answers.username) {
|
|
3257
|
+
env[SINGLE_HOST_ENV.JUMP_USER] = answers.jumpUser;
|
|
3258
|
+
}
|
|
3259
|
+
if (answers.jumpHostKeyFingerprint != null) {
|
|
3260
|
+
env[SINGLE_HOST_ENV.JUMP_HOST_KEY] = answers.jumpHostKeyFingerprint;
|
|
3261
|
+
}
|
|
3121
3262
|
}
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
text += character;
|
|
3135
|
-
}
|
|
3136
|
-
if (part.paint != null) {
|
|
3137
|
-
line += part.paint(text);
|
|
3138
|
-
} else {
|
|
3139
|
-
line += text;
|
|
3140
|
-
}
|
|
3141
|
-
if (used >= width) {
|
|
3142
|
-
break;
|
|
3143
|
-
}
|
|
3263
|
+
if (answers.proxyCommand != null && answers.proxyCommand !== "") {
|
|
3264
|
+
env[SINGLE_HOST_ENV.PROXY_COMMAND] = answers.proxyCommand;
|
|
3265
|
+
}
|
|
3266
|
+
if (answers.readonly == true) {
|
|
3267
|
+
env[SINGLE_HOST_ENV.READONLY] = "true";
|
|
3268
|
+
}
|
|
3269
|
+
if (answers.allowedPaths.length > 0) {
|
|
3270
|
+
env[SINGLE_HOST_ENV.ALLOWED_PATHS] = answers.allowedPaths.join(",");
|
|
3271
|
+
}
|
|
3272
|
+
for (const [name, value] of Object.entries(answers.extraEnv)) {
|
|
3273
|
+
if (MANAGED_ENV_NAMES.includes(name) == true) {
|
|
3274
|
+
continue;
|
|
3144
3275
|
}
|
|
3145
|
-
|
|
3276
|
+
if (answers.jumpHost == null && name.startsWith(JUMP_ENV_PREFIX) == true) {
|
|
3277
|
+
continue;
|
|
3278
|
+
}
|
|
3279
|
+
env[name] = value;
|
|
3146
3280
|
}
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3281
|
+
const launch = launchCommand();
|
|
3282
|
+
return {
|
|
3283
|
+
command: launch.command,
|
|
3284
|
+
args: launch.args,
|
|
3285
|
+
env
|
|
3286
|
+
};
|
|
3287
|
+
}
|
|
3288
|
+
function readPort(value) {
|
|
3289
|
+
if (value == null) {
|
|
3290
|
+
return DEFAULT_SSH_PORT;
|
|
3291
|
+
}
|
|
3292
|
+
const parsed = Number(value);
|
|
3293
|
+
if (Number.isInteger(parsed) == false || parsed <= 0) {
|
|
3294
|
+
return DEFAULT_SSH_PORT;
|
|
3295
|
+
}
|
|
3296
|
+
return parsed;
|
|
3297
|
+
}
|
|
3298
|
+
function isTruthy(value) {
|
|
3299
|
+
if (value == null) {
|
|
3300
|
+
return false;
|
|
3301
|
+
}
|
|
3302
|
+
return TRUTHY_ENV_VALUES.includes(value.trim().toLowerCase());
|
|
3303
|
+
}
|
|
3304
|
+
function entryToAnswers(name, entry) {
|
|
3305
|
+
const env = entry.env;
|
|
3306
|
+
const host = env[SINGLE_HOST_ENV.HOST] ?? "";
|
|
3307
|
+
const username = env[SINGLE_HOST_ENV.USER] ?? "";
|
|
3308
|
+
let auth = "agent";
|
|
3309
|
+
if (env[SINGLE_HOST_ENV.KEY] != null) {
|
|
3310
|
+
auth = "key";
|
|
3311
|
+
} else if (env[SINGLE_HOST_ENV.PASSWORD] != null) {
|
|
3312
|
+
auth = "password";
|
|
3313
|
+
}
|
|
3314
|
+
let allowedPaths = [];
|
|
3315
|
+
const allowedPathsValue = env[SINGLE_HOST_ENV.ALLOWED_PATHS];
|
|
3316
|
+
if (allowedPathsValue != null) {
|
|
3317
|
+
allowedPaths = allowedPathsValue.split(",").map((item) => item.trim()).filter((item) => item !== "");
|
|
3318
|
+
}
|
|
3319
|
+
const extraEnv = {};
|
|
3320
|
+
for (const [variable, value] of Object.entries(env)) {
|
|
3321
|
+
if (MANAGED_ENV_NAMES.includes(variable) == false) {
|
|
3322
|
+
extraEnv[variable] = value;
|
|
3151
3323
|
}
|
|
3152
|
-
this.output.write(`${this.style.success(this.symbols.tick)} ${question} ${this.style.dim("\xB7")} ${this.style.accent(answer)}
|
|
3153
|
-
`);
|
|
3154
3324
|
}
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
const buildLines = () => {
|
|
3178
|
-
const width = this.columns() - 1;
|
|
3179
|
-
const lines = [
|
|
3180
|
-
this.fitLine([
|
|
3181
|
-
{ text: this.symbols.question, paint: this.style.accent },
|
|
3182
|
-
{ text: " " },
|
|
3183
|
-
{ text: question, paint: this.style.bold }
|
|
3184
|
-
], width)
|
|
3185
|
-
];
|
|
3186
|
-
for (let index = 0; index < items.length; index += 1) {
|
|
3187
|
-
const item = items[index];
|
|
3188
|
-
if ("value" in item == false) {
|
|
3189
|
-
lines.push(this.fitLine([{ text: ` ${this.symbols.rule.repeat(ruleWidth)}`, paint: this.style.dim }], width));
|
|
3190
|
-
continue;
|
|
3191
|
-
}
|
|
3192
|
-
const isActive = index === active;
|
|
3193
|
-
const parts = [];
|
|
3194
|
-
if (isActive == true) {
|
|
3195
|
-
parts.push({ text: `${this.symbols.pointer} `, paint: this.style.accent });
|
|
3196
|
-
parts.push({ text: padToWidth(item.label, labelWidths[index]), paint: this.style.accent });
|
|
3197
|
-
} else {
|
|
3198
|
-
parts.push({ text: " " });
|
|
3199
|
-
parts.push({ text: padToWidth(item.label, labelWidths[index]) });
|
|
3200
|
-
}
|
|
3201
|
-
if (item.hint != null && item.hint !== "") {
|
|
3202
|
-
parts.push({ text: ` ${item.hint}`, paint: this.style.dim });
|
|
3203
|
-
}
|
|
3204
|
-
lines.push(this.fitLine(parts, width));
|
|
3205
|
-
}
|
|
3206
|
-
lines.push(this.fitLine([{ text: "\u2191\u2193 move \xB7 enter select \xB7 esc back", paint: this.style.dim }], width));
|
|
3207
|
-
return lines;
|
|
3208
|
-
};
|
|
3209
|
-
const move = (step) => {
|
|
3210
|
-
const position = selectableIndexes.indexOf(active);
|
|
3211
|
-
const next = (position + step + selectableIndexes.length) % selectableIndexes.length;
|
|
3212
|
-
active = selectableIndexes[next];
|
|
3213
|
-
};
|
|
3214
|
-
return new Promise((resolve, reject) => {
|
|
3215
|
-
const finish = () => {
|
|
3216
|
-
this.keyHandler = null;
|
|
3217
|
-
ttyInput.setRawMode?.(false);
|
|
3218
|
-
draw([]);
|
|
3219
|
-
this.output.write(`${ESC}[?25h`);
|
|
3220
|
-
};
|
|
3221
|
-
this.keyHandler = (key) => {
|
|
3222
|
-
if (key === KEY_CTRL_C || key === KEY_CTRL_D) {
|
|
3223
|
-
finish();
|
|
3224
|
-
reject(new PromptClosedError());
|
|
3225
|
-
return;
|
|
3226
|
-
}
|
|
3227
|
-
if (KEY_ENTER.includes(key) == true) {
|
|
3228
|
-
const picked = items[active];
|
|
3229
|
-
finish();
|
|
3230
|
-
this.writeAnswered(question, picked.label);
|
|
3231
|
-
resolve(picked.value);
|
|
3232
|
-
return;
|
|
3233
|
-
}
|
|
3234
|
-
if (key === ESC) {
|
|
3235
|
-
finish();
|
|
3236
|
-
resolve(null);
|
|
3237
|
-
return;
|
|
3238
|
-
}
|
|
3239
|
-
if (KEY_UP.includes(key) == true) {
|
|
3240
|
-
move(-1);
|
|
3241
|
-
} else if (KEY_DOWN.includes(key) == true) {
|
|
3242
|
-
move(1);
|
|
3243
|
-
} else if (KEY_HOME.includes(key) == true) {
|
|
3244
|
-
active = selectableIndexes[0];
|
|
3245
|
-
} else if (KEY_END.includes(key) == true) {
|
|
3246
|
-
active = selectableIndexes[selectableIndexes.length - 1];
|
|
3247
|
-
} else if (/^[1-9]$/.test(key) == true && Number(key) <= selectableIndexes.length) {
|
|
3248
|
-
active = selectableIndexes[Number(key) - 1];
|
|
3249
|
-
} else {
|
|
3250
|
-
return;
|
|
3251
|
-
}
|
|
3252
|
-
draw(buildLines());
|
|
3253
|
-
};
|
|
3254
|
-
ttyInput.setRawMode?.(true);
|
|
3255
|
-
this.output.write(`${ESC}[?25l`);
|
|
3256
|
-
draw(buildLines());
|
|
3257
|
-
});
|
|
3325
|
+
const answers = {
|
|
3326
|
+
name,
|
|
3327
|
+
host,
|
|
3328
|
+
port: readPort(env[SINGLE_HOST_ENV.PORT]),
|
|
3329
|
+
username,
|
|
3330
|
+
auth,
|
|
3331
|
+
keyPath: env[SINGLE_HOST_ENV.KEY],
|
|
3332
|
+
passphrase: env[SINGLE_HOST_ENV.PASSPHRASE],
|
|
3333
|
+
password: env[SINGLE_HOST_ENV.PASSWORD],
|
|
3334
|
+
description: env[SINGLE_HOST_ENV.DESCRIPTION],
|
|
3335
|
+
proxyCommand: env[SINGLE_HOST_ENV.PROXY_COMMAND],
|
|
3336
|
+
readonly: isTruthy(env[SINGLE_HOST_ENV.READONLY]),
|
|
3337
|
+
allowedPaths,
|
|
3338
|
+
hostKeyFingerprint: env[SINGLE_HOST_ENV.HOST_KEY],
|
|
3339
|
+
extraEnv
|
|
3340
|
+
};
|
|
3341
|
+
const jumpHost = env[SINGLE_HOST_ENV.JUMP_HOST];
|
|
3342
|
+
if (jumpHost != null) {
|
|
3343
|
+
answers.jumpHost = jumpHost;
|
|
3344
|
+
answers.jumpPort = readPort(env[SINGLE_HOST_ENV.JUMP_PORT]);
|
|
3345
|
+
answers.jumpUser = env[SINGLE_HOST_ENV.JUMP_USER] ?? username;
|
|
3346
|
+
answers.jumpHostKeyFingerprint = env[SINGLE_HOST_ENV.JUMP_HOST_KEY];
|
|
3258
3347
|
}
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3348
|
+
return answers;
|
|
3349
|
+
}
|
|
3350
|
+
function summarizeEntry(entry) {
|
|
3351
|
+
const env = entry.env;
|
|
3352
|
+
const host = env[SINGLE_HOST_ENV.HOST] ?? "?";
|
|
3353
|
+
const port = env[SINGLE_HOST_ENV.PORT] ?? String(DEFAULT_SSH_PORT);
|
|
3354
|
+
const username = env[SINGLE_HOST_ENV.USER] ?? "?";
|
|
3355
|
+
let auth = "agent";
|
|
3356
|
+
if (env[SINGLE_HOST_ENV.KEY] != null) {
|
|
3357
|
+
auth = "key";
|
|
3358
|
+
} else if (env[SINGLE_HOST_ENV.PASSWORD] != null) {
|
|
3359
|
+
auth = "password";
|
|
3360
|
+
}
|
|
3361
|
+
const notes = [];
|
|
3362
|
+
if (env[SINGLE_HOST_ENV.JUMP_HOST] != null) {
|
|
3363
|
+
notes.push(`jump ${env[SINGLE_HOST_ENV.JUMP_HOST]}`);
|
|
3364
|
+
}
|
|
3365
|
+
if (env[SINGLE_HOST_ENV.PROXY_COMMAND] != null) {
|
|
3366
|
+
notes.push("proxy");
|
|
3367
|
+
}
|
|
3368
|
+
if (isTruthy(env[SINGLE_HOST_ENV.READONLY]) == true) {
|
|
3369
|
+
notes.push("readonly");
|
|
3370
|
+
}
|
|
3371
|
+
if (env[SINGLE_HOST_ENV.ALLOWED_PATHS] != null) {
|
|
3372
|
+
notes.push(`paths ${env[SINGLE_HOST_ENV.ALLOWED_PATHS]}`);
|
|
3373
|
+
}
|
|
3374
|
+
if (env[SINGLE_HOST_ENV.HOST_KEY] != null) {
|
|
3375
|
+
notes.push("pinned");
|
|
3376
|
+
}
|
|
3377
|
+
return {
|
|
3378
|
+
endpoint: `${username}@${host}:${port}`,
|
|
3379
|
+
auth,
|
|
3380
|
+
notes: notes.join(", ")
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3262
3383
|
var PREFERRED_KEY_NAMES = ["id_ed25519", "id_ecdsa", "id_rsa"];
|
|
3263
3384
|
var NON_KEY_FILE_NAMES = ["config", "known_hosts", "known_hosts.old", "authorized_keys", "environment"];
|
|
3264
3385
|
var MAX_KEY_FILE_BYTES = 64 * 1024;
|
|
@@ -3614,10 +3735,12 @@ async function testConnection(prompter, output, state) {
|
|
|
3614
3735
|
if (profile == null) {
|
|
3615
3736
|
output.write("\n");
|
|
3616
3737
|
state.lastTest = "failed";
|
|
3738
|
+
await prompter.pause();
|
|
3617
3739
|
return false;
|
|
3618
3740
|
}
|
|
3619
3741
|
const result = await runConnectionCheck(profile, output);
|
|
3620
3742
|
output.write("\n");
|
|
3743
|
+
await prompter.pause();
|
|
3621
3744
|
if (result.connected == true) {
|
|
3622
3745
|
if (result.hostKeyFingerprint != null && state.answers.hostKeyFingerprint == null) {
|
|
3623
3746
|
const pin = await prompter.askYesNo("Pin this host key? (a changed key then aborts the connection)", true);
|
|
@@ -4726,10 +4849,12 @@ async function checkHost(prompter, output, target, name, entry) {
|
|
|
4726
4849
|
const profile = profileFromEnv(output, entry.env);
|
|
4727
4850
|
if (profile == null) {
|
|
4728
4851
|
output.write("\n");
|
|
4852
|
+
await prompter.pause();
|
|
4729
4853
|
return;
|
|
4730
4854
|
}
|
|
4731
4855
|
const result = await runConnectionCheck(profile, output);
|
|
4732
4856
|
output.write("\n");
|
|
4857
|
+
await prompter.pause();
|
|
4733
4858
|
if (result.connected == false) {
|
|
4734
4859
|
return;
|
|
4735
4860
|
}
|
|
@@ -5765,6 +5890,12 @@ async function main() {
|
|
|
5765
5890
|
instance.shutdown();
|
|
5766
5891
|
process.exit(exitCode);
|
|
5767
5892
|
}
|
|
5893
|
+
process.on("uncaughtException", (error) => {
|
|
5894
|
+
_logger.error(error, "uncaught exception; the server keeps running");
|
|
5895
|
+
});
|
|
5896
|
+
process.on("unhandledRejection", (reason) => {
|
|
5897
|
+
_logger.error(reason, "unhandled rejection; the server keeps running");
|
|
5898
|
+
});
|
|
5768
5899
|
let shuttingDown = false;
|
|
5769
5900
|
const shutdown = (signal) => {
|
|
5770
5901
|
if (shuttingDown == true) {
|
|
@@ -5772,6 +5903,10 @@ async function main() {
|
|
|
5772
5903
|
}
|
|
5773
5904
|
shuttingDown = true;
|
|
5774
5905
|
_logger.info("shutting down", { signal });
|
|
5906
|
+
setTimeout(() => {
|
|
5907
|
+
_logger.warn("shutdown did not finish in time; exiting anyway", { deadline_ms: SHUTDOWN_DEADLINE_MS });
|
|
5908
|
+
process.exit(0);
|
|
5909
|
+
}, SHUTDOWN_DEADLINE_MS).unref();
|
|
5775
5910
|
instance.shutdown();
|
|
5776
5911
|
void instance.server.close().finally(() => {
|
|
5777
5912
|
process.exit(0);
|
|
@@ -5791,7 +5926,8 @@ async function main() {
|
|
|
5791
5926
|
_logger.info("mcp server ready on stdio", {
|
|
5792
5927
|
server: SERVER_NAME,
|
|
5793
5928
|
version: SERVER_VERSION,
|
|
5794
|
-
profile: instance.profile?.name ?? null
|
|
5929
|
+
profile: instance.profile?.name ?? null,
|
|
5930
|
+
pid: process.pid
|
|
5795
5931
|
});
|
|
5796
5932
|
}
|
|
5797
5933
|
main().catch((error) => {
|