@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 +21 -0
- package/README.md +36 -0
- package/dist/index.d.ts +555 -0
- package/dist/index.js +2496 -0
- package/package.json +75 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|