@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.
package/dist/cli.js CHANGED
@@ -84,7 +84,7 @@ import {
84
84
  toggleAaaEntity,
85
85
  updateAaaEntity,
86
86
  writeBackup
87
- } from "./shared/cli-ymyndemx.js";
87
+ } from "./shared/cli-9p1cxztb.js";
88
88
 
89
89
  // src/cli.ts
90
90
  import { existsSync as existsSync2 } from "fs";
@@ -2233,7 +2233,7 @@ function registerPrompts(server) {
2233
2233
  // package.json
2234
2234
  var package_default = {
2235
2235
  name: "@usex/mikrotik-mcp",
2236
- version: "3.35.0",
2236
+ version: "3.37.0",
2237
2237
  description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
2238
2238
  keywords: [
2239
2239
  "ai",
@@ -2326,7 +2326,7 @@ var package_default = {
2326
2326
  "class-variance-authority": "^0.7.1",
2327
2327
  clsx: "^2.1.1",
2328
2328
  gsap: "^3.15.0",
2329
- prettier: "^3.9.3",
2329
+ prettier: "^3.9.4",
2330
2330
  react: "^19.2.7",
2331
2331
  "react-dom": "^19.2.7",
2332
2332
  recharts: "^3.9.0",
package/dist/index.d.ts CHANGED
@@ -240,6 +240,12 @@ declare class MikroTikSSHClient {
240
240
  * Resolves on success; rejects with a clear reason on failure.
241
241
  */
242
242
  uploadFile(remotePath: string, data: Buffer): Promise<void>;
243
+ /**
244
+ * Download a file's bytes from the device over SFTP. `remotePath` is relative
245
+ * to the SFTP default directory (the flash root). Resolves with the raw file
246
+ * contents; rejects with a clear reason on failure.
247
+ */
248
+ downloadFile(remotePath: string): Promise<Buffer>;
243
249
  /** Open a persistent interactive shell channel (used by Safe Mode). */
244
250
  shell(opts?: {
245
251
  term?: string;
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  resolveDeviceName,
22
22
  selectToolModules,
23
23
  setConfig
24
- } from "./shared/library-q35fq2gz.js";
24
+ } from "./shared/library-9pt0yeqy.js";
25
25
  // src/server.ts
26
26
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
27
27
  import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
@@ -131,7 +131,7 @@ function registerPrompts(server) {
131
131
  // package.json
132
132
  var package_default = {
133
133
  name: "@usex/mikrotik-mcp",
134
- version: "3.35.0",
134
+ version: "3.37.0",
135
135
  description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
136
136
  keywords: [
137
137
  "ai",
@@ -224,7 +224,7 @@ var package_default = {
224
224
  "class-variance-authority": "^0.7.1",
225
225
  clsx: "^2.1.1",
226
226
  gsap: "^3.15.0",
227
- prettier: "^3.9.3",
227
+ prettier: "^3.9.4",
228
228
  react: "^19.2.7",
229
229
  "react-dom": "^19.2.7",
230
230
  recharts: "^3.9.0",
@@ -906,6 +906,29 @@ class MikroTikSSHClient {
906
906
  });
907
907
  });
908
908
  }
909
+ downloadFile(remotePath) {
910
+ if (!this.client) {
911
+ return Promise.reject(new Error("Not connected to MikroTik device"));
912
+ }
913
+ const openSftp = this.client.sftp.bind(this.client);
914
+ return new Promise((resolve2, reject) => {
915
+ openSftp((err, sftp) => {
916
+ if (err) {
917
+ reject(new Error(`SFTP subsystem unavailable: ${err.message}`));
918
+ return;
919
+ }
920
+ sftp.readFile(remotePath, (rerr, data) => {
921
+ try {
922
+ sftp.end();
923
+ } catch {}
924
+ if (rerr)
925
+ reject(new Error(`SFTP read failed: ${rerr.message}`));
926
+ else
927
+ resolve2(data);
928
+ });
929
+ });
930
+ });
931
+ }
909
932
  shell(opts = {}) {
910
933
  if (!this.client) {
911
934
  return Promise.reject(new Error("Not connected to MikroTik device"));
@@ -1054,16 +1077,7 @@ class SafeModeManager {
1054
1077
  if (dc.mac) {
1055
1078
  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.";
1056
1079
  }
1057
- const ssh = new MikroTikSSHClient({
1058
- host: dc.host,
1059
- username: dc.username,
1060
- password: dc.password,
1061
- keyFilename: dc.keyFilename,
1062
- privateKey: dc.privateKey,
1063
- keyPassphrase: dc.keyPassphrase,
1064
- port: dc.port,
1065
- timeoutMs: dc.timeoutMs
1066
- });
1080
+ const ssh = new MikroTikSSHClient({ ...sshOptionsOf(dc), jump: resolveJump(dc) });
1067
1081
  if (!await ssh.connect()) {
1068
1082
  return "Error: Failed to connect to MikroTik device for safe mode session.";
1069
1083
  }
@@ -1257,16 +1271,7 @@ async function uploadFileToDevice(deviceName, remotePath, data) {
1257
1271
  if (isMacTelnetDevice(dc)) {
1258
1272
  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.");
1259
1273
  }
1260
- const ssh = new MikroTikSSHClient({
1261
- host: dc.host,
1262
- username: dc.username,
1263
- password: dc.password,
1264
- keyFilename: dc.keyFilename,
1265
- privateKey: dc.privateKey,
1266
- keyPassphrase: dc.keyPassphrase,
1267
- port: dc.port,
1268
- timeoutMs: dc.timeoutMs
1269
- });
1274
+ const ssh = new MikroTikSSHClient({ ...sshOptionsOf(dc), jump: resolveJump(dc) });
1270
1275
  if (!await ssh.connect()) {
1271
1276
  throw new Error(connectErrorMessage(name, dc, ssh.lastError));
1272
1277
  }
@@ -1276,6 +1281,22 @@ async function uploadFileToDevice(deviceName, remotePath, data) {
1276
1281
  ssh.disconnect();
1277
1282
  }
1278
1283
  }
1284
+ async function downloadFileFromDevice(deviceName, remotePath) {
1285
+ const name = resolveDeviceName(deviceName);
1286
+ const dc = getDevice(deviceName);
1287
+ if (isMacTelnetDevice(dc)) {
1288
+ 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.");
1289
+ }
1290
+ const ssh = new MikroTikSSHClient({ ...sshOptionsOf(dc), jump: resolveJump(dc) });
1291
+ if (!await ssh.connect()) {
1292
+ throw new Error(connectErrorMessage(name, dc, ssh.lastError));
1293
+ }
1294
+ try {
1295
+ return await ssh.downloadFile(remotePath);
1296
+ } finally {
1297
+ ssh.disconnect();
1298
+ }
1299
+ }
1279
1300
 
1280
1301
  // src/core/registry.ts
1281
1302
  import { z as z2 } from "zod";
@@ -2777,6 +2798,158 @@ ${rows.slice(0, 40).map((r) => ` \u2022 ${r.name ?? "?"} (${r.type ?? "?"})${r.
2777
2798
  // src/tools/backup.ts
2778
2799
  import { z as z5 } from "zod";
2779
2800
 
2801
+ // src/backups/disk-check.ts
2802
+ var DISK_THRESHOLD_PCT = 90;
2803
+ async function checkDiskSpace(ctx) {
2804
+ try {
2805
+ const raw = await executeMikrotikCommand("/system resource print", ctx);
2806
+ const sys = parseSystemResource(raw);
2807
+ if (!sys || sys.hddUsedPct == null) {
2808
+ return { low: false };
2809
+ }
2810
+ return {
2811
+ low: sys.hddUsedPct >= DISK_THRESHOLD_PCT,
2812
+ usedPct: sys.hddUsedPct,
2813
+ freeBytes: sys.freeHdd,
2814
+ totalBytes: sys.totalHdd
2815
+ };
2816
+ } catch {
2817
+ return { low: false };
2818
+ }
2819
+ }
2820
+
2821
+ // src/backups/vault.ts
2822
+ import {
2823
+ existsSync as existsSync2,
2824
+ mkdirSync,
2825
+ readFileSync as readFileSync4,
2826
+ readdirSync,
2827
+ renameSync,
2828
+ rmSync,
2829
+ statSync,
2830
+ writeFileSync
2831
+ } from "fs";
2832
+ import { basename, join as join4 } from "path";
2833
+ function backupDir() {
2834
+ if (process.env.MIKROTIK_BACKUP_DIR)
2835
+ return process.env.MIKROTIK_BACKUP_DIR;
2836
+ try {
2837
+ const dir = getConfig().backupDir;
2838
+ if (dir)
2839
+ return dir;
2840
+ } catch {}
2841
+ return DEFAULT_BACKUP_DIR;
2842
+ }
2843
+ function safeName(name) {
2844
+ const base = basename(name);
2845
+ if (base !== name || base === "." || base === ".." || !/^[A-Za-z0-9._-]+$/.test(base)) {
2846
+ throw new Error(`invalid backup name: ${name}`);
2847
+ }
2848
+ return base;
2849
+ }
2850
+ function listBackups() {
2851
+ const dir = backupDir();
2852
+ if (!existsSync2(dir))
2853
+ return [];
2854
+ return readdirSync(dir).filter((f) => f.endsWith(".rsc") || f.endsWith(".backup")).map((f) => {
2855
+ const st = statSync(join4(dir, f));
2856
+ const us = f.indexOf("_");
2857
+ return {
2858
+ name: f,
2859
+ bytes: st.size,
2860
+ modified: st.mtimeMs,
2861
+ device: us > 0 ? f.slice(0, us) : undefined,
2862
+ type: f.endsWith(".backup") ? "backup" : "rsc"
2863
+ };
2864
+ }).sort((a, b) => b.modified - a.modified);
2865
+ }
2866
+ function readBackup(name) {
2867
+ return readFileSync4(join4(backupDir(), safeName(name)), "utf8");
2868
+ }
2869
+ function writeBackup(name, content) {
2870
+ const dir = backupDir();
2871
+ mkdirSync(dir, { recursive: true });
2872
+ let final = safeName(name);
2873
+ if (existsSync2(join4(dir, final))) {
2874
+ const dot = final.lastIndexOf(".");
2875
+ const stem = dot > 0 ? final.slice(0, dot) : final;
2876
+ const ext = dot > 0 ? final.slice(dot) : "";
2877
+ let n = 2;
2878
+ while (existsSync2(join4(dir, `${stem}_${n}${ext}`)))
2879
+ n++;
2880
+ final = `${stem}_${n}${ext}`;
2881
+ }
2882
+ writeFileSync(join4(dir, final), content, "utf8");
2883
+ return final;
2884
+ }
2885
+ function writeBinaryBackup(name, data) {
2886
+ const dir = backupDir();
2887
+ mkdirSync(dir, { recursive: true });
2888
+ let final = safeName(name);
2889
+ if (existsSync2(join4(dir, final))) {
2890
+ const dot = final.lastIndexOf(".");
2891
+ const stem = dot > 0 ? final.slice(0, dot) : final;
2892
+ const ext = dot > 0 ? final.slice(dot) : "";
2893
+ let n = 2;
2894
+ while (existsSync2(join4(dir, `${stem}_${n}${ext}`)))
2895
+ n++;
2896
+ final = `${stem}_${n}${ext}`;
2897
+ }
2898
+ writeFileSync(join4(dir, final), data);
2899
+ return final;
2900
+ }
2901
+ function deleteBackup(name) {
2902
+ const p = join4(backupDir(), safeName(name));
2903
+ if (!existsSync2(p))
2904
+ return false;
2905
+ rmSync(p);
2906
+ return true;
2907
+ }
2908
+ function renameBackup(oldName, newName) {
2909
+ const dir = backupDir();
2910
+ const from = join4(dir, safeName(oldName));
2911
+ let to = safeName(newName);
2912
+ if (!to.endsWith(".rsc"))
2913
+ to += ".rsc";
2914
+ const toPath = join4(dir, to);
2915
+ if (!existsSync2(from))
2916
+ throw new Error(`backup not found: ${oldName}`);
2917
+ if (existsSync2(toPath))
2918
+ throw new Error(`a backup named '${to}' already exists`);
2919
+ renameSync(from, toPath);
2920
+ return to;
2921
+ }
2922
+ function exportToCommands(text) {
2923
+ const lines = [];
2924
+ let buf = "";
2925
+ for (const raw of text.split(`
2926
+ `)) {
2927
+ const line = raw.replace(/\r$/, "");
2928
+ buf = buf ? `${buf} ${line.trim()}` : line;
2929
+ if (buf.trimEnd().endsWith("\\")) {
2930
+ buf = buf.trimEnd().slice(0, -1).trimEnd();
2931
+ continue;
2932
+ }
2933
+ lines.push(buf);
2934
+ buf = "";
2935
+ }
2936
+ if (buf)
2937
+ lines.push(buf);
2938
+ const cmds = [];
2939
+ let section = "";
2940
+ for (const line of lines) {
2941
+ const t = line.trim();
2942
+ if (!t || t.startsWith("#"))
2943
+ continue;
2944
+ if (t.startsWith("/")) {
2945
+ section = t;
2946
+ continue;
2947
+ }
2948
+ cmds.push(section ? `${section} ${t}` : t);
2949
+ }
2950
+ return cmds;
2951
+ }
2952
+
2780
2953
  // src/core/datestamp.ts
2781
2954
  var MONTHS2 = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
2782
2955
  function pad(n) {
@@ -2849,6 +3022,12 @@ async function deviceDateStamp(ctx) {
2849
3022
  return hostStamp(new Date);
2850
3023
  }
2851
3024
 
3025
+ // src/core/slug.ts
3026
+ function deviceSlug(name) {
3027
+ const s = (name ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
3028
+ return s || "device";
3029
+ }
3030
+
2852
3031
  // src/tools/backup.ts
2853
3032
  var backupTools = [
2854
3033
  defineTool({
@@ -2865,6 +3044,7 @@ var backupTools = [
2865
3044
  async handler(a, ctx) {
2866
3045
  const name = a.name || `backup_${await deviceDateStamp(ctx)}`;
2867
3046
  ctx.info(`Creating backup: name=${name}`);
3047
+ const disk = await checkDiskSpace(ctx);
2868
3048
  const cmd = new Cmd("/system backup save").set("name", name);
2869
3049
  if (a.dont_encrypt)
2870
3050
  cmd.raw("dont-encrypt=yes");
@@ -2873,13 +3053,35 @@ var backupTools = [
2873
3053
  if (!a.include_password)
2874
3054
  cmd.raw("password-file=no");
2875
3055
  const result = await executeMikrotikCommand(cmd.build(), ctx);
2876
- if (result.includes("saved") || result.trim() === "") {
2877
- const fileDetails = await executeMikrotikCommand(`/file print detail where name=${name}.backup`, ctx);
2878
- return fileDetails ? `Backup created successfully:
3056
+ if (!(result.includes("saved") || result.trim() === "")) {
3057
+ if (disk.low) {
3058
+ return `Failed to create backup (device disk is ${disk.usedPct?.toFixed(0)}% used): ${result}
2879
3059
 
2880
- ${fileDetails}` : `Backup '${name}.backup' created successfully.`;
3060
+ ` + "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.";
3061
+ }
3062
+ return `Failed to create backup: ${result}`;
2881
3063
  }
2882
- return `Failed to create backup: ${result}`;
3064
+ if (disk.low) {
3065
+ const dc = getDevice(ctx.device);
3066
+ if (isMacTelnetDevice(dc)) {
3067
+ 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.";
3068
+ }
3069
+ const deviceFile = `${name}.backup`;
3070
+ try {
3071
+ ctx.info(`Low disk (${disk.usedPct?.toFixed(0)}%) \u2014 downloading backup to local vault`);
3072
+ const data = await downloadFileFromDevice(ctx.device, deviceFile);
3073
+ const device = resolveDeviceName(ctx.device);
3074
+ const vaultName = writeBinaryBackup(`${deviceSlug(device)}_${deviceFile}`, data);
3075
+ await executeMikrotikCommand(`/file remove ${deviceFile}`, ctx);
3076
+ 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.";
3077
+ } catch (e) {
3078
+ 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.`;
3079
+ }
3080
+ }
3081
+ const fileDetails = await executeMikrotikCommand(`/file print detail where name=${name}.backup`, ctx);
3082
+ return fileDetails ? `Backup created successfully:
3083
+
3084
+ ${fileDetails}` : `Backup '${name}.backup' created successfully.`;
2883
3085
  }
2884
3086
  }),
2885
3087
  defineTool({
@@ -2928,6 +3130,21 @@ ${result}`;
2928
3130
  async handler(a, ctx) {
2929
3131
  const name = a.name || `export_${await deviceDateStamp(ctx)}`;
2930
3132
  ctx.info(`Creating export: name=${name}, format=${a.file_format}`);
3133
+ const disk = await checkDiskSpace(ctx);
3134
+ if (disk.low) {
3135
+ ctx.info(`Low disk (${disk.usedPct?.toFixed(0)}%) \u2014 redirecting export to local vault`);
3136
+ const cmd2 = new Cmd("/export");
3137
+ cmd2.raw(a.verbose ? "verbose" : null);
3138
+ cmd2.raw(a.compact ? "compact" : null);
3139
+ cmd2.raw(!a.hide_sensitive ? "show-sensitive" : null);
3140
+ const body = await executeMikrotikCommand(cmd2.build(), ctx);
3141
+ if (isEmpty(body) || looksLikeError(body)) {
3142
+ return `Failed to create export: ${body}`;
3143
+ }
3144
+ const device = resolveDeviceName(ctx.device);
3145
+ const vaultName = writeBackup(`${deviceSlug(device)}_${name}.rsc`, body);
3146
+ 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.`;
3147
+ }
2931
3148
  const extension = a.file_format === "json" || a.file_format === "xml" ? a.file_format : "rsc";
2932
3149
  const fullName = `${name}.${extension}`;
2933
3150
  const cmd = new Cmd("/export");
@@ -2966,6 +3183,20 @@ ${fileDetails}` : `Export '${fullName}' created successfully.`;
2966
3183
  name = `export_${cleanSection}_${await deviceDateStamp(ctx)}`;
2967
3184
  }
2968
3185
  ctx.info(`Exporting section: section=${a.section}, name=${name}`);
3186
+ const disk = await checkDiskSpace(ctx);
3187
+ if (disk.low) {
3188
+ ctx.info(`Low disk (${disk.usedPct?.toFixed(0)}%) \u2014 redirecting section export to local vault`);
3189
+ const cmd2 = new Cmd(`/${a.section} export`);
3190
+ cmd2.raw(!a.hide_sensitive ? "show-sensitive" : null);
3191
+ cmd2.raw(a.compact ? "compact" : null);
3192
+ const body = await executeMikrotikCommand(cmd2.build(), ctx);
3193
+ if (isEmpty(body) || looksLikeError(body)) {
3194
+ return `Failed to export section: ${body}`;
3195
+ }
3196
+ const device = resolveDeviceName(ctx.device);
3197
+ const vaultName = writeBackup(`${deviceSlug(device)}_${name}.rsc`, body);
3198
+ 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.";
3199
+ }
2969
3200
  const cmd = new Cmd(`/${a.section} export`).set("file", name);
2970
3201
  cmd.raw(!a.hide_sensitive ? "show-sensitive" : null);
2971
3202
  cmd.raw(a.compact ? "compact" : null);
@@ -3116,127 +3347,6 @@ ${result}`;
3116
3347
  // src/tools/local-backup.ts
3117
3348
  import { z as z6 } from "zod";
3118
3349
 
3119
- // src/core/slug.ts
3120
- function deviceSlug(name) {
3121
- const s = (name ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
3122
- return s || "device";
3123
- }
3124
-
3125
- // src/backups/vault.ts
3126
- import {
3127
- existsSync as existsSync2,
3128
- mkdirSync,
3129
- readFileSync as readFileSync4,
3130
- readdirSync,
3131
- renameSync,
3132
- rmSync,
3133
- statSync,
3134
- writeFileSync
3135
- } from "fs";
3136
- import { basename, join as join4 } from "path";
3137
- function backupDir() {
3138
- if (process.env.MIKROTIK_BACKUP_DIR)
3139
- return process.env.MIKROTIK_BACKUP_DIR;
3140
- try {
3141
- const dir = getConfig().backupDir;
3142
- if (dir)
3143
- return dir;
3144
- } catch {}
3145
- return DEFAULT_BACKUP_DIR;
3146
- }
3147
- function safeName(name) {
3148
- const base = basename(name);
3149
- if (base !== name || base === "." || base === ".." || !/^[A-Za-z0-9._-]+$/.test(base)) {
3150
- throw new Error(`invalid backup name: ${name}`);
3151
- }
3152
- return base;
3153
- }
3154
- function listBackups() {
3155
- const dir = backupDir();
3156
- if (!existsSync2(dir))
3157
- return [];
3158
- return readdirSync(dir).filter((f) => f.endsWith(".rsc")).map((f) => {
3159
- const st = statSync(join4(dir, f));
3160
- const us = f.indexOf("_");
3161
- return {
3162
- name: f,
3163
- bytes: st.size,
3164
- modified: st.mtimeMs,
3165
- device: us > 0 ? f.slice(0, us) : undefined
3166
- };
3167
- }).sort((a, b) => b.modified - a.modified);
3168
- }
3169
- function readBackup(name) {
3170
- return readFileSync4(join4(backupDir(), safeName(name)), "utf8");
3171
- }
3172
- function writeBackup(name, content) {
3173
- const dir = backupDir();
3174
- mkdirSync(dir, { recursive: true });
3175
- let final = safeName(name);
3176
- if (existsSync2(join4(dir, final))) {
3177
- const dot = final.lastIndexOf(".");
3178
- const stem = dot > 0 ? final.slice(0, dot) : final;
3179
- const ext = dot > 0 ? final.slice(dot) : "";
3180
- let n = 2;
3181
- while (existsSync2(join4(dir, `${stem}_${n}${ext}`)))
3182
- n++;
3183
- final = `${stem}_${n}${ext}`;
3184
- }
3185
- writeFileSync(join4(dir, final), content, "utf8");
3186
- return final;
3187
- }
3188
- function deleteBackup(name) {
3189
- const p = join4(backupDir(), safeName(name));
3190
- if (!existsSync2(p))
3191
- return false;
3192
- rmSync(p);
3193
- return true;
3194
- }
3195
- function renameBackup(oldName, newName) {
3196
- const dir = backupDir();
3197
- const from = join4(dir, safeName(oldName));
3198
- let to = safeName(newName);
3199
- if (!to.endsWith(".rsc"))
3200
- to += ".rsc";
3201
- const toPath = join4(dir, to);
3202
- if (!existsSync2(from))
3203
- throw new Error(`backup not found: ${oldName}`);
3204
- if (existsSync2(toPath))
3205
- throw new Error(`a backup named '${to}' already exists`);
3206
- renameSync(from, toPath);
3207
- return to;
3208
- }
3209
- function exportToCommands(text) {
3210
- const lines = [];
3211
- let buf = "";
3212
- for (const raw of text.split(`
3213
- `)) {
3214
- const line = raw.replace(/\r$/, "");
3215
- buf = buf ? `${buf} ${line.trim()}` : line;
3216
- if (buf.trimEnd().endsWith("\\")) {
3217
- buf = buf.trimEnd().slice(0, -1).trimEnd();
3218
- continue;
3219
- }
3220
- lines.push(buf);
3221
- buf = "";
3222
- }
3223
- if (buf)
3224
- lines.push(buf);
3225
- const cmds = [];
3226
- let section = "";
3227
- for (const line of lines) {
3228
- const t = line.trim();
3229
- if (!t || t.startsWith("#"))
3230
- continue;
3231
- if (t.startsWith("/")) {
3232
- section = t;
3233
- continue;
3234
- }
3235
- cmds.push(section ? `${section} ${t}` : t);
3236
- }
3237
- return cmds;
3238
- }
3239
-
3240
3350
  // src/backups/create.ts
3241
3351
  function labelSlug(label) {
3242
3352
  const s = (label ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
@@ -6031,7 +6141,7 @@ var cache = null;
6031
6141
  async function gateway() {
6032
6142
  if (cache)
6033
6143
  return cache;
6034
- const { moduleCatalog } = await import("./cli-8xwvyzks.js");
6144
+ const { moduleCatalog } = await import("./cli-fpfchpjm.js");
6035
6145
  const forIndex = [];
6036
6146
  const byName = new Map;
6037
6147
  for (const mod of moduleCatalog) {
@@ -14042,13 +14152,8 @@ var sshTestTools = [
14042
14152
  }
14043
14153
  ctx.info(`Testing SSH from MCP host to configured device '${name}' (${dc.host}:${dc.port})`);
14044
14154
  return probe({
14045
- host: dc.host,
14046
- port: dc.port,
14047
- username: dc.username,
14048
- password: dc.password,
14049
- keyFilename: dc.keyFilename,
14050
- privateKey: dc.privateKey,
14051
- keyPassphrase: dc.keyPassphrase,
14155
+ ...sshOptionsOf(dc),
14156
+ jump: resolveJump(dc),
14052
14157
  timeoutMs: Math.min(dc.timeoutMs ?? 1e4, 1e4)
14053
14158
  }, `'${name}' (${dc.host}:${dc.port})`, "/system identity print");
14054
14159
  }
@@ -4,7 +4,7 @@ import {
4
4
  allToolModules,
5
5
  moduleCatalog,
6
6
  selectToolModules
7
- } from "./cli-ymyndemx.js";
7
+ } from "./cli-9p1cxztb.js";
8
8
  export {
9
9
  selectToolModules,
10
10
  moduleCatalog,