@usex/mikrotik-mcp 3.35.0 → 3.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -903,6 +903,29 @@ class MikroTikSSHClient {
903
903
  });
904
904
  });
905
905
  }
906
+ downloadFile(remotePath) {
907
+ if (!this.client) {
908
+ return Promise.reject(new Error("Not connected to MikroTik device"));
909
+ }
910
+ const openSftp = this.client.sftp.bind(this.client);
911
+ return new Promise((resolve2, reject) => {
912
+ openSftp((err, sftp) => {
913
+ if (err) {
914
+ reject(new Error(`SFTP subsystem unavailable: ${err.message}`));
915
+ return;
916
+ }
917
+ sftp.readFile(remotePath, (rerr, data) => {
918
+ try {
919
+ sftp.end();
920
+ } catch {}
921
+ if (rerr)
922
+ reject(new Error(`SFTP read failed: ${rerr.message}`));
923
+ else
924
+ resolve2(data);
925
+ });
926
+ });
927
+ });
928
+ }
906
929
  shell(opts = {}) {
907
930
  if (!this.client) {
908
931
  return Promise.reject(new Error("Not connected to MikroTik device"));
@@ -1048,16 +1071,7 @@ class SafeModeManager {
1048
1071
  if (dc.mac) {
1049
1072
  return "Error: Safe Mode is not supported for a MAC-Telnet device " + `('${this.deviceName}' is reached by MAC ${dc.mac}). ` + "Connect over SSH (configure host/credentials) to use Safe Mode.";
1050
1073
  }
1051
- const ssh = new MikroTikSSHClient({
1052
- host: dc.host,
1053
- username: dc.username,
1054
- password: dc.password,
1055
- keyFilename: dc.keyFilename,
1056
- privateKey: dc.privateKey,
1057
- keyPassphrase: dc.keyPassphrase,
1058
- port: dc.port,
1059
- timeoutMs: dc.timeoutMs
1060
- });
1074
+ const ssh = new MikroTikSSHClient({ ...sshOptionsOf(dc), jump: resolveJump(dc) });
1061
1075
  if (!await ssh.connect()) {
1062
1076
  return "Error: Failed to connect to MikroTik device for safe mode session.";
1063
1077
  }
@@ -1251,16 +1265,7 @@ async function uploadFileToDevice(deviceName, remotePath, data) {
1251
1265
  if (isMacTelnetDevice(dc)) {
1252
1266
  throw new Error(`Cannot transfer a file to '${name}': it is reached over Layer-2 MAC-Telnet, which has no file ` + "transfer. Configure SSH (host + credentials) for this device, or have the router pull the file " + "itself with /tool fetch from a URL it can reach.");
1253
1267
  }
1254
- const ssh = new MikroTikSSHClient({
1255
- host: dc.host,
1256
- username: dc.username,
1257
- password: dc.password,
1258
- keyFilename: dc.keyFilename,
1259
- privateKey: dc.privateKey,
1260
- keyPassphrase: dc.keyPassphrase,
1261
- port: dc.port,
1262
- timeoutMs: dc.timeoutMs
1263
- });
1268
+ const ssh = new MikroTikSSHClient({ ...sshOptionsOf(dc), jump: resolveJump(dc) });
1264
1269
  if (!await ssh.connect()) {
1265
1270
  throw new Error(connectErrorMessage(name, dc, ssh.lastError));
1266
1271
  }
@@ -1270,6 +1275,22 @@ async function uploadFileToDevice(deviceName, remotePath, data) {
1270
1275
  ssh.disconnect();
1271
1276
  }
1272
1277
  }
1278
+ async function downloadFileFromDevice(deviceName, remotePath) {
1279
+ const name = resolveDeviceName(deviceName);
1280
+ const dc = getDevice(deviceName);
1281
+ if (isMacTelnetDevice(dc)) {
1282
+ throw new Error(`Cannot download a file from '${name}': it is reached over Layer-2 MAC-Telnet, which has no ` + "file transfer. Configure SSH (host + credentials) for this device, or use a text export " + "(stdout mode) instead of a binary backup.");
1283
+ }
1284
+ const ssh = new MikroTikSSHClient({ ...sshOptionsOf(dc), jump: resolveJump(dc) });
1285
+ if (!await ssh.connect()) {
1286
+ throw new Error(connectErrorMessage(name, dc, ssh.lastError));
1287
+ }
1288
+ try {
1289
+ return await ssh.downloadFile(remotePath);
1290
+ } finally {
1291
+ ssh.disconnect();
1292
+ }
1293
+ }
1273
1294
 
1274
1295
  // src/core/registry.ts
1275
1296
  import { z as z2 } from "zod";
@@ -1483,6 +1504,67 @@ function parseCertExpiry(detail, nowMs) {
1483
1504
  }
1484
1505
  return out;
1485
1506
  }
1507
+ function parseSize(s) {
1508
+ if (!s)
1509
+ return;
1510
+ const m = s.trim().match(/^([\d.]+)\s*([KMGT]i?B|B)?$/i);
1511
+ if (!m)
1512
+ return;
1513
+ const val = Number.parseFloat(m[1]);
1514
+ if (!Number.isFinite(val))
1515
+ return;
1516
+ const mult = {
1517
+ B: 1,
1518
+ KIB: 1024,
1519
+ MIB: 1024 ** 2,
1520
+ GIB: 1024 ** 3,
1521
+ TIB: 1024 ** 4,
1522
+ KB: 1000,
1523
+ MB: 1e6,
1524
+ GB: 1e9,
1525
+ TB: 1000000000000
1526
+ };
1527
+ return val * (mult[(m[2] ?? "B").toUpperCase()] ?? 1);
1528
+ }
1529
+ function parsePercent(s) {
1530
+ if (!s)
1531
+ return;
1532
+ const m = s.trim().match(/^([\d.]+)\s*%?$/);
1533
+ if (!m)
1534
+ return;
1535
+ const v = Number.parseFloat(m[1]);
1536
+ return Number.isFinite(v) ? v : undefined;
1537
+ }
1538
+ function usedPct(total, free) {
1539
+ if (total == null || free == null || total <= 0)
1540
+ return;
1541
+ return Math.max(0, Math.min(100, (total - free) / total * 100));
1542
+ }
1543
+ function parseSystemResource(text) {
1544
+ const r = parseKeyValues(text);
1545
+ const totalMemory = parseSize(r["total-memory"]);
1546
+ const freeMemory = parseSize(r["free-memory"]);
1547
+ const totalHdd = parseSize(r["total-hdd-space"]);
1548
+ const freeHdd = parseSize(r["free-hdd-space"]);
1549
+ const cpuLoad = parsePercent(r["cpu-load"]);
1550
+ const cpuCount = Number.parseInt(r["cpu-count"] ?? "", 10);
1551
+ const out = {
1552
+ version: r.version || undefined,
1553
+ boardName: r["board-name"] || undefined,
1554
+ architecture: r["architecture-name"] || undefined,
1555
+ cpuCount: Number.isFinite(cpuCount) ? cpuCount : undefined,
1556
+ cpuLoad,
1557
+ freeMemory,
1558
+ totalMemory,
1559
+ memUsedPct: usedPct(totalMemory, freeMemory),
1560
+ freeHdd,
1561
+ totalHdd,
1562
+ hddUsedPct: usedPct(totalHdd, freeHdd),
1563
+ uptime: r.uptime || undefined
1564
+ };
1565
+ const gotMetric = out.cpuLoad != null || out.totalMemory != null || out.totalHdd != null || out.version != null || out.uptime != null;
1566
+ return gotMetric ? out : null;
1567
+ }
1486
1568
  function parseFlagLegend(text) {
1487
1569
  const out = {};
1488
1570
  const line = text.split(`
@@ -2692,6 +2774,158 @@ ${rows.slice(0, 40).map((r) => ` \u2022 ${r.name ?? "?"} (${r.type ?? "?"})${r.
2692
2774
  // src/tools/backup.ts
2693
2775
  import { z as z5 } from "zod";
2694
2776
 
2777
+ // src/backups/disk-check.ts
2778
+ var DISK_THRESHOLD_PCT = 90;
2779
+ async function checkDiskSpace(ctx) {
2780
+ try {
2781
+ const raw = await executeMikrotikCommand("/system resource print", ctx);
2782
+ const sys = parseSystemResource(raw);
2783
+ if (!sys || sys.hddUsedPct == null) {
2784
+ return { low: false };
2785
+ }
2786
+ return {
2787
+ low: sys.hddUsedPct >= DISK_THRESHOLD_PCT,
2788
+ usedPct: sys.hddUsedPct,
2789
+ freeBytes: sys.freeHdd,
2790
+ totalBytes: sys.totalHdd
2791
+ };
2792
+ } catch {
2793
+ return { low: false };
2794
+ }
2795
+ }
2796
+
2797
+ // src/backups/vault.ts
2798
+ import {
2799
+ existsSync as existsSync2,
2800
+ mkdirSync,
2801
+ readFileSync as readFileSync4,
2802
+ readdirSync,
2803
+ renameSync,
2804
+ rmSync,
2805
+ statSync,
2806
+ writeFileSync
2807
+ } from "fs";
2808
+ import { basename, join as join4 } from "path";
2809
+ function backupDir() {
2810
+ if (process.env.MIKROTIK_BACKUP_DIR)
2811
+ return process.env.MIKROTIK_BACKUP_DIR;
2812
+ try {
2813
+ const dir = getConfig().backupDir;
2814
+ if (dir)
2815
+ return dir;
2816
+ } catch {}
2817
+ return DEFAULT_BACKUP_DIR;
2818
+ }
2819
+ function safeName(name) {
2820
+ const base = basename(name);
2821
+ if (base !== name || base === "." || base === ".." || !/^[A-Za-z0-9._-]+$/.test(base)) {
2822
+ throw new Error(`invalid backup name: ${name}`);
2823
+ }
2824
+ return base;
2825
+ }
2826
+ function listBackups() {
2827
+ const dir = backupDir();
2828
+ if (!existsSync2(dir))
2829
+ return [];
2830
+ return readdirSync(dir).filter((f) => f.endsWith(".rsc") || f.endsWith(".backup")).map((f) => {
2831
+ const st = statSync(join4(dir, f));
2832
+ const us = f.indexOf("_");
2833
+ return {
2834
+ name: f,
2835
+ bytes: st.size,
2836
+ modified: st.mtimeMs,
2837
+ device: us > 0 ? f.slice(0, us) : undefined,
2838
+ type: f.endsWith(".backup") ? "backup" : "rsc"
2839
+ };
2840
+ }).sort((a, b) => b.modified - a.modified);
2841
+ }
2842
+ function readBackup(name) {
2843
+ return readFileSync4(join4(backupDir(), safeName(name)), "utf8");
2844
+ }
2845
+ function writeBackup(name, content) {
2846
+ const dir = backupDir();
2847
+ mkdirSync(dir, { recursive: true });
2848
+ let final = safeName(name);
2849
+ if (existsSync2(join4(dir, final))) {
2850
+ const dot = final.lastIndexOf(".");
2851
+ const stem = dot > 0 ? final.slice(0, dot) : final;
2852
+ const ext = dot > 0 ? final.slice(dot) : "";
2853
+ let n = 2;
2854
+ while (existsSync2(join4(dir, `${stem}_${n}${ext}`)))
2855
+ n++;
2856
+ final = `${stem}_${n}${ext}`;
2857
+ }
2858
+ writeFileSync(join4(dir, final), content, "utf8");
2859
+ return final;
2860
+ }
2861
+ function writeBinaryBackup(name, data) {
2862
+ const dir = backupDir();
2863
+ mkdirSync(dir, { recursive: true });
2864
+ let final = safeName(name);
2865
+ if (existsSync2(join4(dir, final))) {
2866
+ const dot = final.lastIndexOf(".");
2867
+ const stem = dot > 0 ? final.slice(0, dot) : final;
2868
+ const ext = dot > 0 ? final.slice(dot) : "";
2869
+ let n = 2;
2870
+ while (existsSync2(join4(dir, `${stem}_${n}${ext}`)))
2871
+ n++;
2872
+ final = `${stem}_${n}${ext}`;
2873
+ }
2874
+ writeFileSync(join4(dir, final), data);
2875
+ return final;
2876
+ }
2877
+ function deleteBackup(name) {
2878
+ const p = join4(backupDir(), safeName(name));
2879
+ if (!existsSync2(p))
2880
+ return false;
2881
+ rmSync(p);
2882
+ return true;
2883
+ }
2884
+ function renameBackup(oldName, newName) {
2885
+ const dir = backupDir();
2886
+ const from = join4(dir, safeName(oldName));
2887
+ let to = safeName(newName);
2888
+ if (!to.endsWith(".rsc"))
2889
+ to += ".rsc";
2890
+ const toPath = join4(dir, to);
2891
+ if (!existsSync2(from))
2892
+ throw new Error(`backup not found: ${oldName}`);
2893
+ if (existsSync2(toPath))
2894
+ throw new Error(`a backup named '${to}' already exists`);
2895
+ renameSync(from, toPath);
2896
+ return to;
2897
+ }
2898
+ function exportToCommands(text) {
2899
+ const lines = [];
2900
+ let buf = "";
2901
+ for (const raw of text.split(`
2902
+ `)) {
2903
+ const line = raw.replace(/\r$/, "");
2904
+ buf = buf ? `${buf} ${line.trim()}` : line;
2905
+ if (buf.trimEnd().endsWith("\\")) {
2906
+ buf = buf.trimEnd().slice(0, -1).trimEnd();
2907
+ continue;
2908
+ }
2909
+ lines.push(buf);
2910
+ buf = "";
2911
+ }
2912
+ if (buf)
2913
+ lines.push(buf);
2914
+ const cmds = [];
2915
+ let section = "";
2916
+ for (const line of lines) {
2917
+ const t = line.trim();
2918
+ if (!t || t.startsWith("#"))
2919
+ continue;
2920
+ if (t.startsWith("/")) {
2921
+ section = t;
2922
+ continue;
2923
+ }
2924
+ cmds.push(section ? `${section} ${t}` : t);
2925
+ }
2926
+ return cmds;
2927
+ }
2928
+
2695
2929
  // src/core/datestamp.ts
2696
2930
  var MONTHS2 = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
2697
2931
  function pad(n) {
@@ -2764,6 +2998,12 @@ async function deviceDateStamp(ctx) {
2764
2998
  return hostStamp(new Date);
2765
2999
  }
2766
3000
 
3001
+ // src/core/slug.ts
3002
+ function deviceSlug(name) {
3003
+ const s = (name ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
3004
+ return s || "device";
3005
+ }
3006
+
2767
3007
  // src/tools/backup.ts
2768
3008
  var backupTools = [
2769
3009
  defineTool({
@@ -2780,6 +3020,7 @@ var backupTools = [
2780
3020
  async handler(a, ctx) {
2781
3021
  const name = a.name || `backup_${await deviceDateStamp(ctx)}`;
2782
3022
  ctx.info(`Creating backup: name=${name}`);
3023
+ const disk = await checkDiskSpace(ctx);
2783
3024
  const cmd = new Cmd("/system backup save").set("name", name);
2784
3025
  if (a.dont_encrypt)
2785
3026
  cmd.raw("dont-encrypt=yes");
@@ -2788,13 +3029,35 @@ var backupTools = [
2788
3029
  if (!a.include_password)
2789
3030
  cmd.raw("password-file=no");
2790
3031
  const result = await executeMikrotikCommand(cmd.build(), ctx);
2791
- if (result.includes("saved") || result.trim() === "") {
2792
- const fileDetails = await executeMikrotikCommand(`/file print detail where name=${name}.backup`, ctx);
2793
- return fileDetails ? `Backup created successfully:
3032
+ if (!(result.includes("saved") || result.trim() === "")) {
3033
+ if (disk.low) {
3034
+ return `Failed to create backup (device disk is ${disk.usedPct?.toFixed(0)}% used): ${result}
2794
3035
 
2795
- ${fileDetails}` : `Backup '${name}.backup' created successfully.`;
3036
+ ` + "Tip: use create_local_backup or create_export instead \u2014 text exports can be captured " + "directly to the local vault without using any device disk space.";
3037
+ }
3038
+ return `Failed to create backup: ${result}`;
3039
+ }
3040
+ if (disk.low) {
3041
+ const dc = getDevice(ctx.device);
3042
+ if (isMacTelnetDevice(dc)) {
3043
+ return `Backup '${name}.backup' created on device, but disk is ${disk.usedPct?.toFixed(0)}% used. ` + "This is a MAC-Telnet device (no SFTP), so the file cannot be downloaded automatically. " + "Free disk space manually or use create_local_backup for a text export that bypasses device storage.";
3044
+ }
3045
+ const deviceFile = `${name}.backup`;
3046
+ try {
3047
+ ctx.info(`Low disk (${disk.usedPct?.toFixed(0)}%) \u2014 downloading backup to local vault`);
3048
+ const data = await downloadFileFromDevice(ctx.device, deviceFile);
3049
+ const device = resolveDeviceName(ctx.device);
3050
+ const vaultName = writeBinaryBackup(`${deviceSlug(device)}_${deviceFile}`, data);
3051
+ await executeMikrotikCommand(`/file remove ${deviceFile}`, ctx);
3052
+ return `[LOW DISK \u2014 ${disk.usedPct?.toFixed(0)}% used] Backup '${deviceFile}' created, downloaded to ` + `local vault as '${vaultName}' (${data.length} bytes) at ${backupDir()}, and removed from ` + "the device to free disk space.";
3053
+ } catch (e) {
3054
+ return `Backup '${name}.backup' created on device, but the automatic download-and-cleanup ` + `failed: ${e instanceof Error ? e.message : String(e)}. The file is still on the device.`;
3055
+ }
2796
3056
  }
2797
- return `Failed to create backup: ${result}`;
3057
+ const fileDetails = await executeMikrotikCommand(`/file print detail where name=${name}.backup`, ctx);
3058
+ return fileDetails ? `Backup created successfully:
3059
+
3060
+ ${fileDetails}` : `Backup '${name}.backup' created successfully.`;
2798
3061
  }
2799
3062
  }),
2800
3063
  defineTool({
@@ -2843,6 +3106,21 @@ ${result}`;
2843
3106
  async handler(a, ctx) {
2844
3107
  const name = a.name || `export_${await deviceDateStamp(ctx)}`;
2845
3108
  ctx.info(`Creating export: name=${name}, format=${a.file_format}`);
3109
+ const disk = await checkDiskSpace(ctx);
3110
+ if (disk.low) {
3111
+ ctx.info(`Low disk (${disk.usedPct?.toFixed(0)}%) \u2014 redirecting export to local vault`);
3112
+ const cmd2 = new Cmd("/export");
3113
+ cmd2.raw(a.verbose ? "verbose" : null);
3114
+ cmd2.raw(a.compact ? "compact" : null);
3115
+ cmd2.raw(!a.hide_sensitive ? "show-sensitive" : null);
3116
+ const body = await executeMikrotikCommand(cmd2.build(), ctx);
3117
+ if (isEmpty(body) || looksLikeError(body)) {
3118
+ return `Failed to create export: ${body}`;
3119
+ }
3120
+ const device = resolveDeviceName(ctx.device);
3121
+ const vaultName = writeBackup(`${deviceSlug(device)}_${name}.rsc`, body);
3122
+ return `[LOW DISK \u2014 ${disk.usedPct?.toFixed(0)}% used] Export saved to LOCAL VAULT as '${vaultName}' ` + `(${Buffer.byteLength(body)} bytes) at ${backupDir()} \u2014 no file was written to the device.`;
3123
+ }
2846
3124
  const extension = a.file_format === "json" || a.file_format === "xml" ? a.file_format : "rsc";
2847
3125
  const fullName = `${name}.${extension}`;
2848
3126
  const cmd = new Cmd("/export");
@@ -2881,6 +3159,20 @@ ${fileDetails}` : `Export '${fullName}' created successfully.`;
2881
3159
  name = `export_${cleanSection}_${await deviceDateStamp(ctx)}`;
2882
3160
  }
2883
3161
  ctx.info(`Exporting section: section=${a.section}, name=${name}`);
3162
+ const disk = await checkDiskSpace(ctx);
3163
+ if (disk.low) {
3164
+ ctx.info(`Low disk (${disk.usedPct?.toFixed(0)}%) \u2014 redirecting section export to local vault`);
3165
+ const cmd2 = new Cmd(`/${a.section} export`);
3166
+ cmd2.raw(!a.hide_sensitive ? "show-sensitive" : null);
3167
+ cmd2.raw(a.compact ? "compact" : null);
3168
+ const body = await executeMikrotikCommand(cmd2.build(), ctx);
3169
+ if (isEmpty(body) || looksLikeError(body)) {
3170
+ return `Failed to export section: ${body}`;
3171
+ }
3172
+ const device = resolveDeviceName(ctx.device);
3173
+ const vaultName = writeBackup(`${deviceSlug(device)}_${name}.rsc`, body);
3174
+ return `[LOW DISK \u2014 ${disk.usedPct?.toFixed(0)}% used] Section export saved to LOCAL VAULT as ` + `'${vaultName}' (${Buffer.byteLength(body)} bytes) at ${backupDir()} \u2014 no file was written ` + "to the device.";
3175
+ }
2884
3176
  const cmd = new Cmd(`/${a.section} export`).set("file", name);
2885
3177
  cmd.raw(!a.hide_sensitive ? "show-sensitive" : null);
2886
3178
  cmd.raw(a.compact ? "compact" : null);
@@ -3031,127 +3323,6 @@ ${result}`;
3031
3323
  // src/tools/local-backup.ts
3032
3324
  import { z as z6 } from "zod";
3033
3325
 
3034
- // src/core/slug.ts
3035
- function deviceSlug(name) {
3036
- const s = (name ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
3037
- return s || "device";
3038
- }
3039
-
3040
- // src/backups/vault.ts
3041
- import {
3042
- existsSync as existsSync2,
3043
- mkdirSync,
3044
- readFileSync as readFileSync4,
3045
- readdirSync,
3046
- renameSync,
3047
- rmSync,
3048
- statSync,
3049
- writeFileSync
3050
- } from "fs";
3051
- import { basename, join as join4 } from "path";
3052
- function backupDir() {
3053
- if (process.env.MIKROTIK_BACKUP_DIR)
3054
- return process.env.MIKROTIK_BACKUP_DIR;
3055
- try {
3056
- const dir = getConfig().backupDir;
3057
- if (dir)
3058
- return dir;
3059
- } catch {}
3060
- return DEFAULT_BACKUP_DIR;
3061
- }
3062
- function safeName(name) {
3063
- const base = basename(name);
3064
- if (base !== name || base === "." || base === ".." || !/^[A-Za-z0-9._-]+$/.test(base)) {
3065
- throw new Error(`invalid backup name: ${name}`);
3066
- }
3067
- return base;
3068
- }
3069
- function listBackups() {
3070
- const dir = backupDir();
3071
- if (!existsSync2(dir))
3072
- return [];
3073
- return readdirSync(dir).filter((f) => f.endsWith(".rsc")).map((f) => {
3074
- const st = statSync(join4(dir, f));
3075
- const us = f.indexOf("_");
3076
- return {
3077
- name: f,
3078
- bytes: st.size,
3079
- modified: st.mtimeMs,
3080
- device: us > 0 ? f.slice(0, us) : undefined
3081
- };
3082
- }).sort((a, b) => b.modified - a.modified);
3083
- }
3084
- function readBackup(name) {
3085
- return readFileSync4(join4(backupDir(), safeName(name)), "utf8");
3086
- }
3087
- function writeBackup(name, content) {
3088
- const dir = backupDir();
3089
- mkdirSync(dir, { recursive: true });
3090
- let final = safeName(name);
3091
- if (existsSync2(join4(dir, final))) {
3092
- const dot = final.lastIndexOf(".");
3093
- const stem = dot > 0 ? final.slice(0, dot) : final;
3094
- const ext = dot > 0 ? final.slice(dot) : "";
3095
- let n = 2;
3096
- while (existsSync2(join4(dir, `${stem}_${n}${ext}`)))
3097
- n++;
3098
- final = `${stem}_${n}${ext}`;
3099
- }
3100
- writeFileSync(join4(dir, final), content, "utf8");
3101
- return final;
3102
- }
3103
- function deleteBackup(name) {
3104
- const p = join4(backupDir(), safeName(name));
3105
- if (!existsSync2(p))
3106
- return false;
3107
- rmSync(p);
3108
- return true;
3109
- }
3110
- function renameBackup(oldName, newName) {
3111
- const dir = backupDir();
3112
- const from = join4(dir, safeName(oldName));
3113
- let to = safeName(newName);
3114
- if (!to.endsWith(".rsc"))
3115
- to += ".rsc";
3116
- const toPath = join4(dir, to);
3117
- if (!existsSync2(from))
3118
- throw new Error(`backup not found: ${oldName}`);
3119
- if (existsSync2(toPath))
3120
- throw new Error(`a backup named '${to}' already exists`);
3121
- renameSync(from, toPath);
3122
- return to;
3123
- }
3124
- function exportToCommands(text) {
3125
- const lines = [];
3126
- let buf = "";
3127
- for (const raw of text.split(`
3128
- `)) {
3129
- const line = raw.replace(/\r$/, "");
3130
- buf = buf ? `${buf} ${line.trim()}` : line;
3131
- if (buf.trimEnd().endsWith("\\")) {
3132
- buf = buf.trimEnd().slice(0, -1).trimEnd();
3133
- continue;
3134
- }
3135
- lines.push(buf);
3136
- buf = "";
3137
- }
3138
- if (buf)
3139
- lines.push(buf);
3140
- const cmds = [];
3141
- let section = "";
3142
- for (const line of lines) {
3143
- const t = line.trim();
3144
- if (!t || t.startsWith("#"))
3145
- continue;
3146
- if (t.startsWith("/")) {
3147
- section = t;
3148
- continue;
3149
- }
3150
- cmds.push(section ? `${section} ${t}` : t);
3151
- }
3152
- return cmds;
3153
- }
3154
-
3155
3326
  // src/backups/create.ts
3156
3327
  function labelSlug(label) {
3157
3328
  const s = (label ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
@@ -5946,7 +6117,7 @@ var cache = null;
5946
6117
  async function gateway() {
5947
6118
  if (cache)
5948
6119
  return cache;
5949
- const { moduleCatalog } = await import("./library-ttqfk3a5.js");
6120
+ const { moduleCatalog } = await import("./library-r6dx5hw2.js");
5950
6121
  const forIndex = [];
5951
6122
  const byName = new Map;
5952
6123
  for (const mod of moduleCatalog) {
@@ -13957,13 +14128,8 @@ var sshTestTools = [
13957
14128
  }
13958
14129
  ctx.info(`Testing SSH from MCP host to configured device '${name}' (${dc.host}:${dc.port})`);
13959
14130
  return probe({
13960
- host: dc.host,
13961
- port: dc.port,
13962
- username: dc.username,
13963
- password: dc.password,
13964
- keyFilename: dc.keyFilename,
13965
- privateKey: dc.privateKey,
13966
- keyPassphrase: dc.keyPassphrase,
14131
+ ...sshOptionsOf(dc),
14132
+ jump: resolveJump(dc),
13967
14133
  timeoutMs: Math.min(dc.timeoutMs ?? 1e4, 1e4)
13968
14134
  }, `'${name}' (${dc.host}:${dc.port})`, "/system identity print");
13969
14135
  }
@@ -4,7 +4,7 @@ import {
4
4
  allToolModules,
5
5
  moduleCatalog,
6
6
  selectToolModules
7
- } from "./library-q35fq2gz.js";
7
+ } from "./library-9pt0yeqy.js";
8
8
  export {
9
9
  selectToolModules,
10
10
  moduleCatalog,