@akms/mcp-ssh 0.1.6 → 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 +5 -0
- package/lib/cli.cjs +477 -364
- package/lib/cli.cjs.map +1 -1
- package/lib/cli.js +480 -367
- package/lib/cli.js.map +1 -1
- package/lib/index.cjs +6 -2
- 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 +6 -2
- package/lib/index.js.map +1 -1
- package/package.json +2 -1
package/lib/cli.cjs
CHANGED
|
@@ -36,7 +36,7 @@ var CWD_MARKER = "__AKMS_SSH_CWD__";
|
|
|
36
36
|
var MAX_DIRECTORY_ENTRIES = 1e3;
|
|
37
37
|
var SERVER_NAME = "akms-mcp-ssh";
|
|
38
38
|
var PACKAGE_NAME = "@akms/mcp-ssh";
|
|
39
|
-
var SERVER_VERSION = "0.1.
|
|
39
|
+
var SERVER_VERSION = "0.1.7";
|
|
40
40
|
|
|
41
41
|
// src/_defs/command-rules.ts
|
|
42
42
|
var CRITICAL_PATH_PATTERN = new RegExp(
|
|
@@ -1649,7 +1649,7 @@ var SshConnection = class {
|
|
|
1649
1649
|
* @throws {Error} If the connection was closed, authentication fails, the host is
|
|
1650
1650
|
* unreachable, the handshake times out, or the carrier cannot be opened.
|
|
1651
1651
|
*/
|
|
1652
|
-
async connect() {
|
|
1652
|
+
async connect(onProgress) {
|
|
1653
1653
|
if (this.disposed == true) {
|
|
1654
1654
|
throw new Error(`ssh connection for '${this.profile.name}' has been closed; open a new session`);
|
|
1655
1655
|
}
|
|
@@ -1671,6 +1671,9 @@ var SshConnection = class {
|
|
|
1671
1671
|
this.observedFingerprint = fingerprint;
|
|
1672
1672
|
});
|
|
1673
1673
|
this.connecting = (async () => {
|
|
1674
|
+
if (this.profile.jump != null || this.profile.proxyCommand != null) {
|
|
1675
|
+
onProgress?.("transport");
|
|
1676
|
+
}
|
|
1674
1677
|
const transport = await openTransport(this.profile, (fingerprint) => {
|
|
1675
1678
|
this.observedJumpFingerprint = fingerprint;
|
|
1676
1679
|
});
|
|
@@ -1681,6 +1684,7 @@ var SshConnection = class {
|
|
|
1681
1684
|
if (transport != null) {
|
|
1682
1685
|
connectConfig.sock = transport.sock;
|
|
1683
1686
|
}
|
|
1687
|
+
onProgress?.("host");
|
|
1684
1688
|
await new Promise((resolve, reject) => {
|
|
1685
1689
|
const client = new Client();
|
|
1686
1690
|
this.pendingClient = client;
|
|
@@ -2379,384 +2383,107 @@ async function downloadFile(connection, remotePath, localPath) {
|
|
|
2379
2383
|
return localStats.size;
|
|
2380
2384
|
}
|
|
2381
2385
|
|
|
2382
|
-
// src/modules/manage/
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
output.write(" The entry has no host.\n");
|
|
2388
|
-
}
|
|
2389
|
-
return profile;
|
|
2390
|
-
} catch (ex) {
|
|
2391
|
-
let detail = String(ex);
|
|
2392
|
-
if (ex instanceof Error) {
|
|
2393
|
-
detail = ex.message;
|
|
2394
|
-
}
|
|
2395
|
-
output.write(` The server would refuse to start with this entry: ${detail}
|
|
2396
|
-
`);
|
|
2397
|
-
return null;
|
|
2398
|
-
}
|
|
2399
|
-
}
|
|
2400
|
-
function describeAuth(profile) {
|
|
2401
|
-
if (profile.privateKeyPath != null) {
|
|
2402
|
-
return `key ${profile.privateKeyPath}`;
|
|
2403
|
-
}
|
|
2404
|
-
if (profile.password != null) {
|
|
2405
|
-
return "password";
|
|
2406
|
-
}
|
|
2407
|
-
return "ssh agent";
|
|
2408
|
-
}
|
|
2409
|
-
async function runConnectionCheck(profile, output) {
|
|
2410
|
-
const endpoint = `${profile.username}@${profile.host}:${profile.port}`;
|
|
2411
|
-
const auth = describeAuth(profile);
|
|
2412
|
-
output.write(`Checking ${profile.name} \u2192 ${endpoint}
|
|
2413
|
-
`);
|
|
2414
|
-
if (profile.proxyCommand != null) {
|
|
2415
|
-
output.write(` via proxy command: ${profile.proxyCommand}
|
|
2416
|
-
`);
|
|
2386
|
+
// src/modules/manage/prompter.ts
|
|
2387
|
+
var PromptClosedError = class extends Error {
|
|
2388
|
+
constructor() {
|
|
2389
|
+
super("input closed before the prompt was answered");
|
|
2390
|
+
this.name = "PromptClosedError";
|
|
2417
2391
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2392
|
+
};
|
|
2393
|
+
var PromptBackError = class extends Error {
|
|
2394
|
+
constructor() {
|
|
2395
|
+
super("prompt left with esc");
|
|
2396
|
+
this.name = "PromptBackError";
|
|
2421
2397
|
}
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2398
|
+
};
|
|
2399
|
+
var ESC = "\x1B";
|
|
2400
|
+
var KEY_UP = [`${ESC}[A`, `${ESC}OA`];
|
|
2401
|
+
var KEY_DOWN = [`${ESC}[B`, `${ESC}OB`];
|
|
2402
|
+
var KEY_RIGHT = [`${ESC}[C`, `${ESC}OC`];
|
|
2403
|
+
var KEY_LEFT = [`${ESC}[D`, `${ESC}OD`];
|
|
2404
|
+
var KEY_HOME = [`${ESC}[H`, `${ESC}OH`, `${ESC}[1~`, ""];
|
|
2405
|
+
var KEY_END = [`${ESC}[F`, `${ESC}OF`, `${ESC}[4~`, ""];
|
|
2406
|
+
var KEY_DELETE = [`${ESC}[3~`];
|
|
2407
|
+
var KEY_ENTER = ["\r", "\n"];
|
|
2408
|
+
var KEY_BACKSPACE = ["\x7F", "\b"];
|
|
2409
|
+
var KEY_CTRL_C = "";
|
|
2410
|
+
var KEY_CTRL_D = "";
|
|
2411
|
+
var KEY_CLEAR_LINE = "";
|
|
2412
|
+
function splitKeys(chunk) {
|
|
2413
|
+
const keys = [];
|
|
2414
|
+
let index = 0;
|
|
2415
|
+
while (index < chunk.length) {
|
|
2416
|
+
const isSequence = chunk[index] === ESC && (chunk[index + 1] === "[" || chunk[index + 1] === "O");
|
|
2417
|
+
if (isSequence == true) {
|
|
2418
|
+
let end = index + 2;
|
|
2419
|
+
while (end < chunk.length && /[0-9;]/.test(chunk[end]) == true) {
|
|
2420
|
+
end += 1;
|
|
2421
|
+
}
|
|
2422
|
+
keys.push(chunk.slice(index, end + 1));
|
|
2423
|
+
index = end + 1;
|
|
2424
|
+
continue;
|
|
2434
2425
|
}
|
|
2435
|
-
|
|
2436
|
-
|
|
2426
|
+
const codePoint = chunk.codePointAt(index) ?? 0;
|
|
2427
|
+
const character = String.fromCodePoint(codePoint);
|
|
2428
|
+
keys.push(character);
|
|
2429
|
+
index += character.length;
|
|
2437
2430
|
}
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
output.write(` FAILED ${endpoint} (${auth})
|
|
2444
|
-
`);
|
|
2445
|
-
output.write(` ${detail}
|
|
2446
|
-
`);
|
|
2431
|
+
return keys;
|
|
2432
|
+
}
|
|
2433
|
+
function isPrintableKey(key) {
|
|
2434
|
+
if (key.startsWith(ESC) == true) {
|
|
2435
|
+
return false;
|
|
2447
2436
|
}
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
`);
|
|
2437
|
+
const codePoint = key.codePointAt(0) ?? 0;
|
|
2438
|
+
return codePoint >= 32 && codePoint !== 127;
|
|
2439
|
+
}
|
|
2440
|
+
function charWidth(codePoint) {
|
|
2441
|
+
if (codePoint < 4352) {
|
|
2442
|
+
return 1;
|
|
2455
2443
|
}
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
pinNote = "pinned";
|
|
2460
|
-
}
|
|
2461
|
-
output.write(` jump host key ${connection.jumpHostKeyFingerprint} (${pinNote})
|
|
2462
|
-
`);
|
|
2444
|
+
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;
|
|
2445
|
+
if (isWide == true) {
|
|
2446
|
+
return 2;
|
|
2463
2447
|
}
|
|
2464
|
-
return
|
|
2465
|
-
connected,
|
|
2466
|
-
hostKeyFingerprint: connection.hostKeyFingerprint,
|
|
2467
|
-
jumpHostKeyFingerprint: connection.jumpHostKeyFingerprint
|
|
2468
|
-
};
|
|
2448
|
+
return 1;
|
|
2469
2449
|
}
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
SINGLE_HOST_ENV.PORT,
|
|
2475
|
-
SINGLE_HOST_ENV.USER,
|
|
2476
|
-
SINGLE_HOST_ENV.KEY,
|
|
2477
|
-
SINGLE_HOST_ENV.PASSPHRASE,
|
|
2478
|
-
SINGLE_HOST_ENV.PASSWORD,
|
|
2479
|
-
SINGLE_HOST_ENV.DESCRIPTION,
|
|
2480
|
-
SINGLE_HOST_ENV.JUMP_HOST,
|
|
2481
|
-
SINGLE_HOST_ENV.JUMP_PORT,
|
|
2482
|
-
SINGLE_HOST_ENV.JUMP_USER,
|
|
2483
|
-
SINGLE_HOST_ENV.PROXY_COMMAND,
|
|
2484
|
-
SINGLE_HOST_ENV.READONLY,
|
|
2485
|
-
SINGLE_HOST_ENV.ALLOWED_PATHS,
|
|
2486
|
-
SINGLE_HOST_ENV.HOST_KEY,
|
|
2487
|
-
SINGLE_HOST_ENV.JUMP_HOST_KEY
|
|
2488
|
-
];
|
|
2489
|
-
var JUMP_ENV_PREFIX = "SSH_JUMP_";
|
|
2490
|
-
var SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]{1,48}$/;
|
|
2491
|
-
function isOurEntry(entry) {
|
|
2492
|
-
if (entry.args.includes(PACKAGE_NAME) == true) {
|
|
2493
|
-
return true;
|
|
2450
|
+
function displayWidth(text) {
|
|
2451
|
+
let width = 0;
|
|
2452
|
+
for (const character of text) {
|
|
2453
|
+
width += charWidth(character.codePointAt(0) ?? 0);
|
|
2494
2454
|
}
|
|
2495
|
-
|
|
2496
|
-
return commandBase === SERVER_NAME || commandBase.startsWith(`${SERVER_NAME}.`) == true;
|
|
2455
|
+
return width;
|
|
2497
2456
|
}
|
|
2498
|
-
function
|
|
2499
|
-
|
|
2500
|
-
|
|
2457
|
+
function padToWidth(text, width) {
|
|
2458
|
+
const missing = width - displayWidth(text);
|
|
2459
|
+
if (missing <= 0) {
|
|
2460
|
+
return text;
|
|
2501
2461
|
}
|
|
2502
|
-
return {
|
|
2462
|
+
return `${text}${" ".repeat(missing)}`;
|
|
2503
2463
|
}
|
|
2504
|
-
function
|
|
2505
|
-
const
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
env[SINGLE_HOST_ENV.USER] = answers.username;
|
|
2511
|
-
if (answers.auth === "key" && answers.keyPath != null) {
|
|
2512
|
-
env[SINGLE_HOST_ENV.KEY] = answers.keyPath;
|
|
2513
|
-
if (answers.passphrase != null && answers.passphrase !== "") {
|
|
2514
|
-
env[SINGLE_HOST_ENV.PASSPHRASE] = answers.passphrase;
|
|
2515
|
-
}
|
|
2516
|
-
}
|
|
2517
|
-
if (answers.auth === "password" && answers.password != null) {
|
|
2518
|
-
env[SINGLE_HOST_ENV.PASSWORD] = answers.password;
|
|
2519
|
-
}
|
|
2520
|
-
if (answers.hostKeyFingerprint != null) {
|
|
2521
|
-
env[SINGLE_HOST_ENV.HOST_KEY] = answers.hostKeyFingerprint;
|
|
2522
|
-
}
|
|
2523
|
-
if (answers.description != null && answers.description !== "") {
|
|
2524
|
-
env[SINGLE_HOST_ENV.DESCRIPTION] = answers.description;
|
|
2525
|
-
}
|
|
2526
|
-
if (answers.jumpHost != null) {
|
|
2527
|
-
env[SINGLE_HOST_ENV.JUMP_HOST] = answers.jumpHost;
|
|
2528
|
-
if (answers.jumpPort != null && answers.jumpPort !== DEFAULT_SSH_PORT) {
|
|
2529
|
-
env[SINGLE_HOST_ENV.JUMP_PORT] = String(answers.jumpPort);
|
|
2530
|
-
}
|
|
2531
|
-
if (answers.jumpUser != null && answers.jumpUser !== answers.username) {
|
|
2532
|
-
env[SINGLE_HOST_ENV.JUMP_USER] = answers.jumpUser;
|
|
2533
|
-
}
|
|
2534
|
-
if (answers.jumpHostKeyFingerprint != null) {
|
|
2535
|
-
env[SINGLE_HOST_ENV.JUMP_HOST_KEY] = answers.jumpHostKeyFingerprint;
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
if (answers.proxyCommand != null && answers.proxyCommand !== "") {
|
|
2539
|
-
env[SINGLE_HOST_ENV.PROXY_COMMAND] = answers.proxyCommand;
|
|
2540
|
-
}
|
|
2541
|
-
if (answers.readonly == true) {
|
|
2542
|
-
env[SINGLE_HOST_ENV.READONLY] = "true";
|
|
2543
|
-
}
|
|
2544
|
-
if (answers.allowedPaths.length > 0) {
|
|
2545
|
-
env[SINGLE_HOST_ENV.ALLOWED_PATHS] = answers.allowedPaths.join(",");
|
|
2546
|
-
}
|
|
2547
|
-
for (const [name, value] of Object.entries(answers.extraEnv)) {
|
|
2548
|
-
if (MANAGED_ENV_NAMES.includes(name) == true) {
|
|
2464
|
+
function groupLabelWidths(items) {
|
|
2465
|
+
const widths = items.map(() => 0);
|
|
2466
|
+
let groupStart = 0;
|
|
2467
|
+
for (let index = 0; index <= items.length; index += 1) {
|
|
2468
|
+
const atBoundary = index === items.length || "value" in items[index] == false;
|
|
2469
|
+
if (atBoundary == false) {
|
|
2549
2470
|
continue;
|
|
2550
2471
|
}
|
|
2551
|
-
|
|
2552
|
-
|
|
2472
|
+
let groupWidth = 0;
|
|
2473
|
+
for (let member = groupStart; member < index; member += 1) {
|
|
2474
|
+
const item = items[member];
|
|
2475
|
+
groupWidth = Math.max(groupWidth, displayWidth(item.label));
|
|
2553
2476
|
}
|
|
2554
|
-
|
|
2477
|
+
for (let member = groupStart; member < index; member += 1) {
|
|
2478
|
+
widths[member] = groupWidth;
|
|
2479
|
+
}
|
|
2480
|
+
groupStart = index + 1;
|
|
2555
2481
|
}
|
|
2556
|
-
|
|
2557
|
-
return {
|
|
2558
|
-
command: launch.command,
|
|
2559
|
-
args: launch.args,
|
|
2560
|
-
env
|
|
2561
|
-
};
|
|
2482
|
+
return widths;
|
|
2562
2483
|
}
|
|
2563
|
-
function
|
|
2564
|
-
if (
|
|
2565
|
-
return
|
|
2566
|
-
}
|
|
2567
|
-
const parsed = Number(value);
|
|
2568
|
-
if (Number.isInteger(parsed) == false || parsed <= 0) {
|
|
2569
|
-
return DEFAULT_SSH_PORT;
|
|
2570
|
-
}
|
|
2571
|
-
return parsed;
|
|
2572
|
-
}
|
|
2573
|
-
function isTruthy(value) {
|
|
2574
|
-
if (value == null) {
|
|
2575
|
-
return false;
|
|
2576
|
-
}
|
|
2577
|
-
return TRUTHY_ENV_VALUES.includes(value.trim().toLowerCase());
|
|
2578
|
-
}
|
|
2579
|
-
function entryToAnswers(name, entry) {
|
|
2580
|
-
const env = entry.env;
|
|
2581
|
-
const host = env[SINGLE_HOST_ENV.HOST] ?? "";
|
|
2582
|
-
const username = env[SINGLE_HOST_ENV.USER] ?? "";
|
|
2583
|
-
let auth = "agent";
|
|
2584
|
-
if (env[SINGLE_HOST_ENV.KEY] != null) {
|
|
2585
|
-
auth = "key";
|
|
2586
|
-
} else if (env[SINGLE_HOST_ENV.PASSWORD] != null) {
|
|
2587
|
-
auth = "password";
|
|
2588
|
-
}
|
|
2589
|
-
let allowedPaths = [];
|
|
2590
|
-
const allowedPathsValue = env[SINGLE_HOST_ENV.ALLOWED_PATHS];
|
|
2591
|
-
if (allowedPathsValue != null) {
|
|
2592
|
-
allowedPaths = allowedPathsValue.split(",").map((item) => item.trim()).filter((item) => item !== "");
|
|
2593
|
-
}
|
|
2594
|
-
const extraEnv = {};
|
|
2595
|
-
for (const [variable, value] of Object.entries(env)) {
|
|
2596
|
-
if (MANAGED_ENV_NAMES.includes(variable) == false) {
|
|
2597
|
-
extraEnv[variable] = value;
|
|
2598
|
-
}
|
|
2599
|
-
}
|
|
2600
|
-
const answers = {
|
|
2601
|
-
name,
|
|
2602
|
-
host,
|
|
2603
|
-
port: readPort(env[SINGLE_HOST_ENV.PORT]),
|
|
2604
|
-
username,
|
|
2605
|
-
auth,
|
|
2606
|
-
keyPath: env[SINGLE_HOST_ENV.KEY],
|
|
2607
|
-
passphrase: env[SINGLE_HOST_ENV.PASSPHRASE],
|
|
2608
|
-
password: env[SINGLE_HOST_ENV.PASSWORD],
|
|
2609
|
-
description: env[SINGLE_HOST_ENV.DESCRIPTION],
|
|
2610
|
-
proxyCommand: env[SINGLE_HOST_ENV.PROXY_COMMAND],
|
|
2611
|
-
readonly: isTruthy(env[SINGLE_HOST_ENV.READONLY]),
|
|
2612
|
-
allowedPaths,
|
|
2613
|
-
hostKeyFingerprint: env[SINGLE_HOST_ENV.HOST_KEY],
|
|
2614
|
-
extraEnv
|
|
2615
|
-
};
|
|
2616
|
-
const jumpHost = env[SINGLE_HOST_ENV.JUMP_HOST];
|
|
2617
|
-
if (jumpHost != null) {
|
|
2618
|
-
answers.jumpHost = jumpHost;
|
|
2619
|
-
answers.jumpPort = readPort(env[SINGLE_HOST_ENV.JUMP_PORT]);
|
|
2620
|
-
answers.jumpUser = env[SINGLE_HOST_ENV.JUMP_USER] ?? username;
|
|
2621
|
-
answers.jumpHostKeyFingerprint = env[SINGLE_HOST_ENV.JUMP_HOST_KEY];
|
|
2622
|
-
}
|
|
2623
|
-
return answers;
|
|
2624
|
-
}
|
|
2625
|
-
function summarizeEntry(entry) {
|
|
2626
|
-
const env = entry.env;
|
|
2627
|
-
const host = env[SINGLE_HOST_ENV.HOST] ?? "?";
|
|
2628
|
-
const port = env[SINGLE_HOST_ENV.PORT] ?? String(DEFAULT_SSH_PORT);
|
|
2629
|
-
const username = env[SINGLE_HOST_ENV.USER] ?? "?";
|
|
2630
|
-
let auth = "agent";
|
|
2631
|
-
if (env[SINGLE_HOST_ENV.KEY] != null) {
|
|
2632
|
-
auth = "key";
|
|
2633
|
-
} else if (env[SINGLE_HOST_ENV.PASSWORD] != null) {
|
|
2634
|
-
auth = "password";
|
|
2635
|
-
}
|
|
2636
|
-
const notes = [];
|
|
2637
|
-
if (env[SINGLE_HOST_ENV.JUMP_HOST] != null) {
|
|
2638
|
-
notes.push(`jump ${env[SINGLE_HOST_ENV.JUMP_HOST]}`);
|
|
2639
|
-
}
|
|
2640
|
-
if (env[SINGLE_HOST_ENV.PROXY_COMMAND] != null) {
|
|
2641
|
-
notes.push("proxy");
|
|
2642
|
-
}
|
|
2643
|
-
if (isTruthy(env[SINGLE_HOST_ENV.READONLY]) == true) {
|
|
2644
|
-
notes.push("readonly");
|
|
2645
|
-
}
|
|
2646
|
-
if (env[SINGLE_HOST_ENV.ALLOWED_PATHS] != null) {
|
|
2647
|
-
notes.push(`paths ${env[SINGLE_HOST_ENV.ALLOWED_PATHS]}`);
|
|
2648
|
-
}
|
|
2649
|
-
if (env[SINGLE_HOST_ENV.HOST_KEY] != null) {
|
|
2650
|
-
notes.push("pinned");
|
|
2651
|
-
}
|
|
2652
|
-
return {
|
|
2653
|
-
endpoint: `${username}@${host}:${port}`,
|
|
2654
|
-
auth,
|
|
2655
|
-
notes: notes.join(", ")
|
|
2656
|
-
};
|
|
2657
|
-
}
|
|
2658
|
-
|
|
2659
|
-
// src/modules/manage/prompter.ts
|
|
2660
|
-
var PromptClosedError = class extends Error {
|
|
2661
|
-
constructor() {
|
|
2662
|
-
super("input closed before the prompt was answered");
|
|
2663
|
-
this.name = "PromptClosedError";
|
|
2664
|
-
}
|
|
2665
|
-
};
|
|
2666
|
-
var PromptBackError = class extends Error {
|
|
2667
|
-
constructor() {
|
|
2668
|
-
super("prompt left with esc");
|
|
2669
|
-
this.name = "PromptBackError";
|
|
2670
|
-
}
|
|
2671
|
-
};
|
|
2672
|
-
var ESC = "\x1B";
|
|
2673
|
-
var KEY_UP = [`${ESC}[A`, `${ESC}OA`];
|
|
2674
|
-
var KEY_DOWN = [`${ESC}[B`, `${ESC}OB`];
|
|
2675
|
-
var KEY_RIGHT = [`${ESC}[C`, `${ESC}OC`];
|
|
2676
|
-
var KEY_LEFT = [`${ESC}[D`, `${ESC}OD`];
|
|
2677
|
-
var KEY_HOME = [`${ESC}[H`, `${ESC}OH`, `${ESC}[1~`, ""];
|
|
2678
|
-
var KEY_END = [`${ESC}[F`, `${ESC}OF`, `${ESC}[4~`, ""];
|
|
2679
|
-
var KEY_DELETE = [`${ESC}[3~`];
|
|
2680
|
-
var KEY_ENTER = ["\r", "\n"];
|
|
2681
|
-
var KEY_BACKSPACE = ["\x7F", "\b"];
|
|
2682
|
-
var KEY_CTRL_C = "";
|
|
2683
|
-
var KEY_CTRL_D = "";
|
|
2684
|
-
var KEY_CLEAR_LINE = "";
|
|
2685
|
-
function splitKeys(chunk) {
|
|
2686
|
-
const keys = [];
|
|
2687
|
-
let index = 0;
|
|
2688
|
-
while (index < chunk.length) {
|
|
2689
|
-
const isSequence = chunk[index] === ESC && (chunk[index + 1] === "[" || chunk[index + 1] === "O");
|
|
2690
|
-
if (isSequence == true) {
|
|
2691
|
-
let end = index + 2;
|
|
2692
|
-
while (end < chunk.length && /[0-9;]/.test(chunk[end]) == true) {
|
|
2693
|
-
end += 1;
|
|
2694
|
-
}
|
|
2695
|
-
keys.push(chunk.slice(index, end + 1));
|
|
2696
|
-
index = end + 1;
|
|
2697
|
-
continue;
|
|
2698
|
-
}
|
|
2699
|
-
const codePoint = chunk.codePointAt(index) ?? 0;
|
|
2700
|
-
const character = String.fromCodePoint(codePoint);
|
|
2701
|
-
keys.push(character);
|
|
2702
|
-
index += character.length;
|
|
2703
|
-
}
|
|
2704
|
-
return keys;
|
|
2705
|
-
}
|
|
2706
|
-
function isPrintableKey(key) {
|
|
2707
|
-
if (key.startsWith(ESC) == true) {
|
|
2708
|
-
return false;
|
|
2709
|
-
}
|
|
2710
|
-
const codePoint = key.codePointAt(0) ?? 0;
|
|
2711
|
-
return codePoint >= 32 && codePoint !== 127;
|
|
2712
|
-
}
|
|
2713
|
-
function charWidth(codePoint) {
|
|
2714
|
-
if (codePoint < 4352) {
|
|
2715
|
-
return 1;
|
|
2716
|
-
}
|
|
2717
|
-
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;
|
|
2718
|
-
if (isWide == true) {
|
|
2719
|
-
return 2;
|
|
2720
|
-
}
|
|
2721
|
-
return 1;
|
|
2722
|
-
}
|
|
2723
|
-
function displayWidth(text) {
|
|
2724
|
-
let width = 0;
|
|
2725
|
-
for (const character of text) {
|
|
2726
|
-
width += charWidth(character.codePointAt(0) ?? 0);
|
|
2727
|
-
}
|
|
2728
|
-
return width;
|
|
2729
|
-
}
|
|
2730
|
-
function padToWidth(text, width) {
|
|
2731
|
-
const missing = width - displayWidth(text);
|
|
2732
|
-
if (missing <= 0) {
|
|
2733
|
-
return text;
|
|
2734
|
-
}
|
|
2735
|
-
return `${text}${" ".repeat(missing)}`;
|
|
2736
|
-
}
|
|
2737
|
-
function groupLabelWidths(items) {
|
|
2738
|
-
const widths = items.map(() => 0);
|
|
2739
|
-
let groupStart = 0;
|
|
2740
|
-
for (let index = 0; index <= items.length; index += 1) {
|
|
2741
|
-
const atBoundary = index === items.length || "value" in items[index] == false;
|
|
2742
|
-
if (atBoundary == false) {
|
|
2743
|
-
continue;
|
|
2744
|
-
}
|
|
2745
|
-
let groupWidth = 0;
|
|
2746
|
-
for (let member = groupStart; member < index; member += 1) {
|
|
2747
|
-
const item = items[member];
|
|
2748
|
-
groupWidth = Math.max(groupWidth, displayWidth(item.label));
|
|
2749
|
-
}
|
|
2750
|
-
for (let member = groupStart; member < index; member += 1) {
|
|
2751
|
-
widths[member] = groupWidth;
|
|
2752
|
-
}
|
|
2753
|
-
groupStart = index + 1;
|
|
2754
|
-
}
|
|
2755
|
-
return widths;
|
|
2756
|
-
}
|
|
2757
|
-
function supportsUnicode() {
|
|
2758
|
-
if (process.platform !== "win32") {
|
|
2759
|
-
return true;
|
|
2484
|
+
function supportsUnicode() {
|
|
2485
|
+
if (process.platform !== "win32") {
|
|
2486
|
+
return true;
|
|
2760
2487
|
}
|
|
2761
2488
|
return process.env.WT_SESSION != null || process.env.TERM_PROGRAM === "vscode" || process.env.TERMINAL_EMULATOR === "JetBrains-JediTerm" || process.env.ConEmuTask != null;
|
|
2762
2489
|
}
|
|
@@ -2832,6 +2559,17 @@ var Prompter = class {
|
|
|
2832
2559
|
get paint() {
|
|
2833
2560
|
return this.style;
|
|
2834
2561
|
}
|
|
2562
|
+
/** Keeps a report visible until Enter on a TTY; Esc goes back. Pipes need no extra answer. */
|
|
2563
|
+
async pause() {
|
|
2564
|
+
if (this.interactive == false) {
|
|
2565
|
+
return;
|
|
2566
|
+
}
|
|
2567
|
+
if (this.inputEnded == true) {
|
|
2568
|
+
throw new PromptClosedError();
|
|
2569
|
+
}
|
|
2570
|
+
const prompt = this.fitLine([{ text: "Press Enter to continue", paint: this.style.dim }], this.columns() - 1);
|
|
2571
|
+
await this.readText(prompt, false);
|
|
2572
|
+
}
|
|
2835
2573
|
/**
|
|
2836
2574
|
* Free-text prompt. Enter accepts `defaultValue` when one is shown.
|
|
2837
2575
|
*
|
|
@@ -3279,7 +3017,378 @@ var Prompter = class {
|
|
|
3279
3017
|
}
|
|
3280
3018
|
};
|
|
3281
3019
|
|
|
3282
|
-
// src/modules/manage/
|
|
3020
|
+
// src/modules/manage/check-progress.ts
|
|
3021
|
+
var ESC2 = "\x1B";
|
|
3022
|
+
var CheckProgress = class {
|
|
3023
|
+
constructor(output) {
|
|
3024
|
+
this.output = output;
|
|
3025
|
+
this.unicode = supportsUnicode();
|
|
3026
|
+
this.frames = this.unicode ? ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] : ["-", "\\", "|", "/"];
|
|
3027
|
+
this.timer = null;
|
|
3028
|
+
this.label = null;
|
|
3029
|
+
this.startedAt = 0;
|
|
3030
|
+
this.frame = 0;
|
|
3031
|
+
this.enabled = output.isTTY === true && process.env.TERM !== "dumb";
|
|
3032
|
+
this.color = process.env.NO_COLOR == null;
|
|
3033
|
+
}
|
|
3034
|
+
/** Advancing means the preceding phase actually completed successfully. */
|
|
3035
|
+
start(label) {
|
|
3036
|
+
if (this.enabled == false) {
|
|
3037
|
+
return;
|
|
3038
|
+
}
|
|
3039
|
+
this.finish(true);
|
|
3040
|
+
this.label = label;
|
|
3041
|
+
this.startedAt = Date.now();
|
|
3042
|
+
this.frame = 0;
|
|
3043
|
+
this.draw(this.frames[0], 36);
|
|
3044
|
+
this.timer = setInterval(() => {
|
|
3045
|
+
this.frame = (this.frame + 1) % this.frames.length;
|
|
3046
|
+
this.draw(this.frames[this.frame], 36);
|
|
3047
|
+
}, 80);
|
|
3048
|
+
this.timer.unref();
|
|
3049
|
+
}
|
|
3050
|
+
/** Leaves the completed phase and its duration visible. */
|
|
3051
|
+
finish(ok) {
|
|
3052
|
+
if (this.label == null) {
|
|
3053
|
+
return;
|
|
3054
|
+
}
|
|
3055
|
+
this.clearTimer();
|
|
3056
|
+
const symbol = ok ? this.unicode ? "\u2714" : "OK" : this.unicode ? "\u2716" : "X";
|
|
3057
|
+
this.draw(symbol, ok ? 32 : 31);
|
|
3058
|
+
this.output.write("\n");
|
|
3059
|
+
this.label = null;
|
|
3060
|
+
}
|
|
3061
|
+
/** Clears an unfinished phase without recording a result. */
|
|
3062
|
+
stop() {
|
|
3063
|
+
this.clearTimer();
|
|
3064
|
+
if (this.label != null) {
|
|
3065
|
+
this.output.write(`\r${ESC2}[2K`);
|
|
3066
|
+
this.label = null;
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
clearTimer() {
|
|
3070
|
+
if (this.timer != null) {
|
|
3071
|
+
clearInterval(this.timer);
|
|
3072
|
+
this.timer = null;
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
draw(symbol, color) {
|
|
3076
|
+
const columns = this.output.columns;
|
|
3077
|
+
const width = Math.max(0, (columns != null && columns > 0 ? columns : 80) - 1);
|
|
3078
|
+
const elapsed = ` ${((Date.now() - this.startedAt) / 1e3).toFixed(1)}s`;
|
|
3079
|
+
const prefix = ` ${symbol} `;
|
|
3080
|
+
const labelWidth = Math.min(48, Math.max(0, width - displayWidth(prefix) - displayWidth(elapsed)));
|
|
3081
|
+
const label = this.fit(this.label ?? "", labelWidth);
|
|
3082
|
+
const line = this.fit(`${prefix}${label}${" ".repeat(labelWidth - displayWidth(label))}${elapsed}`, width);
|
|
3083
|
+
const painted = this.color ? `${ESC2}[${color}m${line}${ESC2}[39m` : line;
|
|
3084
|
+
this.output.write(`\r${ESC2}[2K${painted}`);
|
|
3085
|
+
}
|
|
3086
|
+
fit(text, width) {
|
|
3087
|
+
if (displayWidth(text) <= width) {
|
|
3088
|
+
return text;
|
|
3089
|
+
}
|
|
3090
|
+
if (width < 1) {
|
|
3091
|
+
return "";
|
|
3092
|
+
}
|
|
3093
|
+
let result = "";
|
|
3094
|
+
let used = 0;
|
|
3095
|
+
for (const character of text) {
|
|
3096
|
+
const size = displayWidth(character);
|
|
3097
|
+
if (used + size > width - 1) {
|
|
3098
|
+
break;
|
|
3099
|
+
}
|
|
3100
|
+
result += character;
|
|
3101
|
+
used += size;
|
|
3102
|
+
}
|
|
3103
|
+
return `${result}${this.unicode ? "\u2026" : "~"}`;
|
|
3104
|
+
}
|
|
3105
|
+
};
|
|
3106
|
+
|
|
3107
|
+
// src/modules/manage/connection-check.ts
|
|
3108
|
+
function profileFromEnv(output, env) {
|
|
3109
|
+
try {
|
|
3110
|
+
const profile = loadHostProfile(env);
|
|
3111
|
+
if (profile == null) {
|
|
3112
|
+
output.write(" The entry has no host.\n");
|
|
3113
|
+
}
|
|
3114
|
+
return profile;
|
|
3115
|
+
} catch (ex) {
|
|
3116
|
+
let detail = String(ex);
|
|
3117
|
+
if (ex instanceof Error) {
|
|
3118
|
+
detail = ex.message;
|
|
3119
|
+
}
|
|
3120
|
+
output.write(` The server would refuse to start with this entry: ${detail}
|
|
3121
|
+
`);
|
|
3122
|
+
return null;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
function describeAuth(profile) {
|
|
3126
|
+
if (profile.privateKeyPath != null) {
|
|
3127
|
+
return `key ${profile.privateKeyPath}`;
|
|
3128
|
+
}
|
|
3129
|
+
if (profile.password != null) {
|
|
3130
|
+
return "password";
|
|
3131
|
+
}
|
|
3132
|
+
return "ssh agent";
|
|
3133
|
+
}
|
|
3134
|
+
async function runConnectionCheck(profile, output) {
|
|
3135
|
+
const endpoint = `${profile.username}@${profile.host}:${profile.port}`;
|
|
3136
|
+
const auth = describeAuth(profile);
|
|
3137
|
+
output.write(`Checking ${profile.name} \u2192 ${endpoint}
|
|
3138
|
+
`);
|
|
3139
|
+
if (profile.proxyCommand != null) {
|
|
3140
|
+
output.write(` via proxy command: ${profile.proxyCommand}
|
|
3141
|
+
`);
|
|
3142
|
+
}
|
|
3143
|
+
if (profile.jump != null) {
|
|
3144
|
+
output.write(` via jump host: ${profile.jump.username}@${profile.jump.host}:${profile.jump.port}
|
|
3145
|
+
`);
|
|
3146
|
+
}
|
|
3147
|
+
output.write("\n");
|
|
3148
|
+
const connection = new SshConnection(profile);
|
|
3149
|
+
const progress = new CheckProgress(output);
|
|
3150
|
+
const startedAt = Date.now();
|
|
3151
|
+
let connected = false;
|
|
3152
|
+
let detail = "";
|
|
3153
|
+
try {
|
|
3154
|
+
progress.start("Preparing SSH connection");
|
|
3155
|
+
await connection.connect((stage) => {
|
|
3156
|
+
if (stage === "transport") {
|
|
3157
|
+
progress.start(profile.jump != null ? "Jump host connection and forwarding" : "Starting proxy connection");
|
|
3158
|
+
} else {
|
|
3159
|
+
progress.start("SSH handshake and authentication");
|
|
3160
|
+
}
|
|
3161
|
+
});
|
|
3162
|
+
connected = true;
|
|
3163
|
+
} catch (ex) {
|
|
3164
|
+
detail = String(ex);
|
|
3165
|
+
if (ex instanceof Error) {
|
|
3166
|
+
detail = ex.message;
|
|
3167
|
+
}
|
|
3168
|
+
} finally {
|
|
3169
|
+
progress.finish(connected);
|
|
3170
|
+
connection.disconnect();
|
|
3171
|
+
}
|
|
3172
|
+
const durationMs = Date.now() - startedAt;
|
|
3173
|
+
if (connected == true) {
|
|
3174
|
+
output.write(` OK ${endpoint} (${auth}, ${durationMs}ms)
|
|
3175
|
+
`);
|
|
3176
|
+
} else {
|
|
3177
|
+
output.write(` FAILED ${endpoint} (${auth})
|
|
3178
|
+
`);
|
|
3179
|
+
output.write(` ${detail}
|
|
3180
|
+
`);
|
|
3181
|
+
}
|
|
3182
|
+
if (connection.hostKeyFingerprint != null) {
|
|
3183
|
+
let pinNote = `not pinned \u2014 set ${SINGLE_HOST_ENV.HOST_KEY} to it to detect a changed key`;
|
|
3184
|
+
if (profile.hostKeyFingerprint != null) {
|
|
3185
|
+
pinNote = "pinned";
|
|
3186
|
+
}
|
|
3187
|
+
output.write(` host key ${connection.hostKeyFingerprint} (${pinNote})
|
|
3188
|
+
`);
|
|
3189
|
+
}
|
|
3190
|
+
if (connection.jumpHostKeyFingerprint != null) {
|
|
3191
|
+
let pinNote = `not pinned \u2014 set ${SINGLE_HOST_ENV.JUMP_HOST_KEY} to it to detect a changed key`;
|
|
3192
|
+
if (profile.jump?.hostKeyFingerprint != null) {
|
|
3193
|
+
pinNote = "pinned";
|
|
3194
|
+
}
|
|
3195
|
+
output.write(` jump host key ${connection.jumpHostKeyFingerprint} (${pinNote})
|
|
3196
|
+
`);
|
|
3197
|
+
}
|
|
3198
|
+
return {
|
|
3199
|
+
connected,
|
|
3200
|
+
hostKeyFingerprint: connection.hostKeyFingerprint,
|
|
3201
|
+
jumpHostKeyFingerprint: connection.jumpHostKeyFingerprint
|
|
3202
|
+
};
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
// src/modules/manage/entry.ts
|
|
3206
|
+
var MANAGED_ENV_NAMES = [
|
|
3207
|
+
SINGLE_HOST_ENV.HOST,
|
|
3208
|
+
SINGLE_HOST_ENV.PORT,
|
|
3209
|
+
SINGLE_HOST_ENV.USER,
|
|
3210
|
+
SINGLE_HOST_ENV.KEY,
|
|
3211
|
+
SINGLE_HOST_ENV.PASSPHRASE,
|
|
3212
|
+
SINGLE_HOST_ENV.PASSWORD,
|
|
3213
|
+
SINGLE_HOST_ENV.DESCRIPTION,
|
|
3214
|
+
SINGLE_HOST_ENV.JUMP_HOST,
|
|
3215
|
+
SINGLE_HOST_ENV.JUMP_PORT,
|
|
3216
|
+
SINGLE_HOST_ENV.JUMP_USER,
|
|
3217
|
+
SINGLE_HOST_ENV.PROXY_COMMAND,
|
|
3218
|
+
SINGLE_HOST_ENV.READONLY,
|
|
3219
|
+
SINGLE_HOST_ENV.ALLOWED_PATHS,
|
|
3220
|
+
SINGLE_HOST_ENV.HOST_KEY,
|
|
3221
|
+
SINGLE_HOST_ENV.JUMP_HOST_KEY
|
|
3222
|
+
];
|
|
3223
|
+
var JUMP_ENV_PREFIX = "SSH_JUMP_";
|
|
3224
|
+
var SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]{1,48}$/;
|
|
3225
|
+
function isOurEntry(entry) {
|
|
3226
|
+
if (entry.args.includes(PACKAGE_NAME) == true) {
|
|
3227
|
+
return true;
|
|
3228
|
+
}
|
|
3229
|
+
const commandBase = entry.command.replace(/\\/g, "/").split("/").pop() ?? "";
|
|
3230
|
+
return commandBase === SERVER_NAME || commandBase.startsWith(`${SERVER_NAME}.`) == true;
|
|
3231
|
+
}
|
|
3232
|
+
function launchCommand() {
|
|
3233
|
+
if (process.platform === "win32") {
|
|
3234
|
+
return { command: "cmd", args: ["/c", "npx", "-y", PACKAGE_NAME] };
|
|
3235
|
+
}
|
|
3236
|
+
return { command: "npx", args: ["-y", PACKAGE_NAME] };
|
|
3237
|
+
}
|
|
3238
|
+
function answersToEntry(answers) {
|
|
3239
|
+
const env = {};
|
|
3240
|
+
env[SINGLE_HOST_ENV.HOST] = answers.host;
|
|
3241
|
+
if (answers.port !== DEFAULT_SSH_PORT) {
|
|
3242
|
+
env[SINGLE_HOST_ENV.PORT] = String(answers.port);
|
|
3243
|
+
}
|
|
3244
|
+
env[SINGLE_HOST_ENV.USER] = answers.username;
|
|
3245
|
+
if (answers.auth === "key" && answers.keyPath != null) {
|
|
3246
|
+
env[SINGLE_HOST_ENV.KEY] = answers.keyPath;
|
|
3247
|
+
if (answers.passphrase != null && answers.passphrase !== "") {
|
|
3248
|
+
env[SINGLE_HOST_ENV.PASSPHRASE] = answers.passphrase;
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
if (answers.auth === "password" && answers.password != null) {
|
|
3252
|
+
env[SINGLE_HOST_ENV.PASSWORD] = answers.password;
|
|
3253
|
+
}
|
|
3254
|
+
if (answers.hostKeyFingerprint != null) {
|
|
3255
|
+
env[SINGLE_HOST_ENV.HOST_KEY] = answers.hostKeyFingerprint;
|
|
3256
|
+
}
|
|
3257
|
+
if (answers.description != null && answers.description !== "") {
|
|
3258
|
+
env[SINGLE_HOST_ENV.DESCRIPTION] = answers.description;
|
|
3259
|
+
}
|
|
3260
|
+
if (answers.jumpHost != null) {
|
|
3261
|
+
env[SINGLE_HOST_ENV.JUMP_HOST] = answers.jumpHost;
|
|
3262
|
+
if (answers.jumpPort != null && answers.jumpPort !== DEFAULT_SSH_PORT) {
|
|
3263
|
+
env[SINGLE_HOST_ENV.JUMP_PORT] = String(answers.jumpPort);
|
|
3264
|
+
}
|
|
3265
|
+
if (answers.jumpUser != null && answers.jumpUser !== answers.username) {
|
|
3266
|
+
env[SINGLE_HOST_ENV.JUMP_USER] = answers.jumpUser;
|
|
3267
|
+
}
|
|
3268
|
+
if (answers.jumpHostKeyFingerprint != null) {
|
|
3269
|
+
env[SINGLE_HOST_ENV.JUMP_HOST_KEY] = answers.jumpHostKeyFingerprint;
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
if (answers.proxyCommand != null && answers.proxyCommand !== "") {
|
|
3273
|
+
env[SINGLE_HOST_ENV.PROXY_COMMAND] = answers.proxyCommand;
|
|
3274
|
+
}
|
|
3275
|
+
if (answers.readonly == true) {
|
|
3276
|
+
env[SINGLE_HOST_ENV.READONLY] = "true";
|
|
3277
|
+
}
|
|
3278
|
+
if (answers.allowedPaths.length > 0) {
|
|
3279
|
+
env[SINGLE_HOST_ENV.ALLOWED_PATHS] = answers.allowedPaths.join(",");
|
|
3280
|
+
}
|
|
3281
|
+
for (const [name, value] of Object.entries(answers.extraEnv)) {
|
|
3282
|
+
if (MANAGED_ENV_NAMES.includes(name) == true) {
|
|
3283
|
+
continue;
|
|
3284
|
+
}
|
|
3285
|
+
if (answers.jumpHost == null && name.startsWith(JUMP_ENV_PREFIX) == true) {
|
|
3286
|
+
continue;
|
|
3287
|
+
}
|
|
3288
|
+
env[name] = value;
|
|
3289
|
+
}
|
|
3290
|
+
const launch = launchCommand();
|
|
3291
|
+
return {
|
|
3292
|
+
command: launch.command,
|
|
3293
|
+
args: launch.args,
|
|
3294
|
+
env
|
|
3295
|
+
};
|
|
3296
|
+
}
|
|
3297
|
+
function readPort(value) {
|
|
3298
|
+
if (value == null) {
|
|
3299
|
+
return DEFAULT_SSH_PORT;
|
|
3300
|
+
}
|
|
3301
|
+
const parsed = Number(value);
|
|
3302
|
+
if (Number.isInteger(parsed) == false || parsed <= 0) {
|
|
3303
|
+
return DEFAULT_SSH_PORT;
|
|
3304
|
+
}
|
|
3305
|
+
return parsed;
|
|
3306
|
+
}
|
|
3307
|
+
function isTruthy(value) {
|
|
3308
|
+
if (value == null) {
|
|
3309
|
+
return false;
|
|
3310
|
+
}
|
|
3311
|
+
return TRUTHY_ENV_VALUES.includes(value.trim().toLowerCase());
|
|
3312
|
+
}
|
|
3313
|
+
function entryToAnswers(name, entry) {
|
|
3314
|
+
const env = entry.env;
|
|
3315
|
+
const host = env[SINGLE_HOST_ENV.HOST] ?? "";
|
|
3316
|
+
const username = env[SINGLE_HOST_ENV.USER] ?? "";
|
|
3317
|
+
let auth = "agent";
|
|
3318
|
+
if (env[SINGLE_HOST_ENV.KEY] != null) {
|
|
3319
|
+
auth = "key";
|
|
3320
|
+
} else if (env[SINGLE_HOST_ENV.PASSWORD] != null) {
|
|
3321
|
+
auth = "password";
|
|
3322
|
+
}
|
|
3323
|
+
let allowedPaths = [];
|
|
3324
|
+
const allowedPathsValue = env[SINGLE_HOST_ENV.ALLOWED_PATHS];
|
|
3325
|
+
if (allowedPathsValue != null) {
|
|
3326
|
+
allowedPaths = allowedPathsValue.split(",").map((item) => item.trim()).filter((item) => item !== "");
|
|
3327
|
+
}
|
|
3328
|
+
const extraEnv = {};
|
|
3329
|
+
for (const [variable, value] of Object.entries(env)) {
|
|
3330
|
+
if (MANAGED_ENV_NAMES.includes(variable) == false) {
|
|
3331
|
+
extraEnv[variable] = value;
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
const answers = {
|
|
3335
|
+
name,
|
|
3336
|
+
host,
|
|
3337
|
+
port: readPort(env[SINGLE_HOST_ENV.PORT]),
|
|
3338
|
+
username,
|
|
3339
|
+
auth,
|
|
3340
|
+
keyPath: env[SINGLE_HOST_ENV.KEY],
|
|
3341
|
+
passphrase: env[SINGLE_HOST_ENV.PASSPHRASE],
|
|
3342
|
+
password: env[SINGLE_HOST_ENV.PASSWORD],
|
|
3343
|
+
description: env[SINGLE_HOST_ENV.DESCRIPTION],
|
|
3344
|
+
proxyCommand: env[SINGLE_HOST_ENV.PROXY_COMMAND],
|
|
3345
|
+
readonly: isTruthy(env[SINGLE_HOST_ENV.READONLY]),
|
|
3346
|
+
allowedPaths,
|
|
3347
|
+
hostKeyFingerprint: env[SINGLE_HOST_ENV.HOST_KEY],
|
|
3348
|
+
extraEnv
|
|
3349
|
+
};
|
|
3350
|
+
const jumpHost = env[SINGLE_HOST_ENV.JUMP_HOST];
|
|
3351
|
+
if (jumpHost != null) {
|
|
3352
|
+
answers.jumpHost = jumpHost;
|
|
3353
|
+
answers.jumpPort = readPort(env[SINGLE_HOST_ENV.JUMP_PORT]);
|
|
3354
|
+
answers.jumpUser = env[SINGLE_HOST_ENV.JUMP_USER] ?? username;
|
|
3355
|
+
answers.jumpHostKeyFingerprint = env[SINGLE_HOST_ENV.JUMP_HOST_KEY];
|
|
3356
|
+
}
|
|
3357
|
+
return answers;
|
|
3358
|
+
}
|
|
3359
|
+
function summarizeEntry(entry) {
|
|
3360
|
+
const env = entry.env;
|
|
3361
|
+
const host = env[SINGLE_HOST_ENV.HOST] ?? "?";
|
|
3362
|
+
const port = env[SINGLE_HOST_ENV.PORT] ?? String(DEFAULT_SSH_PORT);
|
|
3363
|
+
const username = env[SINGLE_HOST_ENV.USER] ?? "?";
|
|
3364
|
+
let auth = "agent";
|
|
3365
|
+
if (env[SINGLE_HOST_ENV.KEY] != null) {
|
|
3366
|
+
auth = "key";
|
|
3367
|
+
} else if (env[SINGLE_HOST_ENV.PASSWORD] != null) {
|
|
3368
|
+
auth = "password";
|
|
3369
|
+
}
|
|
3370
|
+
const notes = [];
|
|
3371
|
+
if (env[SINGLE_HOST_ENV.JUMP_HOST] != null) {
|
|
3372
|
+
notes.push(`jump ${env[SINGLE_HOST_ENV.JUMP_HOST]}`);
|
|
3373
|
+
}
|
|
3374
|
+
if (env[SINGLE_HOST_ENV.PROXY_COMMAND] != null) {
|
|
3375
|
+
notes.push("proxy");
|
|
3376
|
+
}
|
|
3377
|
+
if (isTruthy(env[SINGLE_HOST_ENV.READONLY]) == true) {
|
|
3378
|
+
notes.push("readonly");
|
|
3379
|
+
}
|
|
3380
|
+
if (env[SINGLE_HOST_ENV.ALLOWED_PATHS] != null) {
|
|
3381
|
+
notes.push(`paths ${env[SINGLE_HOST_ENV.ALLOWED_PATHS]}`);
|
|
3382
|
+
}
|
|
3383
|
+
if (env[SINGLE_HOST_ENV.HOST_KEY] != null) {
|
|
3384
|
+
notes.push("pinned");
|
|
3385
|
+
}
|
|
3386
|
+
return {
|
|
3387
|
+
endpoint: `${username}@${host}:${port}`,
|
|
3388
|
+
auth,
|
|
3389
|
+
notes: notes.join(", ")
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3283
3392
|
var PREFERRED_KEY_NAMES = ["id_ed25519", "id_ecdsa", "id_rsa"];
|
|
3284
3393
|
var NON_KEY_FILE_NAMES = ["config", "known_hosts", "known_hosts.old", "authorized_keys", "environment"];
|
|
3285
3394
|
var MAX_KEY_FILE_BYTES = 64 * 1024;
|
|
@@ -3635,10 +3744,12 @@ async function testConnection(prompter, output, state) {
|
|
|
3635
3744
|
if (profile == null) {
|
|
3636
3745
|
output.write("\n");
|
|
3637
3746
|
state.lastTest = "failed";
|
|
3747
|
+
await prompter.pause();
|
|
3638
3748
|
return false;
|
|
3639
3749
|
}
|
|
3640
3750
|
const result = await runConnectionCheck(profile, output);
|
|
3641
3751
|
output.write("\n");
|
|
3752
|
+
await prompter.pause();
|
|
3642
3753
|
if (result.connected == true) {
|
|
3643
3754
|
if (result.hostKeyFingerprint != null && state.answers.hostKeyFingerprint == null) {
|
|
3644
3755
|
const pin = await prompter.askYesNo("Pin this host key? (a changed key then aborts the connection)", true);
|
|
@@ -4747,10 +4858,12 @@ async function checkHost(prompter, output, target, name, entry) {
|
|
|
4747
4858
|
const profile = profileFromEnv(output, entry.env);
|
|
4748
4859
|
if (profile == null) {
|
|
4749
4860
|
output.write("\n");
|
|
4861
|
+
await prompter.pause();
|
|
4750
4862
|
return;
|
|
4751
4863
|
}
|
|
4752
4864
|
const result = await runConnectionCheck(profile, output);
|
|
4753
4865
|
output.write("\n");
|
|
4866
|
+
await prompter.pause();
|
|
4754
4867
|
if (result.connected == false) {
|
|
4755
4868
|
return;
|
|
4756
4869
|
}
|