@usex/mikrotik-mcp 3.40.0 → 3.42.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 +2 -2
- package/dist/index.js +2 -2
- package/dist/shared/{cli-28w9b076.js → cli-rptenaf7.js} +1 -1
- package/dist/shared/{cli-jsf7zy89.js → cli-sq4trv31.js} +73 -31
- package/dist/shared/{library-0x8fc0rw.js → library-5cjmn0n7.js} +73 -31
- package/dist/shared/{library-ke4kdmyj.js → library-z22txcv0.js} +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
toggleAaaEntity,
|
|
85
85
|
updateAaaEntity,
|
|
86
86
|
writeBackup
|
|
87
|
-
} from "./shared/cli-
|
|
87
|
+
} from "./shared/cli-sq4trv31.js";
|
|
88
88
|
|
|
89
89
|
// src/cli.ts
|
|
90
90
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -2242,7 +2242,7 @@ function registerPrompts(server) {
|
|
|
2242
2242
|
// package.json
|
|
2243
2243
|
var package_default = {
|
|
2244
2244
|
name: "@usex/mikrotik-mcp",
|
|
2245
|
-
version: "3.
|
|
2245
|
+
version: "3.42.0",
|
|
2246
2246
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
2247
2247
|
keywords: [
|
|
2248
2248
|
"ai",
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
resolveDeviceName,
|
|
22
22
|
selectToolModules,
|
|
23
23
|
setConfig
|
|
24
|
-
} from "./shared/library-
|
|
24
|
+
} from "./shared/library-5cjmn0n7.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.
|
|
134
|
+
version: "3.42.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",
|
|
@@ -6141,7 +6141,7 @@ var cache = null;
|
|
|
6141
6141
|
async function gateway() {
|
|
6142
6142
|
if (cache)
|
|
6143
6143
|
return cache;
|
|
6144
|
-
const { moduleCatalog } = await import("./cli-
|
|
6144
|
+
const { moduleCatalog } = await import("./cli-rptenaf7.js");
|
|
6145
6145
|
const forIndex = [];
|
|
6146
6146
|
const byName = new Map;
|
|
6147
6147
|
for (const mod of moduleCatalog) {
|
|
@@ -18147,21 +18147,34 @@ ${result}`;
|
|
|
18147
18147
|
name: "check_route_path",
|
|
18148
18148
|
title: "Check IPv4 Route Path",
|
|
18149
18149
|
annotations: READ,
|
|
18150
|
-
description: "Resolves which nexthop RouterOS would use for a given IPv4 destination
|
|
18150
|
+
description: "Resolves which nexthop RouterOS would use for a given IPv4 destination " + '\u2014 answers "which gateway will this packet take?" without sending any traffic. ' + "Version-aware: uses `/ip route check` on v6, falls back to " + "`/ip route print where dst-address in <dest> active=yes` on v7+ where the check " + "command was removed. Optionally scoped by `routing_table` (v7) / `routing_mark` (v6) " + "for policy-routing table lookups. " + "For listing all known routes use list_routes; for a named-table view use get_routing_table. " + "Returns the resolved nexthop and interface detail.",
|
|
18151
18151
|
inputSchema: {
|
|
18152
18152
|
destination: z83.string(),
|
|
18153
|
-
|
|
18154
|
-
routing_mark: z83.string().optional()
|
|
18153
|
+
routing_table: z83.string().optional().describe('Policy-routing table name (v7) or routing-mark (v6), e.g. "VPN"')
|
|
18155
18154
|
},
|
|
18156
18155
|
async handler(a, ctx) {
|
|
18157
18156
|
ctx.info(`Checking route path to: ${a.destination}`);
|
|
18158
|
-
const
|
|
18159
|
-
const
|
|
18160
|
-
|
|
18161
|
-
|
|
18157
|
+
const table = a.routing_table;
|
|
18158
|
+
const v6Cmd = new Cmd(`/ip route check ${a.destination}`).opt("routing-mark", table).build();
|
|
18159
|
+
const v6Result = await executeMikrotikCommand(v6Cmd, ctx);
|
|
18160
|
+
if (!commandUnsupported(v6Result) && !looksLikeError(v6Result) && !isEmpty(v6Result)) {
|
|
18161
|
+
return `ROUTE PATH TO ${a.destination}:
|
|
18162
|
+
|
|
18163
|
+
${v6Result}`;
|
|
18164
|
+
}
|
|
18165
|
+
const where = [`dst-address in ${a.destination}`, "active=yes"];
|
|
18166
|
+
if (table)
|
|
18167
|
+
where.push(`routing-table=${quoteValue(table)}`);
|
|
18168
|
+
const v7Cmd = `/ip route print detail where ${where.join(" ")}`;
|
|
18169
|
+
const v7Result = await executeMikrotikCommand(v7Cmd, ctx);
|
|
18170
|
+
if (looksLikeError(v7Result))
|
|
18171
|
+
return `Failed to check route to ${a.destination}: ${v7Result}`;
|
|
18172
|
+
if (isEmpty(v7Result)) {
|
|
18173
|
+
return `No active route to ${a.destination}${table ? ` in table '${table}'` : ""}.`;
|
|
18174
|
+
}
|
|
18162
18175
|
return `ROUTE PATH TO ${a.destination}:
|
|
18163
18176
|
|
|
18164
|
-
${
|
|
18177
|
+
${v7Result}`;
|
|
18165
18178
|
}
|
|
18166
18179
|
}),
|
|
18167
18180
|
defineTool({
|
|
@@ -20650,7 +20663,7 @@ var drDrillTools = [
|
|
|
20650
20663
|
name: "run_failover_drill",
|
|
20651
20664
|
title: "Run Failover DR Drill",
|
|
20652
20665
|
annotations: DANGEROUS,
|
|
20653
|
-
description: "Rehearses a disaster
|
|
20666
|
+
description: "Rehearses a disaster: disables a target (a WAN/tunnel interface, or a route), pings " + "`verify_host` to check the backup path actually carries traffic, then RE-ENABLES the target. " + "Prefers Safe Mode (auto-revert on disconnect) but falls back to direct commands when Safe " + "Mode is unavailable. Proves your failover works without a real outage. Requires `confirm=true` " + "to run (it briefly disrupts traffic on the target); with confirm=false it just describes the " + "drill. Returns whether connectivity held during the simulated failure.",
|
|
20654
20667
|
inputSchema: {
|
|
20655
20668
|
target_type: z98.enum(["interface", "route"]).default("interface"),
|
|
20656
20669
|
target: z98.string().describe("Interface name (e.g. 'ether1-wan') or, for route, a find expression / .id"),
|
|
@@ -20659,37 +20672,66 @@ var drDrillTools = [
|
|
|
20659
20672
|
confirm: z98.boolean().default(false).describe("Must be true to actually run the drill")
|
|
20660
20673
|
},
|
|
20661
20674
|
async handler(a, ctx) {
|
|
20662
|
-
const
|
|
20675
|
+
const findExpr = a.target.startsWith("*") ? `.id=${a.target}` : a.target;
|
|
20676
|
+
const disableCmd = a.target_type === "interface" ? `/interface disable "${a.target}"` : `/ip route disable [find ${findExpr}]`;
|
|
20677
|
+
const enableCmd = a.target_type === "interface" ? `/interface enable "${a.target}"` : `/ip route enable [find ${findExpr}]`;
|
|
20678
|
+
const pingCmd = `/ping address=${a.verify_host} count=${a.ping_count}`;
|
|
20663
20679
|
if (!a.confirm) {
|
|
20664
|
-
return `DRY RUN \u2014 would
|
|
20680
|
+
return `DRY RUN \u2014 would: (1) ${disableCmd}; (2) ${pingCmd}; (3) re-enable. ` + "Set confirm=true to run the drill.";
|
|
20665
20681
|
}
|
|
20666
20682
|
const device = resolveDeviceName(ctx.device);
|
|
20667
|
-
|
|
20668
|
-
|
|
20683
|
+
const isMac = !!getDevice(device).mac;
|
|
20684
|
+
let usedSafeMode = false;
|
|
20685
|
+
if (!isMac) {
|
|
20686
|
+
const mgr = getSafeModeManager(device);
|
|
20687
|
+
try {
|
|
20688
|
+
const en = await mgr.enable();
|
|
20689
|
+
if (!en.startsWith("Error")) {
|
|
20690
|
+
usedSafeMode = true;
|
|
20691
|
+
try {
|
|
20692
|
+
const disableOut2 = await mgr.execute(disableCmd);
|
|
20693
|
+
if (/no such item|failure:|syntax error/i.test(disableOut2)) {
|
|
20694
|
+
return `Could not disable target '${a.target}': ${disableOut2.trim()} (Safe Mode rolled back).`;
|
|
20695
|
+
}
|
|
20696
|
+
const pingOut = await mgr.execute(pingCmd);
|
|
20697
|
+
return formatResult(a, pingOut, "The change has been rolled back (Safe Mode); nothing was committed.");
|
|
20698
|
+
} finally {
|
|
20699
|
+
await mgr.rollback();
|
|
20700
|
+
}
|
|
20701
|
+
}
|
|
20702
|
+
} catch {
|
|
20703
|
+
try {
|
|
20704
|
+
await mgr.rollback();
|
|
20705
|
+
} catch {}
|
|
20706
|
+
}
|
|
20707
|
+
}
|
|
20708
|
+
if (!usedSafeMode) {
|
|
20709
|
+
ctx.info("Safe Mode unavailable \u2014 using direct disable/enable fallback");
|
|
20710
|
+
}
|
|
20711
|
+
const disableOut = await executeMikrotikCommand(disableCmd, ctx);
|
|
20712
|
+
if (looksLikeError(disableOut) || /no such item/i.test(disableOut)) {
|
|
20713
|
+
return `Could not disable target '${a.target}': ${disableOut.trim()}`;
|
|
20669
20714
|
}
|
|
20670
|
-
const mgr = getSafeModeManager(device);
|
|
20671
|
-
const en = await mgr.enable();
|
|
20672
|
-
if (en.startsWith("Error"))
|
|
20673
|
-
return `Could not enter Safe Mode: ${en}`;
|
|
20674
20715
|
try {
|
|
20675
|
-
const
|
|
20676
|
-
|
|
20677
|
-
|
|
20678
|
-
|
|
20679
|
-
const pingOut = await mgr.execute(`/ping address=${a.verify_host} count=${a.ping_count}`);
|
|
20680
|
-
const summary = parsePingSummary(pingOut);
|
|
20681
|
-
const held = summary != null && summary.received > 0;
|
|
20682
|
-
const detail = summary ? `${summary.received}/${summary.sent} replies (${summary.lossPct}% loss)` : "could not parse ping result";
|
|
20683
|
-
const verdict = held ? "\u2705 Backup path HELD \u2014 failover works." : "\u26D4 NO connectivity during the outage \u2014 failover did NOT carry traffic.";
|
|
20684
|
-
return `DR DRILL \u2014 disabled ${a.target_type} '${a.target}', pinged ${a.verify_host}: ${detail}.
|
|
20685
|
-
${verdict}
|
|
20686
|
-
The change has been rolled back (Safe Mode); nothing was committed.`;
|
|
20716
|
+
const pingOut = await executeMikrotikCommand(pingCmd, ctx, {
|
|
20717
|
+
maxMs: (a.ping_count + 2) * 1500
|
|
20718
|
+
});
|
|
20719
|
+
return formatResult(a, pingOut, "The target has been re-enabled (direct command); the drill is complete.");
|
|
20687
20720
|
} finally {
|
|
20688
|
-
await
|
|
20721
|
+
await executeMikrotikCommand(enableCmd, ctx);
|
|
20689
20722
|
}
|
|
20690
20723
|
}
|
|
20691
20724
|
})
|
|
20692
20725
|
];
|
|
20726
|
+
function formatResult(a, pingOut, footer) {
|
|
20727
|
+
const summary = parsePingSummary(pingOut);
|
|
20728
|
+
const held = summary != null && summary.received > 0;
|
|
20729
|
+
const detail = summary ? `${summary.received}/${summary.sent} replies (${summary.lossPct}% loss)` : "could not parse ping result";
|
|
20730
|
+
const verdict = held ? "PASS \u2014 Backup path HELD, failover works." : "FAIL \u2014 NO connectivity during the outage, failover did NOT carry traffic.";
|
|
20731
|
+
return `DR DRILL \u2014 disabled ${a.target_type} '${a.target}', pinged ${a.verify_host}: ${detail}.
|
|
20732
|
+
${verdict}
|
|
20733
|
+
${footer}`;
|
|
20734
|
+
}
|
|
20693
20735
|
|
|
20694
20736
|
// src/tools/safe-mode.ts
|
|
20695
20737
|
var safeModeTools = [
|
|
@@ -6117,7 +6117,7 @@ var cache = null;
|
|
|
6117
6117
|
async function gateway() {
|
|
6118
6118
|
if (cache)
|
|
6119
6119
|
return cache;
|
|
6120
|
-
const { moduleCatalog } = await import("./library-
|
|
6120
|
+
const { moduleCatalog } = await import("./library-z22txcv0.js");
|
|
6121
6121
|
const forIndex = [];
|
|
6122
6122
|
const byName = new Map;
|
|
6123
6123
|
for (const mod of moduleCatalog) {
|
|
@@ -18123,21 +18123,34 @@ ${result}`;
|
|
|
18123
18123
|
name: "check_route_path",
|
|
18124
18124
|
title: "Check IPv4 Route Path",
|
|
18125
18125
|
annotations: READ,
|
|
18126
|
-
description: "Resolves which nexthop RouterOS would use for a given IPv4 destination
|
|
18126
|
+
description: "Resolves which nexthop RouterOS would use for a given IPv4 destination " + '\u2014 answers "which gateway will this packet take?" without sending any traffic. ' + "Version-aware: uses `/ip route check` on v6, falls back to " + "`/ip route print where dst-address in <dest> active=yes` on v7+ where the check " + "command was removed. Optionally scoped by `routing_table` (v7) / `routing_mark` (v6) " + "for policy-routing table lookups. " + "For listing all known routes use list_routes; for a named-table view use get_routing_table. " + "Returns the resolved nexthop and interface detail.",
|
|
18127
18127
|
inputSchema: {
|
|
18128
18128
|
destination: z83.string(),
|
|
18129
|
-
|
|
18130
|
-
routing_mark: z83.string().optional()
|
|
18129
|
+
routing_table: z83.string().optional().describe('Policy-routing table name (v7) or routing-mark (v6), e.g. "VPN"')
|
|
18131
18130
|
},
|
|
18132
18131
|
async handler(a, ctx) {
|
|
18133
18132
|
ctx.info(`Checking route path to: ${a.destination}`);
|
|
18134
|
-
const
|
|
18135
|
-
const
|
|
18136
|
-
|
|
18137
|
-
|
|
18133
|
+
const table = a.routing_table;
|
|
18134
|
+
const v6Cmd = new Cmd(`/ip route check ${a.destination}`).opt("routing-mark", table).build();
|
|
18135
|
+
const v6Result = await executeMikrotikCommand(v6Cmd, ctx);
|
|
18136
|
+
if (!commandUnsupported(v6Result) && !looksLikeError(v6Result) && !isEmpty(v6Result)) {
|
|
18137
|
+
return `ROUTE PATH TO ${a.destination}:
|
|
18138
|
+
|
|
18139
|
+
${v6Result}`;
|
|
18140
|
+
}
|
|
18141
|
+
const where = [`dst-address in ${a.destination}`, "active=yes"];
|
|
18142
|
+
if (table)
|
|
18143
|
+
where.push(`routing-table=${quoteValue(table)}`);
|
|
18144
|
+
const v7Cmd = `/ip route print detail where ${where.join(" ")}`;
|
|
18145
|
+
const v7Result = await executeMikrotikCommand(v7Cmd, ctx);
|
|
18146
|
+
if (looksLikeError(v7Result))
|
|
18147
|
+
return `Failed to check route to ${a.destination}: ${v7Result}`;
|
|
18148
|
+
if (isEmpty(v7Result)) {
|
|
18149
|
+
return `No active route to ${a.destination}${table ? ` in table '${table}'` : ""}.`;
|
|
18150
|
+
}
|
|
18138
18151
|
return `ROUTE PATH TO ${a.destination}:
|
|
18139
18152
|
|
|
18140
|
-
${
|
|
18153
|
+
${v7Result}`;
|
|
18141
18154
|
}
|
|
18142
18155
|
}),
|
|
18143
18156
|
defineTool({
|
|
@@ -20626,7 +20639,7 @@ var drDrillTools = [
|
|
|
20626
20639
|
name: "run_failover_drill",
|
|
20627
20640
|
title: "Run Failover DR Drill",
|
|
20628
20641
|
annotations: DANGEROUS,
|
|
20629
|
-
description: "Rehearses a disaster
|
|
20642
|
+
description: "Rehearses a disaster: disables a target (a WAN/tunnel interface, or a route), pings " + "`verify_host` to check the backup path actually carries traffic, then RE-ENABLES the target. " + "Prefers Safe Mode (auto-revert on disconnect) but falls back to direct commands when Safe " + "Mode is unavailable. Proves your failover works without a real outage. Requires `confirm=true` " + "to run (it briefly disrupts traffic on the target); with confirm=false it just describes the " + "drill. Returns whether connectivity held during the simulated failure.",
|
|
20630
20643
|
inputSchema: {
|
|
20631
20644
|
target_type: z98.enum(["interface", "route"]).default("interface"),
|
|
20632
20645
|
target: z98.string().describe("Interface name (e.g. 'ether1-wan') or, for route, a find expression / .id"),
|
|
@@ -20635,37 +20648,66 @@ var drDrillTools = [
|
|
|
20635
20648
|
confirm: z98.boolean().default(false).describe("Must be true to actually run the drill")
|
|
20636
20649
|
},
|
|
20637
20650
|
async handler(a, ctx) {
|
|
20638
|
-
const
|
|
20651
|
+
const findExpr = a.target.startsWith("*") ? `.id=${a.target}` : a.target;
|
|
20652
|
+
const disableCmd = a.target_type === "interface" ? `/interface disable "${a.target}"` : `/ip route disable [find ${findExpr}]`;
|
|
20653
|
+
const enableCmd = a.target_type === "interface" ? `/interface enable "${a.target}"` : `/ip route enable [find ${findExpr}]`;
|
|
20654
|
+
const pingCmd = `/ping address=${a.verify_host} count=${a.ping_count}`;
|
|
20639
20655
|
if (!a.confirm) {
|
|
20640
|
-
return `DRY RUN \u2014 would
|
|
20656
|
+
return `DRY RUN \u2014 would: (1) ${disableCmd}; (2) ${pingCmd}; (3) re-enable. ` + "Set confirm=true to run the drill.";
|
|
20641
20657
|
}
|
|
20642
20658
|
const device = resolveDeviceName(ctx.device);
|
|
20643
|
-
|
|
20644
|
-
|
|
20659
|
+
const isMac = !!getDevice(device).mac;
|
|
20660
|
+
let usedSafeMode = false;
|
|
20661
|
+
if (!isMac) {
|
|
20662
|
+
const mgr = getSafeModeManager(device);
|
|
20663
|
+
try {
|
|
20664
|
+
const en = await mgr.enable();
|
|
20665
|
+
if (!en.startsWith("Error")) {
|
|
20666
|
+
usedSafeMode = true;
|
|
20667
|
+
try {
|
|
20668
|
+
const disableOut2 = await mgr.execute(disableCmd);
|
|
20669
|
+
if (/no such item|failure:|syntax error/i.test(disableOut2)) {
|
|
20670
|
+
return `Could not disable target '${a.target}': ${disableOut2.trim()} (Safe Mode rolled back).`;
|
|
20671
|
+
}
|
|
20672
|
+
const pingOut = await mgr.execute(pingCmd);
|
|
20673
|
+
return formatResult(a, pingOut, "The change has been rolled back (Safe Mode); nothing was committed.");
|
|
20674
|
+
} finally {
|
|
20675
|
+
await mgr.rollback();
|
|
20676
|
+
}
|
|
20677
|
+
}
|
|
20678
|
+
} catch {
|
|
20679
|
+
try {
|
|
20680
|
+
await mgr.rollback();
|
|
20681
|
+
} catch {}
|
|
20682
|
+
}
|
|
20683
|
+
}
|
|
20684
|
+
if (!usedSafeMode) {
|
|
20685
|
+
ctx.info("Safe Mode unavailable \u2014 using direct disable/enable fallback");
|
|
20686
|
+
}
|
|
20687
|
+
const disableOut = await executeMikrotikCommand(disableCmd, ctx);
|
|
20688
|
+
if (looksLikeError(disableOut) || /no such item/i.test(disableOut)) {
|
|
20689
|
+
return `Could not disable target '${a.target}': ${disableOut.trim()}`;
|
|
20645
20690
|
}
|
|
20646
|
-
const mgr = getSafeModeManager(device);
|
|
20647
|
-
const en = await mgr.enable();
|
|
20648
|
-
if (en.startsWith("Error"))
|
|
20649
|
-
return `Could not enter Safe Mode: ${en}`;
|
|
20650
20691
|
try {
|
|
20651
|
-
const
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
20655
|
-
const pingOut = await mgr.execute(`/ping address=${a.verify_host} count=${a.ping_count}`);
|
|
20656
|
-
const summary = parsePingSummary(pingOut);
|
|
20657
|
-
const held = summary != null && summary.received > 0;
|
|
20658
|
-
const detail = summary ? `${summary.received}/${summary.sent} replies (${summary.lossPct}% loss)` : "could not parse ping result";
|
|
20659
|
-
const verdict = held ? "\u2705 Backup path HELD \u2014 failover works." : "\u26D4 NO connectivity during the outage \u2014 failover did NOT carry traffic.";
|
|
20660
|
-
return `DR DRILL \u2014 disabled ${a.target_type} '${a.target}', pinged ${a.verify_host}: ${detail}.
|
|
20661
|
-
${verdict}
|
|
20662
|
-
The change has been rolled back (Safe Mode); nothing was committed.`;
|
|
20692
|
+
const pingOut = await executeMikrotikCommand(pingCmd, ctx, {
|
|
20693
|
+
maxMs: (a.ping_count + 2) * 1500
|
|
20694
|
+
});
|
|
20695
|
+
return formatResult(a, pingOut, "The target has been re-enabled (direct command); the drill is complete.");
|
|
20663
20696
|
} finally {
|
|
20664
|
-
await
|
|
20697
|
+
await executeMikrotikCommand(enableCmd, ctx);
|
|
20665
20698
|
}
|
|
20666
20699
|
}
|
|
20667
20700
|
})
|
|
20668
20701
|
];
|
|
20702
|
+
function formatResult(a, pingOut, footer) {
|
|
20703
|
+
const summary = parsePingSummary(pingOut);
|
|
20704
|
+
const held = summary != null && summary.received > 0;
|
|
20705
|
+
const detail = summary ? `${summary.received}/${summary.sent} replies (${summary.lossPct}% loss)` : "could not parse ping result";
|
|
20706
|
+
const verdict = held ? "PASS \u2014 Backup path HELD, failover works." : "FAIL \u2014 NO connectivity during the outage, failover did NOT carry traffic.";
|
|
20707
|
+
return `DR DRILL \u2014 disabled ${a.target_type} '${a.target}', pinged ${a.verify_host}: ${detail}.
|
|
20708
|
+
${verdict}
|
|
20709
|
+
${footer}`;
|
|
20710
|
+
}
|
|
20669
20711
|
|
|
20670
20712
|
// src/tools/safe-mode.ts
|
|
20671
20713
|
var safeModeTools = [
|
package/package.json
CHANGED