@usex/mikrotik-mcp 5.5.0 → 5.6.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 +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.js +1 -1
- package/dist/shared/{cli-cvq8pxvn.js → cli-gyqfq9j6.js} +268 -76
- package/dist/shared/{cli-qqwjbjnv.js → cli-h41j14aw.js} +1 -1
- package/dist/shared/{library-5p5mk963.js → library-135yzct0.js} +268 -76
- package/dist/shared/{library-241wsfpw.js → library-v52e871r.js} +1 -1
- package/package.json +1 -1
- package/schemas/tool-catalog.json +17 -5
- package/schemas/tools/add_ospf_interface_template.json +9 -1
- package/schemas/tools/update_wireguard_peer.json +4 -0
|
@@ -42,6 +42,7 @@ var logger = {
|
|
|
42
42
|
|
|
43
43
|
// src/ssh/client.ts
|
|
44
44
|
var RUN_IDLE_TIMEOUT_MS = 60000;
|
|
45
|
+
var RUN_HARD_TIMEOUT_MS = 120000;
|
|
45
46
|
function decodeOutput(data) {
|
|
46
47
|
if (!data || data.length === 0)
|
|
47
48
|
return "";
|
|
@@ -158,11 +159,14 @@ class MikroTikSSHClient {
|
|
|
158
159
|
let settled = false;
|
|
159
160
|
let timer;
|
|
160
161
|
let idleTimer;
|
|
162
|
+
let hardTimer;
|
|
161
163
|
const clearTimers = () => {
|
|
162
164
|
if (timer)
|
|
163
165
|
clearTimeout(timer);
|
|
164
166
|
if (idleTimer)
|
|
165
167
|
clearTimeout(idleTimer);
|
|
168
|
+
if (hardTimer)
|
|
169
|
+
clearTimeout(hardTimer);
|
|
166
170
|
};
|
|
167
171
|
const finish = () => {
|
|
168
172
|
if (settled)
|
|
@@ -204,6 +208,14 @@ class MikroTikSSHClient {
|
|
|
204
208
|
} catch {}
|
|
205
209
|
finish();
|
|
206
210
|
}, opts.maxMs);
|
|
211
|
+
} else {
|
|
212
|
+
hardTimer = setTimeout(() => {
|
|
213
|
+
try {
|
|
214
|
+
stream.signal("INT");
|
|
215
|
+
} catch {}
|
|
216
|
+
const got = Buffer.concat(stdout).length;
|
|
217
|
+
fail(new Error(`MikroTik command exceeded the ${RUN_HARD_TIMEOUT_MS / 1000}s hard timeout and was ` + `aborted (${got} bytes received, output discarded as incomplete). ` + `Command: ${command.slice(0, 120)}`));
|
|
218
|
+
}, RUN_HARD_TIMEOUT_MS);
|
|
207
219
|
}
|
|
208
220
|
stream.on("close", finish).on("data", (d) => {
|
|
209
221
|
stdout.push(d);
|
|
@@ -769,20 +781,36 @@ function deviceLabels() {
|
|
|
769
781
|
}
|
|
770
782
|
return out;
|
|
771
783
|
}
|
|
772
|
-
function
|
|
773
|
-
if (name) {
|
|
774
|
-
if (name in active.devices && isEnabled(active.devices[name]))
|
|
775
|
-
return name;
|
|
776
|
-
const byLabel = deviceKeyForLabel(name);
|
|
777
|
-
if (byLabel)
|
|
778
|
-
return byLabel;
|
|
779
|
-
}
|
|
784
|
+
function defaultDeviceKey() {
|
|
780
785
|
if (active.defaultDevice in active.devices && isEnabled(active.devices[active.defaultDevice])) {
|
|
781
786
|
return active.defaultDevice;
|
|
782
787
|
}
|
|
783
788
|
const firstEnabled = Object.entries(active.devices).find(([, dc]) => isEnabled(dc));
|
|
784
789
|
return firstEnabled ? firstEnabled[0] : active.defaultDevice;
|
|
785
790
|
}
|
|
791
|
+
function tryResolveDeviceName(name) {
|
|
792
|
+
if (!name)
|
|
793
|
+
return;
|
|
794
|
+
if (name in active.devices && isEnabled(active.devices[name]))
|
|
795
|
+
return name;
|
|
796
|
+
return deviceKeyForLabel(name);
|
|
797
|
+
}
|
|
798
|
+
function resolveDeviceName(name) {
|
|
799
|
+
if (!name)
|
|
800
|
+
return defaultDeviceKey();
|
|
801
|
+
const resolved = tryResolveDeviceName(name);
|
|
802
|
+
if (resolved)
|
|
803
|
+
return resolved;
|
|
804
|
+
throw new Error(unknownDeviceMessage(name));
|
|
805
|
+
}
|
|
806
|
+
function unknownDeviceMessage(name) {
|
|
807
|
+
const enabled = listDevices().names;
|
|
808
|
+
const disabled = name in active.devices && !isEnabled(active.devices[name]);
|
|
809
|
+
if (disabled) {
|
|
810
|
+
return `Device '${name}' is disabled. Enable it from the dashboard or config file. Enabled devices: ${enabled.join(", ")}`;
|
|
811
|
+
}
|
|
812
|
+
return `Unknown device '${name}'. Enabled devices: ${enabled.join(", ") || "(none)"}. ` + "Call list_mikrotik_devices for the authoritative current set \u2014 this was NOT run against " + "the default device.";
|
|
813
|
+
}
|
|
786
814
|
function deviceTarget(dc) {
|
|
787
815
|
if (!dc)
|
|
788
816
|
return "?";
|
|
@@ -802,9 +830,6 @@ function resolvedTarget(name) {
|
|
|
802
830
|
return { key, label: dc?.description?.trim() || undefined, target: deviceTarget(dc) };
|
|
803
831
|
}
|
|
804
832
|
function getDevice(name) {
|
|
805
|
-
if (name && !(name in active.devices) && !deviceKeyForLabel(name)) {
|
|
806
|
-
throw new Error(`Unknown device '${name}'. Configured devices: ${Object.keys(active.devices).join(", ")}`);
|
|
807
|
-
}
|
|
808
833
|
const key = resolveDeviceName(name);
|
|
809
834
|
const dc = active.devices[key];
|
|
810
835
|
if (!dc)
|
|
@@ -2209,6 +2234,27 @@ function parseDetailRecords(lines) {
|
|
|
2209
2234
|
flush();
|
|
2210
2235
|
return rows;
|
|
2211
2236
|
}
|
|
2237
|
+
function filterDetailBlocks(text, predicate) {
|
|
2238
|
+
const lines = text.split(/\r?\n/);
|
|
2239
|
+
const firstRecord = lines.findIndex((l) => INDEX_LINE.test(l));
|
|
2240
|
+
if (firstRecord === -1)
|
|
2241
|
+
return text;
|
|
2242
|
+
const preamble = lines.slice(0, firstRecord);
|
|
2243
|
+
const blocks = [];
|
|
2244
|
+
for (const line of lines.slice(firstRecord)) {
|
|
2245
|
+
if (INDEX_LINE.test(line) || blocks.length === 0)
|
|
2246
|
+
blocks.push([]);
|
|
2247
|
+
blocks[blocks.length - 1].push(line);
|
|
2248
|
+
}
|
|
2249
|
+
const kept = blocks.filter((block) => {
|
|
2250
|
+
const [row] = parseDetailRecords(block);
|
|
2251
|
+
return row ? predicate(row) : false;
|
|
2252
|
+
});
|
|
2253
|
+
if (kept.length === 0)
|
|
2254
|
+
return "";
|
|
2255
|
+
return [...preamble, ...kept.flat()].join(`
|
|
2256
|
+
`);
|
|
2257
|
+
}
|
|
2212
2258
|
function parseColumnarRecords(lines) {
|
|
2213
2259
|
const headerIdx = lines.findIndex((l) => /\S/.test(l) && !l.includes("=") && /^[\s#]*[#A-Z]/.test(l) && /[A-Z]/.test(l));
|
|
2214
2260
|
if (headerIdx === -1)
|
|
@@ -2579,8 +2625,20 @@ function portConflictError(result, port) {
|
|
|
2579
2625
|
if (!/configured elsewhere/i.test(result))
|
|
2580
2626
|
return;
|
|
2581
2627
|
const other = result.match(/\*\d+\s*=\s*([A-Za-z][\w-]*)/)?.[1];
|
|
2582
|
-
|
|
2583
|
-
|
|
2628
|
+
if (port == null) {
|
|
2629
|
+
return `the device rejected this change with 'configured elsewhere' \u2014 the command targeted more ` + `than one row, or the value is owned by another item${other ? ` (RouterOS named '${other}')` : ""}. No port was being changed, so this is not a port collision.`;
|
|
2630
|
+
}
|
|
2631
|
+
return other ? `port ${port} is already used by the '${other}' service \u2014 two services can't share a port. Pick a different port, or change/disable '${other}' first (disable_ip_service / set_ip_service).` : `port ${port} is already in use elsewhere \u2014 two items can't share it. Pick a different port, or free it on the conflicting item first.`;
|
|
2632
|
+
}
|
|
2633
|
+
function interfaceValueError(result, fields) {
|
|
2634
|
+
if (!/input does not match any value of interface/i.test(result))
|
|
2635
|
+
return;
|
|
2636
|
+
const sent = fields.filter((f) => f.value !== undefined && f.value !== "");
|
|
2637
|
+
if (sent.length === 0)
|
|
2638
|
+
return;
|
|
2639
|
+
const which = sent.map((f) => `${f.name}='${f.value ?? ""}'`).join(", ");
|
|
2640
|
+
const listHint = sent.map((f) => `${f.name}_list`).join(" / ");
|
|
2641
|
+
return `the device rejected an interface value (${which}) \u2014 it is not the name of an interface ` + `on this router. If you meant an interface LIST (e.g. WAN, LAN), use ${listHint} instead; ` + "list_interfaces shows the valid interface names.";
|
|
2584
2642
|
}
|
|
2585
2643
|
function commandUnsupported(result) {
|
|
2586
2644
|
const t = result.toLowerCase();
|
|
@@ -4399,6 +4457,18 @@ function gatingDecision(requires, multiDevice) {
|
|
|
4399
4457
|
}
|
|
4400
4458
|
return { prefix: `[unavailable on this device: ${unmet}]` };
|
|
4401
4459
|
}
|
|
4460
|
+
var HTML_ENTITIES = /&(amp|lt|gt|quot|#0?39|apos);/i;
|
|
4461
|
+
function htmlEscapedArgument(args) {
|
|
4462
|
+
for (const [key, value] of Object.entries(args)) {
|
|
4463
|
+
if (typeof value !== "string")
|
|
4464
|
+
continue;
|
|
4465
|
+
const hit = value.match(HTML_ENTITIES);
|
|
4466
|
+
if (!hit)
|
|
4467
|
+
continue;
|
|
4468
|
+
return `'${key}' contains HTML-escaped text (${hit[0]}). RouterOS stores it literally, so the ` + `value would be saved with "${hit[0]}" in it and every later exact lookup ` + `(e.g. [find comment="\u2026"]) would fail to match. Send the character itself ` + `(&, <, >, ", ') \u2014 quoting and escaping for the console is handled automatically.`;
|
|
4469
|
+
}
|
|
4470
|
+
return;
|
|
4471
|
+
}
|
|
4402
4472
|
function defineTool(def) {
|
|
4403
4473
|
return {
|
|
4404
4474
|
name: def.name,
|
|
@@ -4432,6 +4502,21 @@ function defineTool(def) {
|
|
|
4432
4502
|
const callback = async (args) => {
|
|
4433
4503
|
const { device, reason, ...rest } = args;
|
|
4434
4504
|
const deviceName = typeof device === "string" ? device : undefined;
|
|
4505
|
+
if (!def.noDevice && deviceName !== undefined && !tryResolveDeviceName(deviceName)) {
|
|
4506
|
+
const text = `Error: ${unknownDeviceMessage(deviceName)}`;
|
|
4507
|
+
logger.error(text);
|
|
4508
|
+
sendLog?.("error", text);
|
|
4509
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
4510
|
+
}
|
|
4511
|
+
if (risk !== "READ") {
|
|
4512
|
+
const escaped = htmlEscapedArgument(rest);
|
|
4513
|
+
if (escaped) {
|
|
4514
|
+
const text = `Error: ${escaped}`;
|
|
4515
|
+
logger.error(text);
|
|
4516
|
+
sendLog?.("error", text);
|
|
4517
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4435
4520
|
const policy = getAccessPolicy();
|
|
4436
4521
|
if (policy.enabled) {
|
|
4437
4522
|
const resolvedDevice = def.noDevice ? undefined : resolveDeviceName(deviceName);
|
|
@@ -8434,7 +8519,12 @@ var complianceAuditTools = [
|
|
|
8434
8519
|
results.push({ device: resolved, report, text });
|
|
8435
8520
|
for (const e of report.evaluatedChecks) {
|
|
8436
8521
|
if (e.result.status === "fail" || e.result.status === "warn") {
|
|
8437
|
-
|
|
8522
|
+
const prev = failTally.get(e.check.id);
|
|
8523
|
+
failTally.set(e.check.id, {
|
|
8524
|
+
count: (prev?.count ?? 0) + 1,
|
|
8525
|
+
label: e.result.label,
|
|
8526
|
+
status: prev?.status === "fail" ? "fail" : e.result.status
|
|
8527
|
+
});
|
|
8438
8528
|
}
|
|
8439
8529
|
}
|
|
8440
8530
|
} catch (err) {
|
|
@@ -8480,11 +8570,12 @@ Error: ${err instanceof Error ? err.message : String(err)}`
|
|
|
8480
8570
|
lines.push("");
|
|
8481
8571
|
}
|
|
8482
8572
|
if (failTally.size > 0) {
|
|
8483
|
-
const sorted = [...failTally.entries()].sort((a2, b) => b[1] - a2[1]).slice(0, 10);
|
|
8573
|
+
const sorted = [...failTally.entries()].sort((a2, b) => b[1].count - a2[1].count).slice(0, 10);
|
|
8484
8574
|
lines.push("\u2500\u2500 MOST COMMON FAILURES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8485
|
-
for (const [id, count] of sorted) {
|
|
8575
|
+
for (const [id, { count, label, status }] of sorted) {
|
|
8486
8576
|
const check = ALL_CHECKS.find((c) => c.id === id);
|
|
8487
|
-
|
|
8577
|
+
const want = check?.title ? ` (should be: ${check.title})` : "";
|
|
8578
|
+
lines.push(` ${String(count).padStart(2)}/${targets.length} devices ${status.toUpperCase().padEnd(4)} ${id.padEnd(30)} ${label}${want}`);
|
|
8488
8579
|
}
|
|
8489
8580
|
lines.push("");
|
|
8490
8581
|
}
|
|
@@ -11010,7 +11101,7 @@ var cache2 = null;
|
|
|
11010
11101
|
async function gateway() {
|
|
11011
11102
|
if (cache2)
|
|
11012
11103
|
return cache2;
|
|
11013
|
-
const { moduleCatalog } = await import("./cli-
|
|
11104
|
+
const { moduleCatalog } = await import("./cli-h41j14aw.js");
|
|
11014
11105
|
const forIndex = [];
|
|
11015
11106
|
const byName = new Map;
|
|
11016
11107
|
for (const mod of moduleCatalog) {
|
|
@@ -16514,20 +16605,36 @@ Management access may be partially restricted \u2014 review immediately.`;
|
|
|
16514
16605
|
import { z as z35 } from "zod";
|
|
16515
16606
|
|
|
16516
16607
|
// src/tools/_resolve-rule-id.ts
|
|
16608
|
+
var VALID_ID = /^(?:\*[0-9a-fA-F]+|\d+)$/;
|
|
16609
|
+
function isValidItemId(id) {
|
|
16610
|
+
return VALID_ID.test(id.trim());
|
|
16611
|
+
}
|
|
16612
|
+
async function listItemIds(scope, ctx) {
|
|
16613
|
+
const idsRaw = await executeMikrotikCommand(`:foreach i in=[${scope} find] do={:put $i}`, ctx);
|
|
16614
|
+
if (isEmpty(idsRaw))
|
|
16615
|
+
return [];
|
|
16616
|
+
return idsRaw.trim().split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
16617
|
+
}
|
|
16517
16618
|
function ruleResolver(scope) {
|
|
16518
16619
|
return async function resolveRuleId(ruleId, ctx) {
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
|
|
16620
|
+
const id = ruleId.trim();
|
|
16621
|
+
if (!isValidItemId(id))
|
|
16622
|
+
return null;
|
|
16623
|
+
if (/^\d+$/.test(id)) {
|
|
16624
|
+
const ids = await listItemIds(scope, ctx);
|
|
16625
|
+
if (ids.length === 0)
|
|
16522
16626
|
return null;
|
|
16523
|
-
const
|
|
16524
|
-
const pos = Number.parseInt(ruleId, 10);
|
|
16627
|
+
const pos = Number.parseInt(id, 10);
|
|
16525
16628
|
return pos >= 0 && pos < ids.length ? ids[pos] : null;
|
|
16526
16629
|
}
|
|
16527
|
-
const count = await executeMikrotikCommand(`${scope} print count-only where .id=${
|
|
16528
|
-
return count.trim() !== "0" ?
|
|
16630
|
+
const count = await executeMikrotikCommand(`${scope} print count-only where .id=${id}`, ctx);
|
|
16631
|
+
return count.trim() !== "0" ? id : null;
|
|
16529
16632
|
};
|
|
16530
16633
|
}
|
|
16634
|
+
function notFoundMessage(what, id, listTool) {
|
|
16635
|
+
const shape = isValidItemId(id) ? "" : ` '${id}' is not a valid reference \u2014 pass the \`.id\` from ${listTool} (e.g. "*1F") or a bare row position (e.g. "3").`;
|
|
16636
|
+
return `${what} '${id}' not found.${shape}`;
|
|
16637
|
+
}
|
|
16531
16638
|
|
|
16532
16639
|
// src/tools/firewall-filter.ts
|
|
16533
16640
|
var isDigits = (s) => /^\d+$/.test(s);
|
|
@@ -16620,8 +16727,13 @@ async function updateFilterRule(a, ctx) {
|
|
|
16620
16727
|
return `Firewall filter rule '${a.rule_id}' not found.`;
|
|
16621
16728
|
const cmd = `/ip firewall filter set ${id} ${updates.join(" ")}`;
|
|
16622
16729
|
const result = await executeMikrotikCommand(cmd, ctx);
|
|
16623
|
-
if (looksLikeError(result))
|
|
16624
|
-
|
|
16730
|
+
if (looksLikeError(result)) {
|
|
16731
|
+
const hint = interfaceValueError(result, [
|
|
16732
|
+
{ name: "in_interface", value: a.in_interface },
|
|
16733
|
+
{ name: "out_interface", value: a.out_interface }
|
|
16734
|
+
]);
|
|
16735
|
+
return `Failed to update firewall filter rule: ${hint ?? result}`;
|
|
16736
|
+
}
|
|
16625
16737
|
const details = await executeMikrotikCommand(`/ip firewall filter print detail where .id=${id}`, ctx);
|
|
16626
16738
|
return `Firewall filter rule updated successfully:
|
|
16627
16739
|
|
|
@@ -16710,7 +16822,10 @@ var firewallFilterTools = [
|
|
|
16710
16822
|
const result = await executeMikrotikCommand(cmd, ctx);
|
|
16711
16823
|
const trimmed = result.trim();
|
|
16712
16824
|
if (looksLikeError(trimmed)) {
|
|
16713
|
-
const hint = placeBeforeError(trimmed, a.place_before)
|
|
16825
|
+
const hint = placeBeforeError(trimmed, a.place_before) ?? interfaceValueError(trimmed, [
|
|
16826
|
+
{ name: "in_interface", value: a.in_interface },
|
|
16827
|
+
{ name: "out_interface", value: a.out_interface }
|
|
16828
|
+
]);
|
|
16714
16829
|
return `Failed to create firewall filter rule: ${hint ?? trimmed}`;
|
|
16715
16830
|
}
|
|
16716
16831
|
const createdId = extractCreatedId(trimmed);
|
|
@@ -16758,18 +16873,31 @@ ${details}`;
|
|
|
16758
16873
|
filters.push(`src-address~"${a.src_address_filter}"`);
|
|
16759
16874
|
if (a.dst_address_filter)
|
|
16760
16875
|
filters.push(`dst-address~"${a.dst_address_filter}"`);
|
|
16761
|
-
if (a.protocol_filter)
|
|
16762
|
-
filters.push(`protocol=${a.protocol_filter}`);
|
|
16763
|
-
if (a.interface_filter) {
|
|
16764
|
-
filters.push(`(in-interface~"${a.interface_filter}" or out-interface~"${a.interface_filter}")`);
|
|
16765
|
-
}
|
|
16766
16876
|
if (a.disabled_only)
|
|
16767
16877
|
filters.push("disabled=yes");
|
|
16768
16878
|
if (a.invalid_only)
|
|
16769
16879
|
filters.push("invalid=yes");
|
|
16770
16880
|
if (a.dynamic_only)
|
|
16771
16881
|
filters.push("dynamic=yes");
|
|
16772
|
-
const
|
|
16882
|
+
const raw = await executeMikrotikCommand(`/ip firewall filter print detail${whereClause(filters)}`, ctx);
|
|
16883
|
+
const protocolWanted = a.protocol_filter?.toLowerCase();
|
|
16884
|
+
const interfaceWanted = a.interface_filter?.toLowerCase();
|
|
16885
|
+
const result = protocolWanted || interfaceWanted ? filterDetailBlocks(raw, (row) => {
|
|
16886
|
+
if (protocolWanted && (row.protocol ?? "").toLowerCase() !== protocolWanted) {
|
|
16887
|
+
return false;
|
|
16888
|
+
}
|
|
16889
|
+
if (interfaceWanted) {
|
|
16890
|
+
const hit = [
|
|
16891
|
+
row["in-interface"],
|
|
16892
|
+
row["out-interface"],
|
|
16893
|
+
row["in-interface-list"],
|
|
16894
|
+
row["out-interface-list"]
|
|
16895
|
+
].some((v) => (v ?? "").toLowerCase().includes(interfaceWanted));
|
|
16896
|
+
if (!hit)
|
|
16897
|
+
return false;
|
|
16898
|
+
}
|
|
16899
|
+
return true;
|
|
16900
|
+
}) : raw;
|
|
16773
16901
|
return isEmpty(result) ? "No firewall filter rules found matching the criteria." : `FIREWALL FILTER RULES:
|
|
16774
16902
|
|
|
16775
16903
|
${result}`;
|
|
@@ -16779,7 +16907,7 @@ ${result}`;
|
|
|
16779
16907
|
name: "get_filter_rule",
|
|
16780
16908
|
title: "Get Firewall Filter Rule",
|
|
16781
16909
|
annotations: READ,
|
|
16782
|
-
description: "Gets the full detail of one IPv4 firewall FILTER rule (`/ip firewall filter`) by id \u2014 " + "every matcher, action, counter and flag. For IPv6 use get_ipv6_filter_rule. " + 'rule_id: preferably the `.id` from list_filter_rules e.g. "*1F". A bare number like "3" ' + "is
|
|
16910
|
+
description: "Gets the full detail of one IPv4 firewall FILTER rule (`/ip firewall filter`) by id \u2014 " + "every matcher, action, counter and flag. For IPv6 use get_ipv6_filter_rule. " + 'rule_id: preferably the `.id` from list_filter_rules e.g. "*1F". A bare number like "3" ' + "is read as the positional ROW INDEX and resolved to whatever `.id` currently sits there \u2014 " + "it is never treated as `.id=*3`, since a `*N` id is hex and reassigned over time. Row " + "positions shift whenever a rule is added, removed or moved, so prefer the `.id`.",
|
|
16783
16911
|
inputSchema: {
|
|
16784
16912
|
rule_id: z35.string().describe('Rule .id e.g. "*1F", or bare position number e.g. "3"')
|
|
16785
16913
|
},
|
|
@@ -16887,10 +17015,23 @@ ${result}`;
|
|
|
16887
17015
|
const id = await resolveFilterRuleId(a.rule_id, ctx);
|
|
16888
17016
|
if (!id)
|
|
16889
17017
|
return `Firewall filter rule '${a.rule_id}' not found.`;
|
|
17018
|
+
const before = await listItemIds("/ip firewall filter", ctx);
|
|
16890
17019
|
const result = await executeMikrotikCommand(`/ip firewall filter move ${id} destination=${a.destination}`, ctx);
|
|
16891
17020
|
if (looksLikeError(result))
|
|
16892
17021
|
return `Failed to move firewall filter rule: ${result}`;
|
|
16893
|
-
|
|
17022
|
+
const after = await listItemIds("/ip firewall filter", ctx);
|
|
17023
|
+
const from = before.indexOf(id);
|
|
17024
|
+
const to = after.indexOf(id);
|
|
17025
|
+
if (to === -1) {
|
|
17026
|
+
return `Firewall filter rule ${id} moved, but it could not be located afterwards \u2014 verify with list_filter_rules.`;
|
|
17027
|
+
}
|
|
17028
|
+
if (to === a.destination) {
|
|
17029
|
+
return `Firewall filter rule '${a.rule_id}' (${id}) moved to position ${to}.`;
|
|
17030
|
+
}
|
|
17031
|
+
if (to === from) {
|
|
17032
|
+
return `NO CHANGE: firewall filter rule '${a.rule_id}' (${id}) is still at position ${to} \u2014 ` + `RouterOS ignored the move to ${a.destination}. Destination is 0-based and counts ` + `ALL rules in the menu (${after.length} total), not just this rule's chain; a ` + "destination at or past the end, or equal to the current position, does nothing.";
|
|
17033
|
+
}
|
|
17034
|
+
return `Firewall filter rule '${a.rule_id}' (${id}) moved from position ${from} to ${to} ` + `(requested ${a.destination} \u2014 RouterOS places the rule BEFORE the item currently at ` + "the destination index, so the final index can differ when moving downward).";
|
|
16894
17035
|
}
|
|
16895
17036
|
}),
|
|
16896
17037
|
defineTool({
|
|
@@ -18630,6 +18771,7 @@ ${result}`;
|
|
|
18630
18771
|
|
|
18631
18772
|
// src/tools/ip-service.ts
|
|
18632
18773
|
import { z as z43 } from "zod";
|
|
18774
|
+
var staticService = (name) => `[find name="${name}" and !dynamic]`;
|
|
18633
18775
|
var ipServiceTools = [
|
|
18634
18776
|
defineTool({
|
|
18635
18777
|
name: "list_ip_services",
|
|
@@ -18675,7 +18817,7 @@ ${result}`;
|
|
|
18675
18817
|
},
|
|
18676
18818
|
async handler(a, ctx) {
|
|
18677
18819
|
ctx.info(`Updating IP service: name=${a.name}`);
|
|
18678
|
-
const cmd = new Cmd(`/ip service set
|
|
18820
|
+
const cmd = new Cmd(`/ip service set ${staticService(a.name)}`).opt("port", a.port).opt("address", a.address).bool("disabled", a.disabled).opt("certificate", a.certificate).opt("vrf", a.vrf).build();
|
|
18679
18821
|
if (!cmd.includes("=", cmd.indexOf("]")))
|
|
18680
18822
|
return "No updates specified.";
|
|
18681
18823
|
const result = await executeMikrotikCommand(cmd, ctx);
|
|
@@ -18699,7 +18841,7 @@ ${details}`;
|
|
|
18699
18841
|
},
|
|
18700
18842
|
async handler(a, ctx) {
|
|
18701
18843
|
ctx.info(`Enabling IP service: name=${a.name}`);
|
|
18702
|
-
const result = await executeMikrotikCommand(`/ip service enable
|
|
18844
|
+
const result = await executeMikrotikCommand(`/ip service enable ${staticService(a.name)}`, ctx);
|
|
18703
18845
|
if (looksLikeError(result))
|
|
18704
18846
|
return `Failed to enable IP service: ${result}`;
|
|
18705
18847
|
return `IP service '${a.name}' enabled successfully.`;
|
|
@@ -18715,7 +18857,7 @@ ${details}`;
|
|
|
18715
18857
|
},
|
|
18716
18858
|
async handler(a, ctx) {
|
|
18717
18859
|
ctx.info(`Disabling IP service: name=${a.name}`);
|
|
18718
|
-
const result = await executeMikrotikCommand(`/ip service disable
|
|
18860
|
+
const result = await executeMikrotikCommand(`/ip service disable ${staticService(a.name)}`, ctx);
|
|
18719
18861
|
if (looksLikeError(result))
|
|
18720
18862
|
return `Failed to disable IP service: ${result}`;
|
|
18721
18863
|
return `IP service '${a.name}' disabled successfully.`;
|
|
@@ -25212,7 +25354,7 @@ var speedTestTools = [
|
|
|
25212
25354
|
},
|
|
25213
25355
|
async handler(a, ctx) {
|
|
25214
25356
|
ctx.info(`Speed test to ${a.address} (test-duration=${a.duration}s)`);
|
|
25215
|
-
const cmd = new Cmd(
|
|
25357
|
+
const cmd = new Cmd("/tool speed-test").set("address", a.address).set("test-duration", `${a.duration}s`).opt("connection-count", a.connection_count).opt("user", a.user).opt("password", a.password).build();
|
|
25216
25358
|
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: a.duration * 1000 + 15000 }));
|
|
25217
25359
|
if (looksLikeError(result))
|
|
25218
25360
|
return `Failed to run speed test to ${a.address}: ${result}`;
|
|
@@ -29895,10 +30037,11 @@ async function collectRoutes(ctx) {
|
|
|
29895
30037
|
dst: r["dst-address"] ?? "",
|
|
29896
30038
|
gateway: r.gateway ?? "",
|
|
29897
30039
|
distance: num(r.distance),
|
|
29898
|
-
active: (r.flags ?? "").includes("A") || r.active
|
|
29899
|
-
dynamic: (r.flags ?? "").includes("D") || r.dynamic
|
|
30040
|
+
active: (r.flags ?? "").includes("A") || isYes(r.active),
|
|
30041
|
+
dynamic: (r.flags ?? "").includes("D") || isYes(r.dynamic)
|
|
29900
30042
|
}));
|
|
29901
|
-
const
|
|
30043
|
+
const isDefaultDst = (dst) => dst === "0.0.0.0/0" || dst === "0.0.0.0/0*";
|
|
30044
|
+
const hasDefault = routes.some((r) => isDefaultDst(r.dst) && r.active);
|
|
29902
30045
|
return { routes, count: routes.length, hasDefault };
|
|
29903
30046
|
}
|
|
29904
30047
|
async function collectOspfNeighbors(ctx) {
|
|
@@ -30409,6 +30552,8 @@ var multiwanTools = [
|
|
|
30409
30552
|
|
|
30410
30553
|
// src/tools/routes.ts
|
|
30411
30554
|
import { z as z99 } from "zod";
|
|
30555
|
+
var resolveRouteId = ruleResolver("/ip route");
|
|
30556
|
+
var routeNotFound = (id) => notFoundMessage("Route", id, "list_routes");
|
|
30412
30557
|
var RouteType2 = z99.enum(["unicast", "blackhole"]);
|
|
30413
30558
|
async function addRoute(a, ctx) {
|
|
30414
30559
|
ctx.info(`Adding route: dst=${a.dst_address}, gateway=${a.gateway}`);
|
|
@@ -30431,10 +30576,13 @@ ${details}` : "Route addition completed but unable to verify.";
|
|
|
30431
30576
|
}
|
|
30432
30577
|
async function setRouteDisabled(routeId, disabled, ctx) {
|
|
30433
30578
|
ctx.info(`Updating route: route_id=${routeId}`);
|
|
30434
|
-
const
|
|
30579
|
+
const id = await resolveRouteId(routeId, ctx);
|
|
30580
|
+
if (!id)
|
|
30581
|
+
return routeNotFound(routeId);
|
|
30582
|
+
const result = await executeMikrotikCommand(`/ip route set ${id} disabled=${yesno(disabled)}`, ctx);
|
|
30435
30583
|
if (looksLikeError(result))
|
|
30436
30584
|
return `Failed to update route: ${result}`;
|
|
30437
|
-
const details = await executeMikrotikCommand(`/ip route print detail where .id=${
|
|
30585
|
+
const details = await executeMikrotikCommand(`/ip route print detail where .id=${id}`, ctx);
|
|
30438
30586
|
return `Route updated successfully:
|
|
30439
30587
|
|
|
30440
30588
|
${details}`;
|
|
@@ -30517,8 +30665,11 @@ ${result}`;
|
|
|
30517
30665
|
},
|
|
30518
30666
|
async handler(a, ctx) {
|
|
30519
30667
|
ctx.info(`Getting route details: route_id=${a.route_id}`);
|
|
30520
|
-
const
|
|
30521
|
-
|
|
30668
|
+
const id = await resolveRouteId(a.route_id, ctx);
|
|
30669
|
+
if (!id)
|
|
30670
|
+
return routeNotFound(a.route_id);
|
|
30671
|
+
const result = await executeMikrotikCommand(`/ip route print detail where .id=${id}`, ctx);
|
|
30672
|
+
return isEmpty(result) ? routeNotFound(a.route_id) : `ROUTE DETAILS:
|
|
30522
30673
|
|
|
30523
30674
|
${result}`;
|
|
30524
30675
|
}
|
|
@@ -30547,7 +30698,10 @@ ${result}`;
|
|
|
30547
30698
|
},
|
|
30548
30699
|
async handler(a, ctx) {
|
|
30549
30700
|
ctx.info(`Updating route: route_id=${a.route_id}`);
|
|
30550
|
-
const
|
|
30701
|
+
const id = await resolveRouteId(a.route_id, ctx);
|
|
30702
|
+
if (!id)
|
|
30703
|
+
return routeNotFound(a.route_id);
|
|
30704
|
+
const base = `/ip route set ${id}`;
|
|
30551
30705
|
const cmd = new Cmd(base);
|
|
30552
30706
|
if (a.dst_address)
|
|
30553
30707
|
cmd.set("dst-address", a.dst_address);
|
|
@@ -30585,8 +30739,8 @@ ${result}`;
|
|
|
30585
30739
|
const result = await executeMikrotikCommand(built, ctx);
|
|
30586
30740
|
if (looksLikeError(result))
|
|
30587
30741
|
return `Failed to update route: ${result}`;
|
|
30588
|
-
const details = await executeMikrotikCommand(`/ip route print detail where .id=${
|
|
30589
|
-
return `Route updated successfully:
|
|
30742
|
+
const details = await executeMikrotikCommand(`/ip route print detail where .id=${id}`, ctx);
|
|
30743
|
+
return `Route ${id} updated successfully:
|
|
30590
30744
|
|
|
30591
30745
|
${details}`;
|
|
30592
30746
|
}
|
|
@@ -30601,13 +30755,16 @@ ${details}`;
|
|
|
30601
30755
|
},
|
|
30602
30756
|
async handler(a, ctx) {
|
|
30603
30757
|
ctx.info(`Removing route: route_id=${a.route_id}`);
|
|
30604
|
-
const
|
|
30605
|
-
if (
|
|
30606
|
-
return
|
|
30607
|
-
const
|
|
30758
|
+
const id = await resolveRouteId(a.route_id, ctx);
|
|
30759
|
+
if (!id)
|
|
30760
|
+
return routeNotFound(a.route_id);
|
|
30761
|
+
const doomed = await executeMikrotikCommand(`/ip route print detail where .id=${id}`, ctx);
|
|
30762
|
+
const result = await executeMikrotikCommand(`/ip route remove ${id}`, ctx);
|
|
30608
30763
|
if (looksLikeError(result))
|
|
30609
30764
|
return `Failed to remove route: ${result}`;
|
|
30610
|
-
return `Route
|
|
30765
|
+
return `Route ${id} removed successfully. Removed entry was:
|
|
30766
|
+
|
|
30767
|
+
${doomed}`;
|
|
30611
30768
|
}
|
|
30612
30769
|
}),
|
|
30613
30770
|
defineTool({
|
|
@@ -32135,14 +32292,22 @@ ${result}`;
|
|
|
32135
32292
|
name: "add_ospf_interface_template",
|
|
32136
32293
|
title: "Add OSPF Interface Template",
|
|
32137
32294
|
annotations: WRITE,
|
|
32138
|
-
description: "Bind interfaces or network prefixes to an OSPF area (`/routing ospf interface-template add`). " + "Match links via `interfaces` (interface or interface-list name) and/or `networks` (prefix, " + "e.g. '10.0.0.0/24'). `type` sets the link model: broadcast (LAN), ptp (point-to-point), " + "ptmp, ptmp-broadcast, nbma, or virtual-link. `passive=true` advertises the subnet without forming OSPF adjacencies " + "(for stub networks). `auth`/`auth_id`/`auth_key` enable per-interface authentication; `hello_interval` " + "e.g. '10s', `dead_interval` e.g. '40s'. " + "Requires the area to already exist (add_ospf_area). " + "Returns the new template id (e.g. '*2') if RouterOS echoes one; use list_ospf_interface_templates to verify.",
|
|
32295
|
+
description: "Bind interfaces or network prefixes to an OSPF area (`/routing ospf interface-template add`). " + "Match links via `interfaces` (interface or interface-list name) and/or `networks` (prefix, " + "e.g. '10.0.0.0/24'). `type` sets the link model: broadcast (LAN), ptp (point-to-point), " + "ptp-unnumbered (point-to-point over an unnumbered link), ptmp, ptmp-broadcast, nbma, or " + "virtual-link. `passive=true` advertises the subnet without forming OSPF adjacencies " + "(for stub networks). `auth`/`auth_id`/`auth_key` enable per-interface authentication; `hello_interval` " + "e.g. '10s', `dead_interval` e.g. '40s'. " + "Requires the area to already exist (add_ospf_area). " + "Returns the new template id (e.g. '*2') if RouterOS echoes one; use list_ospf_interface_templates to verify.",
|
|
32139
32296
|
inputSchema: {
|
|
32140
32297
|
area: z107.string().describe("OSPF area name to attach matched interfaces to"),
|
|
32141
32298
|
interfaces: z107.string().optional().describe("Interface or interface-list name"),
|
|
32142
32299
|
networks: z107.string().optional().describe('Network prefix(es) to enable OSPF on, e.g. "10.0.0.0/24"'),
|
|
32143
32300
|
cost: z107.number().int().optional().describe("Output cost / metric"),
|
|
32144
32301
|
priority: z107.number().int().optional().describe("DR election priority (0 = never DR)"),
|
|
32145
|
-
type: z107.enum([
|
|
32302
|
+
type: z107.enum([
|
|
32303
|
+
"broadcast",
|
|
32304
|
+
"ptp",
|
|
32305
|
+
"ptp-unnumbered",
|
|
32306
|
+
"ptmp",
|
|
32307
|
+
"ptmp-broadcast",
|
|
32308
|
+
"nbma",
|
|
32309
|
+
"virtual-link"
|
|
32310
|
+
]).optional(),
|
|
32146
32311
|
passive: z107.boolean().optional(),
|
|
32147
32312
|
hello_interval: z107.string().optional().describe('e.g. "10s"'),
|
|
32148
32313
|
dead_interval: z107.string().optional().describe('e.g. "40s"'),
|
|
@@ -34988,7 +35153,7 @@ function resolveTargets(targets) {
|
|
|
34988
35153
|
const cfg = getConfig();
|
|
34989
35154
|
const notes = [];
|
|
34990
35155
|
if (Array.isArray(targets)) {
|
|
34991
|
-
const unknown = targets.filter((t) => !(
|
|
35156
|
+
const unknown = targets.filter((t) => !tryResolveDeviceName(t));
|
|
34992
35157
|
if (unknown.length > 0) {
|
|
34993
35158
|
return { devices: [], notes, error: `unknown device(s): ${unknown.join(", ")}` };
|
|
34994
35159
|
}
|
|
@@ -35955,7 +36120,7 @@ var riskByTool = null;
|
|
|
35955
36120
|
async function riskIndex() {
|
|
35956
36121
|
if (riskByTool)
|
|
35957
36122
|
return riskByTool;
|
|
35958
|
-
const { moduleCatalog } = await import("./cli-
|
|
36123
|
+
const { moduleCatalog } = await import("./cli-h41j14aw.js");
|
|
35959
36124
|
const index = new Map;
|
|
35960
36125
|
for (const mod of moduleCatalog) {
|
|
35961
36126
|
for (const tool of mod.tools)
|
|
@@ -42932,6 +43097,8 @@ ${config}`;
|
|
|
42932
43097
|
|
|
42933
43098
|
// src/tools/wireguard.ts
|
|
42934
43099
|
import { z as z138 } from "zod";
|
|
43100
|
+
var resolvePeerId = ruleResolver("/interface wireguard peers");
|
|
43101
|
+
var peerNotFound = (id) => notFoundMessage("WireGuard peer", id, "list_wireguard_peers");
|
|
42935
43102
|
var wireguardTools = [
|
|
42936
43103
|
defineTool({
|
|
42937
43104
|
name: "create_wireguard_interface",
|
|
@@ -43056,17 +43223,26 @@ ${details}`;
|
|
|
43056
43223
|
name: "remove_wireguard_interface",
|
|
43057
43224
|
title: "Remove WireGuard Interface",
|
|
43058
43225
|
annotations: DESTRUCTIVE,
|
|
43059
|
-
description: "Permanently deletes a WireGuard tunnel interface (`/interface wireguard remove`) by name
|
|
43226
|
+
description: "Permanently deletes a WireGuard tunnel interface (`/interface wireguard remove`) by name," + " together with every peer bound to it. Verifies existence first with count-only." + " For removing only a specific peer without touching the interface use remove_wireguard_peer." + " Reports how many peers were removed alongside the interface.",
|
|
43060
43227
|
inputSchema: { name: z138.string() },
|
|
43061
43228
|
async handler(a, ctx) {
|
|
43062
43229
|
ctx.info(`Removing WireGuard interface: name=${a.name}`);
|
|
43063
43230
|
const count = await executeMikrotikCommand(`/interface wireguard print count-only where name="${a.name}"`, ctx);
|
|
43064
43231
|
if (count.trim() === "0")
|
|
43065
43232
|
return `WireGuard interface '${a.name}' not found.`;
|
|
43233
|
+
const peerCount = (await executeMikrotikCommand(`/interface wireguard peers print count-only where interface="${a.name}"`, ctx)).trim();
|
|
43234
|
+
if (peerCount !== "0" && !looksLikeError(peerCount)) {
|
|
43235
|
+
const peerResult = await executeMikrotikCommand(`/interface wireguard peers remove [find interface="${a.name}"]`, ctx);
|
|
43236
|
+
if (looksLikeError(peerResult)) {
|
|
43237
|
+
return `Failed to remove the peers bound to '${a.name}': ${peerResult}
|
|
43238
|
+
` + "The interface was NOT removed \u2014 removing it now would orphan those peers.";
|
|
43239
|
+
}
|
|
43240
|
+
}
|
|
43066
43241
|
const result = await executeMikrotikCommand(`/interface wireguard remove [find name="${a.name}"]`, ctx);
|
|
43067
43242
|
if (looksLikeError(result))
|
|
43068
43243
|
return `Failed to remove WireGuard interface: ${result}`;
|
|
43069
|
-
|
|
43244
|
+
const peers = peerCount === "0" ? "no peers were bound to it" : `${peerCount} peer(s) removed`;
|
|
43245
|
+
return `WireGuard interface '${a.name}' removed successfully (${peers}).`;
|
|
43070
43246
|
}
|
|
43071
43247
|
}),
|
|
43072
43248
|
defineTool({
|
|
@@ -43173,8 +43349,11 @@ ${result}`;
|
|
|
43173
43349
|
},
|
|
43174
43350
|
async handler(a, ctx) {
|
|
43175
43351
|
ctx.info(`Getting WireGuard peer details: peer_id=${a.peer_id}`);
|
|
43176
|
-
const
|
|
43177
|
-
|
|
43352
|
+
const id = await resolvePeerId(a.peer_id, ctx);
|
|
43353
|
+
if (!id)
|
|
43354
|
+
return peerNotFound(a.peer_id);
|
|
43355
|
+
const result = await executeMikrotikCommand(`/interface wireguard peers print detail where .id=${id}`, ctx);
|
|
43356
|
+
return isEmpty(result) ? peerNotFound(a.peer_id) : `WIREGUARD PEER DETAILS:
|
|
43178
43357
|
|
|
43179
43358
|
${result}`;
|
|
43180
43359
|
}
|
|
@@ -43192,6 +43371,7 @@ ${result}`;
|
|
|
43192
43371
|
` + ' Pass "" for endpoint_address or preshared_key to clear them.',
|
|
43193
43372
|
inputSchema: {
|
|
43194
43373
|
peer_id: z138.string().describe('"*N" or "N" from list output e.g. "*2"'),
|
|
43374
|
+
public_key: z138.string().optional().describe("The peer's base64 public key \u2014 re-key a peer in place instead of removing and " + "re-adding it (which would lose the rest of its configuration)."),
|
|
43195
43375
|
allowed_address: z138.string().optional(),
|
|
43196
43376
|
endpoint_address: z138.string().optional(),
|
|
43197
43377
|
endpoint_port: z138.number().int().optional(),
|
|
@@ -43210,15 +43390,18 @@ ${result}`;
|
|
|
43210
43390
|
},
|
|
43211
43391
|
async handler(a, ctx) {
|
|
43212
43392
|
ctx.info(`Updating WireGuard peer: peer_id=${a.peer_id}`);
|
|
43213
|
-
const
|
|
43214
|
-
|
|
43393
|
+
const id = await resolvePeerId(a.peer_id, ctx);
|
|
43394
|
+
if (!id)
|
|
43395
|
+
return peerNotFound(a.peer_id);
|
|
43396
|
+
const base = `/interface wireguard peers set ${id}`;
|
|
43397
|
+
const cmd = new Cmd(base).raw(a.public_key !== undefined ? `public-key=${quoteValue(a.public_key)}` : null).raw(a.allowed_address !== undefined ? `allowed-address=${quoteValue(a.allowed_address)}` : null).raw(a.endpoint_address !== undefined ? a.endpoint_address === "" ? "!endpoint-address" : `endpoint-address=${quoteValue(a.endpoint_address)}` : null).opt("endpoint-port", a.endpoint_port).raw(a.preshared_key !== undefined ? a.preshared_key === "" ? "!preshared-key" : `preshared-key=${quoteValue(a.preshared_key)}` : null).raw(a.persistent_keepalive !== undefined ? `persistent-keepalive=${a.persistent_keepalive}` : null).opt("name", a.name).opt("private-key", a.private_key).bool("responder", a.responder).raw(a.client_address !== undefined ? a.client_address === "" ? "!client-address" : `client-address=${quoteValue(a.client_address)}` : null).raw(a.client_dns !== undefined ? a.client_dns === "" ? "!client-dns" : `client-dns=${quoteValue(a.client_dns)}` : null).raw(a.client_endpoint !== undefined ? a.client_endpoint === "" ? "!client-endpoint" : `client-endpoint=${quoteValue(a.client_endpoint)}` : null).opt("client-keepalive", a.client_keepalive).opt("client-listen-port", a.client_listen_port).raw(a.comment !== undefined ? `comment=${quoteValue(a.comment)}` : null).bool("disabled", a.disabled).build();
|
|
43215
43398
|
if (cmd === base)
|
|
43216
43399
|
return "No updates specified.";
|
|
43217
43400
|
const result = await executeMikrotikCommand(cmd, ctx);
|
|
43218
43401
|
if (looksLikeError(result))
|
|
43219
43402
|
return `Failed to update WireGuard peer: ${result}`;
|
|
43220
|
-
const details = await executeMikrotikCommand(`/interface wireguard peers print detail where .id=${
|
|
43221
|
-
return `WireGuard peer updated successfully:
|
|
43403
|
+
const details = await executeMikrotikCommand(`/interface wireguard peers print detail where .id=${id}`, ctx);
|
|
43404
|
+
return `WireGuard peer ${id} updated successfully:
|
|
43222
43405
|
|
|
43223
43406
|
${details}`;
|
|
43224
43407
|
}
|
|
@@ -43236,13 +43419,16 @@ ${details}`;
|
|
|
43236
43419
|
},
|
|
43237
43420
|
async handler(a, ctx) {
|
|
43238
43421
|
ctx.info(`Removing WireGuard peer: peer_id=${a.peer_id}`);
|
|
43239
|
-
const
|
|
43240
|
-
if (
|
|
43241
|
-
return
|
|
43242
|
-
const
|
|
43422
|
+
const id = await resolvePeerId(a.peer_id, ctx);
|
|
43423
|
+
if (!id)
|
|
43424
|
+
return peerNotFound(a.peer_id);
|
|
43425
|
+
const doomed = await executeMikrotikCommand(`/interface wireguard peers print detail where .id=${id}`, ctx);
|
|
43426
|
+
const result = await executeMikrotikCommand(`/interface wireguard peers remove ${id}`, ctx);
|
|
43243
43427
|
if (looksLikeError(result))
|
|
43244
43428
|
return `Failed to remove WireGuard peer: ${result}`;
|
|
43245
|
-
return `WireGuard peer
|
|
43429
|
+
return `WireGuard peer ${id} removed successfully. Removed entry was:
|
|
43430
|
+
|
|
43431
|
+
${doomed}`;
|
|
43246
43432
|
}
|
|
43247
43433
|
}),
|
|
43248
43434
|
defineTool({
|
|
@@ -43258,10 +43444,13 @@ ${details}`;
|
|
|
43258
43444
|
},
|
|
43259
43445
|
async handler(a, ctx) {
|
|
43260
43446
|
ctx.info(`Enabling WireGuard peer: peer_id=${a.peer_id}`);
|
|
43261
|
-
const
|
|
43447
|
+
const id = await resolvePeerId(a.peer_id, ctx);
|
|
43448
|
+
if (!id)
|
|
43449
|
+
return peerNotFound(a.peer_id);
|
|
43450
|
+
const result = await executeMikrotikCommand(`/interface wireguard peers enable ${id}`, ctx);
|
|
43262
43451
|
if (looksLikeError(result))
|
|
43263
43452
|
return `Failed to enable WireGuard peer: ${result}`;
|
|
43264
|
-
return `WireGuard peer
|
|
43453
|
+
return `WireGuard peer ${id} enabled successfully.`;
|
|
43265
43454
|
}
|
|
43266
43455
|
}),
|
|
43267
43456
|
defineTool({
|
|
@@ -43277,10 +43466,13 @@ ${details}`;
|
|
|
43277
43466
|
},
|
|
43278
43467
|
async handler(a, ctx) {
|
|
43279
43468
|
ctx.info(`Disabling WireGuard peer: peer_id=${a.peer_id}`);
|
|
43280
|
-
const
|
|
43469
|
+
const id = await resolvePeerId(a.peer_id, ctx);
|
|
43470
|
+
if (!id)
|
|
43471
|
+
return peerNotFound(a.peer_id);
|
|
43472
|
+
const result = await executeMikrotikCommand(`/interface wireguard peers disable ${id}`, ctx);
|
|
43281
43473
|
if (looksLikeError(result))
|
|
43282
43474
|
return `Failed to disable WireGuard peer: ${result}`;
|
|
43283
|
-
return `WireGuard peer
|
|
43475
|
+
return `WireGuard peer ${id} disabled successfully.`;
|
|
43284
43476
|
}
|
|
43285
43477
|
}),
|
|
43286
43478
|
defineTool({
|