@tikoci/rosetta 0.2.0 → 0.2.1

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  MCP server for searching [MikroTik RouterOS documentation](https://help.mikrotik.com/docs/spaces/ROS/overview). Gives your AI assistant searchable access to 317 documentation pages, 4,860 property definitions, 40,000-entry command tree, and 144 hardware product specs — with direct links to help.mikrotik.com.
4
4
 
5
- Tested with **Claude Desktop**, **Claude Code**, **VS Code Copilot** (including Copilot CLI), and **VS Code** on macOS and Linux.
5
+ Tested with **Claude Desktop**, **Claude Code**, **VS Code Copilot** (including Copilot CLI), **Cursor**, and **OpenAI Codex** on macOS, Linux, and Windows.
6
6
 
7
7
  ## What is SQL-as-RAG?
8
8
 
@@ -10,7 +10,7 @@ Most retrieval-augmented generation (RAG) systems use vector embeddings to searc
10
10
 
11
11
  For structured technical documentation like RouterOS, full-text search with [BM25 ranking](https://www.sqlite.org/fts5.html#the_bm25_function) beats vector similarity. Technical terms like "dhcp-snooping" or "/ip/firewall/filter" are exact tokens — [porter stemming](https://www.sqlite.org/fts5.html#porter_tokenizer) and proximity matching handle the rest. No embedding pipeline, no vector database, no API keys. Just a single SQLite file that searches in milliseconds.
12
12
 
13
- The data flows: **HTML docs → SQLite extraction → FTS5 indexes → MCP tools → your AI assistant.** The database is built once from MikroTik's official Confluence documentation export, then the MCP server exposes 10 search tools over stdio transport.
13
+ The data flows: **HTML docs → SQLite extraction → FTS5 indexes → MCP tools → your AI assistant.** The database is built once from MikroTik's official Confluence documentation export, then the MCP server exposes 11 search tools over stdio transport.
14
14
 
15
15
  ## What's Inside
16
16
 
@@ -24,45 +24,53 @@ The data flows: **HTML docs → SQLite extraction → FTS5 indexes → MCP tools
24
24
 
25
25
  ## Quick Start
26
26
 
27
- Download a pre-built binary from [Releases](https://github.com/tikoci/rosetta/releases) — no Bun, Node.js, or other tools required.
27
+ ### Option A: Install with Bun (recommended)
28
28
 
29
- ### 1. Download
29
+ Zero install, zero config, no binary signing issues. Requires [Bun](https://bun.sh/) — no Gatekeeper or SmartScreen warnings since there's no compiled binary to sign.
30
30
 
31
- Go to the [latest release](https://github.com/tikoci/rosetta/releases/latest) and download the ZIP for your platform:
31
+ **1. Install Bun** (if you don't have it):
32
32
 
33
- | Platform | File |
34
- |----------|------|
35
- | macOS (Apple Silicon) | `rosetta-macos-arm64.zip` |
36
- | macOS (Intel) | `rosetta-macos-x64.zip` |
37
- | Windows | `rosetta-windows-x64.zip` |
38
- | Linux | `rosetta-linux-x64.zip` |
33
+ ```sh
34
+ # macOS / Linux
35
+ curl -fsSL https://bun.sh/install | bash
39
36
 
40
- Extract the ZIP to a permanent location (e.g., `~/rosetta` or `C:\rosetta`).
37
+ # Windows
38
+ powershell -c "irm bun.sh/install.ps1 | iex"
39
+ ```
41
40
 
42
- ### 2. Run Setup
41
+ **2. Configure your MCP client** with `bunx @tikoci/rosetta` as the command. No setup step needed — the database downloads automatically on first launch (~50 MB compressed).
43
42
 
44
- Open a terminal in the extracted folder and run:
43
+ <details>
44
+ <summary><b>VS Code Copilot</b></summary>
45
45
 
46
- ```sh
47
- ./rosetta --setup
48
- ```
46
+ Open the Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`), choose **"MCP: Add Server…"**, select **"Command (stdio)"**, enter `bunx` as the command, and `@tikoci/rosetta` as the argument.
47
+
48
+ Or add to User Settings JSON (`Cmd+Shift+P` → "Preferences: Open User Settings (JSON)"):
49
49
 
50
- On Windows:
51
- ```powershell
52
- .\rosetta.exe --setup
50
+ ```json
51
+ "mcp": {
52
+ "servers": {
53
+ "rosetta": {
54
+ "command": "bunx",
55
+ "args": ["@tikoci/rosetta"]
56
+ }
57
+ }
58
+ }
53
59
  ```
54
60
 
55
- This downloads the documentation database (~50 MB compressed, ~230 MB on disk) and prints configuration instructions for your MCP client.
61
+ </details>
56
62
 
57
- > **macOS Gatekeeper:** If macOS blocks the binary, go to **System Settings → Privacy & Security** and click **Allow Anyway**, then run again. Or from Terminal: `xattr -d com.apple.quarantine ./rosetta`
58
- >
59
- > **Windows SmartScreen:** If Windows warns about an unrecognized app, click **More info → Run anyway**.
63
+ <details>
64
+ <summary><b>Claude Code</b></summary>
60
65
 
61
- ### 3. Configure Your MCP Client
66
+ ```sh
67
+ claude mcp add rosetta -- bunx @tikoci/rosetta
68
+ ```
62
69
 
63
- The `--setup` command prints the exact config for your platform. Here's what it looks like for each client:
70
+ </details>
64
71
 
65
- #### Claude Desktop
72
+ <details>
73
+ <summary><b>Claude Desktop</b></summary>
66
74
 
67
75
  Edit your Claude Desktop config file:
68
76
  - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
@@ -74,37 +82,97 @@ Add (or merge into existing config):
74
82
  {
75
83
  "mcpServers": {
76
84
  "rosetta": {
77
- "command": "/path/to/rosetta"
85
+ "command": "bunx",
86
+ "args": ["@tikoci/rosetta"]
78
87
  }
79
88
  }
80
89
  }
81
90
  ```
82
91
 
83
- Replace `/path/to/rosetta` with the actual path printed by `--setup`. Then **restart Claude Desktop**.
92
+ > **PATH note:** Claude Desktop on macOS doesn't always inherit your shell PATH. If `bunx` isn't found, use the full path — typically `~/.bun/bin/bunx`. Run `which bunx` to find it, or use `bunx @tikoci/rosetta --setup` which prints the full-path config for you.
84
93
 
85
- #### Claude Code
94
+ Then **restart Claude Desktop**.
86
95
 
87
- ```sh
88
- claude mcp add rosetta /path/to/rosetta
89
- ```
96
+ </details>
97
+
98
+ <details>
99
+ <summary><b>GitHub Copilot CLI</b></summary>
90
100
 
91
- #### VS Code Copilot
101
+ Inside a `copilot` session, type `/mcp add` to open the interactive form:
92
102
 
93
- The simplest way: open the Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`), choose **"MCP: Add Server…"**, select **"Command (stdio)"**, and enter the full path to the `rosetta` binary.
103
+ - **Server Name:** `routeros-rosetta`
104
+ - **Server Type:** 2 (STDIO)
105
+ - **Command:** `bunx @tikoci/rosetta`
94
106
 
95
- Or add to your User Settings JSON (`Cmd+Shift+P` "Preferences: Open User Settings (JSON)"):
107
+ Press <kbd>Tab</kbd> to navigate fields, <kbd>Ctrl+S</kbd> to save.
108
+
109
+ </details>
110
+
111
+ <details>
112
+ <summary><b>Cursor</b></summary>
113
+
114
+ Open **Settings → MCP** and add a new server:
96
115
 
97
116
  ```json
98
- "mcp": {
99
- "servers": {
117
+ {
118
+ "mcpServers": {
100
119
  "rosetta": {
101
- "command": "/path/to/rosetta"
120
+ "command": "bunx",
121
+ "args": ["@tikoci/rosetta"]
102
122
  }
103
123
  }
104
124
  }
105
125
  ```
106
126
 
107
- ### 4. Try It
127
+ </details>
128
+
129
+ <details>
130
+ <summary><b>OpenAI Codex</b></summary>
131
+
132
+ ```sh
133
+ codex mcp add rosetta -- bunx @tikoci/rosetta
134
+ ```
135
+
136
+ > **Note:** ChatGPT Apps require a remote HTTPS MCP endpoint and cannot use local stdio servers like this one. Codex (CLI and desktop app) supports stdio and works with `bunx`.
137
+
138
+ </details>
139
+
140
+ **That's it.** First launch takes a moment to download the database; subsequent starts are instant. The database is stored in `~/.rosetta/ros-help.db`.
141
+
142
+ > **Verify it works:** Run `bunx @tikoci/rosetta --setup` to see the database status and print config for all MCP clients.
143
+ >
144
+ > **Auto-update:** `bunx` checks the npm registry each session and uses the latest published version automatically. No manual update needed. (Note: the `~/.rosetta/ros-help.db` database persists across updates — it's re-downloaded only when missing or when you run `--setup --force`.)
145
+
146
+ ### Option B: Pre-built binary (no runtime needed)
147
+
148
+ Download a compiled binary from [Releases](https://github.com/tikoci/rosetta/releases) — no Bun, Node.js, or other tools required.
149
+
150
+ **1. Download** the ZIP for your platform from the [latest release](https://github.com/tikoci/rosetta/releases/latest):
151
+
152
+ | Platform | File |
153
+ |----------|------|
154
+ | macOS (Apple Silicon) | `rosetta-macos-arm64.zip` |
155
+ | macOS (Intel) | `rosetta-macos-x64.zip` |
156
+ | Windows | `rosetta-windows-x64.zip` |
157
+ | Linux | `rosetta-linux-x64.zip` |
158
+
159
+ Extract the ZIP to a permanent location (e.g., `~/rosetta` or `C:\rosetta`).
160
+
161
+ **2. Run setup** to download the database and see MCP client config:
162
+
163
+ ```sh
164
+ ./rosetta --setup
165
+ ```
166
+
167
+ On Windows: `.\rosetta.exe --setup`
168
+
169
+ > **macOS Gatekeeper:** If macOS blocks the binary: `xattr -d com.apple.quarantine ./rosetta` or go to **System Settings → Privacy & Security → Allow Anyway**.
170
+ >
171
+ > **Windows SmartScreen:** Click **More info → Run anyway**.
172
+
173
+ **3. Configure your MCP client** using the config printed by `--setup`. It uses the full path to the binary — paste it into your MCP client's config as shown.
174
+
175
+ ### Try It
108
176
 
109
177
  Ask your AI assistant questions like:
110
178
 
@@ -117,7 +185,7 @@ Ask your AI assistant questions like:
117
185
 
118
186
  ## MCP Tools
119
187
 
120
- The server provides 10 tools, designed to work together:
188
+ The server provides 11 tools, designed to work together:
121
189
 
122
190
  | Tool | What it does |
123
191
  |------|-------------|
@@ -127,6 +195,7 @@ The server provides 10 tools, designed to work together:
127
195
  | `routeros_search_properties` | Search across 4,860 property names and descriptions |
128
196
  | `routeros_command_tree` | Browse the `/ip/firewall/filter` style command hierarchy |
129
197
  | `routeros_search_callouts` | Search warnings, notes, and tips across all pages |
198
+ | `routeros_search_changelogs` | Search parsed changelog entries — filter by version range, category, breaking changes |
130
199
  | `routeros_command_version_check` | Check which RouterOS versions include a command |
131
200
  | `routeros_device_lookup` | Hardware specs for 144 MikroTik products — filter by architecture, RAM, storage, PoE, wireless, LTE |
132
201
  | `routeros_stats` | Database health: page/property/command counts, coverage stats |
@@ -134,78 +203,18 @@ The server provides 10 tools, designed to work together:
134
203
 
135
204
  The AI assistant typically starts with `routeros_search`, then drills into specific pages, properties, or the command tree based on what it finds. Each tool's description includes workflow hints (e.g., "→ use `routeros_get_page` to read full content") and empty-result suggestions so the AI knows how to chain tools together — this is where most of the tuning effort goes.
136
205
 
137
- ## Alternative: Run with Bun
138
-
139
- If you have [Bun](https://bun.sh/) installed and prefer not to use the pre-built binary — for example, to avoid Gatekeeper/SmartScreen warnings, or to inspect the code you're running — you can run the MCP server directly from source. No HTML export or command tree data is needed; the database is downloaded from GitHub Releases just like the binary option.
140
-
141
- ### 1. Install Bun
142
-
143
- ```sh
144
- # macOS / Linux
145
- curl -fsSL https://bun.sh/install | bash
146
- # or: brew install oven-sh/bun/bun
147
-
148
- # Windows
149
- powershell -c "irm bun.sh/install.ps1 | iex"
150
- ```
151
-
152
- ### 2. Download and Install
153
-
154
- ```sh
155
- git clone https://github.com/tikoci/rosetta.git
156
- cd rosetta
157
- bun install
158
- ```
159
-
160
- Or download the source archive from the [latest release](https://github.com/tikoci/rosetta/releases/latest) ("Source code" ZIP or tarball), extract it, and run `bun install`.
161
-
162
- ### 3. Run Setup
163
-
164
- ```sh
165
- bun run src/mcp.ts --setup
166
- ```
167
-
168
- This downloads the documentation database and prints MCP client configuration. The config uses `bun` as the command with `src/mcp.ts` as the entrypoint:
169
-
170
- #### Claude Desktop
171
-
172
- ```json
173
- {
174
- "mcpServers": {
175
- "rosetta": {
176
- "command": "bun",
177
- "args": ["run", "src/mcp.ts"],
178
- "cwd": "/path/to/rosetta"
179
- }
180
- }
181
- }
182
- ```
183
-
184
- #### Claude Code
185
-
186
- ```sh
187
- claude mcp add rosetta -- bun run src/mcp.ts --cwd /path/to/rosetta
188
- ```
189
-
190
- #### VS Code Copilot
191
-
192
- Open the Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`), choose **"MCP: Add Server…"**, select **"Command (stdio)"**, and enter `bun run src/mcp.ts` with the working directory set to the rosetta folder.
193
-
194
- Or add to your User Settings JSON:
195
-
196
- ```json
197
- "mcp": {
198
- "servers": {
199
- "rosetta": {
200
- "command": "bun",
201
- "args": ["run", "src/mcp.ts"],
202
- "cwd": "/path/to/rosetta"
203
- }
204
- }
205
- }
206
- ```
206
+ ## Troubleshooting
207
207
 
208
- Replace `/path/to/rosetta` with the actual path (printed by `--setup`).
208
+ | Issue | Solution |
209
+ |-------|----------|
210
+ | **First launch is slow** | One-time database download (~50 MB). Subsequent starts are instant. |
211
+ | **`npx @tikoci/rosetta` fails** | This package requires Bun, not Node.js. Use `bunx` instead of `npx`. |
212
+ | **`npm install -g` then `rosetta` fails** | Global npm install works if Bun is on PATH — it delegates to `bun` at runtime. But prefer `bunx` — it's simpler and auto-updates. |
213
+ | **ChatGPT Apps can't connect with `bunx @tikoci/rosetta`** | Expected: ChatGPT Apps supports remote HTTPS MCP endpoints, not local stdio command launch. Use OpenAI Codex for local stdio, or deploy/tunnel a remote MCP URL for ChatGPT. |
214
+ | **Claude Desktop can't find `bunx`** | Claude Desktop on macOS may not inherit shell PATH. Use the full path to bunx (run `which bunx` to find it, typically `~/.bun/bin/bunx`). `bunx @tikoci/rosetta --setup` prints the full-path config. |
215
+ | **macOS Gatekeeper blocks binary** | Use `bunx` install (no Gatekeeper issues), or: `xattr -d com.apple.quarantine ./rosetta` |
216
+ | **Windows SmartScreen warning** | Use `bunx` install (no SmartScreen issues), or click **More info → Run anyway** |
217
+ | **How to update** | `bunx` always uses the latest published version. For binaries, re-download from [Releases](https://github.com/tikoci/rosetta/releases/latest). |
209
218
 
210
219
  ## Building from Source
211
220
 
@@ -271,7 +280,7 @@ This cross-compiles to macOS (arm64 + x64), Windows (x64), and Linux (x64), crea
271
280
 
272
281
  ```text
273
282
  src/
274
- ├── mcp.ts # MCP server (10 tools, stdio) + CLI dispatch
283
+ ├── mcp.ts # MCP server (11 tools, stdio) + CLI dispatch
275
284
  ├── setup.ts # --setup: DB download + MCP client config
276
285
  ├── query.ts # NL → FTS5 query planner, BM25 ranking
277
286
  ├── db.ts # SQLite schema, WAL mode, FTS5 triggers
package/bin/rosetta.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * bin/rosetta.js — Entry point for `npx @tikoci/rosetta` and `bunx @tikoci/rosetta`.
3
+ * bin/rosetta.js — Entry point for `bunx @tikoci/rosetta` (and npx fallback).
4
4
  *
5
5
  * The server uses bun:sqlite and other Bun APIs, so it requires the Bun runtime.
6
6
  * When run under Bun (via bunx), this imports src/mcp.ts directly.
7
- * When run under Node (via npx), this spawns `bun run src/mcp.ts` as a subprocess.
7
+ * When run under Node (via npx), this spawns `bun` as a subprocess if available.
8
8
  */
9
9
 
10
10
  import { dirname, join } from "node:path";
@@ -17,17 +17,23 @@ if (typeof Bun !== "undefined") {
17
17
  // Running under Bun — import TypeScript entry directly
18
18
  await import(entry);
19
19
  } else {
20
- // Running under Node — delegate to Bun subprocess
20
+ // Running under Node — try to delegate to Bun, but warn the user
21
+ console.error("Note: rosetta requires the Bun runtime. Attempting to run via bun...");
22
+ console.error();
21
23
  const { spawn } = await import("node:child_process");
22
24
  const proc = spawn("bun", ["run", entry, ...process.argv.slice(2)], {
23
25
  stdio: "inherit",
24
26
  });
25
27
  proc.on("error", (err) => {
26
28
  if (err.code === "ENOENT") {
27
- console.error("rosetta requires the Bun runtime (bun:sqlite is not available in Node.js).");
29
+ console.error("rosetta requires Bun (bun:sqlite is not available in Node.js).\n");
30
+ console.error("Recommended: install Bun, then use bunx instead of npx:\n");
31
+ console.error(" curl -fsSL https://bun.sh/install | bash");
32
+ console.error(" bunx @tikoci/rosetta --setup\n");
28
33
  console.error("Install Bun: https://bun.sh");
29
34
  process.exit(1);
30
35
  }
36
+ console.error(`Failed to start bun: ${err.message}`);
31
37
  process.exit(1);
32
38
  });
33
39
  proc.on("exit", (code) => process.exit(code ?? 1));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tikoci/rosetta",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "RouterOS documentation as SQLite FTS5 — RAG search + command glossary via MCP",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,6 +25,9 @@
25
25
  "@modelcontextprotocol/sdk": "^1.27.1",
26
26
  "zod": "^4.3.6"
27
27
  },
28
+ "engines": {
29
+ "bun": ">=1.1"
30
+ },
28
31
  "devDependencies": {
29
32
  "@biomejs/biome": "^2.4.7",
30
33
  "@types/bun": "^1.3.10",
package/src/db.ts CHANGED
@@ -20,22 +20,9 @@
20
20
  */
21
21
 
22
22
  import sqlite from "bun:sqlite";
23
- import path from "node:path";
23
+ import { resolveDbPath } from "./paths.ts";
24
24
 
25
- declare const IS_COMPILED: boolean;
26
-
27
- /**
28
- * Resolve the base directory for finding ros-help.db:
29
- * - Compiled binary: directory containing the executable
30
- * - Dev mode: project root (one level up from src/)
31
- */
32
- const baseDir =
33
- typeof IS_COMPILED !== "undefined" && IS_COMPILED
34
- ? path.dirname(process.execPath)
35
- : path.resolve(import.meta.dirname, "..");
36
-
37
- export const DB_PATH =
38
- process.env.DB_PATH?.trim() || path.join(baseDir, "ros-help.db");
25
+ export const DB_PATH = resolveDbPath(import.meta.dirname);
39
26
 
40
27
  export const db = new sqlite(DB_PATH);
41
28
 
package/src/mcp.ts CHANGED
@@ -15,22 +15,21 @@
15
15
  * DB_PATH — absolute path to ros-help.db (default: next to binary or project root)
16
16
  */
17
17
 
18
- declare const VERSION: string;
19
- declare const IS_COMPILED: boolean;
18
+ import { resolveVersion } from "./paths.ts";
19
+
20
+ const RESOLVED_VERSION = resolveVersion(import.meta.dirname);
20
21
 
21
22
  // ── CLI dispatch (before MCP server init) ──
22
23
 
23
24
  const args = process.argv.slice(2);
24
25
 
25
26
  if (args.includes("--version") || args.includes("-v")) {
26
- const ver = typeof VERSION !== "undefined" ? VERSION : "dev";
27
- console.log(`rosetta ${ver}`);
27
+ console.log(`rosetta ${RESOLVED_VERSION}`);
28
28
  process.exit(0);
29
29
  }
30
30
 
31
31
  if (args.includes("--help") || args.includes("-h")) {
32
- const ver = typeof VERSION !== "undefined" ? VERSION : "dev";
33
- console.log(`rosetta ${ver} — MCP server for RouterOS documentation`);
32
+ console.log(`rosetta ${RESOLVED_VERSION} MCP server for RouterOS documentation`);
34
33
  console.log();
35
34
  console.log("Usage:");
36
35
  console.log(" rosetta Start MCP server (stdio transport)");
@@ -65,12 +64,8 @@ const { z } = await import("zod/v3");
65
64
  //
66
65
  // Check if DB has data BEFORE importing db.ts. If empty/missing,
67
66
  // auto-download so db.ts opens the real database.
68
- const _baseDir =
69
- typeof IS_COMPILED !== "undefined" && IS_COMPILED
70
- ? (await import("node:path")).dirname(process.execPath)
71
- : (await import("node:path")).resolve(import.meta.dirname, "..");
72
- const _dbPath =
73
- process.env.DB_PATH?.trim() || (await import("node:path")).join(_baseDir, "ros-help.db");
67
+ const { resolveDbPath } = await import("./paths.ts");
68
+ const _dbPath = resolveDbPath(import.meta.dirname);
74
69
 
75
70
  const _pageCount = (() => {
76
71
  try {
@@ -116,7 +111,7 @@ initDb();
116
111
 
117
112
  const server = new McpServer({
118
113
  name: "rosetta",
119
- version: typeof VERSION !== "undefined" ? VERSION : "0.2.0",
114
+ version: RESOLVED_VERSION,
120
115
  });
121
116
 
122
117
  // ---- routeros_search ----
@@ -451,6 +446,29 @@ Examples:
451
446
 
452
447
  // ---- routeros_search_changelogs ----
453
448
 
449
+ /** Group flat changelog results by version for compact output. */
450
+ function groupChangelogsByVersion(results: Array<{ version: string; released: string | null; category: string; is_breaking: number; description: string }>) {
451
+ const byVersion = new Map<string, { released: string | null; entries: Array<{ category: string; is_breaking: number; description: string }> }>();
452
+ for (const r of results) {
453
+ let group = byVersion.get(r.version);
454
+ if (!group) {
455
+ group = { released: r.released, entries: [] };
456
+ byVersion.set(r.version, group);
457
+ }
458
+ group.entries.push({ category: r.category, is_breaking: r.is_breaking, description: r.description });
459
+ }
460
+ return {
461
+ total_entries: results.length,
462
+ versions: Array.from(byVersion.entries()).map(([version, { released, entries }]) => ({
463
+ version,
464
+ released,
465
+ entry_count: entries.length,
466
+ breaking_count: entries.filter(e => e.is_breaking).length,
467
+ entries,
468
+ })),
469
+ };
470
+ }
471
+
454
472
  server.registerTool(
455
473
  "routeros_search_changelogs",
456
474
  {
@@ -484,7 +502,7 @@ Coverage depends on which versions were extracted — typically matches ros_vers
484
502
  from_version: z
485
503
  .string()
486
504
  .optional()
487
- .describe("Start of version range, inclusive (e.g., '7.21')"),
505
+ .describe("Start of version range, EXCLUSIVE — returns changes AFTER this version (e.g., from_version='7.21.3' excludes 7.21.3 entries, includes 7.22+)"),
488
506
  to_version: z
489
507
  .string()
490
508
  .optional()
@@ -501,10 +519,10 @@ Coverage depends on which versions were extracted — typically matches ros_vers
501
519
  .number()
502
520
  .int()
503
521
  .min(1)
504
- .max(100)
522
+ .max(500)
505
523
  .optional()
506
- .default(20)
507
- .describe("Max results (default 20)"),
524
+ .default(50)
525
+ .describe("Max results (default 50, max 500). Version-range queries often need higher limits."),
508
526
  },
509
527
  },
510
528
  async ({ query, version, from_version, to_version, category, breaking_only, limit }) => {
@@ -535,8 +553,10 @@ Coverage depends on which versions were extracted — typically matches ros_vers
535
553
  ],
536
554
  };
537
555
  }
556
+ // Group by version for compact output — avoids repeating version/released on every entry
557
+ const grouped = groupChangelogsByVersion(results);
538
558
  return {
539
- content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
559
+ content: [{ type: "text", text: JSON.stringify(grouped, null, 2) }],
540
560
  };
541
561
  },
542
562
  );
package/src/paths.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * paths.ts — Shared DB path resolution for all entry points.
3
+ *
4
+ * Three modes:
5
+ * 1. Compiled binary (IS_COMPILED) → next to executable
6
+ * 2. Dev mode (.git exists in project root) → project root
7
+ * 3. Package mode (bunx / bun add -g) → ~/.rosetta/
8
+ *
9
+ * DB_PATH env var overrides all modes.
10
+ * This module must NOT import db.ts or bun:sqlite — it's used before the DB is opened.
11
+ */
12
+
13
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import path from "node:path";
16
+
17
+ declare const IS_COMPILED: boolean;
18
+ declare const VERSION: string;
19
+
20
+ /** True when running as a compiled binary (bun build --compile) */
21
+ export function isCompiled(): boolean {
22
+ try {
23
+ return typeof IS_COMPILED !== "undefined" && IS_COMPILED;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ /** True when running from a git checkout (dev mode) */
30
+ function isDevMode(projectRoot: string): boolean {
31
+ return existsSync(path.join(projectRoot, ".git"));
32
+ }
33
+
34
+ /**
35
+ * Resolve the directory where ros-help.db should live.
36
+ * - Compiled: directory containing the executable
37
+ * - Dev: project root (one level up from src/)
38
+ * - Package (bunx / global install): ~/.rosetta/
39
+ */
40
+ export function resolveBaseDir(srcDir: string): string {
41
+ if (isCompiled()) {
42
+ return path.dirname(process.execPath);
43
+ }
44
+
45
+ const projectRoot = path.resolve(srcDir, "..");
46
+ if (isDevMode(projectRoot)) {
47
+ return projectRoot;
48
+ }
49
+
50
+ // Package mode — stable user-local directory
51
+ const dataDir = path.join(homedir(), ".rosetta");
52
+ mkdirSync(dataDir, { recursive: true });
53
+ return dataDir;
54
+ }
55
+
56
+ /**
57
+ * Resolve the full path to ros-help.db.
58
+ * DB_PATH env var overrides all detection logic.
59
+ */
60
+ export function resolveDbPath(srcDir: string): string {
61
+ const envPath = process.env.DB_PATH?.trim();
62
+ if (envPath) return envPath;
63
+ return path.join(resolveBaseDir(srcDir), "ros-help.db");
64
+ }
65
+
66
+ /** Detect invocation mode: "compiled" | "dev" | "package" */
67
+ export type InvocationMode = "compiled" | "dev" | "package";
68
+
69
+ export function detectMode(srcDir: string): InvocationMode {
70
+ if (isCompiled()) return "compiled";
71
+ const projectRoot = path.resolve(srcDir, "..");
72
+ if (isDevMode(projectRoot)) return "dev";
73
+ return "package";
74
+ }
75
+
76
+ /**
77
+ * Resolve the version string.
78
+ * Compiled mode: injected at build time via --define.
79
+ * Dev/package mode: read from package.json.
80
+ */
81
+ export function resolveVersion(srcDir: string): string {
82
+ try {
83
+ if (typeof VERSION !== "undefined") return VERSION;
84
+ } catch {}
85
+ try {
86
+ const pkgPath = path.join(srcDir, "..", "package.json");
87
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
88
+ return pkg.version ?? "unknown";
89
+ } catch {
90
+ return "unknown";
91
+ }
92
+ }
package/src/query.test.ts CHANGED
@@ -877,13 +877,13 @@ describe("searchChangelogs", () => {
877
877
  }
878
878
  });
879
879
 
880
- test("version range filter works", () => {
881
- const results = searchChangelogs("", { fromVersion: "7.22", toVersion: "7.22.1" });
880
+ test("version range filter works (from_version exclusive, to_version inclusive)", () => {
881
+ const results = searchChangelogs("", { fromVersion: "7.21", toVersion: "7.22.1" });
882
882
  expect(results.length).toBeGreaterThan(0);
883
883
  for (const r of results) {
884
884
  expect(["7.22", "7.22.1"]).toContain(r.version);
885
885
  }
886
- // Should not include 7.21
886
+ // Should not include 7.21 (from_version is exclusive)
887
887
  expect(results.some((r) => r.version === "7.21")).toBe(false);
888
888
  });
889
889
 
@@ -902,7 +902,8 @@ describe("searchChangelogs", () => {
902
902
  });
903
903
 
904
904
  test("FTS combined with version range", () => {
905
- const results = searchChangelogs("MLAG", { fromVersion: "7.22", toVersion: "7.22" });
905
+ // from_version is exclusive, so use 7.21 to include 7.22
906
+ const results = searchChangelogs("MLAG", { fromVersion: "7.21", toVersion: "7.22" });
906
907
  expect(results.length).toBe(1);
907
908
  expect(results[0].category).toBe("bridge");
908
909
  });
package/src/query.ts CHANGED
@@ -825,14 +825,14 @@ function getChangelogVersions(): string[] {
825
825
  return rows.map((r) => r.version).sort(compareVersions);
826
826
  }
827
827
 
828
- /** Filter versions to those within [fromVersion, toVersion] range (inclusive). */
828
+ /** Filter versions to those within (fromVersion, toVersion] range (fromVersion exclusive, toVersion inclusive). */
829
829
  function filterVersionRange(
830
830
  versions: string[],
831
831
  fromVersion?: string,
832
832
  toVersion?: string,
833
833
  ): string[] {
834
834
  return versions.filter((v) => {
835
- if (fromVersion && compareVersions(v, fromVersion) < 0) return false;
835
+ if (fromVersion && compareVersions(v, fromVersion) <= 0) return false;
836
836
  if (toVersion && compareVersions(v, toVersion) > 0) return false;
837
837
  return true;
838
838
  });
@@ -88,25 +88,27 @@ describe("bin/rosetta.js", () => {
88
88
  // ---------------------------------------------------------------------------
89
89
 
90
90
  describe("build-time constants", () => {
91
- test("mcp.ts declares VERSION", () => {
91
+ test("mcp.ts imports resolveVersion from paths.ts", () => {
92
92
  const src = readText("src/mcp.ts");
93
- expect(src).toContain("declare const VERSION");
93
+ expect(src).toContain("resolveVersion");
94
94
  });
95
95
 
96
- test("mcp.ts declares IS_COMPILED", () => {
96
+ test("mcp.ts does not have hardcoded version fallback", () => {
97
97
  const src = readText("src/mcp.ts");
98
- expect(src).toContain("declare const IS_COMPILED");
98
+ expect(src).not.toContain('"0.2.0"');
99
+ expect(src).not.toContain("\"dev\"");
99
100
  });
100
101
 
101
- test("db.ts declares IS_COMPILED", () => {
102
- const src = readText("src/db.ts");
102
+ test("paths.ts declares IS_COMPILED and VERSION", () => {
103
+ const src = readText("src/paths.ts");
103
104
  expect(src).toContain("declare const IS_COMPILED");
105
+ expect(src).toContain("declare const VERSION");
104
106
  });
105
107
 
106
- test("setup.ts declares REPO_URL and VERSION", () => {
108
+ test("setup.ts declares REPO_URL and imports resolveVersion", () => {
107
109
  const src = readText("src/setup.ts");
108
110
  expect(src).toContain("declare const REPO_URL");
109
- expect(src).toContain("declare const VERSION");
111
+ expect(src).toContain("resolveVersion");
110
112
  });
111
113
 
112
114
  test("build script injects all three constants", () => {
package/src/setup.ts CHANGED
@@ -1,51 +1,22 @@
1
1
  /**
2
2
  * setup.ts — Download the RouterOS documentation database and print MCP client config.
3
3
  *
4
- * Called by `rosetta --setup` (compiled binary) or `bun run src/setup.ts` (dev).
4
+ * Called by `rosetta --setup` (compiled binary), `bunx @tikoci/rosetta --setup`,
5
+ * or `bun run src/setup.ts` (dev).
5
6
  * Downloads ros-help.db.gz from the latest GitHub Release, decompresses it,
6
7
  * validates the DB, and prints config snippets for each MCP client.
7
8
  */
8
9
 
10
+ import { execSync } from "node:child_process";
9
11
  import { existsSync, writeFileSync } from "node:fs";
10
- import path from "node:path";
11
12
  import { gunzipSync } from "bun";
13
+ import { detectMode, resolveBaseDir, resolveDbPath, resolveVersion } from "./paths.ts";
12
14
 
13
15
  declare const REPO_URL: string;
14
- declare const VERSION: string;
15
16
 
16
17
  const GITHUB_REPO =
17
18
  typeof REPO_URL !== "undefined" ? REPO_URL : "tikoci/rosetta";
18
- const RELEASE_VERSION =
19
- typeof VERSION !== "undefined" ? VERSION : "dev";
20
-
21
- /** Where the binary (or dev project root) lives */
22
- function getBaseDir(): string {
23
- // If IS_COMPILED is defined, use the executable's directory
24
- // Otherwise use project root (one level up from src/)
25
- try {
26
- // @ts-expect-error IS_COMPILED defined at build time
27
- if (typeof IS_COMPILED !== "undefined" && IS_COMPILED) {
28
- return path.dirname(process.execPath);
29
- }
30
- } catch {
31
- // not compiled
32
- }
33
- return path.resolve(import.meta.dirname, "..");
34
- }
35
-
36
- /** The binary/script path for MCP config */
37
- function getServerCommand(): string {
38
- try {
39
- // @ts-expect-error IS_COMPILED defined at build time
40
- if (typeof IS_COMPILED !== "undefined" && IS_COMPILED) {
41
- return process.execPath;
42
- }
43
- } catch {
44
- // not compiled
45
- }
46
- // Dev mode — bun run src/mcp.ts
47
- return path.resolve(import.meta.dirname, "mcp.ts");
48
- }
19
+ const RELEASE_VERSION = resolveVersion(import.meta.dirname);
49
20
 
50
21
  /** Check if a DB file exists and has actual page data */
51
22
  function dbHasData(dbPath: string): boolean {
@@ -90,10 +61,8 @@ export async function downloadDb(
90
61
  }
91
62
 
92
63
  export async function runSetup(force = false) {
93
- const baseDir = getBaseDir();
94
- const dbPath = path.join(baseDir, "ros-help.db");
95
- const serverCmd = getServerCommand();
96
- const isCompiled = serverCmd === process.execPath;
64
+ const mode = detectMode(import.meta.dirname);
65
+ const dbPath = resolveDbPath(import.meta.dirname);
97
66
 
98
67
  console.log(`rosetta ${RELEASE_VERSION}`);
99
68
  console.log();
@@ -118,7 +87,8 @@ export async function runSetup(force = false) {
118
87
  console.log(`✓ Database ready (${row.c} pages, ${cmdRow.c} commands)`);
119
88
  } catch (e) {
120
89
  console.error(`✗ Database validation failed: ${e}`);
121
- console.error(` Try re-downloading with: ${isCompiled ? path.basename(serverCmd) : "bun run src/setup.ts"} --setup --force`);
90
+ const retryCmd = mode === "compiled" ? "rosetta" : mode === "package" ? "bunx @tikoci/rosetta" : "bun run src/setup.ts";
91
+ console.error(` Try re-downloading with: ${retryCmd} --setup --force`);
122
92
  process.exit(1);
123
93
  }
124
94
 
@@ -128,10 +98,21 @@ export async function runSetup(force = false) {
128
98
  console.log("Configure your MCP client:");
129
99
  console.log("─".repeat(60));
130
100
 
131
- if (isCompiled) {
132
- printCompiledConfig(serverCmd);
101
+ if (mode === "compiled") {
102
+ printCompiledConfig(process.execPath);
103
+ } else if (mode === "package") {
104
+ printPackageConfig();
133
105
  } else {
134
- printDevConfig(baseDir);
106
+ printDevConfig(resolveBaseDir(import.meta.dirname));
107
+ }
108
+ }
109
+
110
+ /** Try to resolve the absolute path to bunx (for clients that don't inherit PATH) */
111
+ function resolveBunxPath(): string | null {
112
+ try {
113
+ return execSync("which bunx", { encoding: "utf-8" }).trim() || null;
114
+ } catch {
115
+ return null;
135
116
  }
136
117
  }
137
118
 
@@ -175,6 +156,78 @@ function printCompiledConfig(serverCmd: string) {
175
156
  console.log(` }`);
176
157
  console.log(` }`);
177
158
  console.log();
159
+
160
+ // Copilot CLI
161
+ console.log("▸ GitHub Copilot CLI");
162
+ console.log(` Inside a copilot session, type /mcp add:`);
163
+ console.log(` Name: routeros-rosetta | Type: STDIO | Command: ${serverCmd}`);
164
+ console.log();
165
+
166
+ // OpenAI Codex
167
+ console.log("▸ OpenAI Codex");
168
+ console.log(` codex mcp add rosetta -- ${serverCmd}`);
169
+ console.log();
170
+ }
171
+
172
+ function printPackageConfig() {
173
+ // Resolve full path to bunx — Claude Desktop doesn't inherit shell PATH
174
+ const bunxFullPath = resolveBunxPath();
175
+
176
+ // Claude Desktop
177
+ const isMac = process.platform === "darwin";
178
+ const configPath = isMac
179
+ ? "~/Library/Application\\ Support/Claude/claude_desktop_config.json"
180
+ : "%APPDATA%\\Claude\\claude_desktop_config.json";
181
+
182
+ const bunxCmd = bunxFullPath ? JSON.stringify(bunxFullPath) : "\"bunx\"";
183
+ console.log();
184
+ console.log("▸ Claude Desktop");
185
+ console.log(` Edit: ${configPath}`);
186
+ console.log();
187
+ console.log(` {`);
188
+ console.log(` "mcpServers": {`);
189
+ console.log(` "rosetta": {`);
190
+ console.log(` "command": ${bunxCmd},`);
191
+ console.log(` "args": ["@tikoci/rosetta"]`);
192
+ console.log(` }`);
193
+ console.log(` }`);
194
+ console.log(` }`);
195
+ console.log();
196
+ if (bunxFullPath) {
197
+ console.log(` Note: Full path used because Claude Desktop may not inherit shell PATH.`);
198
+ console.log();
199
+ }
200
+ console.log(` Then restart Claude Desktop.`);
201
+
202
+ // Claude Code (inherits PATH — short form is fine)
203
+ console.log();
204
+ console.log("▸ Claude Code");
205
+ console.log(` claude mcp add rosetta -- bunx @tikoci/rosetta`);
206
+
207
+ // VS Code Copilot (inherits PATH)
208
+ console.log();
209
+ console.log("▸ VS Code Copilot (User Settings JSON)");
210
+ console.log();
211
+ console.log(` "mcp": {`);
212
+ console.log(` "servers": {`);
213
+ console.log(` "rosetta": {`);
214
+ console.log(` "command": "bunx",`);
215
+ console.log(` "args": ["@tikoci/rosetta"]`);
216
+ console.log(` }`);
217
+ console.log(` }`);
218
+ console.log(` }`);
219
+ console.log();
220
+
221
+ // Copilot CLI (inherits PATH)
222
+ console.log("▸ GitHub Copilot CLI");
223
+ console.log(` Inside a copilot session, type /mcp add:`);
224
+ console.log(` Name: routeros-rosetta | Type: STDIO | Command: bunx @tikoci/rosetta`);
225
+ console.log();
226
+
227
+ // OpenAI Codex (inherits PATH)
228
+ console.log("▸ OpenAI Codex");
229
+ console.log(` codex mcp add rosetta -- bunx @tikoci/rosetta`);
230
+ console.log();
178
231
  }
179
232
 
180
233
  function printDevConfig(baseDir: string) {
@@ -212,6 +265,17 @@ function printDevConfig(baseDir: string) {
212
265
  console.log("▸ VS Code Copilot");
213
266
  console.log(` The repo includes .vscode/mcp.json — just open the folder in VS Code.`);
214
267
  console.log();
268
+
269
+ // Copilot CLI
270
+ console.log("▸ GitHub Copilot CLI");
271
+ console.log(` Inside a copilot session, type /mcp add:`);
272
+ console.log(` Name: routeros-rosetta | Type: STDIO | Command: bun run src/mcp.ts`);
273
+ console.log();
274
+
275
+ // OpenAI Codex
276
+ console.log("▸ OpenAI Codex");
277
+ console.log(` codex mcp add rosetta -- bun run src/mcp.ts`);
278
+ console.log();
215
279
  }
216
280
 
217
281
  // Run directly