@bendyline/gezel-catalog 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bendyline
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,36 @@
1
+ # @bendyline/gezel-catalog
2
+
3
+ Catalog loader for [gezel](https://github.com/bendyline/gezel) — resolves the
4
+ chat, image and video model catalogs, toolsets, connector types, project types,
5
+ gezel role templates and craftbooks that the daemon serves.
6
+
7
+ ```bash
8
+ npm install @bendyline/gezel-catalog
9
+ ```
10
+
11
+ ## Content lives elsewhere
12
+
13
+ This package is the **loader**, not the content. The catalog data ships as
14
+ [`@bendyline/gilde`](https://www.npmjs.com/package/@bendyline/gilde), an
15
+ exact-pinned dependency resolved at runtime through `gildeDataDir()`.
16
+
17
+ ```ts
18
+ import { CatalogService } from '@bendyline/gezel-catalog';
19
+
20
+ const catalog = new CatalogService();
21
+ const models = await catalog.list('chat-model');
22
+ ```
23
+
24
+ Override the content root with `GEZEL_GILDE_DATA_DIR` for tests, evals or
25
+ operator-supplied catalogs.
26
+
27
+ Gilde takes open-source contributions — new models, toolsets and craftbooks are
28
+ proposed there, not here.
29
+
30
+ ## Stability
31
+
32
+ Public API under semver. The catalog *schemas* are defined in
33
+ [`@bendyline/gezel`](https://www.npmjs.com/package/@bendyline/gezel) and follow
34
+ its versioning.
35
+
36
+ MIT © Bendyline
@@ -0,0 +1,555 @@
1
+ import { CatalogKind, CatalogItemSummary, CatalogItemDetail, CatalogItemVersionInfo, CraftbookTestSpec, ToolsetCategory, ToolsetManifest } from '@bendyline/gezel';
2
+
3
+ /**
4
+ * ─ CatalogSource ───────────────────────────────────────────────────
5
+ *
6
+ * Abstracts where catalog items live. Two implementations today:
7
+ *
8
+ * - BundledSource: reads the on-disk `data/` directory shipped with
9
+ * this package. Always available, zero network.
10
+ * - RemoteSource: (TODO) fetches a static index.json + per-item
11
+ * manifests from an HTTP(S) URL. Stubbed until we
12
+ * publish a public catalog repo.
13
+ *
14
+ * The CatalogService (see service.ts) composes multiple sources.
15
+ *
16
+ * On-disk layout under `data/`:
17
+ *
18
+ * {kind-plural}/{shard}/{id}/manifest.json ← identity layer
19
+ * {kind-plural}/{shard}/{id}/versions/{semver}/manifest.json ← per-version
20
+ * {kind-plural}/{shard}/{id}/versions/{semver}/about.md ← (templates) prompt
21
+ *
22
+ * Craftbook templates (V2) replace the per-version manifest + about.md +
23
+ * scripts/ with ONE `versions/{semver}/craftbook.json` (a CraftbookDoc:
24
+ * prose inlined as `description`, scripts inlined as the `scripts` map).
25
+ * The legacy layout is still read as a fallback — user/community roots
26
+ * may carry it.
27
+ */
28
+ interface CatalogSource {
29
+ readonly id: string;
30
+ readonly label: string;
31
+ listKinds(): Promise<CatalogKind[]>;
32
+ list(kind: CatalogKind): Promise<CatalogItemSummary[]>;
33
+ /** When `version` is omitted, returns the auto-resolved latest. */
34
+ get(kind: CatalogKind, id: string, version?: string): Promise<CatalogItemDetail | null>;
35
+ /** All versions of an item, newest first. Empty when item is missing. */
36
+ listVersions(kind: CatalogKind, id: string): Promise<CatalogItemVersionInfo[]>;
37
+ /**
38
+ * Read a file relative to an item's folder. When `version` is set, the
39
+ * version subfolder is checked first; misses fall through to the item
40
+ * root so shared assets (`logo.svg`) don't have to be duplicated per
41
+ * version.
42
+ */
43
+ readItemFile(kind: CatalogKind, id: string, relPath: string, version?: string): Promise<Buffer | null>;
44
+ /**
45
+ * Optional: every file under an item's folder as item-relative paths. Only
46
+ * on-disk sources implement it (used by the `.gzl` exporter); synthetic
47
+ * sources (builtin toolsets) omit it.
48
+ */
49
+ listItemFiles?(kind: CatalogKind, id: string): Promise<string[]>;
50
+ /**
51
+ * Optional: a craftbook's eval descriptor (`versions/<v>/test.json`),
52
+ * tolerant-parsed. Null when the book, version, or sidecar is missing
53
+ * or unparseable. Only on-disk craftbook sources implement it.
54
+ */
55
+ getCraftbookTestSpec?(id: string, version?: string): Promise<{
56
+ version: string;
57
+ spec: CraftbookTestSpec;
58
+ } | null>;
59
+ }
60
+ interface BundledSourceOptions {
61
+ /**
62
+ * Override the on-disk root. Defaults to `data/` next to this package.
63
+ * A function is re-read on every disk access — the live gilde update
64
+ * mechanism uses this to flip the content root without reconstructing
65
+ * the source (CatalogService is built once at boot and held by many
66
+ * subsystems). A string keeps the historical freeze-at-construct
67
+ * behavior.
68
+ */
69
+ dataDir?: string | (() => string);
70
+ /** Source id surfaced via `CatalogService.listSources()`. */
71
+ id?: string;
72
+ /** Human-readable label. */
73
+ label?: string;
74
+ /**
75
+ * When true, skip the per-kind `index.json` fast-path and always walk
76
+ * the per-item folders. Used by the index builder itself (see
77
+ * gilde `tools/build-index.mjs`) to avoid feeding a stale or absent index
78
+ * back into itself, and by tests that want deterministic disk reads.
79
+ */
80
+ noIndex?: boolean;
81
+ /**
82
+ * The running app's version, compared against content `minGezelVersion`
83
+ * floors. Defaults to `GEZEL_VERSION`; injectable so tests can exercise
84
+ * gating against a stamped version (`GEZEL_VERSION` is `0.0.0` in dev
85
+ * checkouts, which bypasses all filtering).
86
+ */
87
+ gezelVersion?: string;
88
+ }
89
+ declare class BundledSource implements CatalogSource {
90
+ readonly id: string;
91
+ readonly label: string;
92
+ private readonly rootProvider;
93
+ private readonly useIndex;
94
+ private readonly gezelVersion;
95
+ constructor(options?: BundledSourceOptions | string);
96
+ private get root();
97
+ /** True when this build satisfies a content `minGezelVersion` floor. */
98
+ private floorSatisfied;
99
+ listKinds(): Promise<CatalogKind[]>;
100
+ list(kind: CatalogKind): Promise<CatalogItemSummary[]>;
101
+ /**
102
+ * Fast-path: when `{kindDir}/index.json` is present, load every
103
+ * summary from that one file instead of walking ~3,800 per-item
104
+ * folders. The index is generated by gilde `tools/build-index.mjs` and
105
+ * embeds the same `CatalogItemManifest` shape this source produces
106
+ * via the slow walk, plus a derived `category` for toolsets.
107
+ *
108
+ * Returns null when the index is missing or unreadable; callers fall
109
+ * back to `listFromDisk()`. Per-entry parse failures are skipped.
110
+ */
111
+ private listFromIndex;
112
+ private listFromDisk;
113
+ get(kind: CatalogKind, id: string, version?: string): Promise<CatalogItemDetail | null>;
114
+ listVersions(kind: CatalogKind, id: string): Promise<CatalogItemVersionInfo[]>;
115
+ readItemFile(kind: CatalogKind, id: string, relPath: string, version?: string): Promise<Buffer | null>;
116
+ /**
117
+ * Every file under an item's folder, as item-relative paths (e.g.
118
+ * `manifest.json`, `versions/1.0.0/pages/gallery/index.html`), sorted. Used
119
+ * by the `.gzl` exporter to pack an item verbatim — pages, seeds, and all
120
+ * assets, not just the ones the manifest names. Read each back with
121
+ * `readItemFile(kind, id, relPath)` (no version → item-relative). Empty when
122
+ * the item folder is missing.
123
+ */
124
+ listItemFiles(kind: CatalogKind, id: string): Promise<string[]>;
125
+ /** Parse a version folder's `craftbook.json`, or null when absent/invalid. */
126
+ private readCraftbookDoc;
127
+ /**
128
+ * A craftbook's eval descriptor (`versions/<v>/test.json`), tolerant-
129
+ * parsed so a spec written by a newer author still loads. Resolution
130
+ * mirrors `get`: no `version` → the latest non-yanked semver. Null on
131
+ * missing book/version/sidecar or an unparseable spec (CI's strict
132
+ * guard is where invalid specs get surfaced loudly — runtime readers
133
+ * degrade quietly).
134
+ */
135
+ getCraftbookTestSpec(id: string, version?: string): Promise<{
136
+ version: string;
137
+ spec: CraftbookTestSpec;
138
+ } | null>;
139
+ private itemDir;
140
+ /** Read + validate the identity (root) manifest for an item. */
141
+ private loadIdentity;
142
+ /** Enumerate `versions/{semver}/manifest.json` entries. Unsorted. */
143
+ private discoverVersionFolders;
144
+ /**
145
+ * Pick a target version. When `requested` is set, validate it exists —
146
+ * an explicit pin deliberately bypasses the yank / `minSupportedVersion`
147
+ * / `minGezelVersion` filters so installed content keeps resolving.
148
+ * Otherwise resolve the highest non-yanked semver above
149
+ * `minSupportedVersion` whose `minGezelVersion` floor this build
150
+ * satisfies. Returns null when nothing satisfies.
151
+ */
152
+ private pickVersion;
153
+ /**
154
+ * Compose identity + version into the resolved (flat) manifest shape
155
+ * that consumers expect. Returns null when identity is missing,
156
+ * version is missing, or validation fails.
157
+ */
158
+ private loadResolvedManifest;
159
+ private readOptional;
160
+ private logoUrlFor;
161
+ }
162
+
163
+ /**
164
+ * Composes one or more `CatalogSource`s behind a single API. Order is
165
+ * priority order — earlier sources shadow later ones on id collision:
166
+ *
167
+ * 1. BuiltinToolsetsSource — synthetic groups for our in-process MCP
168
+ * server. Tools we ship and own.
169
+ * 2. BundledSource — pre-reviewed third-party catalog shipped with
170
+ * the app (`data/`). Hand-curated.
171
+ * 3. CommunitySource — auto-imported entries from the upstream MCP
172
+ * registry (`data/community/`). Permissively-licensed only;
173
+ * treated as a second tier that the UI can label distinctly.
174
+ *
175
+ * UI + HTTP both go through this service.
176
+ */
177
+ declare class CatalogService {
178
+ private readonly sources;
179
+ private readonly contentRootProvider;
180
+ /**
181
+ * @param sources explicit source list (replaces the defaults entirely).
182
+ * @param opts.localRoot a GEZEL_HOME to read user-installed / `.gzl`-imported
183
+ * catalog items from (project types + gezel roles). Appended just ahead of
184
+ * the bundled tier so a user's imported item shadows a same-id bundled one.
185
+ * @param opts.contentRoot dynamic gilde content root for the default
186
+ * bundled + community tiers, re-read on every disk access. The live
187
+ * gilde update mechanism flips it without reconstructing this service
188
+ * (which is built once at boot and held by many subsystems).
189
+ * @param opts.gezelVersion the running app's version, compared against
190
+ * content `minGezelVersion` floors. Defaults to `GEZEL_VERSION`
191
+ * inside each source; injectable so tests can exercise gating (dev
192
+ * checkouts report `0.0.0`, which bypasses all filtering).
193
+ */
194
+ constructor(sources?: CatalogSource[], opts?: {
195
+ localRoot?: string;
196
+ contentRoot?: () => string;
197
+ gezelVersion?: string;
198
+ });
199
+ /**
200
+ * The effective gilde content root, or null when constructed without a
201
+ * provider (tests, CLI one-shots). Cheap and synchronous — ChatManager
202
+ * snapshots it per session to detect a live content flip and rebuild
203
+ * cached model profiles/tuning on the next turn.
204
+ */
205
+ contentRoot(): string | null;
206
+ /** All active sources in priority order (higher trust first). */
207
+ listSources(): Array<{
208
+ id: string;
209
+ label: string;
210
+ }>;
211
+ /**
212
+ * Merged listing across sources. Items from earlier sources (bundled)
213
+ * shadow later sources (remote) on id collision.
214
+ */
215
+ list(kind: CatalogKind): Promise<CatalogItemSummary[]>;
216
+ get(kind: CatalogKind, id: string, sourceId?: string, version?: string): Promise<CatalogItemDetail | null>;
217
+ listVersions(kind: CatalogKind, id: string, sourceId?: string): Promise<CatalogItemVersionInfo[]>;
218
+ readItemFile(kind: CatalogKind, id: string, relPath: string, sourceId?: string, version?: string): Promise<Buffer | null>;
219
+ /** Every file under an item's folder (item-relative paths). For the exporter. */
220
+ listItemFiles(kind: CatalogKind, id: string, sourceId?: string): Promise<string[]>;
221
+ /**
222
+ * A craftbook's eval descriptor (`test.json` sidecar), from the first
223
+ * source that carries one. Null when no source ships it.
224
+ */
225
+ getCraftbookTestSpec(id: string, version?: string): Promise<{
226
+ version: string;
227
+ spec: CraftbookTestSpec;
228
+ } | null>;
229
+ }
230
+
231
+ /**
232
+ * Root of the installed (or link:ed) @bendyline/gilde package — the
233
+ * external content repo that replaced the old in-package `data/` tree.
234
+ *
235
+ * Resolved via the package's `./package.json` export; if gilde ever
236
+ * ships an `exports` map without that subpath, this throws and the
237
+ * service boots with an empty catalog (see the AGENTS.md gotcha and
238
+ * the guard test in gilde-data.test.ts).
239
+ */
240
+ declare function gildePackageRoot(): string;
241
+ /**
242
+ * The catalog content root (kind dirs + community/). Honors
243
+ * GEZEL_GILDE_DATA_DIR (absolute path to a data dir, e.g. a raw
244
+ * ../gilde/data checkout) so tests, evals, and operators can bypass
245
+ * node resolution entirely. Not cached — the override must stay
246
+ * flippable within a process (tests rely on it).
247
+ */
248
+ declare function gildeDataDir(): string;
249
+
250
+ /**
251
+ * The community catalog tier — entries auto-imported from the upstream
252
+ * MCP registry (registry.modelcontextprotocol.io). Same on-disk layout
253
+ * as the bundled catalog, just rooted at `data/community/` so the
254
+ * hand-curated bundled tier stays small and visually distinct.
255
+ *
256
+ * Layout under the community root mirrors `BundledSource`'s expected
257
+ * shape:
258
+ *
259
+ * data/community/toolsets/{shard}/{id}/manifest.json
260
+ * data/community/toolsets/{shard}/{id}/versions/{semver}/manifest.json
261
+ *
262
+ * Only the `toolsets` kind is populated today; other kinds can land
263
+ * here later without further plumbing.
264
+ *
265
+ * Identity collisions are resolved by `CatalogService` priority:
266
+ * BundledSource is registered ahead of CommunitySource, so a community
267
+ * entry with the same id as a bundled one is shadowed.
268
+ */
269
+ declare class CommunitySource extends BundledSource {
270
+ constructor(dataDir?: string | (() => string), gezelVersion?: string);
271
+ }
272
+
273
+ declare class LocalCatalogSource extends BundledSource {
274
+ constructor(home: string, gezelVersion?: string);
275
+ listKinds(): Promise<CatalogKind[]>;
276
+ list(kind: CatalogKind): Promise<CatalogItemSummary[]>;
277
+ get(kind: CatalogKind, id: string, version?: string): Promise<CatalogItemDetail | null>;
278
+ listVersions(kind: CatalogKind, id: string): Promise<CatalogItemVersionInfo[]>;
279
+ readItemFile(kind: CatalogKind, id: string, relPath: string, version?: string): Promise<Buffer | null>;
280
+ listItemFiles(kind: CatalogKind, id: string): Promise<string[]>;
281
+ }
282
+
283
+ /**
284
+ * Categorize a toolset by its identity-manifest fields. The order of
285
+ * checks matches the priority order of `RULES` above.
286
+ *
287
+ * Inputs are lowercased and concatenated into a single haystack —
288
+ * tokenized matching isn't worth the complexity for ~10% better
289
+ * placement of niche entries.
290
+ */
291
+ declare function categorizeToolset(input: {
292
+ id: string;
293
+ name: string;
294
+ description: string;
295
+ tags: readonly string[];
296
+ maintainerName?: string;
297
+ }): ToolsetCategory;
298
+
299
+ /**
300
+ * ─ Built-in toolsets ────────────────────────────────────────────────
301
+ *
302
+ * The main MCP server (`@bendyline/gezel-mcp`) registers 90+ tools.
303
+ * Sending all of them on every chat turn fills 10K+ input tokens of
304
+ * tool schemas before the user's message is even read — enough to
305
+ * trip OpenAI Tier-1 TPM caps on a fresh "hello?" turn. The fix is
306
+ * to bucket those tools into named groups and only expose the groups
307
+ * a given gezel actually needs.
308
+ *
309
+ * Each group surfaces in the catalog as a synthetic toolset manifest
310
+ * with `runtime.kind === 'builtin'`. A gezel's toolsets can install /
311
+ * uninstall these groups exactly like third-party MCP toolsets;
312
+ * `ChatManager` reads the per-gezel toolsets, expands the groups to a
313
+ * tool-name allowlist, and hands it to `McpBridgePool` which strips
314
+ * everything else before serializing for the model.
315
+ *
316
+ * Membership is the single source of truth for role defaults
317
+ * (`role-tool-filter.ts` references group ids, not tool names) and
318
+ * for the picker UI. New tools added to the MCP server should be
319
+ * dropped into a relevant group here so they reach gezels at all.
320
+ *
321
+ * Catalog id format: `builtin.<group-id>` (e.g. `builtin.workspace-fs-read`).
322
+ * The prefix keeps us out of the third-party id namespace.
323
+ */
324
+ interface BuiltinToolsetGroup {
325
+ id: string;
326
+ name: string;
327
+ description: string;
328
+ tools: string[];
329
+ }
330
+ declare const BUILTIN_TOOLSETS: BuiltinToolsetGroup[];
331
+ /** Resolve a group by its `id` (e.g. `'workspace-fs-read'`). */
332
+ declare function getBuiltinToolset(id: string): BuiltinToolsetGroup | undefined;
333
+ /**
334
+ * Inverse map: tool name → group it belongs to. Built once at module
335
+ * load (~60 entries). Used by the auto-injected `## Tools available
336
+ * this turn` block in the system prompt to bucket tool names into
337
+ * named groups so the model sees a structured listing rather than a
338
+ * flat alphabet soup. Tools not in any group (third-party MCP servers
339
+ * the user installed) get classified as "other" by the consumer.
340
+ *
341
+ * If two groups ever name the same tool, the FIRST group in
342
+ * `BUILTIN_TOOLSETS` wins (matches the Map insertion-order iteration).
343
+ * Deliberate subset groups such as `tasks-readonly` may duplicate a
344
+ * strict slice of their base group; other duplication is a manifest bug
345
+ * worth surfacing rather than silently resolving here.
346
+ */
347
+ declare const BUILTIN_TOOL_TO_GROUP: Map<string, BuiltinToolsetGroup>;
348
+ /** Catalog id format: `builtin.<group-id>`. */
349
+ declare function builtinCatalogId(groupId: string): string;
350
+ /**
351
+ * Catalog source that exposes the BUILTIN_TOOLSETS as installable
352
+ * toolsets. Composed alongside `BundledSource` in `CatalogService`.
353
+ */
354
+ declare class BuiltinToolsetsSource implements CatalogSource {
355
+ readonly id = "builtin";
356
+ readonly label = "Built-in";
357
+ listKinds(): Promise<CatalogKind[]>;
358
+ list(kind: CatalogKind): Promise<CatalogItemSummary[]>;
359
+ get(kind: CatalogKind, id: string, version?: string): Promise<CatalogItemDetail | null>;
360
+ listVersions(kind: CatalogKind, id: string): Promise<CatalogItemVersionInfo[]>;
361
+ readItemFile(kind: CatalogKind, id: string, relPath: string, _version?: string): Promise<Buffer | null>;
362
+ }
363
+
364
+ interface NpmInstallOptions {
365
+ manifest: ToolsetManifest & {
366
+ runtime: {
367
+ kind: 'npm-package';
368
+ };
369
+ };
370
+ installRoot: string;
371
+ /** Test/private-registry override. Redirects remain pinned to this exact origin. */
372
+ registry?: string;
373
+ }
374
+ interface NpmInstallResult {
375
+ installPath: string;
376
+ tarballSha256: string;
377
+ }
378
+ declare function installNpmPackageToolset(opts: NpmInstallOptions): Promise<NpmInstallResult>;
379
+ /** SHA-256 of a tarball on disk without buffering the archive in memory. */
380
+ declare function verifyTarballSha256(tarballPath: string): Promise<string>;
381
+ declare function extractNpmPackageTarball(opts: {
382
+ tarballPath: string;
383
+ destination: string;
384
+ expectedName: string;
385
+ expectedVersion: string;
386
+ }): Promise<string>;
387
+ declare function validateNpmArchiveEntry(path: string, type: string): void;
388
+ declare function publishStagedNpmInstall(staging: string, target: string, backup: string): Promise<void>;
389
+ declare function recoverInterruptedNpmInstall(target: string, backup: string): Promise<void>;
390
+
391
+ /**
392
+ * Registry interaction for live gilde content updates. Pure fetch/stage
393
+ * helpers — the stateful orchestration (opt-in gate, scheduler, activation,
394
+ * pruning) lives in the service's GildeUpdateManager. The trust anchor here
395
+ * is registry TLS plus the registry-reported integrity hash, the same trust
396
+ * a build-time `pnpm install` of the pin extends.
397
+ */
398
+ declare const GILDE_PACKAGE_NAME = "@bendyline/gilde";
399
+ interface GildeReleaseInfo {
400
+ version: string;
401
+ tarballUrl: string;
402
+ /** SRI string from the registry (`sha512-<base64>`), when present. */
403
+ integrity?: string;
404
+ /** Legacy sha1 hex from the registry, when present. */
405
+ shasum?: string;
406
+ }
407
+ /**
408
+ * Every plain `major.minor.patch` release of `@bendyline/gilde` the registry
409
+ * knows, unsorted. Prerelease versions and releases whose tarball points off
410
+ * the registry origin are dropped.
411
+ */
412
+ declare function fetchGildeReleases(opts?: {
413
+ registry?: string;
414
+ }): Promise<GildeReleaseInfo[]>;
415
+ /**
416
+ * Same-minor-line update policy: the newest release on the bundled pin's
417
+ * `major.minor` line that is strictly newer than both the pin and the
418
+ * currently-active live version. Null when the install is already current.
419
+ * Line bumps deliberately ride app releases — they may carry schema changes
420
+ * this build's loader cannot represent.
421
+ */
422
+ declare function pickGildePatchUpdate(releases: GildeReleaseInfo[], bundledPin: string, currentLive?: string): GildeReleaseInfo | null;
423
+ /** Numeric compare for plain `major.minor.patch` strings. */
424
+ declare function compareRelease(a: string, b: string): number;
425
+ /** `true` for the plain `major.minor.patch` form gilde releases use. */
426
+ declare function isGildeReleaseVersion(v: string): boolean;
427
+ /**
428
+ * Download + verify + extract one gilde release into `stagingDir`. Returns
429
+ * the extracted `package/` directory (content root is `<packageDir>/data`).
430
+ * Refuses when the registry metadata carries no usable integrity hash. The
431
+ * tarball is removed either way; the caller owns `stagingDir` cleanup on
432
+ * error.
433
+ */
434
+ declare function stageGildeVersion(opts: {
435
+ release: GildeReleaseInfo;
436
+ stagingDir: string;
437
+ registry?: string;
438
+ }): Promise<{
439
+ packageDir: string;
440
+ }>;
441
+
442
+ /**
443
+ * The empirical no-regression gate for live gilde activation. The loader in
444
+ * this build parses content with this build's Zod schemas, and an
445
+ * unparseable item silently vanishes from the catalog — acceptable for a
446
+ * brand-new item authored against newer schemas, catastrophic for one the
447
+ * install already relies on (an installed model would lose its tuning, a
448
+ * scheduled craftbook would stop resolving). So before activating a
449
+ * candidate content root, every item resolvable from the current root must
450
+ * still resolve from the candidate; anything less refuses the update.
451
+ */
452
+ interface GildeContentRegression {
453
+ kind: CatalogKind;
454
+ id: string;
455
+ }
456
+ type GildeContentValidation = {
457
+ ok: true;
458
+ checked: number;
459
+ } | {
460
+ ok: false;
461
+ regressions: GildeContentRegression[];
462
+ };
463
+ declare function validateGildeContentUpgrade(opts: {
464
+ currentDataDir: string;
465
+ candidateDataDir: string;
466
+ /**
467
+ * App version for `minGezelVersion` gating — defaults to `GEZEL_VERSION`
468
+ * inside the sources. Both sides run on the same build, so gating is
469
+ * symmetric: a candidate that retro-gates a currently-resolvable item
470
+ * fails this gate (correctly — that item would vanish for this install),
471
+ * while a brand-new gated item passes (it never resolved here before).
472
+ * Injectable for tests; dev builds (`0.0.0`) never gate.
473
+ */
474
+ gezelVersion?: string;
475
+ }): Promise<GildeContentValidation>;
476
+
477
+ /**
478
+ * Tiny Hugging Face Hub API client used by catalog tooling.
479
+ *
480
+ * Scope: enough to list the files in a model repo and extract their
481
+ * sha256 + size for manifest generation. Not a general-purpose HF SDK —
482
+ * we don't need authentication, dataset endpoints, model cards, or
483
+ * downloads (the install path has its own streaming downloader).
484
+ *
485
+ * The Hub publishes file metadata at `/api/models/<repo>/tree/<rev>`.
486
+ * Each entry looks like:
487
+ *
488
+ * {
489
+ * "type": "file",
490
+ * "path": "model-00001-of-00003.safetensors",
491
+ * "size": 5343268752,
492
+ * "oid": "0b9b0515...", // git blob sha
493
+ * "lfs": { "oid": "2689680915...", ... }, // sha256 of the LFS payload
494
+ * }
495
+ *
496
+ * The `lfs.oid` is the sha256 we want for integrity checks. Plain
497
+ * non-LFS files (small JSONs, tokenizer configs) don't carry an
498
+ * `lfs` block — we fall back to the git blob's `oid`, which the HF
499
+ * client tools also accept for verification.
500
+ */
501
+ interface HfFileEntry {
502
+ /** Path within the repo (e.g. `model.safetensors`, `config.json`). */
503
+ path: string;
504
+ /** Size in bytes (LFS-aware — points at the resolved payload, not the pointer file). */
505
+ sizeBytes: number;
506
+ /**
507
+ * SHA-256 hex string we use for integrity checks. For LFS-stored
508
+ * files this is `lfs.oid` (HF stores the sha256 there). For small
509
+ * non-LFS files, falls back to the git blob `oid` — not a sha256
510
+ * but stable, and the HF client tools accept it.
511
+ */
512
+ sha256: string;
513
+ /** True when the sha came from `lfs.oid` (a real sha256). */
514
+ lfsBacked: boolean;
515
+ }
516
+ interface FetchTreeOptions {
517
+ /** Branch / tag / commit. Defaults to `main`. */
518
+ rev?: string;
519
+ /** Test seam — defaults to global fetch. */
520
+ fetchImpl?: typeof fetch;
521
+ }
522
+ /**
523
+ * List every file in a Hugging Face model repo at the given rev.
524
+ * Recurses into subdirectories so deeply-nested files (rare, but
525
+ * happens for some quants) are returned flattened with their full
526
+ * path.
527
+ */
528
+ declare function fetchHuggingfaceTree(repo: string, opts?: FetchTreeOptions): Promise<HfFileEntry[]>;
529
+ /**
530
+ * Resolve a Hugging Face repo + revision to its concrete commit SHA.
531
+ *
532
+ * `/api/models/<repo>/revision/<rev>` returns the repo metadata for that
533
+ * ref, including `sha` — the 40-char commit the ref currently points at.
534
+ * Catalog tooling calls this with `rev = 'main'` to capture the commit
535
+ * to pin in a manifest, so downloads become content-frozen against an
536
+ * immutable snapshot rather than the moving branch tip.
537
+ */
538
+ declare function fetchHuggingfaceCommit(repo: string, opts?: {
539
+ rev?: string;
540
+ fetchImpl?: typeof fetch;
541
+ }): Promise<string>;
542
+ /**
543
+ * Total bytes across every file in a repo's tree. Used to populate
544
+ * `approxSizeBytes` on a chat-model manifest.
545
+ */
546
+ declare function totalSize(files: ReadonlyArray<HfFileEntry>): number;
547
+ /**
548
+ * Filter the tree down to the files an MLX install actually needs:
549
+ * weight shards (`*.safetensors`), the safetensors index, configs,
550
+ * and tokenizer files. Drops `.gitattributes`, `README.md`, sample
551
+ * images, and other repo metadata that MLX doesn't read at run time.
552
+ */
553
+ declare function selectMlxInstallFiles(files: ReadonlyArray<HfFileEntry>): HfFileEntry[];
554
+
555
+ export { BUILTIN_TOOLSETS, BUILTIN_TOOL_TO_GROUP, type BuiltinToolsetGroup, BuiltinToolsetsSource, BundledSource, type BundledSourceOptions, CatalogService, type CatalogSource, CommunitySource, type FetchTreeOptions, GILDE_PACKAGE_NAME, type GildeContentRegression, type GildeContentValidation, type GildeReleaseInfo, type HfFileEntry, LocalCatalogSource, builtinCatalogId, categorizeToolset, compareRelease, extractNpmPackageTarball, fetchGildeReleases, fetchHuggingfaceCommit, fetchHuggingfaceTree, getBuiltinToolset, gildeDataDir, gildePackageRoot, installNpmPackageToolset, isGildeReleaseVersion, pickGildePatchUpdate, publishStagedNpmInstall, recoverInterruptedNpmInstall, selectMlxInstallFiles, stageGildeVersion, totalSize, validateGildeContentUpgrade, validateNpmArchiveEntry, verifyTarballSha256 };