@usex/mikrotik-mcp 3.51.0 → 3.52.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
@@ -95,7 +95,7 @@ import {
95
95
  toggleAaaEntity,
96
96
  updateAaaEntity,
97
97
  writeBackup
98
- } from "./shared/cli-22wrpg71.js";
98
+ } from "./shared/cli-ewkttc56.js";
99
99
 
100
100
  // src/cli.ts
101
101
  import { existsSync as existsSync2 } from "fs";
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  resolveDeviceName,
23
23
  selectToolModules,
24
24
  setConfig
25
- } from "./shared/library-mwcpnqqg.js";
25
+ } from "./shared/library-n3yvjpkm.js";
26
26
  // src/server.ts
27
27
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
28
28
  import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
@@ -5797,7 +5797,7 @@ function renderComplianceReport(report, device, options) {
5797
5797
  `).trimEnd();
5798
5798
  }
5799
5799
 
5800
- // src/tools/compliance-audit.ts
5800
+ // src/utils/safe-exec.ts
5801
5801
  async function safe(cmd, ctx) {
5802
5802
  try {
5803
5803
  const out = await executeMikrotikCommand(cmd, ctx);
@@ -5806,6 +5806,8 @@ async function safe(cmd, ctx) {
5806
5806
  return "";
5807
5807
  }
5808
5808
  }
5809
+
5810
+ // src/tools/compliance-audit.ts
5809
5811
  async function fetchComplianceState(ctx) {
5810
5812
  const [
5811
5813
  sshRaw,
@@ -7163,6 +7165,31 @@ ${created.join(`
7163
7165
  // src/tools/container.ts
7164
7166
  import { z as z17 } from "zod";
7165
7167
 
7168
+ // src/utils/ip.ts
7169
+ function isIpAddress(s) {
7170
+ return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(s);
7171
+ }
7172
+ function isIpLike(s) {
7173
+ return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d+)?$/.test(s);
7174
+ }
7175
+ function isPrivateIp(ip) {
7176
+ const p = ip.split(".").map(Number);
7177
+ if (p.length !== 4)
7178
+ return false;
7179
+ if (p[0] === 10)
7180
+ return true;
7181
+ if (p[0] === 172 && p[1] >= 16 && p[1] <= 31)
7182
+ return true;
7183
+ if (p[0] === 192 && p[1] === 168)
7184
+ return true;
7185
+ if (p[0] === 169 && p[1] === 254)
7186
+ return true;
7187
+ return false;
7188
+ }
7189
+ // src/utils/num.ts
7190
+ function num(s) {
7191
+ return Number.parseInt(s ?? "0", 10) || 0;
7192
+ }
7166
7193
  // src/utils/or-match.ts
7167
7194
  function orMatch(field, values, op = "~") {
7168
7195
  if (values.length === 0)
@@ -7934,7 +7961,7 @@ var cache = null;
7934
7961
  async function gateway() {
7935
7962
  if (cache)
7936
7963
  return cache;
7937
- const { moduleCatalog } = await import("./cli-dq28zbns.js");
7964
+ const { moduleCatalog } = await import("./cli-thdzk64q.js");
7938
7965
  const forIndex = [];
7939
7966
  const byName = new Map;
7940
7967
  for (const mod of moduleCatalog) {
@@ -8904,7 +8931,7 @@ function rulesFromRows(rows) {
8904
8931
  else
8905
8932
  match[k] = v;
8906
8933
  }
8907
- const num = (s) => {
8934
+ const num2 = (s) => {
8908
8935
  if (s == null)
8909
8936
  return;
8910
8937
  const n = Number(s.replace(/\s/g, ""));
@@ -8917,8 +8944,8 @@ function rulesFromRows(rows) {
8917
8944
  disabled: flags.includes("X"),
8918
8945
  dynamic: flags.includes("D"),
8919
8946
  comment: r.comment,
8920
- packets: num(r.packets),
8921
- bytes: num(r.bytes),
8947
+ packets: num2(r.packets),
8948
+ bytes: num2(r.bytes),
8922
8949
  match,
8923
8950
  transform,
8924
8951
  raw: r
@@ -8987,6 +9014,8 @@ function matchSummary(rule) {
8987
9014
  "dst-port",
8988
9015
  "in-interface",
8989
9016
  "out-interface",
9017
+ "in-interface-list",
9018
+ "out-interface-list",
8990
9019
  "connection-state",
8991
9020
  "src-address-list",
8992
9021
  "dst-address-list"
@@ -9735,11 +9764,12 @@ async function updateFilterRule(a, ctx) {
9735
9764
  }
9736
9765
  if (updates.length === 0)
9737
9766
  return "No updates specified.";
9738
- const cmd = `/ip firewall filter set ${a.rule_id} ${updates.join(" ")}`;
9767
+ const id = /^\d+$/.test(a.rule_id) ? `*${a.rule_id}` : a.rule_id;
9768
+ const cmd = `/ip firewall filter set ${id} ${updates.join(" ")}`;
9739
9769
  const result = await executeMikrotikCommand(cmd, ctx);
9740
9770
  if (looksLikeError(result))
9741
9771
  return `Failed to update firewall filter rule: ${result}`;
9742
- const details = await executeMikrotikCommand(`/ip firewall filter print detail where .id=${a.rule_id}`, ctx);
9772
+ const details = await executeMikrotikCommand(`/ip firewall filter print detail where .id=${id}`, ctx);
9743
9773
  return `Firewall filter rule updated successfully:
9744
9774
 
9745
9775
  ${details}`;
@@ -9894,7 +9924,8 @@ ${result}`;
9894
9924
  },
9895
9925
  async handler(a, ctx) {
9896
9926
  ctx.info(`Getting firewall filter rule details: rule_id=${a.rule_id}`);
9897
- const result = await executeMikrotikCommand(`/ip firewall filter print detail where .id=${a.rule_id}`, ctx);
9927
+ const id = /^\d+$/.test(a.rule_id) ? `*${a.rule_id}` : a.rule_id;
9928
+ const result = await executeMikrotikCommand(`/ip firewall filter print detail where .id=${id}`, ctx);
9898
9929
  return isEmpty(result) ? `Firewall filter rule with ID '${a.rule_id}' not found.` : `FIREWALL FILTER RULE DETAILS:
9899
9930
 
9900
9931
  ${result}`;
@@ -9970,10 +10001,11 @@ ${result}`;
9970
10001
  inputSchema: { rule_id: z28.string() },
9971
10002
  async handler(a, ctx) {
9972
10003
  ctx.info(`Removing firewall filter rule: rule_id=${a.rule_id}`);
9973
- const count = await executeMikrotikCommand(`/ip firewall filter print count-only where .id=${a.rule_id}`, ctx);
10004
+ const id = /^\d+$/.test(a.rule_id) ? `*${a.rule_id}` : a.rule_id;
10005
+ const count = await executeMikrotikCommand(`/ip firewall filter print count-only where .id=${id}`, ctx);
9974
10006
  if (count.trim() === "0")
9975
10007
  return `Firewall filter rule with ID '${a.rule_id}' not found.`;
9976
- const result = await executeMikrotikCommand(`/ip firewall filter remove ${a.rule_id}`, ctx);
10008
+ const result = await executeMikrotikCommand(`/ip firewall filter remove ${id}`, ctx);
9977
10009
  if (looksLikeError(result))
9978
10010
  return `Failed to remove firewall filter rule: ${result}`;
9979
10011
  return `Firewall filter rule with ID '${a.rule_id}' removed successfully.`;
@@ -9990,10 +10022,11 @@ ${result}`;
9990
10022
  },
9991
10023
  async handler(a, ctx) {
9992
10024
  ctx.info(`Moving firewall filter rule: rule_id=${a.rule_id} to position ${a.destination}`);
9993
- const count = await executeMikrotikCommand(`/ip firewall filter print count-only where .id=${a.rule_id}`, ctx);
10025
+ const id = /^\d+$/.test(a.rule_id) ? `*${a.rule_id}` : a.rule_id;
10026
+ const count = await executeMikrotikCommand(`/ip firewall filter print count-only where .id=${id}`, ctx);
9994
10027
  if (count.trim() === "0")
9995
10028
  return `Firewall filter rule with ID '${a.rule_id}' not found.`;
9996
- const result = await executeMikrotikCommand(`/ip firewall filter move ${a.rule_id} destination=${a.destination}`, ctx);
10029
+ const result = await executeMikrotikCommand(`/ip firewall filter move ${id} destination=${a.destination}`, ctx);
9997
10030
  if (looksLikeError(result))
9998
10031
  return `Failed to move firewall filter rule: ${result}`;
9999
10032
  return `Firewall filter rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
@@ -10890,7 +10923,7 @@ function assessUpgradeReadiness(state) {
10890
10923
  });
10891
10924
  }
10892
10925
  const cpuNum = Number.parseInt(state.cpuLoad, 10);
10893
- if (!isNaN(cpuNum) && cpuNum > 80) {
10926
+ if (!Number.isNaN(cpuNum) && cpuNum > 80) {
10894
10927
  checks.push({
10895
10928
  label: "CPU load",
10896
10929
  status: "caution",
@@ -10903,8 +10936,8 @@ function assessUpgradeReadiness(state) {
10903
10936
  detail: `${state.cpuLoad}`
10904
10937
  });
10905
10938
  }
10906
- const free = parseMem(state.freeMemory);
10907
- const total = parseMem(state.totalMemory);
10939
+ const free = parseSize(state.freeMemory) ?? 0;
10940
+ const total = parseSize(state.totalMemory) ?? 0;
10908
10941
  if (free > 0 && total > 0) {
10909
10942
  const pct2 = Math.round(free / total * 100);
10910
10943
  if (pct2 < 20) {
@@ -10941,20 +10974,6 @@ function assessUpgradeReadiness(state) {
10941
10974
  }
10942
10975
  return checks;
10943
10976
  }
10944
- function parseMem(s) {
10945
- const m = s.match(/([\d.]+)\s*(MiB|GiB|KiB)?/i);
10946
- if (!m)
10947
- return 0;
10948
- const val = Number.parseFloat(m[1]);
10949
- const unit = (m[2] ?? "").toLowerCase();
10950
- if (unit === "gib")
10951
- return val * 1024 * 1024 * 1024;
10952
- if (unit === "mib")
10953
- return val * 1024 * 1024;
10954
- if (unit === "kib")
10955
- return val * 1024;
10956
- return val;
10957
- }
10958
10977
  function renderFirmwareStatus(state, device) {
10959
10978
  const lines = [];
10960
10979
  lines.push(`FIRMWARE STATUS \u2014 ${device}`);
@@ -11092,14 +11111,6 @@ function renderReadiness(checks) {
11092
11111
  }
11093
11112
 
11094
11113
  // src/tools/firmware-lifecycle.ts
11095
- async function safe2(cmd, ctx) {
11096
- try {
11097
- const out = await executeMikrotikCommand(cmd, ctx);
11098
- return looksLikeError(out) ? "" : out;
11099
- } catch {
11100
- return "";
11101
- }
11102
- }
11103
11114
  function parsePackages(raw) {
11104
11115
  if (!raw)
11105
11116
  return [];
@@ -11144,10 +11155,10 @@ function parseUpdateInfo(raw) {
11144
11155
  }
11145
11156
  async function fetchFirmwareState(ctx) {
11146
11157
  const [resourceRaw, packagesRaw, routerboardRaw, updateRaw] = await Promise.all([
11147
- safe2("/system resource print", ctx),
11148
- safe2("/system package print detail", ctx),
11149
- safe2("/system routerboard print", ctx),
11150
- safe2("/system package update print", ctx)
11158
+ safe("/system resource print", ctx),
11159
+ safe("/system package print detail", ctx),
11160
+ safe("/system routerboard print", ctx),
11161
+ safe("/system package update print", ctx)
11151
11162
  ]);
11152
11163
  const res = parseKeyValues(resourceRaw);
11153
11164
  return {
@@ -11165,12 +11176,12 @@ async function fetchFirmwareState(ctx) {
11165
11176
  }
11166
11177
  async function captureHealthSnapshot(ctx) {
11167
11178
  const [resourceRaw, healthRaw, routesRaw, interfacesRaw, pppRaw, dhcpRaw] = await Promise.all([
11168
- safe2("/system resource print", ctx),
11169
- safe2("/system health print", ctx),
11170
- safe2("/ip route print count-only", ctx),
11171
- safe2("/interface print detail", ctx),
11172
- safe2("/ppp active print count-only", ctx),
11173
- safe2("/ip dhcp-server lease print count-only where status=bound", ctx)
11179
+ safe("/system resource print", ctx),
11180
+ safe("/system health print", ctx),
11181
+ safe("/ip route print count-only", ctx),
11182
+ safe("/interface print detail", ctx),
11183
+ safe("/ppp active print count-only", ctx),
11184
+ safe("/ip dhcp-server lease print count-only where status=bound", ctx)
11174
11185
  ]);
11175
11186
  const res = parseKeyValues(resourceRaw);
11176
11187
  const ifRows = interfacesRaw ? parseRecords(interfacesRaw).rows : [];
@@ -11270,8 +11281,8 @@ var firmwareLifecycleTools = [
11270
11281
  if (looksLikeError(chResult))
11271
11282
  return `Failed to set channel: ${chResult}`;
11272
11283
  }
11273
- const checkResult = await executeMikrotikCommand("/system package update check-for-updates once", ctx);
11274
- const updateRaw = await safe2("/system package update print", ctx);
11284
+ await executeMikrotikCommand("/system package update check-for-updates once", ctx);
11285
+ const updateRaw = await safe("/system package update print", ctx);
11275
11286
  const updateInfo = parseUpdateInfo(updateRaw);
11276
11287
  if (!updateInfo?.updateAvailable) {
11277
11288
  return `No update available on '${device}'.
@@ -11285,7 +11296,7 @@ var firmwareLifecycleTools = [
11285
11296
  if (looksLikeError(dlResult)) {
11286
11297
  return `Failed to download update packages: ${dlResult}`;
11287
11298
  }
11288
- const postRaw = await safe2("/system package update print", ctx);
11299
+ const postRaw = await safe("/system package update print", ctx);
11289
11300
  const postStatus = parseKeyValues(postRaw);
11290
11301
  const lines = [];
11291
11302
  lines.push(`FIRMWARE STAGED \u2014 ${device}`);
@@ -11318,7 +11329,7 @@ var firmwareLifecycleTools = [
11318
11329
  return "Upgrade not confirmed. Pass confirm=true to proceed. The device WILL reboot.";
11319
11330
  const device = resolveDeviceName(ctx.device);
11320
11331
  ctx.info(`Firmware upgrade on '${device}'`);
11321
- const updateRaw = await safe2("/system package update print", ctx);
11332
+ const updateRaw = await safe("/system package update print", ctx);
11322
11333
  const updateInfo = parseUpdateInfo(updateRaw);
11323
11334
  if (!updateInfo?.updateAvailable) {
11324
11335
  return `No update available on '${device}'. ` + `Run \`firmware_check\` or \`firmware_stage\` first.
@@ -11427,7 +11438,7 @@ var firmwareLifecycleTools = [
11427
11438
  lines.push(` Current: ${state.routerboard.currentFirmware} \u2192 Available: ${state.routerboard.upgradeFirmware}`);
11428
11439
  lines.push(" Use `firmware_upgrade` with `upgrade_routerboard=true` to include it.");
11429
11440
  }
11430
- const schedRaw = await safe2('/system scheduler print detail where name="mcp-firmware-upgrade"', ctx);
11441
+ const schedRaw = await safe('/system scheduler print detail where name="mcp-firmware-upgrade"', ctx);
11431
11442
  if (schedRaw && !isEmpty(schedRaw)) {
11432
11443
  lines.push("");
11433
11444
  lines.push("PENDING SCHEDULED UPGRADE:");
@@ -20385,7 +20396,7 @@ function analyzeRootCause(data) {
20385
20396
  summary: `${boundLeases.length} active DHCP leases`
20386
20397
  });
20387
20398
  }
20388
- if (isIpAddress(data.target)) {
20399
+ if (isIpAddress(data.target) && isPrivateIp(data.target)) {
20389
20400
  const lease = data.dhcpLeases.find((l) => l.address === data.target);
20390
20401
  const arp = data.arpEntries.find((e) => e.address === data.target);
20391
20402
  if (!lease && !arp) {
@@ -20640,7 +20651,7 @@ function correlateRootCauses(data, evidence, causes) {
20640
20651
  dimensions: ["resources"]
20641
20652
  });
20642
20653
  }
20643
- if (isIpAddress(data.target)) {
20654
+ if (isIpAddress(data.target) && isPrivateIp(data.target)) {
20644
20655
  const arp = data.arpEntries.find((e) => e.address === data.target);
20645
20656
  if (arp && !arp.complete && data.ping?.lossPct === 100) {
20646
20657
  causes.push({
@@ -20695,9 +20706,6 @@ function correlateRootCauses(data, evidence, causes) {
20695
20706
  });
20696
20707
  }
20697
20708
  }
20698
- function isIpAddress(s) {
20699
- return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(s);
20700
- }
20701
20709
  function formatRule(r) {
20702
20710
  const parts = [`chain=${r.chain}`, `action=${r.action}`];
20703
20711
  if (r.srcAddress)
@@ -20857,19 +20865,8 @@ ${footer}`;
20857
20865
  }
20858
20866
 
20859
20867
  // src/tools/root-cause.ts
20860
- async function safe3(cmd, ctx) {
20861
- try {
20862
- const out = await executeMikrotikCommand(cmd, ctx);
20863
- return looksLikeError(out) ? "" : out;
20864
- } catch {
20865
- return "";
20866
- }
20867
- }
20868
- function num(s) {
20869
- return Number.parseInt(s ?? "0", 10) || 0;
20870
- }
20871
20868
  async function collectInterfaces(ctx) {
20872
- const raw = await safe3("/interface print detail", ctx);
20869
+ const raw = await safe("/interface print detail", ctx);
20873
20870
  if (!raw)
20874
20871
  return [];
20875
20872
  return parseRecords(raw).rows.map((r) => ({
@@ -20887,7 +20884,7 @@ async function collectInterfaces(ctx) {
20887
20884
  }));
20888
20885
  }
20889
20886
  async function collectRoutes(ctx) {
20890
- const raw = await safe3("/ip route print detail", ctx);
20887
+ const raw = await safe("/ip route print detail", ctx);
20891
20888
  if (!raw)
20892
20889
  return { routes: [], count: 0, hasDefault: false };
20893
20890
  const rows = parseRecords(raw).rows;
@@ -20902,7 +20899,7 @@ async function collectRoutes(ctx) {
20902
20899
  return { routes, count: routes.length, hasDefault };
20903
20900
  }
20904
20901
  async function collectOspfNeighbors(ctx) {
20905
- const raw = await safe3("/routing ospf neighbor print detail", ctx);
20902
+ const raw = await safe("/routing ospf neighbor print detail", ctx);
20906
20903
  if (!raw)
20907
20904
  return [];
20908
20905
  return parseRecords(raw).rows.map((r) => ({
@@ -20914,9 +20911,9 @@ async function collectOspfNeighbors(ctx) {
20914
20911
  }));
20915
20912
  }
20916
20913
  async function collectBgpPeers(ctx) {
20917
- const raw = await safe3("/routing bgp session print detail", ctx);
20914
+ const raw = await safe("/routing bgp session print detail", ctx);
20918
20915
  if (!raw) {
20919
- const raw2 = await safe3("/routing bgp peer print detail", ctx);
20916
+ const raw2 = await safe("/routing bgp peer print detail", ctx);
20920
20917
  if (!raw2)
20921
20918
  return [];
20922
20919
  return parseRecords(raw2).rows.map((r) => ({
@@ -20936,12 +20933,12 @@ async function collectBgpPeers(ctx) {
20936
20933
  }));
20937
20934
  }
20938
20935
  async function collectFirewallRules(target, ctx) {
20939
- const raw = await safe3("/ip firewall filter print detail", ctx);
20936
+ const raw = await safe("/ip firewall filter print detail", ctx);
20940
20937
  if (!raw)
20941
20938
  return { matching: [], totalCount: 0 };
20942
20939
  const rows = parseRecords(raw).rows;
20943
20940
  const all = rows.map(parseFirewallRow);
20944
- const isIp = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(target);
20941
+ const isIp = isIpAddress(target);
20945
20942
  const matching = isIp ? all.filter((r) => !r.srcAddress || r.srcAddress === target || !r.dstAddress || r.dstAddress === target) : all;
20946
20943
  return { matching, totalCount: all.length };
20947
20944
  }
@@ -20961,13 +20958,13 @@ function parseFirewallRow(r, i) {
20961
20958
  };
20962
20959
  }
20963
20960
  async function collectNatRules(ctx) {
20964
- const raw = await safe3("/ip firewall nat print detail", ctx);
20961
+ const raw = await safe("/ip firewall nat print detail", ctx);
20965
20962
  if (!raw)
20966
20963
  return [];
20967
20964
  return parseRecords(raw).rows.map(parseFirewallRow);
20968
20965
  }
20969
20966
  async function collectArp(ctx) {
20970
- const raw = await safe3("/ip arp print detail", ctx);
20967
+ const raw = await safe("/ip arp print detail", ctx);
20971
20968
  if (!raw)
20972
20969
  return [];
20973
20970
  return parseRecords(raw).rows.map((r) => ({
@@ -20979,7 +20976,7 @@ async function collectArp(ctx) {
20979
20976
  }));
20980
20977
  }
20981
20978
  async function collectDhcpLeases(ctx) {
20982
- const raw = await safe3("/ip dhcp-server lease print detail", ctx);
20979
+ const raw = await safe("/ip dhcp-server lease print detail", ctx);
20983
20980
  if (!raw)
20984
20981
  return [];
20985
20982
  return parseRecords(raw).rows.map((r) => ({
@@ -20992,7 +20989,7 @@ async function collectDhcpLeases(ctx) {
20992
20989
  }));
20993
20990
  }
20994
20991
  async function collectLogs(target, ctx) {
20995
- const raw = await safe3('/log print where topics~"error" or topics~"warning" or topics~"critical" or topics~"firewall" or topics~"system"', ctx);
20992
+ const raw = await safe('/log print where topics~"error" or topics~"warning" or topics~"critical" or topics~"firewall" or topics~"system"', ctx);
20996
20993
  if (!raw)
20997
20994
  return [];
20998
20995
  const rows = parseRecords(raw).rows;
@@ -21003,7 +21000,7 @@ async function collectLogs(target, ctx) {
21003
21000
  }));
21004
21001
  }
21005
21002
  async function collectTunnels(ctx) {
21006
- const raw = await safe3('/interface print detail where type~"gre|ipip|eoip|vxlan|wireguard|ovpn|sstp|pptp|l2tp"', ctx);
21003
+ const raw = await safe('/interface print detail where type~"gre|ipip|eoip|vxlan|wireguard|ovpn|sstp|pptp|l2tp"', ctx);
21007
21004
  if (!raw)
21008
21005
  return [];
21009
21006
  return parseRecords(raw).rows.map((r) => ({
@@ -21038,27 +21035,27 @@ async function collectDiagnosticData(target, ctx, dimensions) {
21038
21035
  logs,
21039
21036
  tunnels
21040
21037
  ] = await Promise.all([
21041
- dims.has("connectivity") ? safe3(`/ping ${quoteValue(target)} count=5`, ctx) : Promise.resolve(""),
21038
+ dims.has("connectivity") ? safe(`/ping ${quoteValue(target)} count=5`, ctx) : Promise.resolve(""),
21042
21039
  dims.has("interfaces") ? collectInterfaces(ctx) : Promise.resolve([]),
21043
21040
  dims.has("routing") ? collectRoutes(ctx) : Promise.resolve({ routes: [], count: 0, hasDefault: true }),
21044
21041
  dims.has("routing") ? collectOspfNeighbors(ctx) : Promise.resolve([]),
21045
21042
  dims.has("routing") ? collectBgpPeers(ctx) : Promise.resolve([]),
21046
21043
  dims.has("firewall") ? collectFirewallRules(target, ctx) : Promise.resolve({ matching: [], totalCount: 0 }),
21047
21044
  dims.has("nat") ? collectNatRules(ctx) : Promise.resolve([]),
21048
- dims.has("nat") ? safe3("/ip firewall connection print count-only", ctx) : Promise.resolve("0"),
21045
+ dims.has("nat") ? safe("/ip firewall connection print count-only", ctx) : Promise.resolve("0"),
21049
21046
  dims.has("arp_dhcp") ? collectArp(ctx) : Promise.resolve([]),
21050
21047
  dims.has("arp_dhcp") ? collectDhcpLeases(ctx) : Promise.resolve([]),
21051
- dims.has("dns") && !isIpLike(target) ? safe3(`[:resolve ${quoteValue(target)}]`, ctx) : Promise.resolve(undefined),
21052
- dims.has("dns") ? safe3("/ip dns print", ctx) : Promise.resolve(""),
21053
- dims.has("resources") ? safe3("/system resource print", ctx) : Promise.resolve(""),
21048
+ dims.has("dns") && !isIpLike(target) ? safe(`[:resolve ${quoteValue(target)}]`, ctx) : Promise.resolve(undefined),
21049
+ dims.has("dns") ? safe("/ip dns print", ctx) : Promise.resolve(""),
21050
+ dims.has("resources") ? safe("/system resource print", ctx) : Promise.resolve(""),
21054
21051
  dims.has("logs") ? collectLogs(target, ctx) : Promise.resolve([]),
21055
21052
  dims.has("vpn") ? collectTunnels(ctx) : Promise.resolve([])
21056
21053
  ]);
21057
21054
  const ping = pingResult ? parsePingSummary(pingResult) ?? undefined : undefined;
21058
21055
  const dnsKv = parseKeyValues(dnsSettingsRaw);
21059
21056
  const resKv = parseKeyValues(resourceRaw);
21060
- const totalMem = parseMemMiB(resKv["total-memory"]);
21061
- const freeMem = parseMemMiB(resKv["free-memory"]);
21057
+ const totalMem = parseSize(resKv["total-memory"]) ?? 0;
21058
+ const freeMem = parseSize(resKv["free-memory"]) ?? 0;
21062
21059
  const memUsedPct = totalMem > 0 ? Math.round((totalMem - freeMem) / totalMem * 100) : 0;
21063
21060
  return {
21064
21061
  target,
@@ -21077,9 +21074,9 @@ async function collectDiagnosticData(target, ctx, dimensions) {
21077
21074
  arpEntries,
21078
21075
  dhcpLeases,
21079
21076
  dnsResolveResult: dnsResult ?? undefined,
21080
- dnsServers: dnsKv.servers ?? "",
21077
+ dnsServers: [dnsKv.servers, dnsKv["dynamic-servers"]].filter(Boolean).join(",") || "",
21081
21078
  dnsAllowRemote: (dnsKv["allow-remote-requests"] ?? "").toLowerCase() === "yes",
21082
- cpuLoad: num(resKv["cpu-load"]),
21079
+ cpuLoad: parsePercent(resKv["cpu-load"]) ?? 0,
21083
21080
  memoryUsedPct: memUsedPct,
21084
21081
  uptime: resKv.uptime ?? "",
21085
21082
  rosVersion: resKv.version ?? "",
@@ -21087,23 +21084,6 @@ async function collectDiagnosticData(target, ctx, dimensions) {
21087
21084
  tunnelInterfaces: tunnels
21088
21085
  };
21089
21086
  }
21090
- function isIpLike(s) {
21091
- return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d+)?$/.test(s);
21092
- }
21093
- function parseMemMiB(s) {
21094
- if (!s)
21095
- return 0;
21096
- const m = s.match(/([\d.]+)\s*(MiB|GiB|KiB)?/i);
21097
- if (!m)
21098
- return 0;
21099
- const v = Number.parseFloat(m[1]);
21100
- const u = (m[2] ?? "").toLowerCase();
21101
- if (u === "gib")
21102
- return v * 1024;
21103
- if (u === "kib")
21104
- return v / 1024;
21105
- return v;
21106
- }
21107
21087
  var dimensionEnum = z86.enum(ALL_DIMENSIONS).describe("Diagnostic dimension to investigate.");
21108
21088
  var rootCauseTools = [
21109
21089
  defineTool({
@@ -21145,9 +21125,9 @@ var rootCauseTools = [
21145
21125
  const device = resolveDeviceName(ctx.device);
21146
21126
  ctx.info(`Tracing path from '${device}' to ${a.target}`);
21147
21127
  const [traceResult, pingResult, routeResult] = await Promise.all([
21148
- safe3(`/tool traceroute ${quoteValue(a.target)} count=${a.count} use-dns=${a.use_dns ? "yes" : "no"}`, ctx),
21149
- safe3(`/ping ${quoteValue(a.target)} count=5`, ctx),
21150
- safe3(`/ip route print where dst-address=0.0.0.0/0`, ctx)
21128
+ safe(`/tool traceroute ${quoteValue(a.target)} count=${a.count} use-dns=${a.use_dns ? "yes" : "no"}`, ctx),
21129
+ safe(`/ping ${quoteValue(a.target)} count=5`, ctx),
21130
+ safe(`/ip route print where dst-address=0.0.0.0/0`, ctx)
21151
21131
  ]);
21152
21132
  const ping = parsePingSummary(pingResult);
21153
21133
  const lines = [];
@@ -21209,7 +21189,7 @@ var rootCauseTools = [
21209
21189
  filters.push(`message~"${a.keyword}"`);
21210
21190
  }
21211
21191
  const where = filters.length > 0 ? ` where ${filters.join(" ")}` : "";
21212
- const raw = await safe3(`/log print detail${where}`, ctx);
21192
+ const raw = await safe(`/log print detail${where}`, ctx);
21213
21193
  if (!raw || isEmpty(raw)) {
21214
21194
  return `No matching log events found on '${device}' in the last ${a.time_window}.`;
21215
21195
  }
@@ -4,7 +4,7 @@ import {
4
4
  allToolModules,
5
5
  moduleCatalog,
6
6
  selectToolModules
7
- } from "./cli-22wrpg71.js";
7
+ } from "./cli-ewkttc56.js";
8
8
  export {
9
9
  selectToolModules,
10
10
  moduleCatalog,
@@ -4,7 +4,7 @@ import {
4
4
  allToolModules,
5
5
  moduleCatalog,
6
6
  selectToolModules
7
- } from "./library-mwcpnqqg.js";
7
+ } from "./library-n3yvjpkm.js";
8
8
  export {
9
9
  selectToolModules,
10
10
  moduleCatalog,