trueicon 0.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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +334 -0
  3. package/dist/cache/downloader.d.ts +18 -0
  4. package/dist/cache/downloader.js +131 -0
  5. package/dist/cache/downloader.js.map +1 -0
  6. package/dist/config/ensureIndex.d.ts +16 -0
  7. package/dist/config/ensureIndex.js +41 -0
  8. package/dist/config/ensureIndex.js.map +1 -0
  9. package/dist/config/loadConfig.d.ts +12 -0
  10. package/dist/config/loadConfig.js +71 -0
  11. package/dist/config/loadConfig.js.map +1 -0
  12. package/dist/config/versions.d.ts +16 -0
  13. package/dist/config/versions.js +31 -0
  14. package/dist/config/versions.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +34 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/indexer/buildIndex.d.ts +27 -0
  19. package/dist/indexer/buildIndex.js +83 -0
  20. package/dist/indexer/buildIndex.js.map +1 -0
  21. package/dist/indexer/types.d.ts +35 -0
  22. package/dist/indexer/types.js +2 -0
  23. package/dist/indexer/types.js.map +1 -0
  24. package/dist/providers/adapter.d.ts +20 -0
  25. package/dist/providers/adapter.js +10 -0
  26. package/dist/providers/adapter.js.map +1 -0
  27. package/dist/providers/adapters/heroicons.d.ts +4 -0
  28. package/dist/providers/adapters/heroicons.js +73 -0
  29. package/dist/providers/adapters/heroicons.js.map +1 -0
  30. package/dist/providers/adapters/index.d.ts +2 -0
  31. package/dist/providers/adapters/index.js +10 -0
  32. package/dist/providers/adapters/index.js.map +1 -0
  33. package/dist/providers/adapters/lucide.d.ts +2 -0
  34. package/dist/providers/adapters/lucide.js +57 -0
  35. package/dist/providers/adapters/lucide.js.map +1 -0
  36. package/dist/providers/adapters/react-icons.d.ts +3 -0
  37. package/dist/providers/adapters/react-icons.js +66 -0
  38. package/dist/providers/adapters/react-icons.js.map +1 -0
  39. package/dist/providers/jsLiteral.d.ts +20 -0
  40. package/dist/providers/jsLiteral.js +137 -0
  41. package/dist/providers/jsLiteral.js.map +1 -0
  42. package/dist/providers/registry.d.ts +9 -0
  43. package/dist/providers/registry.js +23 -0
  44. package/dist/providers/registry.js.map +1 -0
  45. package/dist/providers/svg.d.ts +9 -0
  46. package/dist/providers/svg.js +97 -0
  47. package/dist/providers/svg.js.map +1 -0
  48. package/dist/search/search.d.ts +26 -0
  49. package/dist/search/search.js +45 -0
  50. package/dist/search/search.js.map +1 -0
  51. package/dist/server.d.ts +4 -0
  52. package/dist/server.js +17 -0
  53. package/dist/server.js.map +1 -0
  54. package/dist/synonyms/loadSynonyms.d.ts +5 -0
  55. package/dist/synonyms/loadSynonyms.js +20 -0
  56. package/dist/synonyms/loadSynonyms.js.map +1 -0
  57. package/dist/synonyms/synonyms.json +80 -0
  58. package/dist/tools/context.d.ts +26 -0
  59. package/dist/tools/context.js +43 -0
  60. package/dist/tools/context.js.map +1 -0
  61. package/dist/tools/getIcon.d.ts +16 -0
  62. package/dist/tools/getIcon.js +50 -0
  63. package/dist/tools/getIcon.js.map +1 -0
  64. package/dist/tools/listProviders.d.ts +20 -0
  65. package/dist/tools/listProviders.js +22 -0
  66. package/dist/tools/listProviders.js.map +1 -0
  67. package/dist/tools/ping.d.ts +2 -0
  68. package/dist/tools/ping.js +12 -0
  69. package/dist/tools/ping.js.map +1 -0
  70. package/dist/tools/searchIcons.d.ts +31 -0
  71. package/dist/tools/searchIcons.js +97 -0
  72. package/dist/tools/searchIcons.js.map +1 -0
  73. package/package.json +38 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 manikumarkv
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,334 @@
1
+ # TrueIcon
2
+
3
+ TrueIcon is an [MCP](https://modelcontextprotocol.io) server that gives AI coding assistants exact, version-correct icon references. Your assistant searches the icon packages your project actually uses (`lucide-react`, `react-icons`, `@heroicons/react`) and gets back real icon names, import paths and a ready-to-paste `import` line.
4
+
5
+ ## Why
6
+
7
+ AI assistants often guess icon names. The guess can be an icon that never existed, one renamed a few releases ago, or one from a different library, and you only find out when the build fails. TrueIcon closes that gap:
8
+
9
+ - It reads which icon packages and versions your project uses.
10
+ - It downloads those exact versions from npm and indexes every icon once.
11
+ - The assistant calls `search_icons` and gets results that are guaranteed to exist in that version, for example `import { Trash2 } from 'lucide-react';`.
12
+
13
+ ## Supported providers
14
+
15
+ | Provider id | npm package | Icon naming |
16
+ | ------------- | ------------------ | ---------------------------------------------------------------- |
17
+ | `lucide` | `lucide-react` | Lucide's file names, e.g. `trash-2` → `Trash2` |
18
+ | `heroicons` | `@heroicons/react` | `<icon>-<size>-<style>`, e.g. `trash-24-outline` → `TrashIcon` |
19
+ | `react-icons` | `react-icons` | `<set>-<icon>`, e.g. `fa6-beer-mug-empty` → `FaBeerMugEmpty` |
20
+
21
+ Tools accept either the provider id or the npm package name (`"lucide"` or `"lucide-react"`). Usage snippets are for React.
22
+
23
+ ## Install
24
+
25
+ TrueIcon needs Node.js 20 or newer.
26
+
27
+ ```sh
28
+ # Run without installing (this is what the MCP configs below do)
29
+ npx -y trueicon
30
+
31
+ # Or install globally and run the `trueicon` binary
32
+ npm i -g trueicon
33
+ trueicon
34
+ ```
35
+
36
+ `trueicon` is a stdio MCP server. Your MCP client starts it; running it by hand only prints `trueicon: v0.1.0 running on stdio` to stderr and waits for JSON-RPC on stdin.
37
+
38
+ ## Quick start
39
+
40
+ 1. Add a `.iconmcp.json` to your project root that lists your icon packages:
41
+
42
+ ```json
43
+ {
44
+ "providers": [
45
+ { "package": "lucide-react" },
46
+ { "package": "@heroicons/react", "version": "2.1.5" }
47
+ ]
48
+ }
49
+ ```
50
+
51
+ 2. Register TrueIcon with your MCP client ([Claude Code](#claude-code) or [Claude Desktop](#claude-desktop)).
52
+ 3. Ask your assistant for an icon. The first search for each package downloads and indexes it, which takes a few seconds. Later searches use the local cache.
53
+
54
+ ## Configuration: `.iconmcp.json`
55
+
56
+ TrueIcon looks for `.iconmcp.json` in the project directory. That is `$TRUEICON_PROJECT_DIR` if set, otherwise the server's working directory.
57
+
58
+ ```json
59
+ {
60
+ "providers": [
61
+ { "package": "lucide-react" },
62
+ { "package": "react-icons", "version": "5.3.0" },
63
+ { "package": "@heroicons/react", "version": "^2.1.0" }
64
+ ]
65
+ }
66
+ ```
67
+
68
+ | Field | Type | Required | Meaning |
69
+ | ----------------------- | ------ | -------- | ---------------------------------------------------------------------------------------- |
70
+ | `providers` | array | yes | Icon packages the project uses. `search_icons` searches all of them by default. |
71
+ | `providers[].package` | string | yes | npm package name: `lucide-react`, `react-icons` or `@heroicons/react`. |
72
+ | `providers[].version` | string | no | Exact version or npm range. If omitted, it is read from `package.json` (see below). |
73
+
74
+ - If the file is missing, no providers are configured. `search_icons` then only works when you pass `provider` explicitly, and `get_icon` still works.
75
+ - Unsupported packages in the list are skipped, and `search_icons` reports them as a warning.
76
+ - Invalid JSON or a malformed entry makes the tools return an error that names the file and the bad field.
77
+
78
+ ### Environment variables
79
+
80
+ | Variable | Default | Purpose |
81
+ | ---------------------- | --------------------- | ---------------------------------------------------- |
82
+ | `TRUEICON_PROJECT_DIR` | working directory | Project root holding `.iconmcp.json` and `package.json` |
83
+ | `TRUEICON_CACHE` | `~/.trueicon/cache` | Where downloaded packages and indexes are stored |
84
+
85
+ ## Versions
86
+
87
+ ### Auto-detection
88
+
89
+ A provider's version is resolved in this order:
90
+
91
+ 1. The `version` argument passed to the tool call, if any.
92
+ 2. The provider's `version` in `.iconmcp.json`.
93
+ 3. The version range declared for the package in the project's `package.json`, checking `dependencies` first and then `devDependencies`.
94
+
95
+ If none of these is available, the tool asks you to pin the version or add the package to `package.json`. TrueIcon reads the declared range from `package.json`. It does not read `node_modules` or the lockfile. For a range, it indexes the range's base version: `^0.460.0` indexes `lucide-react@0.460.0`. For `a || b` ranges, only the first part counts. To match an exact installed version, pin it in `.iconmcp.json`.
96
+
97
+ ### Version policy
98
+
99
+ Indexes are keyed by **major.minor**:
100
+
101
+ - **Patch versions are ignored.** One index serves all of `0.460.x`. The index built from `0.460.0` answers requests for `0.460.3`.
102
+ - **A minor change gets its own index.** Bumping `lucide-react` from `0.460` to `0.461` builds a fresh index on the next search, with no manual step.
103
+ - **Majors are strict.** A different major is always a separate index and is never served from another major's index.
104
+ - **Cached indexes rebuild automatically** when the bundled `synonyms.json` changes (detected by hash) or the index format changes.
105
+
106
+ ## Using it with Claude
107
+
108
+ ### Claude Code
109
+
110
+ Add TrueIcon from your project directory:
111
+
112
+ ```sh
113
+ claude mcp add trueicon -- npx -y trueicon
114
+ ```
115
+
116
+ Or commit a `.mcp.json` at the project root to share it with your team:
117
+
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "trueicon": {
122
+ "command": "npx",
123
+ "args": ["-y", "trueicon"]
124
+ }
125
+ }
126
+ }
127
+ ```
128
+
129
+ Claude Code starts the server in your project directory, so it finds `.iconmcp.json` and `package.json` there. If it runs from somewhere else, add `"env": { "TRUEICON_PROJECT_DIR": "/absolute/path/to/project" }`.
130
+
131
+ ### Claude Desktop
132
+
133
+ Claude Desktop doesn't start servers in your project directory, so set `TRUEICON_PROJECT_DIR`. Edit `claude_desktop_config.json`: `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows.
134
+
135
+ ```json
136
+ {
137
+ "mcpServers": {
138
+ "trueicon": {
139
+ "command": "npx",
140
+ "args": ["-y", "trueicon"],
141
+ "env": {
142
+ "TRUEICON_PROJECT_DIR": "/absolute/path/to/your/project"
143
+ }
144
+ }
145
+ }
146
+ }
147
+ ```
148
+
149
+ Restart Claude Desktop after editing the file.
150
+
151
+ ## Tools
152
+
153
+ Every tool returns a single JSON text block. On failure, the block is `{"error": "..."}` and the MCP result is flagged with `isError: true`.
154
+
155
+ ### `search_icons`
156
+
157
+ Searches the index and returns ranked matches with import statements.
158
+
159
+ | Argument | Type | Required | Description |
160
+ | ---------- | ------- | -------- | ------------------------------------------------------------------------------------ |
161
+ | `query` | string | yes | What the icon should depict, e.g. `"trash"` |
162
+ | `provider` | string | no | Provider id or package. Default: every provider in `.iconmcp.json` |
163
+ | `version` | string | no | Version or range. Default: resolved as described in [Versions](#versions) |
164
+ | `style` | string | no | Exact style filter: `"outline"` or `"solid"` (lucide icons are all `outline`) |
165
+ | `set` | string | no | Exact set filter, e.g. `"fa6"` or `"md"` for react-icons |
166
+ | `limit` | integer | no | Maximum results, 1 to 50, default 10 |
167
+
168
+ Example call:
169
+
170
+ ```json
171
+ { "query": "trash", "provider": "lucide", "limit": 3 }
172
+ ```
173
+
174
+ Response:
175
+
176
+ ```json
177
+ {
178
+ "results": [
179
+ { "name": "trash", "importName": "Trash", "importPath": "lucide-react", "package": "lucide-react",
180
+ "version": "0.460.0", "style": "outline", "set": "lucide",
181
+ "usage": "import { Trash } from 'lucide-react';", "score": 2.0e-14 },
182
+ { "name": "trash-2", "importName": "Trash2", "importPath": "lucide-react", "package": "lucide-react",
183
+ "version": "0.460.0", "style": "outline", "set": "lucide",
184
+ "usage": "import { Trash2 } from 'lucide-react';", "score": 1.6e-6 },
185
+ { "name": "delete", "importName": "Delete", "importPath": "lucide-react", "package": "lucide-react",
186
+ "version": "0.460.0", "style": "outline", "set": "lucide",
187
+ "usage": "import { Delete } from 'lucide-react';", "score": 1.2e-4 }
188
+ ]
189
+ }
190
+ ```
191
+
192
+ How search works:
193
+
194
+ - `provider`, `style` and `set` are exact, case-insensitive filters. They are applied before ranking.
195
+ - Ranking uses [Fuse.js](https://www.fusejs.io/) fuzzy matching over the icon name, import name, keywords and tags. Small typos are tolerated: `"detele"` finds `Delete`.
196
+ - `score` runs from `0` (perfect) to `1`, so lower is better. Results from several providers are merged and sorted by score.
197
+ - Short keyword queries (`"trash"`, `"settings"`, `"beer"`) work best. The query is matched as one string, so a multi-word phrase such as `"trash can"` may return fewer results than its main keyword alone.
198
+ - If one provider fails, for example because its version can't be resolved or the download fails, its results are skipped and a `warnings` array explains why. The other providers still return results.
199
+
200
+ ### `list_providers`
201
+
202
+ Takes no arguments. Returns the providers configured in `.iconmcp.json` with their resolved versions, plus every provider TrueIcon supports.
203
+
204
+ ```json
205
+ {
206
+ "configured": [
207
+ { "id": "lucide", "package": "lucide-react", "version": "^0.460.0", "source": "package.json" },
208
+ { "id": "heroicons", "package": "@heroicons/react", "version": "2.1.5", "source": "iconmcp.json" }
209
+ ],
210
+ "registry": [
211
+ { "id": "react-icons", "package": "react-icons", "description": "Aggregated icon sets (Font Awesome, Material, Feather, and more) as React components" },
212
+ { "id": "lucide", "package": "lucide-react", "description": "Lucide icons as React components" },
213
+ { "id": "heroicons", "package": "@heroicons/react", "description": "Heroicons by the Tailwind CSS team as React components" }
214
+ ]
215
+ }
216
+ ```
217
+
218
+ `source` is `"iconmcp.json"` or `"package.json"`. `version` and `source` are `null` when neither file provides a version. `id` is `null` for a configured package TrueIcon doesn't support.
219
+
220
+ ### `get_icon`
221
+
222
+ Gets the full record and import statement for an icon whose name the assistant already knows.
223
+
224
+ | Argument | Type | Required | Description |
225
+ | ---------- | ------ | -------- | ------------------------------------------------------------------ |
226
+ | `name` | string | yes | Icon name (`"trash-2"`) or import name (`"Trash2"`). Exact match first, then case-insensitive |
227
+ | `provider` | string | yes | Provider id or package |
228
+ | `version` | string | no | Version or range. Default: resolved as described in [Versions](#versions) |
229
+
230
+ Example call:
231
+
232
+ ```json
233
+ { "name": "Trash2", "provider": "lucide" }
234
+ ```
235
+
236
+ Response:
237
+
238
+ ```json
239
+ {
240
+ "id": "lucide-react@0.460:trash-2",
241
+ "name": "trash-2",
242
+ "importName": "Trash2",
243
+ "importPath": "lucide-react",
244
+ "provider": "lucide",
245
+ "package": "lucide-react",
246
+ "version": "0.460.0",
247
+ "style": "outline",
248
+ "set": "lucide",
249
+ "categories": [],
250
+ "tags": [],
251
+ "keywords": ["trash", "2", "delete", "remove", "bin", "garbage", "rubbish"],
252
+ "svg": "<path d=\"M3 6h18\"/><path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"/>…",
253
+ "usage": "import { Trash2 } from 'lucide-react';"
254
+ }
255
+ ```
256
+
257
+ `svg` is the icon's inner SVG markup, meaning the children of the root `<svg>` element. Heroicons uses the same import name in every size and style (`TrashIcon`). Pass the full variant name, such as `"trash-24-outline"`, to get a specific one.
258
+
259
+ ### `ping`
260
+
261
+ A health check that returns `{"status":"ok","server":"trueicon"}`.
262
+
263
+ ## Indexing and caching
264
+
265
+ The first time a tool needs `package@major.minor`, TrueIcon does the following:
266
+
267
+ 1. It downloads the package tarball from `https://registry.npmjs.org`, verifies its sha512 integrity, and extracts it into the cache.
268
+ 2. It parses the package's shipped files with the provider's adapter. Nothing is executed. Icons are read from the compiled source.
269
+ 3. It writes `index.json` (one record per icon) and `meta.json` (exact version, synonyms hash, index format, build time).
270
+
271
+ Later calls only read `index.json`. Package files are never touched at query time, and your `node_modules` is never read or modified. If several tool calls need the same index at once, they share one download.
272
+
273
+ The cache root is `~/.trueicon/cache`, or `$TRUEICON_CACHE` if set:
274
+
275
+ ```
276
+ ~/.trueicon/cache/
277
+ ├── lucide-react@0.460/ # extracted package + index.json + meta.json
278
+ ├── react-icons@5.3/ # extracted package + index.json + meta.json
279
+ ├── heroicons-react@2.1/ # extracted @heroicons/react package
280
+ └── @heroicons/react@2.1/ # index.json + meta.json for @heroicons/react
281
+ ```
282
+
283
+ - Downloads go to `<package>@<major.minor>`, where scoped names are flattened: `@heroicons/react` becomes `heroicons-react`. A `.download-complete` marker is written last, and a directory without it is treated as partial and replaced.
284
+ - Indexes go to `<package>@<major.minor>/index.json` and `meta.json`. For unscoped packages this is the same directory as the download.
285
+ - The cache is safe to delete. It is rebuilt on demand, which needs network access.
286
+
287
+ Each record's `keywords` combine the name parts, the tags, and synonym expansions from the bundled `synonyms.json`. The expansions are added at index time, so `"bin"` finds `Trash2` without any extra work at query time.
288
+
289
+ ## Contributing
290
+
291
+ ```sh
292
+ git clone https://github.com/manikumarkv/trueicon.git
293
+ cd trueicon
294
+ npm ci
295
+ npm run build # compile to dist/
296
+ npm test # vitest
297
+ npm run lint # eslint
298
+ npm run typecheck # tsc --noEmit
299
+ ```
300
+
301
+ CI runs lint, typecheck and tests on Node 20 and 22 for every push and pull request.
302
+
303
+ ### Extending `synonyms.json`
304
+
305
+ `src/synonyms/synonyms.json` maps a term to extra search terms:
306
+
307
+ ```json
308
+ {
309
+ "trash": ["delete", "remove", "bin", "garbage", "rubbish"],
310
+ "logout": ["sign-out", "signout", "exit", "leave"]
311
+ }
312
+ ```
313
+
314
+ - Keys are matched against an icon's name parts (the name split on `-`) and its tags. `trash-2` matches the key `trash`.
315
+ - Values are added to that icon's `keywords`.
316
+ - Expansion is one-way. If `bin` should also find icons named `delete`, add both `"trash": ["bin"]` and `"delete": ["bin"]`, or add a reverse entry.
317
+ - Write keys and values in lowercase, and give every key a non-empty array of strings. `tests/synonyms.test.ts` checks this.
318
+ - Changing the file changes its hash, so cached indexes rebuild automatically on the next search.
319
+
320
+ ### Adding a provider
321
+
322
+ 1. **Register it** in `src/providers/registry.ts` with a stable `id`, the npm `package` and a short `description`.
323
+ 2. **Write an adapter** in `src/providers/adapters/<provider>.ts` that exports `parseIcons(packageDir: string): RawIcon[]` (see `src/providers/adapter.ts`). It gets the extracted package directory and returns one `RawIcon` per icon:
324
+ - `name`: kebab-case and **unique within the package**, because it becomes part of the record id. Use `toKebabCase` from `adapter.ts`. If the package has variants with clashing component names, add the variant to the name, as the heroicons and react-icons adapters do.
325
+ - `importName` and `importPath`: the exact export and module specifier a user would import.
326
+ - `svg`: the inner SVG markup. `LiteralCursor` (`src/providers/jsLiteral.ts`) parses JS object and array literals without executing code. `toSvgAttrs` and `renderSvg` (`src/providers/svg.ts`) turn React props into SVG markup.
327
+ - Optional `style`, `set`, `categories` and `tags`.
328
+ - Put a comment at the top of the adapter describing the package's file layout, as the existing adapters do.
329
+ 3. **Wire it up** in `src/providers/adapters/index.ts` by adding it to `ADAPTERS` under the provider id.
330
+ 4. **Test it.** Add a small pinned fixture under `tests/fixtures/<provider>/` that mirrors the package layout, with a few real icon files plus any files the adapter must skip. Then add `tests/adapters/<provider>.test.ts`, covering name mapping, import paths, SVG output and `buildIndex` record ids like the existing adapter tests. `tests/adapters/common.test.ts` fails if a registered provider has no adapter.
331
+
332
+ ## License
333
+
334
+ [MIT](LICENSE) © 2026 manikumarkv
@@ -0,0 +1,18 @@
1
+ export declare const REGISTRY_URL = "https://registry.npmjs.org";
2
+ export declare const MARKER_FILE = ".download-complete";
3
+ export interface DownloadOptions {
4
+ cacheRoot?: string;
5
+ }
6
+ export interface DownloadResult {
7
+ dir: string;
8
+ version: string;
9
+ }
10
+ export declare function resolveCacheRoot(opts?: DownloadOptions): string;
11
+ export declare function sanitizePackageName(pkg: string): string;
12
+ export declare function cacheDirName(pkg: string, version: string): string;
13
+ /**
14
+ * Downloads and extracts pkg@version from the npm registry into the local cache (see layout
15
+ * above). `version` may be an exact version or a dist-tag such as "latest". Returns the
16
+ * cache dir and the exact version it contains; skips the download if already cached.
17
+ */
18
+ export declare function downloadPackage(pkg: string, version: string, opts?: DownloadOptions): Promise<DownloadResult>;
@@ -0,0 +1,131 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { Readable } from "node:stream";
6
+ import { pipeline } from "node:stream/promises";
7
+ import { x as extractTar } from "tar";
8
+ /*
9
+ * Cache layout
10
+ * ------------
11
+ * Each downloaded package lives in its own directory:
12
+ *
13
+ * <cacheRoot>/<sanitizedPackage>@<major>.<minor>/
14
+ *
15
+ * - cacheRoot: opts.cacheRoot, else $TRUEICON_CACHE, else ~/.trueicon/cache
16
+ * - sanitizedPackage: package name with the leading "@" removed and "/" replaced by "-"
17
+ * (e.g. "@heroicons/react" -> "heroicons-react")
18
+ * - Only major.minor is kept: patch releases of an icon package share one cache entry
19
+ * (e.g. @heroicons/react@1.0.6 -> heroicons-react@1.0).
20
+ *
21
+ * The tarball's "package/" prefix is stripped, so <dir>/package.json is the package manifest.
22
+ * A ".download-complete" marker (containing the exact version) is written last; a directory
23
+ * without it is treated as a partial download and replaced on the next call.
24
+ */
25
+ export const REGISTRY_URL = "https://registry.npmjs.org";
26
+ export const MARKER_FILE = ".download-complete";
27
+ const EXACT_VERSION = /^(\d+)\.(\d+)\.\d+(?:[-+].*)?$/;
28
+ // Scoped or unscoped npm package name; rejects anything that could escape the cache root.
29
+ const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
30
+ export function resolveCacheRoot(opts = {}) {
31
+ return opts.cacheRoot ?? process.env.TRUEICON_CACHE ?? join(homedir(), ".trueicon", "cache");
32
+ }
33
+ export function sanitizePackageName(pkg) {
34
+ return pkg.replace(/^@/, "").replace(/\//g, "-");
35
+ }
36
+ // Returns the cache directory name for an exact version, e.g. "heroicons-react@1.0".
37
+ export function cacheDirName(pkg, version) {
38
+ const match = EXACT_VERSION.exec(version);
39
+ if (!match)
40
+ throw new Error(`Expected an exact semver version for ${pkg}, got "${version}"`);
41
+ return `${sanitizePackageName(pkg)}@${match[1]}.${match[2]}`;
42
+ }
43
+ async function readMarker(dir) {
44
+ try {
45
+ return (await readFile(join(dir, MARKER_FILE), "utf8")).trim();
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ }
51
+ async function fetchOrThrow(url, what) {
52
+ try {
53
+ return await fetch(url);
54
+ }
55
+ catch (error) {
56
+ throw new Error(`Network error fetching ${what} from ${url}: ${String(error)}`, { cause: error });
57
+ }
58
+ }
59
+ async function fetchVersionMetadata(pkg, version) {
60
+ const url = `${REGISTRY_URL}/${pkg}/${encodeURIComponent(version)}`;
61
+ const res = await fetchOrThrow(url, `metadata for ${pkg}@${version}`);
62
+ if (res.status === 404)
63
+ throw new Error(`Unknown package or version: ${pkg}@${version}`);
64
+ if (!res.ok)
65
+ throw new Error(`Registry returned HTTP ${res.status} for ${pkg}@${version}`);
66
+ const meta = (await res.json());
67
+ if (!meta.dist?.tarball)
68
+ throw new Error(`Registry metadata for ${pkg}@${version} has no dist.tarball`);
69
+ return meta;
70
+ }
71
+ // Verifies an SRI string's sha512 entry (if any) against the tarball bytes.
72
+ function verifyIntegrity(data, integrity, label) {
73
+ const expected = integrity
74
+ ?.split(/\s+/)
75
+ .find((entry) => entry.startsWith("sha512-"))
76
+ ?.slice("sha512-".length);
77
+ if (!expected)
78
+ return;
79
+ const actual = createHash("sha512").update(data).digest("base64");
80
+ if (actual !== expected) {
81
+ throw new Error(`Integrity check failed for ${label}: expected sha512-${expected}, got sha512-${actual}`);
82
+ }
83
+ }
84
+ /**
85
+ * Downloads and extracts pkg@version from the npm registry into the local cache (see layout
86
+ * above). `version` may be an exact version or a dist-tag such as "latest". Returns the
87
+ * cache dir and the exact version it contains; skips the download if already cached.
88
+ */
89
+ export async function downloadPackage(pkg, version, opts = {}) {
90
+ if (!PACKAGE_NAME.test(pkg))
91
+ throw new Error(`Invalid npm package name: "${pkg}"`);
92
+ const cacheRoot = resolveCacheRoot(opts);
93
+ // Exact versions can be checked against the cache without touching the network.
94
+ if (EXACT_VERSION.test(version)) {
95
+ const dir = join(cacheRoot, cacheDirName(pkg, version));
96
+ const cached = await readMarker(dir);
97
+ if (cached)
98
+ return { dir, version: cached };
99
+ }
100
+ const meta = await fetchVersionMetadata(pkg, version);
101
+ const dir = join(cacheRoot, cacheDirName(pkg, meta.version));
102
+ const cached = await readMarker(dir);
103
+ if (cached)
104
+ return { dir, version: cached };
105
+ const label = `${pkg}@${meta.version}`;
106
+ const tarballUrl = meta.dist.tarball;
107
+ const res = await fetchOrThrow(tarballUrl, `tarball for ${label}`);
108
+ if (!res.ok)
109
+ throw new Error(`Tarball download for ${label} failed with HTTP ${res.status}`);
110
+ let data;
111
+ try {
112
+ data = Buffer.from(await res.arrayBuffer());
113
+ }
114
+ catch (error) {
115
+ throw new Error(`Network error reading tarball for ${label}: ${String(error)}`, { cause: error });
116
+ }
117
+ verifyIntegrity(data, meta.dist.integrity, label);
118
+ // Clear any partial previous attempt, then extract (dropping the tarball's "package/" prefix).
119
+ await rm(dir, { recursive: true, force: true });
120
+ await mkdir(dir, { recursive: true });
121
+ try {
122
+ await pipeline(Readable.from(data), extractTar({ cwd: dir, strip: 1 }));
123
+ }
124
+ catch (error) {
125
+ await rm(dir, { recursive: true, force: true });
126
+ throw new Error(`Failed to extract tarball for ${label}: ${String(error)}`, { cause: error });
127
+ }
128
+ await writeFile(join(dir, MARKER_FILE), `${meta.version}\n`);
129
+ return { dir, version: meta.version };
130
+ }
131
+ //# sourceMappingURL=downloader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downloader.js","sourceRoot":"","sources":["../../src/cache/downloader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { Synonyms } from "../indexer/types.js";
2
+ import { type IndexAction } from "./versions.js";
3
+ export interface EnsureIndexOptions {
4
+ cacheRoot: string;
5
+ providerId: string;
6
+ packageName: string;
7
+ /** Exact version or npm range, e.g. "^0.460.0". */
8
+ version: string;
9
+ synonyms: Synonyms;
10
+ }
11
+ export interface EnsureIndexResult {
12
+ cacheDir: string;
13
+ action: IndexAction;
14
+ }
15
+ /** Makes sure an up-to-date index for packageName@version exists under cacheRoot. */
16
+ export declare function ensureIndex(opts: EnsureIndexOptions): Promise<EnsureIndexResult>;
@@ -0,0 +1,41 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { downloadPackage } from "../cache/downloader.js";
4
+ import { buildIndexFromPackage, hashSynonyms, META_FILE } from "../indexer/buildIndex.js";
5
+ import { indexKey, parseVersion, resolveIndexAction } from "./versions.js";
6
+ // meta.json is written last by writeIndex, so its presence marks a complete index.
7
+ async function readMeta(cacheDir) {
8
+ try {
9
+ return JSON.parse(await readFile(join(cacheDir, META_FILE), "utf8"));
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ // In-progress builds by index dir, so concurrent tool calls share one download instead of racing.
16
+ const inFlight = new Map();
17
+ /** Makes sure an up-to-date index for packageName@version exists under cacheRoot. */
18
+ export function ensureIndex(opts) {
19
+ const cacheDir = join(opts.cacheRoot, indexKey(opts.packageName, opts.version));
20
+ const pending = inFlight.get(cacheDir);
21
+ if (pending)
22
+ return pending;
23
+ const run = ensureIndexAt(cacheDir, opts).finally(() => inFlight.delete(cacheDir));
24
+ inFlight.set(cacheDir, run);
25
+ return run;
26
+ }
27
+ async function ensureIndexAt(cacheDir, opts) {
28
+ const { cacheRoot, providerId, packageName, version, synonyms } = opts;
29
+ const action = resolveIndexAction(version, await readMeta(cacheDir), hashSynonyms(synonyms));
30
+ if (action === "use")
31
+ return { cacheDir, action };
32
+ // Download the range's base version exactly so the result lands under the same index key.
33
+ const { major, minor, patch } = parseVersion(version);
34
+ const downloaded = await downloadPackage(packageName, `${major}.${minor}.${patch}`, { cacheRoot });
35
+ const built = await buildIndexFromPackage(downloaded.dir, providerId, downloaded.version, cacheRoot, synonyms);
36
+ if (built.dir !== cacheDir) {
37
+ throw new Error(`Index for ${packageName}@${version} was written to ${built.dir}, expected ${cacheDir}`);
38
+ }
39
+ return { cacheDir, action };
40
+ }
41
+ //# sourceMappingURL=ensureIndex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensureIndex.js","sourceRoot":"","sources":["../../src/config/ensureIndex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1F,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,kBAAkB,EAAoB,MAAM,eAAe,CAAC;AAgB7F,mFAAmF;AACnF,KAAK,UAAU,QAAQ,CAAC,QAAgB;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAc,CAAC;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAsC,CAAC;AAE/D,qFAAqF;AACrF,MAAM,UAAU,WAAW,CAAC,IAAwB;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAChF,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,MAAM,GAAG,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IACnF,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC5B,OAAO,GAAG,CAAC;AACb,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,IAAwB;IACrE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACvE,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,EAAE,MAAM,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7F,IAAI,MAAM,KAAK,KAAK;QAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAElD,0FAA0F;IAC1F,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC,WAAW,EAAE,GAAG,KAAK,IAAI,KAAK,IAAI,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IACnG,MAAM,KAAK,GAAG,MAAM,qBAAqB,CAAC,UAAU,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC/G,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,aAAa,WAAW,IAAI,OAAO,mBAAmB,KAAK,CAAC,GAAG,cAAc,QAAQ,EAAE,CAAC,CAAC;IAC3G,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,12 @@
1
+ export declare const CONFIG_FILE = ".iconmcp.json";
2
+ export interface ProviderConfig {
3
+ package: string;
4
+ version?: string;
5
+ }
6
+ export interface ProjectConfig {
7
+ providers: ProviderConfig[];
8
+ }
9
+ /** Reads <projectDir>/.iconmcp.json; a missing file means no configured providers. */
10
+ export declare function loadProjectConfig(projectDir: string): ProjectConfig;
11
+ /** Returns the version range declared for packageName in <projectDir>/package.json. */
12
+ export declare function detectVersion(projectDir: string, packageName: string): string;