@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/dist/index.js
ADDED
|
@@ -0,0 +1,2496 @@
|
|
|
1
|
+
// src/service.ts
|
|
2
|
+
import { join as join4 } from "path";
|
|
3
|
+
|
|
4
|
+
// src/builtin-toolset-icons.ts
|
|
5
|
+
var SVG_ATTRS = 'xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"';
|
|
6
|
+
var BUILTIN_TOOLSET_ICONS = {
|
|
7
|
+
// Constellation of nodes — recall as a network of remembered points.
|
|
8
|
+
memory: `<svg ${SVG_ATTRS}><circle cx="6" cy="8" r="1.5"/><circle cx="12" cy="5" r="1.5"/><circle cx="18" cy="8" r="1.5"/><circle cx="9" cy="14" r="1.5"/><circle cx="15" cy="14" r="1.5"/><circle cx="12" cy="20" r="1.5"/><path d="M6 8l3 6M12 5l-3 9M12 5l3 9M18 8l-3 6M9 14l3 6M15 14l-3 6"/></svg>`,
|
|
9
|
+
// Folder + magnifying glass — read/inspect, no mutation.
|
|
10
|
+
"workspace-fs-read": `<svg ${SVG_ATTRS}><path d="M3 7a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2V17a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/><circle cx="14" cy="13" r="2"/><path d="M15.5 14.5L17 16"/></svg>`,
|
|
11
|
+
// Folder + pencil — mutate.
|
|
12
|
+
"workspace-fs-write": `<svg ${SVG_ATTRS}><path d="M3 7a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2V17a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/><path d="M11 14l4-4 2 2-4 4h-2v-2z"/></svg>`,
|
|
13
|
+
// Code braces + locator dot — navigate symbols.
|
|
14
|
+
"code-intel": `<svg ${SVG_ATTRS}><path d="M8 4C6 4 6 8 4 8c2 0 2 4 4 4M16 4c2 0 2 4 4 4-2 0-2 4-4 4"/><circle cx="12" cy="17" r="3"/><path d="M14.5 19.5L18 22"/></svg>`,
|
|
15
|
+
// Shield + magnifier — inspect the codebase for weaknesses.
|
|
16
|
+
"security-intel": `<svg ${SVG_ATTRS}><path d="M12 3l7 3v5c0 4.5-3 7.5-7 9-4-1.5-7-4.5-7-9V6l7-3z"/><circle cx="11" cy="10" r="2.5"/><path d="M12.8 11.8L15 14"/></svg>`,
|
|
17
|
+
// Document + magnifier — search/read converted docs.
|
|
18
|
+
"doc-intel": `<svg ${SVG_ATTRS}><path d="M13 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h7"/><path d="M13 3v5h5"/><circle cx="16" cy="15" r="3"/><path d="M18 17l2.5 2.5"/></svg>`,
|
|
19
|
+
// Picture frame + mountains — image library.
|
|
20
|
+
"image-intel": `<svg ${SVG_ATTRS}><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="8.5" cy="10" r="1.5"/><path d="M21 16l-5-5-4 4-2-2-7 7"/></svg>`,
|
|
21
|
+
// Person node linked to mentions — cross-file entities.
|
|
22
|
+
"entity-intel": `<svg ${SVG_ATTRS}><circle cx="7" cy="8" r="2.5"/><path d="M3 19a4 4 0 0 1 8 0"/><circle cx="18" cy="6" r="1.5"/><circle cx="18" cy="12" r="1.5"/><circle cx="18" cy="18" r="1.5"/><path d="M9.5 8H16M11 17h5.5M12 12h4.5"/></svg>`,
|
|
23
|
+
// Stacked rectangles + a zip-pull notch.
|
|
24
|
+
archives: `<svg ${SVG_ATTRS}><rect x="3" y="6" width="18" height="5" rx="1"/><rect x="3" y="13" width="18" height="6" rx="1"/><path d="M10 16h4"/></svg>`,
|
|
25
|
+
// Page with corner fold + body lines.
|
|
26
|
+
documents: `<svg ${SVG_ATTRS}><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9l-6-6z"/><path d="M14 3v6h6"/><path d="M8 13h8M8 17h6"/></svg>`,
|
|
27
|
+
// Cube/package — outputs you produce and bundle.
|
|
28
|
+
artifacts: `<svg ${SVG_ATTRS}><path d="M21 8L12 3 3 8v8l9 5 9-5V8z"/><path d="M3 8l9 5 9-5"/><path d="M12 13v8"/></svg>`,
|
|
29
|
+
// Checkbox + list lines.
|
|
30
|
+
tasks: `<svg ${SVG_ATTRS}><rect x="3" y="4" width="6" height="6" rx="1"/><path d="M4.5 7L6 8.5 8 5.5"/><rect x="3" y="14" width="6" height="6" rx="1"/><path d="M12 7h9M12 17h9"/></svg>`,
|
|
31
|
+
// Three connected nodes — a small team.
|
|
32
|
+
"team-management": `<svg ${SVG_ATTRS}><circle cx="12" cy="6" r="2.5"/><circle cx="6" cy="17" r="2.5"/><circle cx="18" cy="17" r="2.5"/><path d="M10.5 8L7.5 14.5M13.5 8l3 6.5M8.5 17h7"/></svg>`,
|
|
33
|
+
// Terminal-like frame with a prompt arrow.
|
|
34
|
+
"code-execution": `<svg ${SVG_ATTRS}><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 10l3 2-3 2M13 14h4"/></svg>`,
|
|
35
|
+
// Browser window + a tiny pointer.
|
|
36
|
+
"browser-automation": `<svg ${SVG_ATTRS}><rect x="3" y="4" width="18" height="14" rx="2"/><path d="M3 9h18"/><circle cx="6" cy="6.5" r="0.5" fill="currentColor"/><circle cx="8" cy="6.5" r="0.5" fill="currentColor"/><circle cx="10" cy="6.5" r="0.5" fill="currentColor"/><path d="M11 13l3 6 1-3 3-1z"/></svg>`,
|
|
37
|
+
// Branch graph — two nodes on a trunk + a side node.
|
|
38
|
+
git: `<svg ${SVG_ATTRS}><circle cx="6" cy="6" r="2"/><circle cx="6" cy="18" r="2"/><circle cx="18" cy="12" r="2"/><path d="M6 8v8"/><path d="M6 12c0-3 3-6 6-6 2 0 4 2 4 4"/></svg>`,
|
|
39
|
+
// Globe with meridian + equator.
|
|
40
|
+
web: `<svg ${SVG_ATTRS}><circle cx="12" cy="12" r="9"/><ellipse cx="12" cy="12" rx="4" ry="9"/><path d="M3 12h18"/></svg>`,
|
|
41
|
+
// Picture frame — sun + mountain.
|
|
42
|
+
images: `<svg ${SVG_ATTRS}><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="8.5" cy="9" r="1.5"/><path d="M4 18l5-5 3 3 4-4 4 4"/></svg>`,
|
|
43
|
+
// Clock + back arrow — looking back over time.
|
|
44
|
+
history: `<svg ${SVG_ATTRS}><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/><path d="M12 8v4l3 2"/></svg>`,
|
|
45
|
+
handboek: `<svg ${SVG_ATTRS}><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><path d="M10 2v7l2.5-1.8L15 9V2"/></svg>`,
|
|
46
|
+
// Speech bubble with a question mark.
|
|
47
|
+
interaction: `<svg ${SVG_ATTRS}><path d="M21 12a8 8 0 0 1-12.5 6.5L3 20l1.5-5.5A8 8 0 1 1 21 12z"/><path d="M9.5 9.5a2.5 2.5 0 0 1 5 0c0 1.5-2.5 2-2.5 3.5"/><circle cx="12" cy="16" r="0.6" fill="currentColor"/></svg>`
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/builtin-toolsets.ts
|
|
51
|
+
var BUILTIN_VERSION = "1.0.0";
|
|
52
|
+
var BUILTIN_RELEASED_AT = "2026-04-25T00:00:00Z";
|
|
53
|
+
var BUILTIN_TOOLSETS = [
|
|
54
|
+
{
|
|
55
|
+
id: "memory",
|
|
56
|
+
name: "Memory",
|
|
57
|
+
description: "Persistent notes a gezel can search, recall, and write back. Backed by the gezel's per-day memory files plus a vector index.",
|
|
58
|
+
tools: ["search_memory", "save_memory", "list_memories"]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "workspace-fs-read",
|
|
62
|
+
name: "Workspace File Reading",
|
|
63
|
+
description: "Read, list, search, and diff files in the project workspace. Mirrors Node's `fs` reads plus content/glob search. Diagnose-only \u2014 pair with `workspace-fs-write` to actually mutate.",
|
|
64
|
+
tools: [
|
|
65
|
+
"list_dir",
|
|
66
|
+
"read_file",
|
|
67
|
+
"read_files",
|
|
68
|
+
"stat",
|
|
69
|
+
"validate",
|
|
70
|
+
"grep_files",
|
|
71
|
+
"find_files",
|
|
72
|
+
"diff_files"
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "code-intel",
|
|
77
|
+
name: "Code Intelligence",
|
|
78
|
+
description: "Navigate and understand the codebase via the workspace index instead of reading whole files: outline a file's symbols, jump to a definition, read just one symbol's source, find usages, and map the repo. Every result carries line ranges so the model reads only the span it needs \u2014 a big context saver for small/medium local models.",
|
|
79
|
+
tools: [
|
|
80
|
+
"outline_file",
|
|
81
|
+
"find_symbol",
|
|
82
|
+
"read_symbol",
|
|
83
|
+
"find_references",
|
|
84
|
+
"map_repo",
|
|
85
|
+
"search_code",
|
|
86
|
+
"file_review",
|
|
87
|
+
"list_file_issues"
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "security-intel",
|
|
92
|
+
name: "Security Intelligence",
|
|
93
|
+
description: "Static security analysis pushed into the index and reused as tools: run a whole-repo scan (dependency inventory + opportunistic semgrep/osv-scanner/gitleaks), get a posture overview with candidate systemic themes, list findings by severity/category, map the attack surface (entry points, routes, auth boundaries, secret touchpoints), inventory dependencies with advisories, and trace import-graph reachability for source\u2192sink flows. The substrate for a deep, systemic security review.",
|
|
94
|
+
tools: [
|
|
95
|
+
"security_scan",
|
|
96
|
+
"security_overview",
|
|
97
|
+
"scan_findings",
|
|
98
|
+
"map_attack_surface",
|
|
99
|
+
"list_dependencies",
|
|
100
|
+
"trace_taint"
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "doc-intel",
|
|
105
|
+
name: "Document Intelligence",
|
|
106
|
+
description: "Search and read office documents (Word, PDF, PowerPoint, Excel) that gezel has converted to markdown in the index. Keyword-search across a document corpus and read any document as scannable markdown on demand \u2014 no need to wrestle with binary files.",
|
|
107
|
+
tools: ["search_docs", "read_doc_as_markdown"]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "image-intel",
|
|
111
|
+
name: "Image Intelligence",
|
|
112
|
+
description: "Navigate an indexed image library: search images by filename/caption/dimensions, summarize a folder of images for review or reorganizing, and find visually similar images. Built for folder-operations over large image collections.",
|
|
113
|
+
tools: ["search_images", "find_similar_images", "describe_folder"]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "entity-intel",
|
|
117
|
+
name: "Entity Intelligence",
|
|
118
|
+
description: 'Cross-file entities the index resolved from structured metadata \u2014 email senders, document parties \u2014 and where each appears. Ask "every email from X" or "every contract naming Y" and get a dated, cross-file view.',
|
|
119
|
+
tools: ["find_entity", "list_entity_mentions"]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: "workspace-fs-write",
|
|
123
|
+
name: "Workspace File Writing",
|
|
124
|
+
description: "Create, write, surgically edit, rename, and delete files in the project workspace. Always paired with `workspace-fs-read` for the roles that build (developer, designer, reviewer); split out so coordinator roles (voorman) can investigate without being tempted to do the building themselves.",
|
|
125
|
+
tools: [
|
|
126
|
+
"write_file",
|
|
127
|
+
"append_to_file",
|
|
128
|
+
"replace_in_file",
|
|
129
|
+
"replace_lines",
|
|
130
|
+
"apply_patch",
|
|
131
|
+
"insert_at_marker",
|
|
132
|
+
"copy_artifact_to_workspace",
|
|
133
|
+
"make_dir",
|
|
134
|
+
"delete_path",
|
|
135
|
+
"rename"
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "archives",
|
|
140
|
+
name: "Archive Tools",
|
|
141
|
+
description: "List and extract zip / tar archives in the workspace.",
|
|
142
|
+
tools: ["list_archive", "extract_archive"]
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "documents",
|
|
146
|
+
name: "Documents",
|
|
147
|
+
description: "Cross-project shared library \u2014 mission docs, guidelines, and any markdown the user wants every gezel to be able to find.",
|
|
148
|
+
tools: [
|
|
149
|
+
"list_documents",
|
|
150
|
+
"read_document",
|
|
151
|
+
"write_document",
|
|
152
|
+
"delete_document",
|
|
153
|
+
"search_documents"
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: "artifacts",
|
|
158
|
+
name: "Project Artifacts",
|
|
159
|
+
description: "Project-scoped read-write outputs (reports, scratch files, scripts a gezel produces, and large outputs auto-saved by tools that exceed the inline cap).",
|
|
160
|
+
tools: ["list_artifacts", "read_artifact", "write_artifact", "grep_artifact"]
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "tasks",
|
|
164
|
+
name: "Task Management",
|
|
165
|
+
description: "Create, assign, advance, and report on tasks. The full task surface including notes, steps, and child instance spawning.",
|
|
166
|
+
tools: [
|
|
167
|
+
"list_tasks",
|
|
168
|
+
"get_task",
|
|
169
|
+
"list_craftbooks",
|
|
170
|
+
"suggest_craftbook",
|
|
171
|
+
"import_skill",
|
|
172
|
+
"create_task",
|
|
173
|
+
"start_plan",
|
|
174
|
+
"invoke_craftbook",
|
|
175
|
+
"update_task",
|
|
176
|
+
"set_outcomes",
|
|
177
|
+
"verify_outcome",
|
|
178
|
+
"add_verification_step",
|
|
179
|
+
"set_task_status",
|
|
180
|
+
"activate_task",
|
|
181
|
+
"list_task_children",
|
|
182
|
+
"add_task_step",
|
|
183
|
+
"advance_task_step",
|
|
184
|
+
"assign_task",
|
|
185
|
+
"read_task_notes",
|
|
186
|
+
"write_task_note",
|
|
187
|
+
"spawn_task_instances"
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: "craftbooks",
|
|
192
|
+
name: "Craftbook Authoring",
|
|
193
|
+
description: "Read and write a whole craftbook, add/remove/reorder steps, set the entry step, and attach focused deliverable gates. `craftbook_write` is the atomic create/replace path; the large surgical update schema is loaded only inside an explicit craftbook-editing session. The unified surface targets either a task's embedded craftbook or a standalone local template.",
|
|
194
|
+
tools: [
|
|
195
|
+
"craftbook_read",
|
|
196
|
+
"craftbook_write",
|
|
197
|
+
"craftbook_add_step",
|
|
198
|
+
"set_step_deliverable",
|
|
199
|
+
"craftbook_remove_step",
|
|
200
|
+
"craftbook_reorder_steps",
|
|
201
|
+
"craftbook_set_entry",
|
|
202
|
+
"craftbook_update",
|
|
203
|
+
"export_task_craftbook"
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "tasks-readonly",
|
|
208
|
+
name: "Task Visibility",
|
|
209
|
+
description: 'Read-only task surface for delegation roles \u2014 list tasks, inspect a specific task, read its notes. The "ping projects to see status" subset, without any of the mutation tools (create, update, assign, status, advance) that belong to the assignee or voorman. Pair with `team-management` for a complete delegation surface.',
|
|
210
|
+
tools: ["list_tasks", "get_task", "list_craftbooks", "suggest_craftbook", "read_task_notes"]
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: "craftbook-launch",
|
|
214
|
+
name: "Craftbook Launcher",
|
|
215
|
+
description: "Procedure-first task launch surface for coordinators: rank applicable craftbooks and invoke a selected recipe after its required setup is ready.",
|
|
216
|
+
tools: ["list_craftbooks", "suggest_craftbook", "invoke_craftbook"]
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: "team-management",
|
|
220
|
+
name: "Team & Project Management",
|
|
221
|
+
description: "Spin up other gezels, message them, browse the gilde of templates, and manage projects + project membership. The Meester surface.",
|
|
222
|
+
tools: [
|
|
223
|
+
"list_gezels",
|
|
224
|
+
"create_gezel",
|
|
225
|
+
"update_gezel",
|
|
226
|
+
"ensure_gezel",
|
|
227
|
+
"message_gezel",
|
|
228
|
+
"list_projects",
|
|
229
|
+
"start_project",
|
|
230
|
+
"fetch_repo",
|
|
231
|
+
"fetch_diff",
|
|
232
|
+
"update_project",
|
|
233
|
+
"list_gilde",
|
|
234
|
+
"list_project_types",
|
|
235
|
+
"apply_project_type",
|
|
236
|
+
"start_project_from_type",
|
|
237
|
+
"export_project_type",
|
|
238
|
+
"import_project_type",
|
|
239
|
+
"list_project_gezels",
|
|
240
|
+
"add_gezel_to_project",
|
|
241
|
+
"remove_gezel_from_project",
|
|
242
|
+
"list_suggested_work",
|
|
243
|
+
"enable_suggested_work",
|
|
244
|
+
"disable_suggested_work"
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: "audio",
|
|
249
|
+
name: "Audio",
|
|
250
|
+
description: "Speech-to-text (`transcribe_audio`) and text-to-speech (`synthesize_speech`). Heavy \u2014 only useful for gezels that do voice work.",
|
|
251
|
+
tools: ["transcribe_audio", "synthesize_speech"]
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: "code-execution",
|
|
255
|
+
name: "Code Execution",
|
|
256
|
+
description: "Run Node scripts, npm install, run package scripts, invoke npx. Sandboxed to the project workspace.",
|
|
257
|
+
tools: [
|
|
258
|
+
"run_nodejs_script",
|
|
259
|
+
"derive_file",
|
|
260
|
+
"npm_install",
|
|
261
|
+
"run_npx",
|
|
262
|
+
"run_installed_script",
|
|
263
|
+
"run_package_script",
|
|
264
|
+
"list_packages",
|
|
265
|
+
"list_package_scripts",
|
|
266
|
+
"list_scripts",
|
|
267
|
+
"get_script_run"
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: "browser-automation",
|
|
272
|
+
name: "Browser Automation",
|
|
273
|
+
description: "Drive a headless Chromium via Playwright scripts. Heavy \u2014 only useful for gezels that genuinely need browser work.",
|
|
274
|
+
tools: ["run_playwright_script"]
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: "git",
|
|
278
|
+
name: "Git & GitHub",
|
|
279
|
+
description: "Run read-only git commands inside the project workspace, inspect GitHub pull requests, post review comments, open PRs, and check workflow status for linked project repositories.",
|
|
280
|
+
tools: [
|
|
281
|
+
"run_git",
|
|
282
|
+
"github_pr_list",
|
|
283
|
+
"github_pr_view",
|
|
284
|
+
"github_pr_files",
|
|
285
|
+
"github_pr_diff",
|
|
286
|
+
"github_pr_comments",
|
|
287
|
+
"github_pr_comment",
|
|
288
|
+
"github_pr_create",
|
|
289
|
+
"github_workflow_runs",
|
|
290
|
+
"github_check_status"
|
|
291
|
+
]
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "web",
|
|
295
|
+
name: "Web Access",
|
|
296
|
+
description: "Search the web (when a keyed backend like Brave is configured) or Wikipedia, fetch URL contents, and find interactive elements on a browser-controlled page (after a Playwright navigate / click / type). `web_search` only registers when a real keyed backend is configured; `wikipedia_search` only registers for non-cloud models (cloud models already have Wikipedia in training).",
|
|
297
|
+
tools: ["web_search", "wikipedia_search", "fetch_url", "browser_find_page_element"]
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
id: "images",
|
|
301
|
+
name: "Image Tools",
|
|
302
|
+
description: "Render charts/diagrams, read and describe existing images, and generate new images via the configured image model.",
|
|
303
|
+
tools: [
|
|
304
|
+
"render_image",
|
|
305
|
+
"read_image_as_base64",
|
|
306
|
+
"generate_image",
|
|
307
|
+
"describe_image",
|
|
308
|
+
"read_image_metadata"
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: "videos",
|
|
313
|
+
name: "Video Tools",
|
|
314
|
+
description: "Generate short video clips via the configured local video model (LTX / WAN).",
|
|
315
|
+
tools: ["generate_video"]
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: "history",
|
|
319
|
+
name: "Audit & History",
|
|
320
|
+
description: "Search the install-wide audit log of who did what and when, plus past chat transcripts.",
|
|
321
|
+
tools: ["search_history", "search_sessions"]
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: "handboek",
|
|
325
|
+
name: "Handboek",
|
|
326
|
+
description: "Consult gezel's built-in documentation for meta questions about gezel itself \u2014 roles, craftbooks, projects, memory, models, setup. In every role's kit so a gezel answers 'how does gezel work?' from the real docs instead of guessing.",
|
|
327
|
+
tools: ["how_do_i"]
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: "interaction",
|
|
331
|
+
name: "User Interaction",
|
|
332
|
+
description: "Pose a structured question mid-turn \u2014 to the user (`ask_user_question`), to a specific gezel (`ask_gezel`), or to a role-shaped specialist (`ask_specialist`) \u2014 instead of guessing.",
|
|
333
|
+
tools: ["ask_user_question", "ask_gezel", "ask_specialist"]
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: "role-delegation",
|
|
337
|
+
name: "Role Delegation (downward)",
|
|
338
|
+
description: "Hand work to a teammate by ROLE instead of by name: `delegate_<role>` (async hand-off) and `consult_<role>` (sync question) for each doer role \u2014 developer, designer, reviewer, planner, researcher, builder, writer, image-generator. The target lives in the tool identity (no free-text `gezel`/`role` argument), which small/medium local models route far more reliably. Each resolves to the project gezel of that role, creating one on demand. Orchestrator (meester/voorman) surface; gated by the `tools.gezels-as-roles` behavior.",
|
|
339
|
+
tools: [
|
|
340
|
+
"delegate_developer",
|
|
341
|
+
"consult_developer",
|
|
342
|
+
"delegate_designer",
|
|
343
|
+
"consult_designer",
|
|
344
|
+
"delegate_reviewer",
|
|
345
|
+
"consult_reviewer",
|
|
346
|
+
"delegate_planner",
|
|
347
|
+
"consult_planner",
|
|
348
|
+
"delegate_researcher",
|
|
349
|
+
"consult_researcher",
|
|
350
|
+
"delegate_builder",
|
|
351
|
+
"consult_builder",
|
|
352
|
+
"delegate_writer",
|
|
353
|
+
"consult_writer",
|
|
354
|
+
"delegate_image_generator",
|
|
355
|
+
"consult_image_generator"
|
|
356
|
+
]
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
id: "role-delegation-escalation",
|
|
360
|
+
name: "Role Delegation (escalation)",
|
|
361
|
+
description: "Reach a coordination role by ROLE: `delegate_voorman` / `consult_voorman` and `delegate_meester` / `consult_meester`. The escalation counterpart to `role-delegation` \u2014 lets a specialist hand work or questions UP to their voorman or the meester. Gated by the `tools.gezels-as-roles` behavior.",
|
|
362
|
+
tools: ["delegate_voorman", "consult_voorman", "delegate_meester", "consult_meester"]
|
|
363
|
+
}
|
|
364
|
+
];
|
|
365
|
+
var BUILTIN_BY_ID = new Map(BUILTIN_TOOLSETS.map((g) => [g.id, g]));
|
|
366
|
+
function getBuiltinToolset(id) {
|
|
367
|
+
return BUILTIN_BY_ID.get(id);
|
|
368
|
+
}
|
|
369
|
+
var BUILTIN_TOOL_TO_GROUP = new Map(
|
|
370
|
+
BUILTIN_TOOLSETS.flatMap((g) => g.tools.map((toolName) => [toolName, g]))
|
|
371
|
+
);
|
|
372
|
+
function builtinCatalogId(groupId) {
|
|
373
|
+
return `builtin.${groupId}`;
|
|
374
|
+
}
|
|
375
|
+
var SOURCE_ID = "builtin";
|
|
376
|
+
function manifestForGroup(g) {
|
|
377
|
+
return {
|
|
378
|
+
schemaVersion: 1,
|
|
379
|
+
kind: "toolset",
|
|
380
|
+
id: builtinCatalogId(g.id),
|
|
381
|
+
name: g.name,
|
|
382
|
+
description: g.description,
|
|
383
|
+
tags: ["built-in"],
|
|
384
|
+
maintainer: { name: "Gezel" },
|
|
385
|
+
version: BUILTIN_VERSION,
|
|
386
|
+
releasedAt: BUILTIN_RELEASED_AT,
|
|
387
|
+
logo: "icon.svg",
|
|
388
|
+
tools: g.tools.map((name) => ({ name, description: "" })),
|
|
389
|
+
runtime: { kind: "builtin", toolsetGroupId: g.id },
|
|
390
|
+
config: [],
|
|
391
|
+
availableVersions: [BUILTIN_VERSION]
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
var BuiltinToolsetsSource = class {
|
|
395
|
+
id = SOURCE_ID;
|
|
396
|
+
label = "Built-in";
|
|
397
|
+
async listKinds() {
|
|
398
|
+
return ["toolset"];
|
|
399
|
+
}
|
|
400
|
+
async list(kind) {
|
|
401
|
+
if (kind !== "toolset") return [];
|
|
402
|
+
return BUILTIN_TOOLSETS.map((g) => {
|
|
403
|
+
const manifest = manifestForGroup(g);
|
|
404
|
+
return {
|
|
405
|
+
sourceId: this.id,
|
|
406
|
+
kind,
|
|
407
|
+
manifest,
|
|
408
|
+
...BUILTIN_TOOLSET_ICONS[g.id] ? { iconSvg: BUILTIN_TOOLSET_ICONS[g.id] } : {}
|
|
409
|
+
};
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
async get(kind, id, version) {
|
|
413
|
+
if (kind !== "toolset") return null;
|
|
414
|
+
if (version !== void 0 && version !== BUILTIN_VERSION) return null;
|
|
415
|
+
const groupId = id.startsWith("builtin.") ? id.slice("builtin.".length) : null;
|
|
416
|
+
if (!groupId) return null;
|
|
417
|
+
const g = BUILTIN_BY_ID.get(groupId);
|
|
418
|
+
if (!g) return null;
|
|
419
|
+
const manifest = manifestForGroup(g);
|
|
420
|
+
return {
|
|
421
|
+
sourceId: this.id,
|
|
422
|
+
kind,
|
|
423
|
+
manifest,
|
|
424
|
+
...BUILTIN_TOOLSET_ICONS[g.id] ? { iconSvg: BUILTIN_TOOLSET_ICONS[g.id] } : {}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
async listVersions(kind, id) {
|
|
428
|
+
if (kind !== "toolset") return [];
|
|
429
|
+
const groupId = id.startsWith("builtin.") ? id.slice("builtin.".length) : null;
|
|
430
|
+
if (!groupId || !BUILTIN_BY_ID.has(groupId)) return [];
|
|
431
|
+
return [{ version: BUILTIN_VERSION, releasedAt: BUILTIN_RELEASED_AT, yanked: false }];
|
|
432
|
+
}
|
|
433
|
+
async readItemFile(kind, id, relPath, _version) {
|
|
434
|
+
if (kind !== "toolset" || relPath !== "icon.svg") return null;
|
|
435
|
+
if (!id.startsWith("builtin.")) return null;
|
|
436
|
+
const groupId = id.slice("builtin.".length);
|
|
437
|
+
const svg = BUILTIN_TOOLSET_ICONS[groupId];
|
|
438
|
+
if (!svg) return null;
|
|
439
|
+
return Buffer.from(svg, "utf8");
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// src/community-source.ts
|
|
444
|
+
import { join as join3 } from "path";
|
|
445
|
+
|
|
446
|
+
// src/gilde-data.ts
|
|
447
|
+
import { createRequire } from "module";
|
|
448
|
+
import { dirname, join } from "path";
|
|
449
|
+
function gildePackageRoot() {
|
|
450
|
+
const require2 = createRequire(import.meta.url);
|
|
451
|
+
return dirname(require2.resolve("@bendyline/gilde/package.json"));
|
|
452
|
+
}
|
|
453
|
+
function gildeDataDir() {
|
|
454
|
+
const override = process.env.GEZEL_GILDE_DATA_DIR?.trim();
|
|
455
|
+
if (override) return override;
|
|
456
|
+
return join(gildePackageRoot(), "data");
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/source.ts
|
|
460
|
+
import { readFile, readdir } from "fs/promises";
|
|
461
|
+
import { join as join2 } from "path";
|
|
462
|
+
import {
|
|
463
|
+
CRAFTBOOK_TEST_FILENAME,
|
|
464
|
+
CatalogItemIdentitySchema,
|
|
465
|
+
ChatModelVersionManifestSchema,
|
|
466
|
+
ConnectorTypeVersionManifestSchema,
|
|
467
|
+
CraftbookTemplateVersionManifestSchema,
|
|
468
|
+
GEZEL_VERSION,
|
|
469
|
+
GezelTemplateVersionManifestSchema,
|
|
470
|
+
ImageModelVersionManifestSchema,
|
|
471
|
+
ProjectTypeVersionManifestSchema,
|
|
472
|
+
ToolsetVersionManifestSchema,
|
|
473
|
+
VideoModelVersionManifestSchema,
|
|
474
|
+
compareSemver,
|
|
475
|
+
expandStepDeliverables,
|
|
476
|
+
formatCraftbookDocErrors,
|
|
477
|
+
isSemver,
|
|
478
|
+
maxMinGezelVersion,
|
|
479
|
+
parseCraftbookDoc,
|
|
480
|
+
parseCraftbookTestSpec,
|
|
481
|
+
satisfiesMinGezelVersion
|
|
482
|
+
} from "@bendyline/gezel";
|
|
483
|
+
var KINDS = [
|
|
484
|
+
"toolset",
|
|
485
|
+
"gezel-template",
|
|
486
|
+
"craftbook-template",
|
|
487
|
+
"project-type",
|
|
488
|
+
"connector-type",
|
|
489
|
+
"chat-model",
|
|
490
|
+
"image-model",
|
|
491
|
+
"video-model"
|
|
492
|
+
];
|
|
493
|
+
var KIND_DIR = {
|
|
494
|
+
toolset: "toolsets",
|
|
495
|
+
"gezel-template": "gezel-templates",
|
|
496
|
+
"craftbook-template": "craftbook-templates",
|
|
497
|
+
"project-type": "project-types",
|
|
498
|
+
"connector-type": "connector-types",
|
|
499
|
+
"chat-model": "chat-models",
|
|
500
|
+
"image-model": "image-models",
|
|
501
|
+
"video-model": "video-models"
|
|
502
|
+
};
|
|
503
|
+
function shardPrefix(id) {
|
|
504
|
+
return id.slice(0, 2).toLowerCase();
|
|
505
|
+
}
|
|
506
|
+
function isAbsentDir(err) {
|
|
507
|
+
const code = err?.code;
|
|
508
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
509
|
+
}
|
|
510
|
+
function defaultBundledDataDir() {
|
|
511
|
+
return gildeDataDir();
|
|
512
|
+
}
|
|
513
|
+
var BundledSource = class {
|
|
514
|
+
id;
|
|
515
|
+
label;
|
|
516
|
+
rootProvider;
|
|
517
|
+
useIndex;
|
|
518
|
+
gezelVersion;
|
|
519
|
+
constructor(options = {}) {
|
|
520
|
+
const opts = typeof options === "string" ? { dataDir: options } : options;
|
|
521
|
+
const dataDir = opts.dataDir;
|
|
522
|
+
if (typeof dataDir === "function") {
|
|
523
|
+
this.rootProvider = dataDir;
|
|
524
|
+
} else {
|
|
525
|
+
const fixed = dataDir ?? defaultBundledDataDir();
|
|
526
|
+
this.rootProvider = () => fixed;
|
|
527
|
+
}
|
|
528
|
+
this.id = opts.id ?? "bundled";
|
|
529
|
+
this.label = opts.label ?? "Bundled";
|
|
530
|
+
this.useIndex = !opts.noIndex;
|
|
531
|
+
this.gezelVersion = opts.gezelVersion ?? GEZEL_VERSION;
|
|
532
|
+
}
|
|
533
|
+
get root() {
|
|
534
|
+
return this.rootProvider();
|
|
535
|
+
}
|
|
536
|
+
/** True when this build satisfies a content `minGezelVersion` floor. */
|
|
537
|
+
floorSatisfied(floor) {
|
|
538
|
+
return satisfiesMinGezelVersion(floor, this.gezelVersion);
|
|
539
|
+
}
|
|
540
|
+
async listKinds() {
|
|
541
|
+
return KINDS;
|
|
542
|
+
}
|
|
543
|
+
async list(kind) {
|
|
544
|
+
if (this.useIndex) {
|
|
545
|
+
const indexed = await this.listFromIndex(kind);
|
|
546
|
+
if (indexed) return indexed;
|
|
547
|
+
}
|
|
548
|
+
return this.listFromDisk(kind);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Fast-path: when `{kindDir}/index.json` is present, load every
|
|
552
|
+
* summary from that one file instead of walking ~3,800 per-item
|
|
553
|
+
* folders. The index is generated by gilde `tools/build-index.mjs` and
|
|
554
|
+
* embeds the same `CatalogItemManifest` shape this source produces
|
|
555
|
+
* via the slow walk, plus a derived `category` for toolsets.
|
|
556
|
+
*
|
|
557
|
+
* Returns null when the index is missing or unreadable; callers fall
|
|
558
|
+
* back to `listFromDisk()`. Per-entry parse failures are skipped.
|
|
559
|
+
*/
|
|
560
|
+
async listFromIndex(kind) {
|
|
561
|
+
const indexPath = join2(this.root, KIND_DIR[kind], "index.json");
|
|
562
|
+
let raw;
|
|
563
|
+
try {
|
|
564
|
+
raw = await readFile(indexPath, "utf8");
|
|
565
|
+
} catch {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
let parsed;
|
|
569
|
+
try {
|
|
570
|
+
parsed = JSON.parse(raw);
|
|
571
|
+
} catch (err) {
|
|
572
|
+
console.warn(`[catalog] failed to parse ${indexPath}:`, err);
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
const obj = parsed;
|
|
576
|
+
if (obj.kind !== kind || !Array.isArray(obj.entries)) {
|
|
577
|
+
console.warn(`[catalog] ${indexPath}: kind/entries mismatch \u2014 falling back to disk walk`);
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
const items = [];
|
|
581
|
+
for (const raw2 of obj.entries) {
|
|
582
|
+
const e = raw2;
|
|
583
|
+
if (!e.manifest || typeof e.manifest !== "object") continue;
|
|
584
|
+
let manifest = e.manifest;
|
|
585
|
+
if (!this.floorSatisfied(manifest.minGezelVersion)) {
|
|
586
|
+
const reResolved = await this.loadResolvedManifest(kind, manifest.id);
|
|
587
|
+
if (!reResolved) continue;
|
|
588
|
+
manifest = reResolved;
|
|
589
|
+
}
|
|
590
|
+
const item = {
|
|
591
|
+
sourceId: this.id,
|
|
592
|
+
kind,
|
|
593
|
+
manifest,
|
|
594
|
+
...typeof e.iconSvg === "string" ? { iconSvg: e.iconSvg } : {}
|
|
595
|
+
};
|
|
596
|
+
const logo = this.logoUrlFor(kind, manifest.id, manifest);
|
|
597
|
+
if (logo) item.logoUrl = logo;
|
|
598
|
+
items.push(item);
|
|
599
|
+
}
|
|
600
|
+
items.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name));
|
|
601
|
+
return items;
|
|
602
|
+
}
|
|
603
|
+
async listFromDisk(kind) {
|
|
604
|
+
const base = join2(this.root, KIND_DIR[kind]);
|
|
605
|
+
let shards = [];
|
|
606
|
+
try {
|
|
607
|
+
shards = await readdir(base);
|
|
608
|
+
} catch (err) {
|
|
609
|
+
if (isAbsentDir(err)) return [];
|
|
610
|
+
throw err;
|
|
611
|
+
}
|
|
612
|
+
const items = [];
|
|
613
|
+
for (const shard of shards) {
|
|
614
|
+
if (shard === "index.json") continue;
|
|
615
|
+
let ids = [];
|
|
616
|
+
try {
|
|
617
|
+
ids = await readdir(join2(base, shard));
|
|
618
|
+
} catch (err) {
|
|
619
|
+
if (isAbsentDir(err)) continue;
|
|
620
|
+
throw err;
|
|
621
|
+
}
|
|
622
|
+
for (const id of ids) {
|
|
623
|
+
const manifest = await this.loadResolvedManifest(kind, id);
|
|
624
|
+
if (!manifest) continue;
|
|
625
|
+
items.push({
|
|
626
|
+
sourceId: this.id,
|
|
627
|
+
kind,
|
|
628
|
+
manifest,
|
|
629
|
+
logoUrl: this.logoUrlFor(kind, id, manifest)
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
items.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name));
|
|
634
|
+
return items;
|
|
635
|
+
}
|
|
636
|
+
async get(kind, id, version) {
|
|
637
|
+
const manifest = await this.loadResolvedManifest(kind, id, version);
|
|
638
|
+
if (!manifest) return null;
|
|
639
|
+
const itemDir = this.itemDir(kind, id);
|
|
640
|
+
const versionDir = join2(itemDir, "versions", manifest.version);
|
|
641
|
+
const readme = await this.readOptional(join2(itemDir, "readme.md"));
|
|
642
|
+
let about;
|
|
643
|
+
if ((manifest.kind === "gezel-template" || manifest.kind === "craftbook-template") && manifest.about) {
|
|
644
|
+
about = await this.readOptional(join2(versionDir, manifest.about)) ?? void 0;
|
|
645
|
+
} else if (manifest.kind === "craftbook-template") {
|
|
646
|
+
about = (await this.readCraftbookDoc(versionDir))?.description;
|
|
647
|
+
} else if (manifest.kind === "project-type" && manifest.aboutTemplate) {
|
|
648
|
+
about = await this.readOptional(join2(versionDir, manifest.aboutTemplate)) ?? void 0;
|
|
649
|
+
}
|
|
650
|
+
return {
|
|
651
|
+
sourceId: this.id,
|
|
652
|
+
kind,
|
|
653
|
+
manifest,
|
|
654
|
+
logoUrl: this.logoUrlFor(kind, id, manifest),
|
|
655
|
+
...readme ? { readme } : {},
|
|
656
|
+
...about ? { about } : {}
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
async listVersions(kind, id) {
|
|
660
|
+
const identity = await this.loadIdentity(kind, id);
|
|
661
|
+
if (!identity) return [];
|
|
662
|
+
const versions = await this.discoverVersionFolders(kind, id);
|
|
663
|
+
if (versions.length === 0) return [];
|
|
664
|
+
const yanked = new Set(identity.yankedVersions);
|
|
665
|
+
const minSupported = identity.minSupportedVersion;
|
|
666
|
+
const out = [];
|
|
667
|
+
for (const v of versions) {
|
|
668
|
+
if (minSupported && safeCompare(v.version, minSupported) < 0) continue;
|
|
669
|
+
if (!this.floorSatisfied(v.minGezelVersion)) continue;
|
|
670
|
+
out.push({
|
|
671
|
+
version: v.version,
|
|
672
|
+
releasedAt: v.releasedAt,
|
|
673
|
+
yanked: yanked.has(v.version)
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
out.sort((a, b) => safeCompare(b.version, a.version));
|
|
677
|
+
return out;
|
|
678
|
+
}
|
|
679
|
+
async readItemFile(kind, id, relPath, version) {
|
|
680
|
+
if (relPath.includes("..") || relPath.startsWith("/")) return null;
|
|
681
|
+
const itemDir = this.itemDir(kind, id);
|
|
682
|
+
if (version) {
|
|
683
|
+
if (!isSemver(version)) return null;
|
|
684
|
+
const versioned = join2(itemDir, "versions", version, relPath);
|
|
685
|
+
try {
|
|
686
|
+
return await readFile(versioned);
|
|
687
|
+
} catch {
|
|
688
|
+
if (kind === "craftbook-template") {
|
|
689
|
+
const scriptName = /^scripts\/(.+)\.ts$/.exec(relPath)?.[1];
|
|
690
|
+
if (scriptName) {
|
|
691
|
+
const doc = await this.readCraftbookDoc(join2(itemDir, "versions", version));
|
|
692
|
+
const source = doc?.scripts?.[scriptName];
|
|
693
|
+
if (source !== void 0) return Buffer.from(source, "utf8");
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
try {
|
|
699
|
+
return await readFile(join2(itemDir, relPath));
|
|
700
|
+
} catch {
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Every file under an item's folder, as item-relative paths (e.g.
|
|
706
|
+
* `manifest.json`, `versions/1.0.0/pages/gallery/index.html`), sorted. Used
|
|
707
|
+
* by the `.gzl` exporter to pack an item verbatim — pages, seeds, and all
|
|
708
|
+
* assets, not just the ones the manifest names. Read each back with
|
|
709
|
+
* `readItemFile(kind, id, relPath)` (no version → item-relative). Empty when
|
|
710
|
+
* the item folder is missing.
|
|
711
|
+
*/
|
|
712
|
+
async listItemFiles(kind, id) {
|
|
713
|
+
const dir = this.itemDir(kind, id);
|
|
714
|
+
let entries;
|
|
715
|
+
try {
|
|
716
|
+
entries = await readdir(dir, { recursive: true, withFileTypes: true });
|
|
717
|
+
} catch {
|
|
718
|
+
return [];
|
|
719
|
+
}
|
|
720
|
+
const out = [];
|
|
721
|
+
for (const e of entries) {
|
|
722
|
+
if (!e.isFile()) continue;
|
|
723
|
+
const parent = e.parentPath ?? e.path ?? dir;
|
|
724
|
+
const abs = join2(parent, e.name);
|
|
725
|
+
const rel = abs.startsWith(dir) ? abs.slice(dir.length).replace(/^[/\\]+/, "") : e.name;
|
|
726
|
+
out.push(rel.split("\\").join("/"));
|
|
727
|
+
}
|
|
728
|
+
out.sort();
|
|
729
|
+
return out;
|
|
730
|
+
}
|
|
731
|
+
/** Parse a version folder's `craftbook.json`, or null when absent/invalid. */
|
|
732
|
+
async readCraftbookDoc(versionDir) {
|
|
733
|
+
const text = await this.readOptional(join2(versionDir, "craftbook.json"));
|
|
734
|
+
if (text === null) return null;
|
|
735
|
+
const parsed = parseCraftbookDoc(text, "json");
|
|
736
|
+
return parsed.ok ? parsed.doc : null;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* A craftbook's eval descriptor (`versions/<v>/test.json`), tolerant-
|
|
740
|
+
* parsed so a spec written by a newer author still loads. Resolution
|
|
741
|
+
* mirrors `get`: no `version` → the latest non-yanked semver. Null on
|
|
742
|
+
* missing book/version/sidecar or an unparseable spec (CI's strict
|
|
743
|
+
* guard is where invalid specs get surfaced loudly — runtime readers
|
|
744
|
+
* degrade quietly).
|
|
745
|
+
*/
|
|
746
|
+
async getCraftbookTestSpec(id, version) {
|
|
747
|
+
const identity = await this.loadIdentity("craftbook-template", id);
|
|
748
|
+
if (!identity) return null;
|
|
749
|
+
const picked = await this.pickVersion("craftbook-template", id, identity, version);
|
|
750
|
+
if (!picked) return null;
|
|
751
|
+
const file = join2(
|
|
752
|
+
this.itemDir("craftbook-template", id),
|
|
753
|
+
"versions",
|
|
754
|
+
picked,
|
|
755
|
+
CRAFTBOOK_TEST_FILENAME
|
|
756
|
+
);
|
|
757
|
+
const text = await this.readOptional(file);
|
|
758
|
+
if (text === null) return null;
|
|
759
|
+
let raw;
|
|
760
|
+
try {
|
|
761
|
+
raw = JSON.parse(text);
|
|
762
|
+
} catch {
|
|
763
|
+
console.warn(`[catalog] ${file}: test spec is not valid JSON`);
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
const parsed = parseCraftbookTestSpec(raw, { mode: "tolerant" });
|
|
767
|
+
if (!parsed.ok) {
|
|
768
|
+
console.warn(`[catalog] ${file}: invalid test spec \u2014 ${parsed.errors[0]}`);
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
return { version: picked, spec: parsed.spec };
|
|
772
|
+
}
|
|
773
|
+
itemDir(kind, id) {
|
|
774
|
+
return join2(this.root, KIND_DIR[kind], shardPrefix(id), id);
|
|
775
|
+
}
|
|
776
|
+
/** Read + validate the identity (root) manifest for an item. */
|
|
777
|
+
async loadIdentity(kind, id) {
|
|
778
|
+
const file = join2(this.itemDir(kind, id), "manifest.json");
|
|
779
|
+
let raw;
|
|
780
|
+
try {
|
|
781
|
+
raw = await readFile(file, "utf8");
|
|
782
|
+
} catch {
|
|
783
|
+
return null;
|
|
784
|
+
}
|
|
785
|
+
try {
|
|
786
|
+
const parsed = CatalogItemIdentitySchema.parse(JSON.parse(raw));
|
|
787
|
+
if (parsed.kind !== kind) {
|
|
788
|
+
console.warn(
|
|
789
|
+
`[catalog] ${file}: identity kind=${parsed.kind} doesn't match directory kind=${kind}, skipping`
|
|
790
|
+
);
|
|
791
|
+
return null;
|
|
792
|
+
}
|
|
793
|
+
if (parsed.id !== id) {
|
|
794
|
+
console.warn(
|
|
795
|
+
`[catalog] ${file}: identity id=${parsed.id} doesn't match directory id=${id}, skipping`
|
|
796
|
+
);
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
if (!this.floorSatisfied(parsed.minGezelVersion)) return null;
|
|
800
|
+
return parsed;
|
|
801
|
+
} catch (err) {
|
|
802
|
+
console.warn(`[catalog] invalid identity manifest ${file}:`, err);
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/** Enumerate `versions/{semver}/manifest.json` entries. Unsorted. */
|
|
807
|
+
async discoverVersionFolders(kind, id) {
|
|
808
|
+
const versionsDir = join2(this.itemDir(kind, id), "versions");
|
|
809
|
+
let names;
|
|
810
|
+
try {
|
|
811
|
+
names = await readdir(versionsDir);
|
|
812
|
+
} catch {
|
|
813
|
+
return [];
|
|
814
|
+
}
|
|
815
|
+
const out = [];
|
|
816
|
+
for (const name of names) {
|
|
817
|
+
if (!isSemver(name)) continue;
|
|
818
|
+
const candidates = kind === "craftbook-template" ? ["craftbook.json", "manifest.json"] : ["manifest.json"];
|
|
819
|
+
for (const filename of candidates) {
|
|
820
|
+
const versionFile = join2(versionsDir, name, filename);
|
|
821
|
+
let raw;
|
|
822
|
+
try {
|
|
823
|
+
raw = await readFile(versionFile, "utf8");
|
|
824
|
+
} catch {
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
try {
|
|
828
|
+
const json = JSON.parse(raw);
|
|
829
|
+
const version = typeof json.version === "string" ? json.version : null;
|
|
830
|
+
const releasedAt = typeof json.releasedAt === "string" ? json.releasedAt : null;
|
|
831
|
+
if (!version || !releasedAt) continue;
|
|
832
|
+
if (version !== name) {
|
|
833
|
+
console.warn(
|
|
834
|
+
`[catalog] ${versionFile}: version=${version} doesn't match folder name=${name}, skipping`
|
|
835
|
+
);
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
out.push({
|
|
839
|
+
version,
|
|
840
|
+
releasedAt,
|
|
841
|
+
...typeof json.minGezelVersion === "string" ? { minGezelVersion: json.minGezelVersion } : {}
|
|
842
|
+
});
|
|
843
|
+
} catch {
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return out;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Pick a target version. When `requested` is set, validate it exists —
|
|
853
|
+
* an explicit pin deliberately bypasses the yank / `minSupportedVersion`
|
|
854
|
+
* / `minGezelVersion` filters so installed content keeps resolving.
|
|
855
|
+
* Otherwise resolve the highest non-yanked semver above
|
|
856
|
+
* `minSupportedVersion` whose `minGezelVersion` floor this build
|
|
857
|
+
* satisfies. Returns null when nothing satisfies.
|
|
858
|
+
*/
|
|
859
|
+
async pickVersion(kind, id, identity, requested) {
|
|
860
|
+
const folders = await this.discoverVersionFolders(kind, id);
|
|
861
|
+
if (folders.length === 0) return null;
|
|
862
|
+
if (requested) {
|
|
863
|
+
return folders.some((f) => f.version === requested) ? requested : null;
|
|
864
|
+
}
|
|
865
|
+
const yanked = new Set(identity.yankedVersions);
|
|
866
|
+
const minSupported = identity.minSupportedVersion;
|
|
867
|
+
const eligible = folders.filter((f) => {
|
|
868
|
+
if (yanked.has(f.version)) return false;
|
|
869
|
+
if (minSupported && safeCompare(f.version, minSupported) < 0) return false;
|
|
870
|
+
if (!this.floorSatisfied(f.minGezelVersion)) return false;
|
|
871
|
+
return true;
|
|
872
|
+
}).map((f) => f.version);
|
|
873
|
+
if (eligible.length === 0) return null;
|
|
874
|
+
eligible.sort((a, b) => safeCompare(b, a));
|
|
875
|
+
return eligible[0] ?? null;
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Compose identity + version into the resolved (flat) manifest shape
|
|
879
|
+
* that consumers expect. Returns null when identity is missing,
|
|
880
|
+
* version is missing, or validation fails.
|
|
881
|
+
*/
|
|
882
|
+
async loadResolvedManifest(kind, id, version) {
|
|
883
|
+
const identity = await this.loadIdentity(kind, id);
|
|
884
|
+
if (!identity) return null;
|
|
885
|
+
const chosen = await this.pickVersion(kind, id, identity, version);
|
|
886
|
+
if (!chosen) {
|
|
887
|
+
if (version) {
|
|
888
|
+
console.warn(`[catalog] ${kind}/${id}: requested version ${version} not found on disk`);
|
|
889
|
+
} else {
|
|
890
|
+
const folders = await this.discoverVersionFolders(kind, id);
|
|
891
|
+
const yanked = new Set(identity.yankedVersions);
|
|
892
|
+
const everythingIneligible = folders.length > 0 && folders.every((f) => yanked.has(f.version) || !this.floorSatisfied(f.minGezelVersion));
|
|
893
|
+
if (!everythingIneligible) {
|
|
894
|
+
console.warn(`[catalog] ${kind}/${id}: no eligible versions on disk`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
const versionDir = join2(this.itemDir(kind, id), "versions", chosen);
|
|
900
|
+
const yankedSet = new Set(identity.yankedVersions);
|
|
901
|
+
const minSupportedVersion = identity.minSupportedVersion;
|
|
902
|
+
const allFolders = await this.discoverVersionFolders(kind, id);
|
|
903
|
+
const availableVersions = allFolders.filter((f) => {
|
|
904
|
+
if (yankedSet.has(f.version)) return false;
|
|
905
|
+
if (minSupportedVersion && safeCompare(f.version, minSupportedVersion) < 0) return false;
|
|
906
|
+
if (!this.floorSatisfied(f.minGezelVersion)) return false;
|
|
907
|
+
return true;
|
|
908
|
+
}).map((f) => f.version).sort((a, b) => safeCompare(b, a));
|
|
909
|
+
if (kind === "craftbook-template" && identity.kind === "craftbook-template") {
|
|
910
|
+
const docText = await this.readOptional(join2(versionDir, "craftbook.json"));
|
|
911
|
+
if (docText !== null) {
|
|
912
|
+
const parsed = parseCraftbookDoc(docText, "json");
|
|
913
|
+
if (!parsed.ok) {
|
|
914
|
+
console.warn(
|
|
915
|
+
`[catalog] invalid craftbook document ${join2(versionDir, "craftbook.json")}:
|
|
916
|
+
${formatCraftbookDocErrors(parsed.errors)}`
|
|
917
|
+
);
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
return craftbookManifestFromDoc(identity, parsed.doc, chosen, availableVersions);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
const versionFile = join2(versionDir, "manifest.json");
|
|
924
|
+
let versionPayload;
|
|
925
|
+
try {
|
|
926
|
+
versionPayload = JSON.parse(await readFile(versionFile, "utf8"));
|
|
927
|
+
} catch (err) {
|
|
928
|
+
console.warn(`[catalog] failed to read ${versionFile}:`, err);
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
const parsedVersion = parseVersionPayload(kind, versionPayload);
|
|
932
|
+
if (!parsedVersion) {
|
|
933
|
+
console.warn(`[catalog] invalid version manifest ${versionFile}`);
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
if (kind === "chat-model") {
|
|
937
|
+
const v = parsedVersion;
|
|
938
|
+
if (!v.ollama && !v.llamaCpp && !v.mlx && !v.ds4) {
|
|
939
|
+
console.warn(
|
|
940
|
+
`[catalog] ${versionFile}: chat-model version has no ollama / llamaCpp / mlx / ds4 source \u2014 skipping`
|
|
941
|
+
);
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (kind === "video-model") {
|
|
946
|
+
const v = parsedVersion;
|
|
947
|
+
if (!v.source || !Array.isArray(v.source.files) || v.source.files.length === 0) {
|
|
948
|
+
console.warn(
|
|
949
|
+
`[catalog] ${versionFile}: video-model version has no source files \u2014 skipping`
|
|
950
|
+
);
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return mergeIdentityAndVersion(kind, identity, parsedVersion, availableVersions);
|
|
955
|
+
}
|
|
956
|
+
async readOptional(path) {
|
|
957
|
+
try {
|
|
958
|
+
return await readFile(path, "utf8");
|
|
959
|
+
} catch {
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
logoUrlFor(kind, id, manifest) {
|
|
964
|
+
if (!manifest.logo) return void 0;
|
|
965
|
+
if (/^https?:\/\//.test(manifest.logo)) return manifest.logo;
|
|
966
|
+
return `/api/catalog/${kind}/${encodeURIComponent(id)}/file/${encodeURIComponent(
|
|
967
|
+
manifest.logo
|
|
968
|
+
)}?source=${encodeURIComponent(this.id)}`;
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
function craftbookManifestFromDoc(identity, doc, version, availableVersions) {
|
|
972
|
+
if (!doc.releasedAt) {
|
|
973
|
+
console.warn(`[catalog] craftbook-template/${identity.id}@${version}: doc has no releasedAt`);
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
const steps = expandStepDeliverables(doc.steps);
|
|
977
|
+
const entryStepId = doc.entryStepId ?? steps[0].id;
|
|
978
|
+
const scriptNames = Object.keys(doc.scripts ?? {});
|
|
979
|
+
const minGezelVersion = maxMinGezelVersion(identity.minGezelVersion, doc.minGezelVersion);
|
|
980
|
+
return {
|
|
981
|
+
schemaVersion: 1,
|
|
982
|
+
kind: "craftbook-template",
|
|
983
|
+
id: identity.id,
|
|
984
|
+
name: identity.name,
|
|
985
|
+
description: identity.description,
|
|
986
|
+
tags: identity.tags,
|
|
987
|
+
maintainer: identity.maintainer,
|
|
988
|
+
...identity.logo !== void 0 ? { logo: identity.logo } : {},
|
|
989
|
+
...identity.license !== void 0 ? { license: identity.license } : {},
|
|
990
|
+
...minGezelVersion !== void 0 ? { minGezelVersion } : {},
|
|
991
|
+
version,
|
|
992
|
+
releasedAt: doc.releasedAt,
|
|
993
|
+
role: identity.role,
|
|
994
|
+
...identity.workflow ? { workflow: identity.workflow } : {},
|
|
995
|
+
// The prose lives inline on the doc (`description`) — there is no
|
|
996
|
+
// separate about.md file to point at. `get()` surfaces it directly.
|
|
997
|
+
about: "",
|
|
998
|
+
steps,
|
|
999
|
+
entryStepId,
|
|
1000
|
+
...doc.defaultAssignee ? { defaultAssignee: doc.defaultAssignee } : {},
|
|
1001
|
+
...doc.basedOn ? { basedOn: doc.basedOn } : {},
|
|
1002
|
+
...doc.plan !== void 0 ? { plan: doc.plan } : {},
|
|
1003
|
+
...doc.triggers ? { triggers: doc.triggers } : {},
|
|
1004
|
+
...doc.scripts ? { scripts: doc.scripts } : {},
|
|
1005
|
+
...scriptNames.length > 0 ? { bundledScripts: scriptNames.map((n) => `${n}.ts`) } : {},
|
|
1006
|
+
...doc.paramSchema ? { paramSchema: doc.paramSchema } : {},
|
|
1007
|
+
...doc.command ? { command: doc.command } : {},
|
|
1008
|
+
...doc.requirements ? { requirements: doc.requirements } : {},
|
|
1009
|
+
...doc.runModes ? { runModes: doc.runModes } : {},
|
|
1010
|
+
...doc.toolsets ? { toolsets: doc.toolsets } : {},
|
|
1011
|
+
...doc.connectors ? { connectors: doc.connectors } : {},
|
|
1012
|
+
...doc.hooks ? { hooks: doc.hooks } : {},
|
|
1013
|
+
...doc.spawn ? { spawn: doc.spawn } : {},
|
|
1014
|
+
availableVersions
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
function safeCompare(a, b) {
|
|
1018
|
+
try {
|
|
1019
|
+
return compareSemver(a, b);
|
|
1020
|
+
} catch {
|
|
1021
|
+
return 0;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
function parseVersionPayload(kind, raw) {
|
|
1025
|
+
try {
|
|
1026
|
+
if (kind === "toolset") {
|
|
1027
|
+
const p2 = ToolsetVersionManifestSchema.parse(raw);
|
|
1028
|
+
return { ...p2, __kind: "toolset" };
|
|
1029
|
+
}
|
|
1030
|
+
if (kind === "gezel-template") {
|
|
1031
|
+
const p2 = GezelTemplateVersionManifestSchema.parse(raw);
|
|
1032
|
+
return { ...p2, __kind: "gezel-template" };
|
|
1033
|
+
}
|
|
1034
|
+
if (kind === "craftbook-template") {
|
|
1035
|
+
const p2 = CraftbookTemplateVersionManifestSchema.parse(raw);
|
|
1036
|
+
return { ...p2, __kind: "craftbook-template" };
|
|
1037
|
+
}
|
|
1038
|
+
if (kind === "project-type") {
|
|
1039
|
+
const p2 = ProjectTypeVersionManifestSchema.parse(raw);
|
|
1040
|
+
return { ...p2, __kind: "project-type" };
|
|
1041
|
+
}
|
|
1042
|
+
if (kind === "connector-type") {
|
|
1043
|
+
const p2 = ConnectorTypeVersionManifestSchema.parse(raw);
|
|
1044
|
+
return { ...p2, __kind: "connector-type" };
|
|
1045
|
+
}
|
|
1046
|
+
if (kind === "chat-model") {
|
|
1047
|
+
const p2 = ChatModelVersionManifestSchema.parse(raw);
|
|
1048
|
+
return { ...p2, __kind: "chat-model" };
|
|
1049
|
+
}
|
|
1050
|
+
if (kind === "video-model") {
|
|
1051
|
+
const p2 = VideoModelVersionManifestSchema.parse(raw);
|
|
1052
|
+
return { ...p2, __kind: "video-model" };
|
|
1053
|
+
}
|
|
1054
|
+
const p = ImageModelVersionManifestSchema.parse(raw);
|
|
1055
|
+
return { ...p, __kind: "image-model" };
|
|
1056
|
+
} catch {
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function mergeIdentityAndVersion(kind, identity, version, availableVersions) {
|
|
1061
|
+
const minGezelVersion = maxMinGezelVersion(identity.minGezelVersion, version.minGezelVersion);
|
|
1062
|
+
if (kind === "toolset" && identity.kind === "toolset" && version.__kind === "toolset") {
|
|
1063
|
+
return {
|
|
1064
|
+
schemaVersion: 1,
|
|
1065
|
+
kind: "toolset",
|
|
1066
|
+
id: identity.id,
|
|
1067
|
+
name: identity.name,
|
|
1068
|
+
description: identity.description,
|
|
1069
|
+
tags: identity.tags,
|
|
1070
|
+
maintainer: identity.maintainer,
|
|
1071
|
+
...identity.logo !== void 0 ? { logo: identity.logo } : {},
|
|
1072
|
+
...identity.license !== void 0 ? { license: identity.license } : {},
|
|
1073
|
+
...minGezelVersion !== void 0 ? { minGezelVersion } : {},
|
|
1074
|
+
version: version.version,
|
|
1075
|
+
releasedAt: version.releasedAt,
|
|
1076
|
+
runtime: version.runtime,
|
|
1077
|
+
tools: version.tools,
|
|
1078
|
+
config: version.config,
|
|
1079
|
+
...version.requirements ? { requirements: version.requirements } : {},
|
|
1080
|
+
...version.notes !== void 0 ? { notes: version.notes } : {},
|
|
1081
|
+
availableVersions,
|
|
1082
|
+
...identity.category ? { category: identity.category } : {}
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
if (kind === "craftbook-template" && identity.kind === "craftbook-template" && version.__kind === "craftbook-template") {
|
|
1086
|
+
return {
|
|
1087
|
+
schemaVersion: 1,
|
|
1088
|
+
kind: "craftbook-template",
|
|
1089
|
+
id: identity.id,
|
|
1090
|
+
name: identity.name,
|
|
1091
|
+
description: identity.description,
|
|
1092
|
+
tags: identity.tags,
|
|
1093
|
+
maintainer: identity.maintainer,
|
|
1094
|
+
...identity.logo !== void 0 ? { logo: identity.logo } : {},
|
|
1095
|
+
...identity.license !== void 0 ? { license: identity.license } : {},
|
|
1096
|
+
...minGezelVersion !== void 0 ? { minGezelVersion } : {},
|
|
1097
|
+
version: version.version,
|
|
1098
|
+
releasedAt: version.releasedAt,
|
|
1099
|
+
role: identity.role,
|
|
1100
|
+
...identity.workflow ? { workflow: identity.workflow } : {},
|
|
1101
|
+
about: version.about,
|
|
1102
|
+
steps: version.steps,
|
|
1103
|
+
entryStepId: version.entryStepId,
|
|
1104
|
+
...version.defaultAssignee ? { defaultAssignee: version.defaultAssignee } : {},
|
|
1105
|
+
...version.basedOn ? { basedOn: version.basedOn } : {},
|
|
1106
|
+
...version.plan !== void 0 ? { plan: version.plan } : {},
|
|
1107
|
+
...version.notes !== void 0 ? { notes: version.notes } : {},
|
|
1108
|
+
...version.triggers ? { triggers: version.triggers } : {},
|
|
1109
|
+
...version.hooks ? { hooks: version.hooks } : {},
|
|
1110
|
+
...version.bundledScripts ? { bundledScripts: version.bundledScripts } : {},
|
|
1111
|
+
...version.paramSchema ? { paramSchema: version.paramSchema } : {},
|
|
1112
|
+
...version.command ? { command: version.command } : {},
|
|
1113
|
+
...version.requirements ? { requirements: version.requirements } : {},
|
|
1114
|
+
...version.runModes ? { runModes: version.runModes } : {},
|
|
1115
|
+
...version.toolsets ? { toolsets: version.toolsets } : {},
|
|
1116
|
+
...version.connectors ? { connectors: version.connectors } : {},
|
|
1117
|
+
availableVersions
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
if (kind === "project-type" && identity.kind === "project-type" && version.__kind === "project-type") {
|
|
1121
|
+
return {
|
|
1122
|
+
schemaVersion: 1,
|
|
1123
|
+
kind: "project-type",
|
|
1124
|
+
id: identity.id,
|
|
1125
|
+
name: identity.name,
|
|
1126
|
+
description: identity.description,
|
|
1127
|
+
tags: identity.tags,
|
|
1128
|
+
...identity.category !== void 0 ? { category: identity.category } : {},
|
|
1129
|
+
maintainer: identity.maintainer,
|
|
1130
|
+
...identity.logo !== void 0 ? { logo: identity.logo } : {},
|
|
1131
|
+
...identity.license !== void 0 ? { license: identity.license } : {},
|
|
1132
|
+
...minGezelVersion !== void 0 ? { minGezelVersion } : {},
|
|
1133
|
+
version: version.version,
|
|
1134
|
+
releasedAt: version.releasedAt,
|
|
1135
|
+
...version.extends !== void 0 ? { extends: version.extends } : {},
|
|
1136
|
+
...version.meesterManaged !== void 0 ? { meesterManaged: version.meesterManaged } : {},
|
|
1137
|
+
...version.tabVisibility !== void 0 ? { tabVisibility: version.tabVisibility } : {},
|
|
1138
|
+
...version.mode !== void 0 ? { mode: version.mode } : {},
|
|
1139
|
+
...version.leadLabel !== void 0 ? { leadLabel: version.leadLabel } : {},
|
|
1140
|
+
...version.leanProfile !== void 0 ? { leanProfile: version.leanProfile } : {},
|
|
1141
|
+
...version.indexingEnabled !== void 0 ? { indexingEnabled: version.indexingEnabled } : {},
|
|
1142
|
+
...version.params !== void 0 ? { params: version.params } : {},
|
|
1143
|
+
...version.nameTemplate !== void 0 ? { nameTemplate: version.nameTemplate } : {},
|
|
1144
|
+
...version.aboutTemplate !== void 0 ? { aboutTemplate: version.aboutTemplate } : {},
|
|
1145
|
+
...version.missionTemplate !== void 0 ? { missionTemplate: version.missionTemplate } : {},
|
|
1146
|
+
gezels: version.gezels,
|
|
1147
|
+
toolsets: version.toolsets,
|
|
1148
|
+
craftbooks: version.craftbooks,
|
|
1149
|
+
...version.scripts !== void 0 ? { scripts: version.scripts } : {},
|
|
1150
|
+
tools: version.tools,
|
|
1151
|
+
...version.pages !== void 0 ? { pages: version.pages } : {},
|
|
1152
|
+
schedules: version.schedules,
|
|
1153
|
+
workspaceSeed: version.workspaceSeed,
|
|
1154
|
+
artifactsSeed: version.artifactsSeed,
|
|
1155
|
+
...version.notes !== void 0 ? { notes: version.notes } : {},
|
|
1156
|
+
availableVersions
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
if (kind === "connector-type" && identity.kind === "connector-type" && version.__kind === "connector-type") {
|
|
1160
|
+
return {
|
|
1161
|
+
schemaVersion: 1,
|
|
1162
|
+
kind: "connector-type",
|
|
1163
|
+
id: identity.id,
|
|
1164
|
+
name: identity.name,
|
|
1165
|
+
description: identity.description,
|
|
1166
|
+
tags: identity.tags,
|
|
1167
|
+
maintainer: identity.maintainer,
|
|
1168
|
+
...identity.logo !== void 0 ? { logo: identity.logo } : {},
|
|
1169
|
+
...identity.license !== void 0 ? { license: identity.license } : {},
|
|
1170
|
+
...minGezelVersion !== void 0 ? { minGezelVersion } : {},
|
|
1171
|
+
version: version.version,
|
|
1172
|
+
releasedAt: version.releasedAt,
|
|
1173
|
+
driver: version.driver,
|
|
1174
|
+
...version.configSchema !== void 0 ? { configSchema: version.configSchema } : {},
|
|
1175
|
+
...version.secretShape !== void 0 ? { secretShape: version.secretShape } : {},
|
|
1176
|
+
source: version.source,
|
|
1177
|
+
normalize: version.normalize,
|
|
1178
|
+
actions: version.actions,
|
|
1179
|
+
...version.completeness !== void 0 ? { completeness: version.completeness } : {},
|
|
1180
|
+
...version.notes !== void 0 ? { notes: version.notes } : {},
|
|
1181
|
+
availableVersions
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
if (kind === "gezel-template" && identity.kind === "gezel-template" && version.__kind === "gezel-template") {
|
|
1185
|
+
return {
|
|
1186
|
+
schemaVersion: 1,
|
|
1187
|
+
kind: "gezel-template",
|
|
1188
|
+
id: identity.id,
|
|
1189
|
+
name: identity.name,
|
|
1190
|
+
description: identity.description,
|
|
1191
|
+
tags: identity.tags,
|
|
1192
|
+
maintainer: identity.maintainer,
|
|
1193
|
+
...identity.logo !== void 0 ? { logo: identity.logo } : {},
|
|
1194
|
+
...identity.license !== void 0 ? { license: identity.license } : {},
|
|
1195
|
+
...minGezelVersion !== void 0 ? { minGezelVersion } : {},
|
|
1196
|
+
version: version.version,
|
|
1197
|
+
releasedAt: version.releasedAt,
|
|
1198
|
+
role: identity.role,
|
|
1199
|
+
about: version.about,
|
|
1200
|
+
...version.suggestedProvider ? { suggestedProvider: version.suggestedProvider } : {},
|
|
1201
|
+
...version.suggestedModel ? { suggestedModel: version.suggestedModel } : {},
|
|
1202
|
+
suggestedTools: version.suggestedTools,
|
|
1203
|
+
meesterCandidate: identity.meesterCandidate,
|
|
1204
|
+
...version.notes !== void 0 ? { notes: version.notes } : {},
|
|
1205
|
+
...version.frontmatter ? { frontmatter: version.frontmatter } : {},
|
|
1206
|
+
...version.nameSuggestions ? { nameSuggestions: version.nameSuggestions } : {},
|
|
1207
|
+
...version.suggestedCraftbooks ? { suggestedCraftbooks: version.suggestedCraftbooks } : {},
|
|
1208
|
+
availableVersions
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
if (kind === "chat-model" && identity.kind === "chat-model" && version.__kind === "chat-model") {
|
|
1212
|
+
return {
|
|
1213
|
+
schemaVersion: 1,
|
|
1214
|
+
kind: "chat-model",
|
|
1215
|
+
id: identity.id,
|
|
1216
|
+
name: identity.name,
|
|
1217
|
+
description: identity.description,
|
|
1218
|
+
tags: identity.tags,
|
|
1219
|
+
maintainer: identity.maintainer,
|
|
1220
|
+
...identity.logo !== void 0 ? { logo: identity.logo } : {},
|
|
1221
|
+
...identity.license !== void 0 ? { license: identity.license } : {},
|
|
1222
|
+
...identity.licenseClass !== void 0 ? { licenseClass: identity.licenseClass } : {},
|
|
1223
|
+
...identity.licenseShortName !== void 0 ? { licenseShortName: identity.licenseShortName } : {},
|
|
1224
|
+
...identity.licenseUrl !== void 0 ? { licenseUrl: identity.licenseUrl } : {},
|
|
1225
|
+
...identity.recoScore !== void 0 ? { recoScore: identity.recoScore } : {},
|
|
1226
|
+
...minGezelVersion !== void 0 ? { minGezelVersion } : {},
|
|
1227
|
+
version: version.version,
|
|
1228
|
+
releasedAt: version.releasedAt,
|
|
1229
|
+
parameterSize: identity.parameterSize,
|
|
1230
|
+
approxSizeBytes: version.approxSizeBytes,
|
|
1231
|
+
supportsTools: identity.supportsTools,
|
|
1232
|
+
...identity.contextWindow !== void 0 ? { contextWindow: identity.contextWindow } : {},
|
|
1233
|
+
// KV geometry is architecture-determined, so it rides on identity and
|
|
1234
|
+
// must be forwarded explicitly — this merge builds the resolved
|
|
1235
|
+
// manifest from a fixed field list, which is why authoring the fields
|
|
1236
|
+
// alone did nothing: they were silently dropped here and the
|
|
1237
|
+
// pre-install fit badge kept pricing weights only.
|
|
1238
|
+
...identity.kvBytesPerTokenF16 !== void 0 ? { kvBytesPerTokenF16: identity.kvBytesPerTokenF16 } : {},
|
|
1239
|
+
...identity.kvFixedBytesF16 !== void 0 ? { kvFixedBytesF16: identity.kvFixedBytesF16 } : {},
|
|
1240
|
+
...identity.upstream !== void 0 ? { upstream: identity.upstream } : {},
|
|
1241
|
+
...identity.category ? { category: identity.category } : {},
|
|
1242
|
+
...identity.style ? { style: identity.style } : {},
|
|
1243
|
+
...identity.behaviors ? { behaviors: identity.behaviors } : {},
|
|
1244
|
+
...identity.tuning ? { tuning: identity.tuning } : {},
|
|
1245
|
+
...version.ollama ? { ollama: version.ollama } : {},
|
|
1246
|
+
...version.llamaCpp ? { llamaCpp: version.llamaCpp } : {},
|
|
1247
|
+
...version.mlx ? { mlx: version.mlx } : {},
|
|
1248
|
+
...version.ds4 ? { ds4: version.ds4 } : {},
|
|
1249
|
+
...version.notes !== void 0 ? { notes: version.notes } : {},
|
|
1250
|
+
availableVersions
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
if (kind === "image-model" && identity.kind === "image-model" && version.__kind === "image-model") {
|
|
1254
|
+
return {
|
|
1255
|
+
schemaVersion: 1,
|
|
1256
|
+
kind: "image-model",
|
|
1257
|
+
id: identity.id,
|
|
1258
|
+
name: identity.name,
|
|
1259
|
+
description: identity.description,
|
|
1260
|
+
tags: identity.tags,
|
|
1261
|
+
maintainer: identity.maintainer,
|
|
1262
|
+
...identity.logo !== void 0 ? { logo: identity.logo } : {},
|
|
1263
|
+
...identity.license !== void 0 ? { license: identity.license } : {},
|
|
1264
|
+
...identity.licenseClass !== void 0 ? { licenseClass: identity.licenseClass } : {},
|
|
1265
|
+
...identity.licenseShortName !== void 0 ? { licenseShortName: identity.licenseShortName } : {},
|
|
1266
|
+
...identity.licenseUrl !== void 0 ? { licenseUrl: identity.licenseUrl } : {},
|
|
1267
|
+
...identity.recoScore !== void 0 ? { recoScore: identity.recoScore } : {},
|
|
1268
|
+
...minGezelVersion !== void 0 ? { minGezelVersion } : {},
|
|
1269
|
+
version: version.version,
|
|
1270
|
+
releasedAt: version.releasedAt,
|
|
1271
|
+
downloadUrl: version.downloadUrl,
|
|
1272
|
+
sha256: version.sha256,
|
|
1273
|
+
approxSizeBytes: version.approxSizeBytes,
|
|
1274
|
+
...version.quantization ? { quantization: version.quantization } : {},
|
|
1275
|
+
recommendedSteps: identity.recommendedSteps,
|
|
1276
|
+
...identity.widthRange ? { widthRange: identity.widthRange } : {},
|
|
1277
|
+
...identity.heightRange ? { heightRange: identity.heightRange } : {},
|
|
1278
|
+
...identity.upstream !== void 0 ? { upstream: identity.upstream } : {},
|
|
1279
|
+
...identity.category ? { category: identity.category } : {},
|
|
1280
|
+
weightsKind: identity.weightsKind,
|
|
1281
|
+
...identity.supportsImg2Img !== void 0 ? { supportsImg2Img: identity.supportsImg2Img } : {},
|
|
1282
|
+
auxiliaryFiles: version.auxiliaryFiles,
|
|
1283
|
+
hardwareTier: identity.hardwareTier,
|
|
1284
|
+
minRamGB: identity.minRamGB,
|
|
1285
|
+
commercialUse: identity.commercialUse,
|
|
1286
|
+
...version.notes !== void 0 ? { notes: version.notes } : {},
|
|
1287
|
+
availableVersions
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
if (kind === "video-model" && identity.kind === "video-model" && version.__kind === "video-model") {
|
|
1291
|
+
return {
|
|
1292
|
+
schemaVersion: 1,
|
|
1293
|
+
kind: "video-model",
|
|
1294
|
+
id: identity.id,
|
|
1295
|
+
name: identity.name,
|
|
1296
|
+
description: identity.description,
|
|
1297
|
+
tags: identity.tags,
|
|
1298
|
+
maintainer: identity.maintainer,
|
|
1299
|
+
...identity.logo !== void 0 ? { logo: identity.logo } : {},
|
|
1300
|
+
...identity.license !== void 0 ? { license: identity.license } : {},
|
|
1301
|
+
...identity.licenseClass !== void 0 ? { licenseClass: identity.licenseClass } : {},
|
|
1302
|
+
...identity.licenseShortName !== void 0 ? { licenseShortName: identity.licenseShortName } : {},
|
|
1303
|
+
...identity.licenseUrl !== void 0 ? { licenseUrl: identity.licenseUrl } : {},
|
|
1304
|
+
...identity.recoScore !== void 0 ? { recoScore: identity.recoScore } : {},
|
|
1305
|
+
...minGezelVersion !== void 0 ? { minGezelVersion } : {},
|
|
1306
|
+
version: version.version,
|
|
1307
|
+
releasedAt: version.releasedAt,
|
|
1308
|
+
family: identity.family,
|
|
1309
|
+
...identity.load !== void 0 ? { load: identity.load } : {},
|
|
1310
|
+
source: version.source,
|
|
1311
|
+
approxSizeBytes: version.source.approxSizeBytes,
|
|
1312
|
+
recommendedSteps: identity.recommendedSteps,
|
|
1313
|
+
...identity.guidanceScale !== void 0 ? { guidanceScale: identity.guidanceScale } : {},
|
|
1314
|
+
defaultNumFrames: identity.defaultNumFrames,
|
|
1315
|
+
...identity.numFramesRange ? { numFramesRange: identity.numFramesRange } : {},
|
|
1316
|
+
defaultFps: identity.defaultFps,
|
|
1317
|
+
...identity.fpsRange ? { fpsRange: identity.fpsRange } : {},
|
|
1318
|
+
...identity.maxDurationSeconds !== void 0 ? { maxDurationSeconds: identity.maxDurationSeconds } : {},
|
|
1319
|
+
...identity.widthRange ? { widthRange: identity.widthRange } : {},
|
|
1320
|
+
...identity.heightRange ? { heightRange: identity.heightRange } : {},
|
|
1321
|
+
...identity.defaultWidth !== void 0 ? { defaultWidth: identity.defaultWidth } : {},
|
|
1322
|
+
...identity.defaultHeight !== void 0 ? { defaultHeight: identity.defaultHeight } : {},
|
|
1323
|
+
supportsImageToVideo: identity.supportsImageToVideo,
|
|
1324
|
+
...identity.upstream !== void 0 ? { upstream: identity.upstream } : {},
|
|
1325
|
+
hardwareTier: identity.hardwareTier,
|
|
1326
|
+
minVramGB: identity.minVramGB,
|
|
1327
|
+
commercialUse: identity.commercialUse,
|
|
1328
|
+
...version.notes !== void 0 ? { notes: version.notes } : {},
|
|
1329
|
+
availableVersions
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
return null;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// src/community-source.ts
|
|
1336
|
+
function defaultCommunityDataDir() {
|
|
1337
|
+
return join3(gildeDataDir(), "community");
|
|
1338
|
+
}
|
|
1339
|
+
var CommunitySource = class extends BundledSource {
|
|
1340
|
+
constructor(dataDir = defaultCommunityDataDir(), gezelVersion) {
|
|
1341
|
+
super({
|
|
1342
|
+
dataDir,
|
|
1343
|
+
id: "community",
|
|
1344
|
+
label: "MCP Registry (community)",
|
|
1345
|
+
...gezelVersion !== void 0 ? { gezelVersion } : {}
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1350
|
+
// src/local-source.ts
|
|
1351
|
+
var LOCAL_KINDS = /* @__PURE__ */ new Set([
|
|
1352
|
+
"project-type",
|
|
1353
|
+
"gezel-template",
|
|
1354
|
+
"connector-type",
|
|
1355
|
+
"toolset"
|
|
1356
|
+
]);
|
|
1357
|
+
var LocalCatalogSource = class extends BundledSource {
|
|
1358
|
+
constructor(home, gezelVersion) {
|
|
1359
|
+
super({
|
|
1360
|
+
dataDir: home,
|
|
1361
|
+
id: "local",
|
|
1362
|
+
label: "Installed",
|
|
1363
|
+
noIndex: true,
|
|
1364
|
+
...gezelVersion !== void 0 ? { gezelVersion } : {}
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
async listKinds() {
|
|
1368
|
+
return [...LOCAL_KINDS];
|
|
1369
|
+
}
|
|
1370
|
+
async list(kind) {
|
|
1371
|
+
if (!LOCAL_KINDS.has(kind)) return [];
|
|
1372
|
+
return super.list(kind);
|
|
1373
|
+
}
|
|
1374
|
+
async get(kind, id, version) {
|
|
1375
|
+
if (!LOCAL_KINDS.has(kind)) return null;
|
|
1376
|
+
return super.get(kind, id, version);
|
|
1377
|
+
}
|
|
1378
|
+
async listVersions(kind, id) {
|
|
1379
|
+
if (!LOCAL_KINDS.has(kind)) return [];
|
|
1380
|
+
return super.listVersions(kind, id);
|
|
1381
|
+
}
|
|
1382
|
+
async readItemFile(kind, id, relPath, version) {
|
|
1383
|
+
if (!LOCAL_KINDS.has(kind)) return null;
|
|
1384
|
+
return super.readItemFile(kind, id, relPath, version);
|
|
1385
|
+
}
|
|
1386
|
+
async listItemFiles(kind, id) {
|
|
1387
|
+
if (!LOCAL_KINDS.has(kind)) return [];
|
|
1388
|
+
return super.listItemFiles(kind, id);
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
// src/service.ts
|
|
1393
|
+
var CatalogService = class {
|
|
1394
|
+
sources;
|
|
1395
|
+
contentRootProvider;
|
|
1396
|
+
/**
|
|
1397
|
+
* @param sources explicit source list (replaces the defaults entirely).
|
|
1398
|
+
* @param opts.localRoot a GEZEL_HOME to read user-installed / `.gzl`-imported
|
|
1399
|
+
* catalog items from (project types + gezel roles). Appended just ahead of
|
|
1400
|
+
* the bundled tier so a user's imported item shadows a same-id bundled one.
|
|
1401
|
+
* @param opts.contentRoot dynamic gilde content root for the default
|
|
1402
|
+
* bundled + community tiers, re-read on every disk access. The live
|
|
1403
|
+
* gilde update mechanism flips it without reconstructing this service
|
|
1404
|
+
* (which is built once at boot and held by many subsystems).
|
|
1405
|
+
* @param opts.gezelVersion the running app's version, compared against
|
|
1406
|
+
* content `minGezelVersion` floors. Defaults to `GEZEL_VERSION`
|
|
1407
|
+
* inside each source; injectable so tests can exercise gating (dev
|
|
1408
|
+
* checkouts report `0.0.0`, which bypasses all filtering).
|
|
1409
|
+
*/
|
|
1410
|
+
constructor(sources, opts) {
|
|
1411
|
+
this.contentRootProvider = opts?.contentRoot ?? null;
|
|
1412
|
+
if (sources && sources.length > 0) {
|
|
1413
|
+
this.sources = sources;
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
const contentRoot = opts?.contentRoot;
|
|
1417
|
+
const gezelVersion = opts?.gezelVersion;
|
|
1418
|
+
const local = opts?.localRoot ? [new LocalCatalogSource(opts.localRoot, gezelVersion)] : [];
|
|
1419
|
+
this.sources = [
|
|
1420
|
+
new BuiltinToolsetsSource(),
|
|
1421
|
+
...local,
|
|
1422
|
+
new BundledSource({
|
|
1423
|
+
...contentRoot ? { dataDir: contentRoot } : {},
|
|
1424
|
+
...gezelVersion !== void 0 ? { gezelVersion } : {}
|
|
1425
|
+
}),
|
|
1426
|
+
new CommunitySource(
|
|
1427
|
+
contentRoot ? () => join4(contentRoot(), "community") : void 0,
|
|
1428
|
+
gezelVersion
|
|
1429
|
+
)
|
|
1430
|
+
];
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* The effective gilde content root, or null when constructed without a
|
|
1434
|
+
* provider (tests, CLI one-shots). Cheap and synchronous — ChatManager
|
|
1435
|
+
* snapshots it per session to detect a live content flip and rebuild
|
|
1436
|
+
* cached model profiles/tuning on the next turn.
|
|
1437
|
+
*/
|
|
1438
|
+
contentRoot() {
|
|
1439
|
+
return this.contentRootProvider ? this.contentRootProvider() : null;
|
|
1440
|
+
}
|
|
1441
|
+
/** All active sources in priority order (higher trust first). */
|
|
1442
|
+
listSources() {
|
|
1443
|
+
return this.sources.map((s) => ({ id: s.id, label: s.label }));
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Merged listing across sources. Items from earlier sources (bundled)
|
|
1447
|
+
* shadow later sources (remote) on id collision.
|
|
1448
|
+
*/
|
|
1449
|
+
async list(kind) {
|
|
1450
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1451
|
+
const merged = [];
|
|
1452
|
+
for (const source of this.sources) {
|
|
1453
|
+
const items = await source.list(kind);
|
|
1454
|
+
for (const item of items) {
|
|
1455
|
+
if (seen.has(item.manifest.id)) continue;
|
|
1456
|
+
seen.add(item.manifest.id);
|
|
1457
|
+
merged.push(item);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
return merged;
|
|
1461
|
+
}
|
|
1462
|
+
async get(kind, id, sourceId, version) {
|
|
1463
|
+
for (const source of this.sources) {
|
|
1464
|
+
if (sourceId && source.id !== sourceId) continue;
|
|
1465
|
+
const detail = await source.get(kind, id, version);
|
|
1466
|
+
if (detail) return detail;
|
|
1467
|
+
}
|
|
1468
|
+
return null;
|
|
1469
|
+
}
|
|
1470
|
+
async listVersions(kind, id, sourceId) {
|
|
1471
|
+
for (const source of this.sources) {
|
|
1472
|
+
if (sourceId && source.id !== sourceId) continue;
|
|
1473
|
+
const versions = await source.listVersions(kind, id);
|
|
1474
|
+
if (versions.length > 0) return versions;
|
|
1475
|
+
}
|
|
1476
|
+
return [];
|
|
1477
|
+
}
|
|
1478
|
+
async readItemFile(kind, id, relPath, sourceId, version) {
|
|
1479
|
+
for (const source of this.sources) {
|
|
1480
|
+
if (sourceId && source.id !== sourceId) continue;
|
|
1481
|
+
const buf = await source.readItemFile(kind, id, relPath, version);
|
|
1482
|
+
if (buf) return buf;
|
|
1483
|
+
}
|
|
1484
|
+
return null;
|
|
1485
|
+
}
|
|
1486
|
+
/** Every file under an item's folder (item-relative paths). For the exporter. */
|
|
1487
|
+
async listItemFiles(kind, id, sourceId) {
|
|
1488
|
+
for (const source of this.sources) {
|
|
1489
|
+
if (sourceId && source.id !== sourceId) continue;
|
|
1490
|
+
if (!source.listItemFiles) continue;
|
|
1491
|
+
const files = await source.listItemFiles(kind, id);
|
|
1492
|
+
if (files.length > 0) return files;
|
|
1493
|
+
}
|
|
1494
|
+
return [];
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* A craftbook's eval descriptor (`test.json` sidecar), from the first
|
|
1498
|
+
* source that carries one. Null when no source ships it.
|
|
1499
|
+
*/
|
|
1500
|
+
async getCraftbookTestSpec(id, version) {
|
|
1501
|
+
for (const source of this.sources) {
|
|
1502
|
+
if (!source.getCraftbookTestSpec) continue;
|
|
1503
|
+
const found = await source.getCraftbookTestSpec(id, version);
|
|
1504
|
+
if (found) return found;
|
|
1505
|
+
}
|
|
1506
|
+
return null;
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
|
|
1510
|
+
// src/categorize.ts
|
|
1511
|
+
var RULES = [
|
|
1512
|
+
{
|
|
1513
|
+
category: "search",
|
|
1514
|
+
keywords: [
|
|
1515
|
+
"search",
|
|
1516
|
+
"scrape",
|
|
1517
|
+
"scraping",
|
|
1518
|
+
"crawl",
|
|
1519
|
+
"crawler",
|
|
1520
|
+
"web fetch",
|
|
1521
|
+
"web search",
|
|
1522
|
+
"lookup",
|
|
1523
|
+
"duckduckgo",
|
|
1524
|
+
"serpapi",
|
|
1525
|
+
"tavily",
|
|
1526
|
+
"brave search",
|
|
1527
|
+
"exa-",
|
|
1528
|
+
"perplexity",
|
|
1529
|
+
"kagi",
|
|
1530
|
+
"searxng"
|
|
1531
|
+
]
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
category: "dev-tools",
|
|
1535
|
+
keywords: [
|
|
1536
|
+
"github",
|
|
1537
|
+
"gitlab",
|
|
1538
|
+
"bitbucket",
|
|
1539
|
+
"sentry",
|
|
1540
|
+
"pull request",
|
|
1541
|
+
"pull-request",
|
|
1542
|
+
"issue tracker",
|
|
1543
|
+
"code review",
|
|
1544
|
+
"lint",
|
|
1545
|
+
"test runner",
|
|
1546
|
+
"devops",
|
|
1547
|
+
"ci/cd",
|
|
1548
|
+
"jenkins",
|
|
1549
|
+
"circleci",
|
|
1550
|
+
"github actions",
|
|
1551
|
+
"pagerduty",
|
|
1552
|
+
"datadog",
|
|
1553
|
+
"logs ",
|
|
1554
|
+
"monitoring",
|
|
1555
|
+
"observability",
|
|
1556
|
+
"apm ",
|
|
1557
|
+
"compiler",
|
|
1558
|
+
"package manager",
|
|
1559
|
+
"npm ",
|
|
1560
|
+
"pypi",
|
|
1561
|
+
" pip ",
|
|
1562
|
+
"docker hub",
|
|
1563
|
+
"container registry",
|
|
1564
|
+
"release notes",
|
|
1565
|
+
"deploy",
|
|
1566
|
+
"deployment",
|
|
1567
|
+
"vercel",
|
|
1568
|
+
"netlify",
|
|
1569
|
+
"fly.io",
|
|
1570
|
+
"render.com",
|
|
1571
|
+
"heroku"
|
|
1572
|
+
]
|
|
1573
|
+
},
|
|
1574
|
+
{
|
|
1575
|
+
category: "data",
|
|
1576
|
+
keywords: [
|
|
1577
|
+
"database",
|
|
1578
|
+
"postgres",
|
|
1579
|
+
"postgresql",
|
|
1580
|
+
"mysql",
|
|
1581
|
+
"mariadb",
|
|
1582
|
+
"sqlite",
|
|
1583
|
+
"mongodb",
|
|
1584
|
+
" mongo ",
|
|
1585
|
+
"redis",
|
|
1586
|
+
"elasticsearch",
|
|
1587
|
+
"opensearch",
|
|
1588
|
+
"clickhouse",
|
|
1589
|
+
"snowflake",
|
|
1590
|
+
"bigquery",
|
|
1591
|
+
"duckdb",
|
|
1592
|
+
"cassandra",
|
|
1593
|
+
"dynamodb",
|
|
1594
|
+
"firestore",
|
|
1595
|
+
"firebase",
|
|
1596
|
+
"supabase",
|
|
1597
|
+
"data warehouse",
|
|
1598
|
+
"data lake",
|
|
1599
|
+
"spreadsheet",
|
|
1600
|
+
"airtable",
|
|
1601
|
+
"data analytics",
|
|
1602
|
+
"metabase",
|
|
1603
|
+
"tableau",
|
|
1604
|
+
"looker",
|
|
1605
|
+
"csv",
|
|
1606
|
+
"excel ",
|
|
1607
|
+
"sql ",
|
|
1608
|
+
"graphql",
|
|
1609
|
+
"data pipeline",
|
|
1610
|
+
"etl "
|
|
1611
|
+
]
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
category: "cloud",
|
|
1615
|
+
keywords: [
|
|
1616
|
+
" aws ",
|
|
1617
|
+
"amazon web services",
|
|
1618
|
+
" s3 ",
|
|
1619
|
+
"ec2",
|
|
1620
|
+
"lambda",
|
|
1621
|
+
" azure ",
|
|
1622
|
+
" gcp ",
|
|
1623
|
+
"google cloud",
|
|
1624
|
+
"cloudflare",
|
|
1625
|
+
"kubernetes",
|
|
1626
|
+
" k8s ",
|
|
1627
|
+
"docker",
|
|
1628
|
+
"containers",
|
|
1629
|
+
"terraform",
|
|
1630
|
+
"pulumi",
|
|
1631
|
+
"helm",
|
|
1632
|
+
"serverless",
|
|
1633
|
+
"cloud run",
|
|
1634
|
+
"fargate",
|
|
1635
|
+
"cloudformation",
|
|
1636
|
+
"digitalocean",
|
|
1637
|
+
"linode"
|
|
1638
|
+
]
|
|
1639
|
+
},
|
|
1640
|
+
{
|
|
1641
|
+
category: "productivity",
|
|
1642
|
+
keywords: [
|
|
1643
|
+
"calendar",
|
|
1644
|
+
"tasks",
|
|
1645
|
+
"todo",
|
|
1646
|
+
" jira",
|
|
1647
|
+
"linear ",
|
|
1648
|
+
" asana",
|
|
1649
|
+
"trello",
|
|
1650
|
+
"clickup",
|
|
1651
|
+
"project management",
|
|
1652
|
+
"task management",
|
|
1653
|
+
"kanban",
|
|
1654
|
+
"monday.com",
|
|
1655
|
+
"shortcut",
|
|
1656
|
+
"pivotaltracker",
|
|
1657
|
+
"basecamp",
|
|
1658
|
+
"time tracking",
|
|
1659
|
+
"pomodoro",
|
|
1660
|
+
"reminder"
|
|
1661
|
+
]
|
|
1662
|
+
},
|
|
1663
|
+
{
|
|
1664
|
+
category: "communication",
|
|
1665
|
+
keywords: [
|
|
1666
|
+
"slack",
|
|
1667
|
+
"discord",
|
|
1668
|
+
" email",
|
|
1669
|
+
"gmail",
|
|
1670
|
+
"outlook",
|
|
1671
|
+
"teams ",
|
|
1672
|
+
"whatsapp",
|
|
1673
|
+
"telegram",
|
|
1674
|
+
"twitter",
|
|
1675
|
+
" x.com",
|
|
1676
|
+
"linkedin",
|
|
1677
|
+
"mastodon",
|
|
1678
|
+
"bluesky",
|
|
1679
|
+
"reddit",
|
|
1680
|
+
"messaging",
|
|
1681
|
+
"chat ",
|
|
1682
|
+
"sms ",
|
|
1683
|
+
"twilio",
|
|
1684
|
+
"mailchimp",
|
|
1685
|
+
"mailgun",
|
|
1686
|
+
"sendgrid",
|
|
1687
|
+
"newsletter",
|
|
1688
|
+
"matrix",
|
|
1689
|
+
"rocket.chat"
|
|
1690
|
+
]
|
|
1691
|
+
},
|
|
1692
|
+
{
|
|
1693
|
+
category: "content",
|
|
1694
|
+
keywords: [
|
|
1695
|
+
"notion",
|
|
1696
|
+
" wiki",
|
|
1697
|
+
"document",
|
|
1698
|
+
"documents",
|
|
1699
|
+
"docx",
|
|
1700
|
+
" pdf",
|
|
1701
|
+
"markdown",
|
|
1702
|
+
"obsidian",
|
|
1703
|
+
"confluence",
|
|
1704
|
+
"cms",
|
|
1705
|
+
"wordpress",
|
|
1706
|
+
"blog",
|
|
1707
|
+
"medium",
|
|
1708
|
+
"substack",
|
|
1709
|
+
"ghost ",
|
|
1710
|
+
"translate",
|
|
1711
|
+
"translation",
|
|
1712
|
+
" rss",
|
|
1713
|
+
"note-taking",
|
|
1714
|
+
"note taking",
|
|
1715
|
+
"evernote",
|
|
1716
|
+
"google docs",
|
|
1717
|
+
"sharepoint",
|
|
1718
|
+
"onedrive",
|
|
1719
|
+
"dropbox",
|
|
1720
|
+
"box.com",
|
|
1721
|
+
"file storage",
|
|
1722
|
+
"pdf parser",
|
|
1723
|
+
"docs "
|
|
1724
|
+
]
|
|
1725
|
+
},
|
|
1726
|
+
{
|
|
1727
|
+
category: "media",
|
|
1728
|
+
keywords: [
|
|
1729
|
+
"image",
|
|
1730
|
+
"video",
|
|
1731
|
+
"audio",
|
|
1732
|
+
"photo",
|
|
1733
|
+
"figma",
|
|
1734
|
+
"sketch",
|
|
1735
|
+
"music",
|
|
1736
|
+
"render",
|
|
1737
|
+
"design",
|
|
1738
|
+
"spotify",
|
|
1739
|
+
"youtube",
|
|
1740
|
+
"vimeo",
|
|
1741
|
+
"soundcloud",
|
|
1742
|
+
"twitch",
|
|
1743
|
+
"streaming",
|
|
1744
|
+
"podcast",
|
|
1745
|
+
"tts ",
|
|
1746
|
+
"text-to-speech",
|
|
1747
|
+
"speech-to-text",
|
|
1748
|
+
"transcribe",
|
|
1749
|
+
"transcription",
|
|
1750
|
+
"whisper",
|
|
1751
|
+
"stable diffusion",
|
|
1752
|
+
"midjourney",
|
|
1753
|
+
"dalle",
|
|
1754
|
+
"dall-e",
|
|
1755
|
+
"flux",
|
|
1756
|
+
"photo editor",
|
|
1757
|
+
"image editor",
|
|
1758
|
+
"image generation",
|
|
1759
|
+
"image generator",
|
|
1760
|
+
"video generation"
|
|
1761
|
+
]
|
|
1762
|
+
},
|
|
1763
|
+
{
|
|
1764
|
+
category: "ai",
|
|
1765
|
+
keywords: [
|
|
1766
|
+
"llm ",
|
|
1767
|
+
"embedding",
|
|
1768
|
+
"embeddings",
|
|
1769
|
+
"vector db",
|
|
1770
|
+
"vector database",
|
|
1771
|
+
"vector store",
|
|
1772
|
+
"pinecone",
|
|
1773
|
+
"weaviate",
|
|
1774
|
+
"qdrant",
|
|
1775
|
+
"chroma db",
|
|
1776
|
+
"chromadb",
|
|
1777
|
+
"milvus",
|
|
1778
|
+
"lancedb",
|
|
1779
|
+
"langchain",
|
|
1780
|
+
"llamaindex",
|
|
1781
|
+
"agentic",
|
|
1782
|
+
"openai api",
|
|
1783
|
+
"anthropic api",
|
|
1784
|
+
" rag ",
|
|
1785
|
+
"retrieval-augmented",
|
|
1786
|
+
"huggingface",
|
|
1787
|
+
"hugging face",
|
|
1788
|
+
"inference engine",
|
|
1789
|
+
"fine-tuning",
|
|
1790
|
+
"fine-tune",
|
|
1791
|
+
"prompt engineering",
|
|
1792
|
+
"prompt library",
|
|
1793
|
+
"agent framework",
|
|
1794
|
+
"agent orchestration",
|
|
1795
|
+
"multi-agent",
|
|
1796
|
+
"ml ops",
|
|
1797
|
+
"mlops"
|
|
1798
|
+
]
|
|
1799
|
+
}
|
|
1800
|
+
];
|
|
1801
|
+
function categorizeToolset(input) {
|
|
1802
|
+
const haystack = [
|
|
1803
|
+
input.name,
|
|
1804
|
+
input.description,
|
|
1805
|
+
input.id,
|
|
1806
|
+
input.tags.join(" "),
|
|
1807
|
+
input.maintainerName ?? ""
|
|
1808
|
+
].join(" ").toLowerCase();
|
|
1809
|
+
for (const rule of RULES) {
|
|
1810
|
+
for (const kw of rule.keywords) {
|
|
1811
|
+
if (haystack.includes(kw)) return rule.category;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
return "other";
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
// src/install/npm-package.ts
|
|
1818
|
+
import { spawn } from "child_process";
|
|
1819
|
+
import { createHash, randomUUID } from "crypto";
|
|
1820
|
+
import { createReadStream } from "fs";
|
|
1821
|
+
import { mkdir, open, readFile as readFile2, realpath, rename, rm, stat, writeFile } from "fs/promises";
|
|
1822
|
+
import { dirname as dirname2, isAbsolute, join as join5, relative, resolve } from "path";
|
|
1823
|
+
import {
|
|
1824
|
+
HttpStatusError,
|
|
1825
|
+
PNPM_HOISTED_NODE_LINKER,
|
|
1826
|
+
resolvePnpmInvocation,
|
|
1827
|
+
retryTransient
|
|
1828
|
+
} from "@bendyline/gezel";
|
|
1829
|
+
import * as tar from "tar";
|
|
1830
|
+
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
|
|
1831
|
+
var MAX_PACKUMENT_BYTES = 5 * 1024 * 1024;
|
|
1832
|
+
var MAX_TARBALL_BYTES = 100 * 1024 * 1024;
|
|
1833
|
+
var MAX_UNPACKED_BYTES = 500 * 1024 * 1024;
|
|
1834
|
+
var MAX_ARCHIVE_ENTRIES = 5e4;
|
|
1835
|
+
var REGISTRY_TIMEOUT_MS = 3e4;
|
|
1836
|
+
var DOWNLOAD_TIMEOUT_MS = 2 * 6e4;
|
|
1837
|
+
var INSTALL_TIMEOUT_MS = 5 * 6e4;
|
|
1838
|
+
var NETWORK_ATTEMPTS = 3;
|
|
1839
|
+
async function installNpmPackageToolset(opts) {
|
|
1840
|
+
const { manifest } = opts;
|
|
1841
|
+
const { package: packageName, version, sha256: expectedHash, entry } = manifest.runtime;
|
|
1842
|
+
const registry = validateRegistry(opts.registry ?? DEFAULT_NPM_REGISTRY);
|
|
1843
|
+
const slug = `${packageName.replace("/", "__")}@${version}`;
|
|
1844
|
+
const target = join5(opts.installRoot, slug);
|
|
1845
|
+
const staging = `${target}.staging-${process.pid}-${randomUUID()}`;
|
|
1846
|
+
const backup = `${target}.previous`;
|
|
1847
|
+
await mkdir(opts.installRoot, { recursive: true });
|
|
1848
|
+
await recoverInterruptedNpmInstall(target, backup);
|
|
1849
|
+
await mkdir(staging, { recursive: true });
|
|
1850
|
+
try {
|
|
1851
|
+
const tarballUrl = await retryTransient(
|
|
1852
|
+
() => resolveTarballUrl(packageName, version, registry),
|
|
1853
|
+
{ attempts: NETWORK_ATTEMPTS, label: `${packageName}@${version} metadata` }
|
|
1854
|
+
);
|
|
1855
|
+
const tarballPath = join5(staging, "package.tgz");
|
|
1856
|
+
const actualHash = await retryTransient(
|
|
1857
|
+
() => downloadTarball(tarballUrl, tarballPath, registry.origin),
|
|
1858
|
+
{ attempts: NETWORK_ATTEMPTS, label: `${packageName}@${version} tarball` }
|
|
1859
|
+
);
|
|
1860
|
+
if (actualHash !== expectedHash) {
|
|
1861
|
+
throw new Error(
|
|
1862
|
+
`[catalog] sha256 mismatch for ${packageName}@${version}: manifest=${expectedHash} actual=${actualHash}. Refusing to install.`
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
await extractNpmPackageTarball({
|
|
1866
|
+
tarballPath,
|
|
1867
|
+
destination: staging,
|
|
1868
|
+
expectedName: packageName,
|
|
1869
|
+
expectedVersion: version
|
|
1870
|
+
});
|
|
1871
|
+
await rm(tarballPath, { force: true });
|
|
1872
|
+
const packageDir = join5(staging, "package");
|
|
1873
|
+
const pnpm = resolvePnpmInvocation(
|
|
1874
|
+
[
|
|
1875
|
+
"install",
|
|
1876
|
+
"--prod",
|
|
1877
|
+
"--ignore-scripts",
|
|
1878
|
+
"--frozen-lockfile=false",
|
|
1879
|
+
PNPM_HOISTED_NODE_LINKER
|
|
1880
|
+
],
|
|
1881
|
+
{
|
|
1882
|
+
pnpmPath: process.env.GEZEL_PNPM_PATH,
|
|
1883
|
+
nodePath: process.env.GEZEL_NODE_PATH,
|
|
1884
|
+
processExecPath: process.execPath,
|
|
1885
|
+
platform: process.platform
|
|
1886
|
+
}
|
|
1887
|
+
);
|
|
1888
|
+
await runAndWait(pnpm.command, pnpm.args, packageDir, pnpm.shell);
|
|
1889
|
+
await resolvePackageEntry(packageDir, entry);
|
|
1890
|
+
await publishStagedNpmInstall(staging, target, backup);
|
|
1891
|
+
return { installPath: join5(target, "package"), tarballSha256: actualHash };
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
await rm(staging, { recursive: true, force: true }).catch(() => {
|
|
1894
|
+
});
|
|
1895
|
+
throw err;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
async function verifyTarballSha256(tarballPath) {
|
|
1899
|
+
const hash = createHash("sha256");
|
|
1900
|
+
for await (const chunk of createReadStream(tarballPath)) hash.update(chunk);
|
|
1901
|
+
return hash.digest("hex");
|
|
1902
|
+
}
|
|
1903
|
+
function validateRegistry(input) {
|
|
1904
|
+
const registry = new URL(input.endsWith("/") ? input : `${input}/`);
|
|
1905
|
+
const loopback = registry.hostname === "127.0.0.1" || registry.hostname === "localhost";
|
|
1906
|
+
if (registry.protocol !== "https:" && !(loopback && registry.protocol === "http:") || registry.username || registry.password) {
|
|
1907
|
+
throw new Error(
|
|
1908
|
+
"[catalog] npm registry must be HTTPS (or loopback HTTP) without URL credentials"
|
|
1909
|
+
);
|
|
1910
|
+
}
|
|
1911
|
+
return registry;
|
|
1912
|
+
}
|
|
1913
|
+
async function resolveTarballUrl(packageName, version, registry) {
|
|
1914
|
+
const url = new URL(encodePackumentPath(packageName), registry);
|
|
1915
|
+
const { response, dispose } = await fetchSameOrigin(url, registry.origin, REGISTRY_TIMEOUT_MS);
|
|
1916
|
+
try {
|
|
1917
|
+
if (!response.ok) {
|
|
1918
|
+
throw new HttpStatusError(
|
|
1919
|
+
response.status,
|
|
1920
|
+
`[catalog] registry returned HTTP ${response.status}`
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
const raw = await readBoundedBody(response, MAX_PACKUMENT_BYTES);
|
|
1924
|
+
let packument;
|
|
1925
|
+
try {
|
|
1926
|
+
packument = JSON.parse(new TextDecoder().decode(raw));
|
|
1927
|
+
} catch {
|
|
1928
|
+
throw new Error("[catalog] registry returned malformed package metadata");
|
|
1929
|
+
}
|
|
1930
|
+
const tarball = packument.versions?.[version]?.dist?.tarball;
|
|
1931
|
+
if (!tarball) throw new Error(`[catalog] version ${version} of ${packageName} was not found`);
|
|
1932
|
+
const parsed = new URL(tarball, registry);
|
|
1933
|
+
assertSameOrigin(parsed, registry.origin);
|
|
1934
|
+
return parsed.href;
|
|
1935
|
+
} finally {
|
|
1936
|
+
dispose();
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
function encodePackumentPath(packageName) {
|
|
1940
|
+
return packageName.startsWith("@") ? packageName.replace("/", "%2f") : packageName;
|
|
1941
|
+
}
|
|
1942
|
+
async function downloadTarball(url, dest, origin) {
|
|
1943
|
+
const { response, dispose } = await fetchSameOrigin(new URL(url), origin, DOWNLOAD_TIMEOUT_MS);
|
|
1944
|
+
const handle = await open(dest, "wx", 384);
|
|
1945
|
+
let total = 0;
|
|
1946
|
+
const hash = createHash("sha256");
|
|
1947
|
+
try {
|
|
1948
|
+
if (!response.ok || !response.body) {
|
|
1949
|
+
throw new HttpStatusError(
|
|
1950
|
+
response.status,
|
|
1951
|
+
`[catalog] tarball download returned HTTP ${response.status}`
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
const declared = Number(response.headers.get("content-length"));
|
|
1955
|
+
if (Number.isFinite(declared) && declared > MAX_TARBALL_BYTES) {
|
|
1956
|
+
throw new Error("[catalog] tarball exceeds the 100 MiB download limit");
|
|
1957
|
+
}
|
|
1958
|
+
const reader = response.body.getReader();
|
|
1959
|
+
try {
|
|
1960
|
+
while (true) {
|
|
1961
|
+
const { done, value } = await reader.read();
|
|
1962
|
+
if (done) break;
|
|
1963
|
+
total += value.byteLength;
|
|
1964
|
+
if (total > MAX_TARBALL_BYTES) {
|
|
1965
|
+
throw new Error("[catalog] tarball exceeds the 100 MiB download limit");
|
|
1966
|
+
}
|
|
1967
|
+
hash.update(value);
|
|
1968
|
+
let offset = 0;
|
|
1969
|
+
while (offset < value.byteLength) {
|
|
1970
|
+
const written = await handle.write(value, offset, value.byteLength - offset);
|
|
1971
|
+
offset += written.bytesWritten;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
} finally {
|
|
1975
|
+
await reader.cancel().catch(() => {
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
return hash.digest("hex");
|
|
1979
|
+
} catch (err) {
|
|
1980
|
+
await rm(dest, { force: true }).catch(() => {
|
|
1981
|
+
});
|
|
1982
|
+
throw err;
|
|
1983
|
+
} finally {
|
|
1984
|
+
dispose();
|
|
1985
|
+
await handle.close();
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
async function extractNpmPackageTarball(opts) {
|
|
1989
|
+
await inspectTarball(opts.tarballPath);
|
|
1990
|
+
await tar.extract({
|
|
1991
|
+
file: opts.tarballPath,
|
|
1992
|
+
cwd: opts.destination,
|
|
1993
|
+
strict: true,
|
|
1994
|
+
preservePaths: false
|
|
1995
|
+
});
|
|
1996
|
+
const packageDir = join5(opts.destination, "package");
|
|
1997
|
+
await validateExtractedPackage(packageDir, opts.expectedName, opts.expectedVersion);
|
|
1998
|
+
return packageDir;
|
|
1999
|
+
}
|
|
2000
|
+
async function inspectTarball(path) {
|
|
2001
|
+
let entries = 0;
|
|
2002
|
+
let unpackedBytes = 0;
|
|
2003
|
+
await tar.list({
|
|
2004
|
+
file: path,
|
|
2005
|
+
strict: true,
|
|
2006
|
+
onentry: (entry) => {
|
|
2007
|
+
entries += 1;
|
|
2008
|
+
unpackedBytes += entry.size;
|
|
2009
|
+
if (entries > MAX_ARCHIVE_ENTRIES || unpackedBytes > MAX_UNPACKED_BYTES) {
|
|
2010
|
+
throw new Error("[catalog] package archive exceeds extraction limits");
|
|
2011
|
+
}
|
|
2012
|
+
validateNpmArchiveEntry(entry.path, entry.type);
|
|
2013
|
+
}
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
function validateNpmArchiveEntry(path, type) {
|
|
2017
|
+
const withoutDot = path.startsWith("./") ? path.slice(2) : path;
|
|
2018
|
+
const clean = type === "Directory" && withoutDot.endsWith("/") ? withoutDot.slice(0, -1) : withoutDot;
|
|
2019
|
+
const parts = clean.split("/");
|
|
2020
|
+
const hasWindowsUnsafeSegment = parts.some((part) => {
|
|
2021
|
+
const stem = part.split(".")[0]?.toUpperCase() ?? "";
|
|
2022
|
+
return part.includes(":") || part.endsWith(" ") || part.endsWith(".") || RESERVED_WINDOWS_BASENAMES.has(stem);
|
|
2023
|
+
});
|
|
2024
|
+
if (clean.includes("\\") || clean.includes("\0") || clean.startsWith("/") || parts[0] !== "package" || parts.some((part) => part === ".." || part === "" || part === ".") || hasWindowsUnsafeSegment || !["File", "Directory"].includes(type)) {
|
|
2025
|
+
throw new Error(`[catalog] unsafe package archive entry: ${path}`);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
async function validateExtractedPackage(packageDir, expectedName, expectedVersion) {
|
|
2029
|
+
const raw = await readFile2(join5(packageDir, "package.json"), "utf8");
|
|
2030
|
+
const parsed = JSON.parse(raw);
|
|
2031
|
+
if (parsed.name !== expectedName || parsed.version !== expectedVersion) {
|
|
2032
|
+
throw new Error("[catalog] extracted package identity does not match the pinned manifest");
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
async function resolvePackageEntry(packageDir, entry) {
|
|
2036
|
+
const root = await realpath(packageDir);
|
|
2037
|
+
const candidate = await realpath(resolve(root, entry));
|
|
2038
|
+
const rel = relative(root, candidate);
|
|
2039
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
2040
|
+
throw new Error("[catalog] package entry escapes the installed package");
|
|
2041
|
+
}
|
|
2042
|
+
if (!(await stat(candidate)).isFile()) throw new Error("[catalog] package entry is not a file");
|
|
2043
|
+
return candidate;
|
|
2044
|
+
}
|
|
2045
|
+
async function readBoundedBody(response, maxBytes) {
|
|
2046
|
+
if (!response.body) throw new Error("[catalog] registry returned an empty response");
|
|
2047
|
+
const declared = Number(response.headers.get("content-length"));
|
|
2048
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
2049
|
+
throw new Error("[catalog] registry metadata response is too large");
|
|
2050
|
+
}
|
|
2051
|
+
const chunks = [];
|
|
2052
|
+
let total = 0;
|
|
2053
|
+
const reader = response.body.getReader();
|
|
2054
|
+
try {
|
|
2055
|
+
while (true) {
|
|
2056
|
+
const { done, value } = await reader.read();
|
|
2057
|
+
if (done) break;
|
|
2058
|
+
total += value.byteLength;
|
|
2059
|
+
if (total > maxBytes) throw new Error("[catalog] registry metadata response is too large");
|
|
2060
|
+
chunks.push(value);
|
|
2061
|
+
}
|
|
2062
|
+
} finally {
|
|
2063
|
+
await reader.cancel().catch(() => {
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
const joined = new Uint8Array(total);
|
|
2067
|
+
let offset = 0;
|
|
2068
|
+
for (const chunk of chunks) {
|
|
2069
|
+
joined.set(chunk, offset);
|
|
2070
|
+
offset += chunk.byteLength;
|
|
2071
|
+
}
|
|
2072
|
+
return joined;
|
|
2073
|
+
}
|
|
2074
|
+
async function fetchSameOrigin(initial, origin, timeoutMs, headers) {
|
|
2075
|
+
let current = initial;
|
|
2076
|
+
for (let redirects = 0; redirects <= 3; redirects++) {
|
|
2077
|
+
assertSameOrigin(current, origin);
|
|
2078
|
+
const controller = new AbortController();
|
|
2079
|
+
const timer = setTimeout(
|
|
2080
|
+
() => controller.abort(new Error("[catalog] registry request timed out")),
|
|
2081
|
+
timeoutMs
|
|
2082
|
+
);
|
|
2083
|
+
try {
|
|
2084
|
+
const response = await fetch(current, {
|
|
2085
|
+
redirect: "manual",
|
|
2086
|
+
signal: controller.signal,
|
|
2087
|
+
...headers ? { headers } : {}
|
|
2088
|
+
});
|
|
2089
|
+
if (response.status >= 300 && response.status < 400) {
|
|
2090
|
+
clearTimeout(timer);
|
|
2091
|
+
const location = response.headers.get("location");
|
|
2092
|
+
if (!location || redirects === 3) throw new Error("[catalog] invalid registry redirect");
|
|
2093
|
+
current = new URL(location, current);
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
return { response, dispose: () => clearTimeout(timer) };
|
|
2097
|
+
} catch (err) {
|
|
2098
|
+
clearTimeout(timer);
|
|
2099
|
+
throw err;
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
throw new Error("[catalog] too many registry redirects");
|
|
2103
|
+
}
|
|
2104
|
+
function assertSameOrigin(url, origin) {
|
|
2105
|
+
if (url.origin !== origin || url.username || url.password) {
|
|
2106
|
+
throw new Error("[catalog] refusing to send registry traffic to an untrusted origin");
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
var RESERVED_WINDOWS_BASENAMES = /* @__PURE__ */ new Set([
|
|
2110
|
+
"CON",
|
|
2111
|
+
"PRN",
|
|
2112
|
+
"AUX",
|
|
2113
|
+
"NUL",
|
|
2114
|
+
...Array.from({ length: 9 }, (_, i) => `COM${i + 1}`),
|
|
2115
|
+
...Array.from({ length: 9 }, (_, i) => `LPT${i + 1}`)
|
|
2116
|
+
]);
|
|
2117
|
+
async function publishStagedNpmInstall(staging, target, backup) {
|
|
2118
|
+
await rm(backup, { recursive: true, force: true });
|
|
2119
|
+
let hadTarget = false;
|
|
2120
|
+
try {
|
|
2121
|
+
await stat(target);
|
|
2122
|
+
hadTarget = true;
|
|
2123
|
+
} catch {
|
|
2124
|
+
}
|
|
2125
|
+
if (hadTarget) await rename(target, backup);
|
|
2126
|
+
try {
|
|
2127
|
+
await rename(staging, target);
|
|
2128
|
+
} catch (err) {
|
|
2129
|
+
if (hadTarget) await rename(backup, target).catch(() => {
|
|
2130
|
+
});
|
|
2131
|
+
throw err;
|
|
2132
|
+
}
|
|
2133
|
+
await rm(backup, { recursive: true, force: true });
|
|
2134
|
+
}
|
|
2135
|
+
async function recoverInterruptedNpmInstall(target, backup) {
|
|
2136
|
+
try {
|
|
2137
|
+
await stat(target);
|
|
2138
|
+
} catch {
|
|
2139
|
+
try {
|
|
2140
|
+
await rename(backup, target);
|
|
2141
|
+
} catch {
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
function runAndWait(command, args, cwd, shell) {
|
|
2146
|
+
return new Promise((resolvePromise, reject) => {
|
|
2147
|
+
const child = spawn(command, args, {
|
|
2148
|
+
cwd,
|
|
2149
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2150
|
+
// DETACHED_PROCESS on Windows / a new process group on POSIX. The
|
|
2151
|
+
// Windows half is load-bearing: the machine service's restricted SID
|
|
2152
|
+
// cannot allocate a console, so a console-subsystem child started any
|
|
2153
|
+
// other way dies as `spawn EPERM`. `windowsHide` (CREATE_NO_WINDOW)
|
|
2154
|
+
// does not help — it still allocates one.
|
|
2155
|
+
detached: true,
|
|
2156
|
+
shell
|
|
2157
|
+
});
|
|
2158
|
+
let output = "";
|
|
2159
|
+
let settled = false;
|
|
2160
|
+
const capture = (chunk) => {
|
|
2161
|
+
if (output.length < 32e3) output += chunk.toString("utf8").slice(0, 32e3 - output.length);
|
|
2162
|
+
};
|
|
2163
|
+
child.stdout?.on("data", capture);
|
|
2164
|
+
child.stderr?.on("data", capture);
|
|
2165
|
+
const timer = setTimeout(() => {
|
|
2166
|
+
killProcessTree(child);
|
|
2167
|
+
finish(new Error(`[catalog] dependency install exceeded ${INSTALL_TIMEOUT_MS}ms`));
|
|
2168
|
+
}, INSTALL_TIMEOUT_MS);
|
|
2169
|
+
timer.unref?.();
|
|
2170
|
+
const finish = (error) => {
|
|
2171
|
+
if (settled) return;
|
|
2172
|
+
settled = true;
|
|
2173
|
+
clearTimeout(timer);
|
|
2174
|
+
if (error) reject(error);
|
|
2175
|
+
else resolvePromise();
|
|
2176
|
+
};
|
|
2177
|
+
child.on("error", (err) => finish(err));
|
|
2178
|
+
child.on(
|
|
2179
|
+
"close",
|
|
2180
|
+
(code) => finish(
|
|
2181
|
+
code === 0 ? void 0 : new Error(`[catalog] dependency install failed (exit ${code}): ${output.trim()}`)
|
|
2182
|
+
)
|
|
2183
|
+
);
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
2186
|
+
function killProcessTree(child) {
|
|
2187
|
+
if (process.platform === "win32" && child.pid) {
|
|
2188
|
+
const killer = spawn("taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], {
|
|
2189
|
+
detached: true,
|
|
2190
|
+
stdio: "ignore"
|
|
2191
|
+
});
|
|
2192
|
+
killer.once("error", () => child.kill("SIGKILL"));
|
|
2193
|
+
killer.unref();
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
if (child.pid) {
|
|
2197
|
+
try {
|
|
2198
|
+
process.kill(-child.pid, "SIGKILL");
|
|
2199
|
+
return;
|
|
2200
|
+
} catch {
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
child.kill("SIGKILL");
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// src/live/gilde-npm.ts
|
|
2207
|
+
import { createHash as createHash2 } from "crypto";
|
|
2208
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
2209
|
+
import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
|
|
2210
|
+
import { join as join6 } from "path";
|
|
2211
|
+
import { HttpStatusError as HttpStatusError2, retryTransient as retryTransient2 } from "@bendyline/gezel";
|
|
2212
|
+
var GILDE_PACKAGE_NAME = "@bendyline/gilde";
|
|
2213
|
+
var MAX_PACKUMENT_BYTES2 = 5 * 1024 * 1024;
|
|
2214
|
+
var REGISTRY_TIMEOUT_MS2 = 3e4;
|
|
2215
|
+
var NETWORK_ATTEMPTS2 = 3;
|
|
2216
|
+
var PACKUMENT_ACCEPT = "application/vnd.npm.install-v1+json, application/json;q=0.9";
|
|
2217
|
+
var RELEASE_VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
2218
|
+
async function fetchGildeReleases(opts = {}) {
|
|
2219
|
+
const registry = validateRegistry(opts.registry ?? DEFAULT_NPM_REGISTRY);
|
|
2220
|
+
return retryTransient2(() => fetchReleasesOnce(registry), {
|
|
2221
|
+
attempts: NETWORK_ATTEMPTS2,
|
|
2222
|
+
label: `${GILDE_PACKAGE_NAME} metadata`
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
async function fetchReleasesOnce(registry) {
|
|
2226
|
+
const url = new URL(encodePackumentPath(GILDE_PACKAGE_NAME), registry);
|
|
2227
|
+
const { response, dispose } = await fetchSameOrigin(url, registry.origin, REGISTRY_TIMEOUT_MS2, {
|
|
2228
|
+
accept: PACKUMENT_ACCEPT
|
|
2229
|
+
});
|
|
2230
|
+
try {
|
|
2231
|
+
if (!response.ok) {
|
|
2232
|
+
throw new HttpStatusError2(
|
|
2233
|
+
response.status,
|
|
2234
|
+
`[catalog] registry returned HTTP ${response.status}`
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
const raw = await readBoundedBody(response, MAX_PACKUMENT_BYTES2);
|
|
2238
|
+
let packument;
|
|
2239
|
+
try {
|
|
2240
|
+
packument = JSON.parse(new TextDecoder().decode(raw));
|
|
2241
|
+
} catch {
|
|
2242
|
+
throw new Error("[catalog] registry returned malformed package metadata");
|
|
2243
|
+
}
|
|
2244
|
+
const releases = [];
|
|
2245
|
+
for (const [version, info] of Object.entries(packument.versions ?? {})) {
|
|
2246
|
+
if (!RELEASE_VERSION_RE.test(version)) continue;
|
|
2247
|
+
const tarball = info?.dist?.tarball;
|
|
2248
|
+
if (typeof tarball !== "string" || tarball.length === 0) continue;
|
|
2249
|
+
let parsed;
|
|
2250
|
+
try {
|
|
2251
|
+
parsed = new URL(tarball, registry);
|
|
2252
|
+
} catch {
|
|
2253
|
+
continue;
|
|
2254
|
+
}
|
|
2255
|
+
if (parsed.origin !== registry.origin) continue;
|
|
2256
|
+
releases.push({
|
|
2257
|
+
version,
|
|
2258
|
+
tarballUrl: parsed.href,
|
|
2259
|
+
integrity: typeof info?.dist?.integrity === "string" ? info.dist.integrity : void 0,
|
|
2260
|
+
shasum: typeof info?.dist?.shasum === "string" ? info.dist.shasum : void 0
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
return releases;
|
|
2264
|
+
} finally {
|
|
2265
|
+
dispose();
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
function pickGildePatchUpdate(releases, bundledPin, currentLive) {
|
|
2269
|
+
if (!RELEASE_VERSION_RE.test(bundledPin)) return null;
|
|
2270
|
+
const [pinMajor, pinMinor] = bundledPin.split(".");
|
|
2271
|
+
const floor = currentLive && RELEASE_VERSION_RE.test(currentLive) && compareRelease(currentLive, bundledPin) > 0 ? currentLive : bundledPin;
|
|
2272
|
+
let best = null;
|
|
2273
|
+
for (const release of releases) {
|
|
2274
|
+
if (!RELEASE_VERSION_RE.test(release.version)) continue;
|
|
2275
|
+
const [major, minor] = release.version.split(".");
|
|
2276
|
+
if (major !== pinMajor || minor !== pinMinor) continue;
|
|
2277
|
+
if (compareRelease(release.version, floor) <= 0) continue;
|
|
2278
|
+
if (!best || compareRelease(release.version, best.version) > 0) best = release;
|
|
2279
|
+
}
|
|
2280
|
+
return best;
|
|
2281
|
+
}
|
|
2282
|
+
function compareRelease(a, b) {
|
|
2283
|
+
const pa = a.split(".").map((n) => Number.parseInt(n, 10));
|
|
2284
|
+
const pb = b.split(".").map((n) => Number.parseInt(n, 10));
|
|
2285
|
+
for (let i = 0; i < 3; i++) {
|
|
2286
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
2287
|
+
if (diff !== 0) return diff;
|
|
2288
|
+
}
|
|
2289
|
+
return 0;
|
|
2290
|
+
}
|
|
2291
|
+
function isGildeReleaseVersion(v) {
|
|
2292
|
+
return RELEASE_VERSION_RE.test(v);
|
|
2293
|
+
}
|
|
2294
|
+
async function stageGildeVersion(opts) {
|
|
2295
|
+
const registry = validateRegistry(opts.registry ?? DEFAULT_NPM_REGISTRY);
|
|
2296
|
+
const { release } = opts;
|
|
2297
|
+
assertVerifiable(release);
|
|
2298
|
+
await mkdir2(opts.stagingDir, { recursive: true });
|
|
2299
|
+
const tarballPath = join6(opts.stagingDir, "package.tgz");
|
|
2300
|
+
try {
|
|
2301
|
+
await retryTransient2(() => downloadTarball(release.tarballUrl, tarballPath, registry.origin), {
|
|
2302
|
+
attempts: NETWORK_ATTEMPTS2,
|
|
2303
|
+
label: `${GILDE_PACKAGE_NAME}@${release.version} tarball`
|
|
2304
|
+
});
|
|
2305
|
+
await verifyReleaseIntegrity(tarballPath, release);
|
|
2306
|
+
const packageDir = await extractNpmPackageTarball({
|
|
2307
|
+
tarballPath,
|
|
2308
|
+
destination: opts.stagingDir,
|
|
2309
|
+
expectedName: GILDE_PACKAGE_NAME,
|
|
2310
|
+
expectedVersion: release.version
|
|
2311
|
+
});
|
|
2312
|
+
return { packageDir };
|
|
2313
|
+
} finally {
|
|
2314
|
+
await rm2(tarballPath, { force: true }).catch(() => {
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
function assertVerifiable(release) {
|
|
2319
|
+
if (parseSha512Sri(release.integrity)) return;
|
|
2320
|
+
if (release.shasum && /^[0-9a-f]{40}$/i.test(release.shasum)) return;
|
|
2321
|
+
throw new Error(
|
|
2322
|
+
`[catalog] registry metadata for ${GILDE_PACKAGE_NAME}@${release.version} carries no usable integrity hash; refusing to download`
|
|
2323
|
+
);
|
|
2324
|
+
}
|
|
2325
|
+
async function verifyReleaseIntegrity(tarballPath, release) {
|
|
2326
|
+
const sri = parseSha512Sri(release.integrity);
|
|
2327
|
+
if (sri) {
|
|
2328
|
+
const actual = await hashFile(tarballPath, "sha512", "base64");
|
|
2329
|
+
if (actual !== sri) {
|
|
2330
|
+
throw new Error(
|
|
2331
|
+
`[catalog] sha512 integrity mismatch for ${GILDE_PACKAGE_NAME}@${release.version}; refusing to install`
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
const shasum = release.shasum?.toLowerCase();
|
|
2337
|
+
if (shasum && /^[0-9a-f]{40}$/.test(shasum)) {
|
|
2338
|
+
const actual = await hashFile(tarballPath, "sha1", "hex");
|
|
2339
|
+
if (actual !== shasum) {
|
|
2340
|
+
throw new Error(
|
|
2341
|
+
`[catalog] sha1 shasum mismatch for ${GILDE_PACKAGE_NAME}@${release.version}; refusing to install`
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
throw new Error(
|
|
2347
|
+
`[catalog] registry metadata for ${GILDE_PACKAGE_NAME}@${release.version} carries no usable integrity hash; refusing to install`
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
function parseSha512Sri(integrity) {
|
|
2351
|
+
if (!integrity) return null;
|
|
2352
|
+
for (const entry of integrity.trim().split(/\s+/)) {
|
|
2353
|
+
if (entry.startsWith("sha512-") && entry.length > "sha512-".length) {
|
|
2354
|
+
return entry.slice("sha512-".length);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
return null;
|
|
2358
|
+
}
|
|
2359
|
+
async function hashFile(path, algorithm, encoding) {
|
|
2360
|
+
const hash = createHash2(algorithm);
|
|
2361
|
+
for await (const chunk of createReadStream2(path)) hash.update(chunk);
|
|
2362
|
+
return hash.digest(encoding);
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// src/live/validate.ts
|
|
2366
|
+
import { join as join7 } from "path";
|
|
2367
|
+
var MAX_REPORTED_REGRESSIONS = 25;
|
|
2368
|
+
async function validateGildeContentUpgrade(opts) {
|
|
2369
|
+
const gezelVersion = opts.gezelVersion;
|
|
2370
|
+
const versionOpt = gezelVersion !== void 0 ? { gezelVersion } : {};
|
|
2371
|
+
const currentBundled = new BundledSource({ dataDir: opts.currentDataDir, ...versionOpt });
|
|
2372
|
+
const current = new CatalogService([
|
|
2373
|
+
currentBundled,
|
|
2374
|
+
new CommunitySource(join7(opts.currentDataDir, "community"), gezelVersion)
|
|
2375
|
+
]);
|
|
2376
|
+
const candidate = new CatalogService([
|
|
2377
|
+
new BundledSource({ dataDir: opts.candidateDataDir, ...versionOpt }),
|
|
2378
|
+
new CommunitySource(join7(opts.candidateDataDir, "community"), gezelVersion)
|
|
2379
|
+
]);
|
|
2380
|
+
const kinds = await currentBundled.listKinds();
|
|
2381
|
+
const regressions = [];
|
|
2382
|
+
let checked = 0;
|
|
2383
|
+
for (const kind of kinds) {
|
|
2384
|
+
const items = await current.list(kind);
|
|
2385
|
+
for (const item of items) {
|
|
2386
|
+
checked += 1;
|
|
2387
|
+
const resolved = await candidate.get(kind, item.manifest.id).catch(() => null);
|
|
2388
|
+
if (!resolved) {
|
|
2389
|
+
regressions.push({ kind, id: item.manifest.id });
|
|
2390
|
+
if (regressions.length >= MAX_REPORTED_REGRESSIONS) {
|
|
2391
|
+
return { ok: false, regressions };
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
if (regressions.length > 0) return { ok: false, regressions };
|
|
2397
|
+
return { ok: true, checked };
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// src/hf-api.ts
|
|
2401
|
+
var HF_HUB_BASE = "https://huggingface.co";
|
|
2402
|
+
async function fetchHuggingfaceTree(repo, opts = {}) {
|
|
2403
|
+
const rev = opts.rev ?? "main";
|
|
2404
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
2405
|
+
const out = [];
|
|
2406
|
+
await walk(repo, rev, "", out, fetchImpl);
|
|
2407
|
+
return out;
|
|
2408
|
+
}
|
|
2409
|
+
async function fetchHuggingfaceCommit(repo, opts = {}) {
|
|
2410
|
+
const rev = opts.rev ?? "main";
|
|
2411
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
2412
|
+
const url = `${HF_HUB_BASE}/api/models/${repo}/revision/${encodeURIComponent(rev)}`;
|
|
2413
|
+
const res = await fetchImpl(url);
|
|
2414
|
+
if (!res.ok) {
|
|
2415
|
+
throw new Error(`[hf] commit resolve ${url} failed: ${res.status} ${res.statusText}`);
|
|
2416
|
+
}
|
|
2417
|
+
const body = await res.json();
|
|
2418
|
+
if (!body.sha || !/^[0-9a-f]{40}$/.test(body.sha)) {
|
|
2419
|
+
throw new Error(`[hf] commit resolve ${url} returned no valid sha (got ${String(body.sha)})`);
|
|
2420
|
+
}
|
|
2421
|
+
return body.sha;
|
|
2422
|
+
}
|
|
2423
|
+
async function walk(repo, rev, subpath, out, fetchImpl) {
|
|
2424
|
+
const url = `${HF_HUB_BASE}/api/models/${repo}/tree/${rev}${subpath ? `/${subpath}` : ""}`;
|
|
2425
|
+
const res = await fetchImpl(url);
|
|
2426
|
+
if (!res.ok) {
|
|
2427
|
+
throw new Error(`[hf] tree fetch ${url} failed: ${res.status} ${res.statusText}`);
|
|
2428
|
+
}
|
|
2429
|
+
const entries = await res.json();
|
|
2430
|
+
for (const e of entries) {
|
|
2431
|
+
if (e.type === "directory") {
|
|
2432
|
+
await walk(repo, rev, e.path, out, fetchImpl);
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
if (e.type !== "file") continue;
|
|
2436
|
+
const lfsOid = e.lfs?.oid;
|
|
2437
|
+
const sha256 = lfsOid ?? e.oid ?? "";
|
|
2438
|
+
if (!sha256) continue;
|
|
2439
|
+
out.push({
|
|
2440
|
+
path: e.path,
|
|
2441
|
+
sizeBytes: e.size ?? 0,
|
|
2442
|
+
sha256,
|
|
2443
|
+
lfsBacked: Boolean(lfsOid)
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
function totalSize(files) {
|
|
2448
|
+
return files.reduce((sum, f) => sum + f.sizeBytes, 0);
|
|
2449
|
+
}
|
|
2450
|
+
function selectMlxInstallFiles(files) {
|
|
2451
|
+
const KEEP = (path) => {
|
|
2452
|
+
if (/\.safetensors(\.index\.json)?$/.test(path)) return true;
|
|
2453
|
+
if (/^(?:config|generation_config|preprocessor_config|processor_config|video_preprocessor_config|tokenizer_config|special_tokens_map)\.json$/.test(
|
|
2454
|
+
path
|
|
2455
|
+
))
|
|
2456
|
+
return true;
|
|
2457
|
+
if (path === "tokenizer.json") return true;
|
|
2458
|
+
if (path === "tokenizer.model") return true;
|
|
2459
|
+
if (path === "vocab.json") return true;
|
|
2460
|
+
if (path === "merges.txt") return true;
|
|
2461
|
+
if (path === "chat_template.jinja") return true;
|
|
2462
|
+
return false;
|
|
2463
|
+
};
|
|
2464
|
+
return files.filter((f) => KEEP(f.path));
|
|
2465
|
+
}
|
|
2466
|
+
export {
|
|
2467
|
+
BUILTIN_TOOLSETS,
|
|
2468
|
+
BUILTIN_TOOL_TO_GROUP,
|
|
2469
|
+
BuiltinToolsetsSource,
|
|
2470
|
+
BundledSource,
|
|
2471
|
+
CatalogService,
|
|
2472
|
+
CommunitySource,
|
|
2473
|
+
GILDE_PACKAGE_NAME,
|
|
2474
|
+
LocalCatalogSource,
|
|
2475
|
+
builtinCatalogId,
|
|
2476
|
+
categorizeToolset,
|
|
2477
|
+
compareRelease,
|
|
2478
|
+
extractNpmPackageTarball,
|
|
2479
|
+
fetchGildeReleases,
|
|
2480
|
+
fetchHuggingfaceCommit,
|
|
2481
|
+
fetchHuggingfaceTree,
|
|
2482
|
+
getBuiltinToolset,
|
|
2483
|
+
gildeDataDir,
|
|
2484
|
+
gildePackageRoot,
|
|
2485
|
+
installNpmPackageToolset,
|
|
2486
|
+
isGildeReleaseVersion,
|
|
2487
|
+
pickGildePatchUpdate,
|
|
2488
|
+
publishStagedNpmInstall,
|
|
2489
|
+
recoverInterruptedNpmInstall,
|
|
2490
|
+
selectMlxInstallFiles,
|
|
2491
|
+
stageGildeVersion,
|
|
2492
|
+
totalSize,
|
|
2493
|
+
validateGildeContentUpgrade,
|
|
2494
|
+
validateNpmArchiveEntry,
|
|
2495
|
+
verifyTarballSha256
|
|
2496
|
+
};
|