@usex/mikrotik-mcp 3.60.0 → 4.1.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/README.md CHANGED
@@ -113,7 +113,7 @@ precedence over a password. Full configuration reference:
113
113
  ### From source
114
114
 
115
115
  ```bash
116
- git clone https://github.com/ali-master/mikrotik-mcp && cd mikrotik-mcp
116
+ git clone https://github.com/mikrotik-mcp/mikrotik-mcp && cd mikrotik-mcp
117
117
  bun install
118
118
  bun run start # serve from source
119
119
  bun run build # bundle to dist/
package/dist/cli.js CHANGED
@@ -106,7 +106,7 @@ import {
106
106
  updateAaaEntity,
107
107
  updateSummaryLine,
108
108
  writeBackup
109
- } from "./shared/cli-ghatyv7e.js";
109
+ } from "./shared/cli-2dtt405d.js";
110
110
 
111
111
  // src/cli.ts
112
112
  import { existsSync as existsSync2 } from "fs";
@@ -1564,9 +1564,14 @@ function configPayload() {
1564
1564
  devices: cfg.devices,
1565
1565
  defaultDevice: cfg.defaultDevice,
1566
1566
  mcp: cfg.mcp,
1567
+ s3: cfg.s3,
1567
1568
  dashboard: cfg.dashboard,
1569
+ ssh: cfg.ssh,
1568
1570
  readOnly: cfg.readOnly,
1569
- s3: cfg.s3
1571
+ tools: cfg.tools,
1572
+ memory: cfg.memory,
1573
+ backupDir: cfg.backupDir,
1574
+ disableUpdateCheck: cfg.disableUpdateCheck
1570
1575
  });
1571
1576
  }
1572
1577
  function sseResponse(transportLabel) {
@@ -2718,11 +2723,31 @@ accepts an optional "device" argument to choose which router it runs on; omit it
2718
2723
  to use the default. Use list_mikrotik_devices to see them. For cross-device work
2719
2724
  (e.g. a tunnel between two routers) configure each side by passing the matching
2720
2725
  "device", then verify reachability with ping/traceroute from each end.`;
2726
+ var MEMORY_INSTRUCTIONS = `
2727
+
2728
+ Persistent memory \u2014 you have a knowledge graph that survives across sessions
2729
+ (entities, observations, relations in a local SQLite database). USE IT on every
2730
+ task so context is never lost between conversations:
2731
+ 1. AT THE START of a task, recall what you already know before touching the
2732
+ device: call \`memory_search_nodes\` for the device/subject at hand (or
2733
+ \`memory_read_graph\` for the whole picture on a fresh device). Apply what you
2734
+ find \u2014 do not re-discover facts you already recorded.
2735
+ 2. WHILE WORKING, when you learn a durable fact about the network, a device, a
2736
+ user, or a config pattern (RouterOS version, port layout, VLAN scheme, WAN
2737
+ uplink, owner, recurring fix), record it: \`memory_create_entities\` for new
2738
+ subjects, \`memory_add_observations\` for facts about existing ones, and
2739
+ \`memory_create_relations\` to link them (e.g. router --provides_dhcp_for-->
2740
+ subnet). Every device you touch is auto-added as an entity, so attach
2741
+ observations to it by name.
2742
+ 3. Prefer specific, reusable facts over transient state. Skip one-off command
2743
+ output; record what will still be true next session.`;
2721
2744
  function createServer(opts = {}) {
2722
2745
  process.title = `Mikrotik MCP Server v${VERSION}`;
2723
2746
  const { names, default: defaultDevice } = listDevices();
2724
2747
  const readOnly = getConfig().readOnly;
2725
2748
  let instructions = names.length > 1 ? INSTRUCTIONS + MULTI_DEVICE_INSTRUCTIONS.replace("{{names}}", names.join(", ")).replace("{{default}}", defaultDevice) : INSTRUCTIONS;
2749
+ if (getConfig().memory.enabled)
2750
+ instructions += MEMORY_INSTRUCTIONS;
2726
2751
  if (!getConfig().disableUpdateCheck) {
2727
2752
  const cached = loadFileCacheSync();
2728
2753
  if (cached) {
package/dist/index.d.ts CHANGED
@@ -308,10 +308,20 @@ declare class SafeModeManager {
308
308
  rollback(): Promise<string>;
309
309
  status(): string;
310
310
  /**
311
- * Read from the channel until `isDone(cleaned)` is satisfied or the timeout
312
- * elapses. Defaults to "any RouterOS prompt appeared". Mode transitions pass a
311
+ * Read from the channel until `isDone(cleaned)` is satisfied or the shell goes
312
+ * SILENT. Defaults to "any RouterOS prompt appeared". Mode transitions pass a
313
313
  * stricter predicate so they wait for the prompt that reflects the NEW state
314
314
  * (safe-mode marker present/absent), not merely the first prompt-shaped line.
315
+ *
316
+ * The timeout is an IDLE timeout, not a total budget: every data chunk resets
317
+ * it, so a legitimately slow-but-streaming command (`/terse` on a large
318
+ * config can emit for tens of seconds) never trips it — only a genuine wedge,
319
+ * where no byte has arrived for `idleMs`, does. A separate hard cap (`hardCapMs`)
320
+ * bounds the pathological "dribbles a byte forever" case so the read can't hang
321
+ * indefinitely. Either expiry resolves with `timedOut: true` so callers can tell
322
+ * "the prompt appeared" from "the shell stopped answering" — the latter means the
323
+ * interactive Safe-Mode session is wedged and must abort rather than letting every
324
+ * subsequent command burn its own timeout.
315
325
  */
316
326
  private readUntilPrompt;
317
327
  /**
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  selectToolModules,
30
30
  setConfig,
31
31
  updateSummaryLine
32
- } from "./shared/library-7j22z9a8.js";
32
+ } from "./shared/library-pqnndx9v.js";
33
33
  // src/server.ts
34
34
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
35
35
  import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
@@ -201,11 +201,31 @@ accepts an optional "device" argument to choose which router it runs on; omit it
201
201
  to use the default. Use list_mikrotik_devices to see them. For cross-device work
202
202
  (e.g. a tunnel between two routers) configure each side by passing the matching
203
203
  "device", then verify reachability with ping/traceroute from each end.`;
204
+ var MEMORY_INSTRUCTIONS = `
205
+
206
+ Persistent memory \u2014 you have a knowledge graph that survives across sessions
207
+ (entities, observations, relations in a local SQLite database). USE IT on every
208
+ task so context is never lost between conversations:
209
+ 1. AT THE START of a task, recall what you already know before touching the
210
+ device: call \`memory_search_nodes\` for the device/subject at hand (or
211
+ \`memory_read_graph\` for the whole picture on a fresh device). Apply what you
212
+ find \u2014 do not re-discover facts you already recorded.
213
+ 2. WHILE WORKING, when you learn a durable fact about the network, a device, a
214
+ user, or a config pattern (RouterOS version, port layout, VLAN scheme, WAN
215
+ uplink, owner, recurring fix), record it: \`memory_create_entities\` for new
216
+ subjects, \`memory_add_observations\` for facts about existing ones, and
217
+ \`memory_create_relations\` to link them (e.g. router --provides_dhcp_for-->
218
+ subnet). Every device you touch is auto-added as an entity, so attach
219
+ observations to it by name.
220
+ 3. Prefer specific, reusable facts over transient state. Skip one-off command
221
+ output; record what will still be true next session.`;
204
222
  function createServer(opts = {}) {
205
223
  process.title = `Mikrotik MCP Server v${VERSION}`;
206
224
  const { names, default: defaultDevice } = listDevices();
207
225
  const readOnly = getConfig().readOnly;
208
226
  let instructions = names.length > 1 ? INSTRUCTIONS + MULTI_DEVICE_INSTRUCTIONS.replace("{{names}}", names.join(", ")).replace("{{default}}", defaultDevice) : INSTRUCTIONS;
227
+ if (getConfig().memory.enabled)
228
+ instructions += MEMORY_INSTRUCTIONS;
209
229
  if (!getConfig().disableUpdateCheck) {
210
230
  const cached = loadFileCacheSync();
211
231
  if (cached) {
@@ -436,7 +436,22 @@ function parseDevicesSource(raw, fromFile) {
436
436
  const mcp = structured && obj.mcp && typeof obj.mcp === "object" ? obj.mcp : undefined;
437
437
  const ssh = structured && obj.ssh && typeof obj.ssh === "object" ? obj.ssh : undefined;
438
438
  const memory = structured && obj.memory && typeof obj.memory === "object" ? obj.memory : undefined;
439
- return { devices, defaultDevice, s3, dashboard, tools, mcp, ssh, memory };
439
+ const readOnly = structured && typeof obj.readOnly === "boolean" ? obj.readOnly : undefined;
440
+ const disableUpdateCheck = structured && typeof obj.disableUpdateCheck === "boolean" ? obj.disableUpdateCheck : undefined;
441
+ const backupDir = structured && typeof obj.backupDir === "string" ? obj.backupDir : undefined;
442
+ return {
443
+ devices,
444
+ defaultDevice,
445
+ s3,
446
+ dashboard,
447
+ tools,
448
+ mcp,
449
+ ssh,
450
+ memory,
451
+ readOnly,
452
+ disableUpdateCheck,
453
+ backupDir
454
+ };
440
455
  }
441
456
  var configSource = { path: DEFAULT_CONFIG_FILE, fromFile: false };
442
457
  function getConfigSource() {
@@ -445,6 +460,13 @@ function getConfigSource() {
445
460
  function loadConfig(argv = process.argv.slice(2)) {
446
461
  const flags = parseFlags(argv);
447
462
  const pick = (flag, ...envNames) => flags[flag] ?? env(...envNames);
463
+ const overlay = (file, over) => {
464
+ const out = { ...file };
465
+ for (const [k, v] of Object.entries(over))
466
+ if (v !== undefined)
467
+ out[k] = v;
468
+ return out;
469
+ };
448
470
  const jumpHostName = pick("jump-host", "MIKROTIK_JUMP_HOST");
449
471
  const jumpHost = jumpHostName ? {
450
472
  host: jumpHostName,
@@ -485,6 +507,9 @@ function loadConfig(argv = process.argv.slice(2)) {
485
507
  let fileMcp;
486
508
  let fileSsh;
487
509
  let fileMemory;
510
+ let fileReadOnly;
511
+ let fileDisableUpdateCheck;
512
+ let fileBackupDir;
488
513
  if (configFile || devicesInline) {
489
514
  const src = configFile ? parseDevicesSource(configFile, true) : parseDevicesSource(devicesInline, false);
490
515
  for (const [name, dc] of Object.entries(src.devices))
@@ -499,8 +524,11 @@ function loadConfig(argv = process.argv.slice(2)) {
499
524
  fileMcp = src.mcp;
500
525
  fileSsh = src.ssh;
501
526
  fileMemory = src.memory;
527
+ fileReadOnly = src.readOnly;
528
+ fileDisableUpdateCheck = src.disableUpdateCheck;
529
+ fileBackupDir = src.backupDir;
502
530
  }
503
- const s3 = {
531
+ const s3 = overlay(fileS3, {
504
532
  accessKeyId: pick("s3-access-key-id", "S3_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID"),
505
533
  secretAccessKey: pick("s3-secret-access-key", "S3_SECRET_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY"),
506
534
  sessionToken: pick("s3-session-token", "S3_SESSION_TOKEN", "AWS_SESSION_TOKEN"),
@@ -508,12 +536,11 @@ function loadConfig(argv = process.argv.slice(2)) {
508
536
  endpoint: pick("s3-endpoint", "S3_ENDPOINT", "AWS_ENDPOINT"),
509
537
  bucket: pick("s3-bucket", "S3_BUCKET", "AWS_BUCKET"),
510
538
  prefix: pick("s3-prefix", "MIKROTIK_S3_PREFIX"),
511
- presignExpiresIn: pick("s3-presign-expires-in", "MIKROTIK_S3_PRESIGN_EXPIRES_IN"),
512
- ...fileS3
513
- };
539
+ presignExpiresIn: pick("s3-presign-expires-in", "MIKROTIK_S3_PRESIGN_EXPIRES_IN")
540
+ });
514
541
  const appViewsRaw = pick("app-views", "MIKROTIK_MCP__APP_VIEWS");
515
542
  const appViewsEnv = appViewsRaw === undefined ? undefined : !/^(0|false|no|off)$/i.test(appViewsRaw);
516
- const mcp = {
543
+ const mcp = overlay(fileMcp, {
517
544
  transport: pick("transport", "MIKROTIK_MCP__TRANSPORT", "MCP_TRANSPORT"),
518
545
  host: pick("mcp-host", "MIKROTIK_MCP__HOST"),
519
546
  port: pick("mcp-port", "MIKROTIK_MCP__PORT"),
@@ -521,22 +548,22 @@ function loadConfig(argv = process.argv.slice(2)) {
521
548
  allowedOrigins: pick("mcp-allowed-origins", "MIKROTIK_MCP__ALLOWED_ORIGINS"),
522
549
  corsOrigins: pick("mcp-cors-origins", "MIKROTIK_MCP__CORS_ORIGINS"),
523
550
  toolPageSize: pick("tool-page-size", "MIKROTIK_MCP__TOOL_PAGE_SIZE"),
524
- appViews: appViewsEnv,
525
- ...fileMcp
526
- };
551
+ appViews: appViewsEnv
552
+ });
527
553
  const isTruthy = (v) => /^(1|true|yes|on)$/i.test(v ?? "");
528
- const readOnly = isTruthy(pick("read-only", "MIKROTIK_READ_ONLY"));
529
- const disableUpdateCheck = isTruthy(pick("disable-update-check", "MIKROTIK_DISABLE_UPDATE_CHECK"));
554
+ const boolFileFlag = (flagVal, fileVal) => flagVal !== undefined ? isTruthy(flagVal) : fileVal;
555
+ const readOnly = boolFileFlag(pick("read-only", "MIKROTIK_READ_ONLY"), fileReadOnly);
556
+ const disableUpdateCheck = boolFileFlag(pick("disable-update-check", "MIKROTIK_DISABLE_UPDATE_CHECK"), fileDisableUpdateCheck);
557
+ const backupDir = pick("backup-dir", "MIKROTIK_BACKUP_DIR") ?? fileBackupDir;
530
558
  const csv = (v) => v === undefined ? undefined : v.split(",").map((s) => s.trim()).filter(Boolean);
531
- const tools = {
559
+ const tools = overlay(fileTools, {
532
560
  enabledModules: csv(pick("tools-enabled-modules", "MIKROTIK_TOOLS__ENABLED_MODULES")),
533
561
  disabledModules: csv(pick("tools-disabled-modules", "MIKROTIK_TOOLS__DISABLED_MODULES")),
534
562
  enabledGroups: csv(pick("tools-enabled-groups", "MIKROTIK_TOOLS__ENABLED_GROUPS")),
535
- disabledGroups: csv(pick("tools-disabled-groups", "MIKROTIK_TOOLS__DISABLED_GROUPS")),
536
- ...fileTools
537
- };
563
+ disabledGroups: csv(pick("tools-disabled-groups", "MIKROTIK_TOOLS__DISABLED_GROUPS"))
564
+ });
538
565
  const boolOpt = (v) => v === undefined ? undefined : isTruthy(v);
539
- const dashboard = {
566
+ const dashboard = overlay(fileDashboard, {
540
567
  enabled: boolOpt(pick("dashboard", "MIKROTIK_DASHBOARD__ENABLED", "MIKROTIK_DASHBOARD")),
541
568
  host: pick("dashboard-host", "MIKROTIK_DASHBOARD__HOST"),
542
569
  port: pick("dashboard-port", "MIKROTIK_DASHBOARD__PORT"),
@@ -545,20 +572,17 @@ function loadConfig(argv = process.argv.slice(2)) {
545
572
  captureBody: boolOpt(pick("dashboard-capture-body", "MIKROTIK_DASHBOARD__CAPTURE_BODY")),
546
573
  redactInput: boolOpt(pick("dashboard-redact-input", "MIKROTIK_DASHBOARD__REDACT_INPUT")),
547
574
  maxBodyBytes: pick("dashboard-max-body-bytes", "MIKROTIK_DASHBOARD__MAX_BODY_BYTES"),
548
- token: pick("dashboard-token", "MIKROTIK_DASHBOARD__TOKEN"),
549
- ...fileDashboard
550
- };
551
- const ssh = {
575
+ token: pick("dashboard-token", "MIKROTIK_DASHBOARD__TOKEN")
576
+ });
577
+ const ssh = overlay(fileSsh, {
552
578
  keepAlive: boolOpt(pick("ssh-keep-alive", "MIKROTIK_SSH__KEEP_ALIVE")),
553
579
  keepAliveInterval: pick("ssh-keepalive-interval", "MIKROTIK_SSH__KEEPALIVE_INTERVAL"),
554
- idleTimeout: pick("ssh-idle-timeout", "MIKROTIK_SSH__IDLE_TIMEOUT"),
555
- ...fileSsh
556
- };
557
- const memory = {
580
+ idleTimeout: pick("ssh-idle-timeout", "MIKROTIK_SSH__IDLE_TIMEOUT")
581
+ });
582
+ const memory = overlay(fileMemory, {
558
583
  enabled: boolOpt(pick("memory-enabled", "MIKROTIK_MEMORY__ENABLED")),
559
- dbPath: pick("memory-db", "MIKROTIK_MEMORY__DB_PATH"),
560
- ...fileMemory
561
- };
584
+ dbPath: pick("memory-db", "MIKROTIK_MEMORY__DB_PATH")
585
+ });
562
586
  const hasS3 = !!(s3.accessKeyId || s3.bucket || s3.endpoint);
563
587
  const raw = {
564
588
  devices: Object.keys(devices).length ? devices : { default: {} },
@@ -567,6 +591,7 @@ function loadConfig(argv = process.argv.slice(2)) {
567
591
  dashboard,
568
592
  readOnly,
569
593
  disableUpdateCheck,
594
+ backupDir,
570
595
  tools,
571
596
  ssh,
572
597
  memory,
@@ -1208,6 +1233,8 @@ function stripAnsi(text) {
1208
1233
  return text.replace(ANSI_RE, "");
1209
1234
  }
1210
1235
  var CTRL_X = "\x18";
1236
+ var IDLE_TIMEOUT_MS = 15000;
1237
+ var HARD_CAP_MS = 120000;
1211
1238
  function isSafeModeActivated(response) {
1212
1239
  if (response.includes("<SAFE>"))
1213
1240
  return true;
@@ -1283,7 +1310,7 @@ class SafeModeManager {
1283
1310
  `);
1284
1311
  const { text, timedOut } = await this.readUntilPrompt();
1285
1312
  if (timedOut) {
1286
- throw new Error(`Safe Mode shell did not return a prompt within 15s (command: ${command}). The interactive ` + "session appears wedged \u2014 some RouterOS builds/terminals don't support Safe Mode over SSH. " + "Apply the change with the direct write tools instead (verify each with a read).");
1313
+ throw new Error(`Safe Mode shell went silent for ${IDLE_TIMEOUT_MS / 1000}s (command: ${command}). The ` + "interactive session appears wedged \u2014 some RouterOS builds/terminals don't support Safe " + "Mode over SSH. Apply the change with the direct write tools instead (verify each with a read).");
1287
1314
  }
1288
1315
  return this.extractOutput(text, command);
1289
1316
  });
@@ -1337,25 +1364,32 @@ class SafeModeManager {
1337
1364
  status() {
1338
1365
  return this.active ? "Safe mode is ACTIVE. Changes are pending \u2014 they are NOT yet persisted. " + "Call commit_safe_mode to persist or rollback_safe_mode to revert." : "Safe mode is NOT active. Changes take effect and persist immediately.";
1339
1366
  }
1340
- readUntilPrompt(timeoutMs = 15000, isDone = (c) => PROMPT_RE.test(c)) {
1367
+ readUntilPrompt(idleMs = IDLE_TIMEOUT_MS, isDone = (c) => PROMPT_RE.test(c), hardCapMs = HARD_CAP_MS) {
1341
1368
  const channel = this.channel;
1342
1369
  if (!channel)
1343
1370
  return Promise.resolve({ text: "", timedOut: false });
1344
1371
  return new Promise((resolve2) => {
1345
1372
  let buf = "";
1346
- let timer;
1373
+ let idleTimer;
1374
+ const hardTimer = setTimeout(() => finish(stripAnsi(buf), true), hardCapMs);
1375
+ function armIdle2() {
1376
+ clearTimeout(idleTimer);
1377
+ idleTimer = setTimeout(() => finish(stripAnsi(buf), true), idleMs);
1378
+ }
1347
1379
  function onData(chunk) {
1348
1380
  buf += decodeOutput(chunk);
1381
+ armIdle2();
1349
1382
  const cleaned = stripAnsi(buf);
1350
1383
  if (isDone(cleaned))
1351
1384
  finish(cleaned, false);
1352
1385
  }
1353
1386
  function finish(result, timedOut) {
1354
- clearTimeout(timer);
1387
+ clearTimeout(idleTimer);
1388
+ clearTimeout(hardTimer);
1355
1389
  channel.removeListener("data", onData);
1356
1390
  resolve2({ text: result, timedOut });
1357
1391
  }
1358
- timer = setTimeout(() => finish(stripAnsi(buf), true), timeoutMs);
1392
+ armIdle2();
1359
1393
  channel.on("data", onData);
1360
1394
  });
1361
1395
  }
@@ -5264,7 +5298,7 @@ var changePlanTools = [
5264
5298
  const enabled2 = await safe.enable();
5265
5299
  if (enabled2.startsWith("Error"))
5266
5300
  throw new Error(enabled2);
5267
- const APPLY_BUDGET_MS = 90000;
5301
+ const APPLY_BUDGET_MS = 180000;
5268
5302
  const startedAt = Date.now();
5269
5303
  const overBudget = () => Date.now() - startedAt > APPLY_BUDGET_MS;
5270
5304
  const applyAndDiff = async () => {
@@ -8330,7 +8364,7 @@ var cache = null;
8330
8364
  async function gateway() {
8331
8365
  if (cache)
8332
8366
  return cache;
8333
- const { moduleCatalog } = await import("./cli-7yjsmkwg.js");
8367
+ const { moduleCatalog } = await import("./cli-kq9jry12.js");
8334
8368
  const forIndex = [];
8335
8369
  const byName = new Map;
8336
8370
  for (const mod of moduleCatalog) {
@@ -22181,7 +22215,7 @@ ${result}`;
22181
22215
  name: "check_route_path",
22182
22216
  title: "Check IPv4 Route Path",
22183
22217
  annotations: READ,
22184
- 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.",
22218
+ 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 <dest> in dst-address 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.",
22185
22219
  inputSchema: {
22186
22220
  destination: z88.string(),
22187
22221
  routing_table: z88.string().optional().describe('Policy-routing table name (v7) or routing-mark (v6), e.g. "VPN"')
@@ -22196,7 +22230,7 @@ ${result}`;
22196
22230
 
22197
22231
  ${v6Result}`;
22198
22232
  }
22199
- const where = [`dst-address in ${a.destination}`, "active=yes"];
22233
+ const where = [`${a.destination} in dst-address`, "active=yes"];
22200
22234
  if (table && table !== "main")
22201
22235
  where.push(`routing-table=${quoteValue(table)}`);
22202
22236
  const v7Cmd = `/ip route print detail where ${where.join(" ")}`;
@@ -24933,7 +24967,7 @@ var SERVER_NAME = "mikrotik-mcp";
24933
24967
  var PKG_META = pkg;
24934
24968
 
24935
24969
  // src/core/update-check.ts
24936
- var GITHUB_API = "https://api.github.com/repos/ali-master/mikrotik-mcp/releases/latest";
24970
+ var GITHUB_API = "https://api.github.com/repos/mikrotik-mcp/mikrotik-mcp/releases/latest";
24937
24971
  var MEMORY_CACHE_TTL = 15 * 60 * 1000;
24938
24972
  var FILE_CACHE_TTL = 6 * 60 * 60 * 1000;
24939
24973
  var CACHE_PATH = join6(homedir2(), ".mikrotik-mcp", "update-check.json");
@@ -24991,13 +25025,15 @@ async function fetchLatestRelease() {
24991
25025
  throw new Error(`GitHub API ${res.status}`);
24992
25026
  const gh = await res.json();
24993
25027
  const latestVersion = gh.tag_name.replace(/^v/, "");
25028
+ const cmp = compareVersions(latestVersion, VERSION);
24994
25029
  const data = {
24995
25030
  version: latestVersion,
24996
25031
  name: gh.name || `v${latestVersion}`,
24997
25032
  body: gh.body || "",
24998
25033
  publishedAt: gh.published_at,
24999
25034
  url: gh.html_url,
25000
- isNewer: compareVersions(latestVersion, VERSION) > 0,
25035
+ isNewer: cmp > 0,
25036
+ isAhead: cmp < 0,
25001
25037
  currentVersion: VERSION
25002
25038
  };
25003
25039
  memoryCache = { data, fetchedAt: Date.now() };
@@ -25105,14 +25141,18 @@ var serverPulseTools = [
25105
25141
  const sep = "\u2500".repeat(50);
25106
25142
  sections.push(`SERVER PULSE \u2014 ${SERVER_TITLE} v${VERSION}`, sep, "Package: @usex/mikrotik-mcp", `Version: ${VERSION}`, `Uptime: ${formatUptime(uptime)}`, `Website: ${WEBSITE_URL}`);
25107
25143
  if (result.release) {
25108
- const freshness = assessFreshness(VERSION, result.release.version);
25109
- const label = freshnessLabel(freshness);
25110
25144
  const age = timeAgo(result.release.publishedAt);
25111
- sections.push("", `UPDATE STATUS: ${label}`, sep, `Current: v${VERSION}`, `Latest: v${result.release.version} (${result.release.name})`, `Published: ${age}`, `Freshness: ${freshness.toUpperCase()}`);
25112
- if (result.release.isNewer) {
25113
- sections.push("", ">>> A newer version is available! <<<");
25145
+ if (result.release.isAhead) {
25146
+ sections.push("", "UPDATE STATUS: AHEAD OF LATEST RELEASE", sep, `Current: v${VERSION}`, `Published: v${result.release.version} (${result.release.name})`, `Released: ${age}`, `Freshness: DEV BUILD`, "", "You are running ahead of the latest published release.");
25114
25147
  } else {
25115
- sections.push("", "You are running the latest version.");
25148
+ const freshness = assessFreshness(VERSION, result.release.version);
25149
+ const label = freshnessLabel(freshness);
25150
+ sections.push("", `UPDATE STATUS: ${label}`, sep, `Current: v${VERSION}`, `Latest: v${result.release.version} (${result.release.name})`, `Published: ${age}`, `Freshness: ${freshness.toUpperCase()}`);
25151
+ if (result.release.isNewer) {
25152
+ sections.push("", ">>> A newer version is available! <<<");
25153
+ } else {
25154
+ sections.push("", "You are running the latest version.");
25155
+ }
25116
25156
  }
25117
25157
  if (result.release.isNewer) {
25118
25158
  sections.push("", "UPGRADE", sep);
@@ -4,7 +4,7 @@ import {
4
4
  allToolModules,
5
5
  moduleCatalog,
6
6
  selectToolModules
7
- } from "./cli-ghatyv7e.js";
7
+ } from "./cli-2dtt405d.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-7j22z9a8.js";
7
+ } from "./library-pqnndx9v.js";
8
8
  export {
9
9
  selectToolModules,
10
10
  moduleCatalog,