@wingman-ai/gateway 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -111
- package/dist/agent/config/agentConfig.cjs +2 -0
- package/dist/agent/config/agentConfig.d.ts +6 -0
- package/dist/agent/config/agentConfig.js +2 -0
- package/dist/agent/config/agentLoader.cjs +21 -18
- package/dist/agent/config/agentLoader.js +22 -19
- package/dist/agent/config/toolRegistry.cjs +19 -0
- package/dist/agent/config/toolRegistry.d.ts +4 -0
- package/dist/agent/config/toolRegistry.js +17 -1
- package/dist/agent/middleware/additional-messages.cjs +115 -11
- package/dist/agent/middleware/additional-messages.d.ts +9 -0
- package/dist/agent/middleware/additional-messages.js +115 -11
- package/dist/agent/tests/agentLoader.test.cjs +45 -0
- package/dist/agent/tests/agentLoader.test.js +45 -0
- package/dist/agent/tests/toolRegistry.test.cjs +2 -0
- package/dist/agent/tests/toolRegistry.test.js +2 -0
- package/dist/agent/tools/node_invoke.cjs +146 -0
- package/dist/agent/tools/node_invoke.d.ts +86 -0
- package/dist/agent/tools/node_invoke.js +109 -0
- package/dist/cli/commands/gateway.cjs +1 -1
- package/dist/cli/commands/gateway.js +1 -1
- package/dist/cli/commands/init.cjs +135 -1
- package/dist/cli/commands/init.js +136 -2
- package/dist/cli/commands/skill.cjs +7 -3
- package/dist/cli/commands/skill.js +7 -3
- package/dist/cli/config/loader.cjs +7 -3
- package/dist/cli/config/loader.js +7 -3
- package/dist/cli/config/schema.cjs +27 -9
- package/dist/cli/config/schema.d.ts +18 -4
- package/dist/cli/config/schema.js +23 -8
- package/dist/cli/core/agentInvoker.cjs +70 -14
- package/dist/cli/core/agentInvoker.d.ts +10 -0
- package/dist/cli/core/agentInvoker.js +70 -14
- package/dist/cli/services/skillRepository.cjs +155 -69
- package/dist/cli/services/skillRepository.d.ts +7 -2
- package/dist/cli/services/skillRepository.js +155 -69
- package/dist/cli/services/skillService.cjs +93 -26
- package/dist/cli/services/skillService.d.ts +7 -0
- package/dist/cli/services/skillService.js +96 -29
- package/dist/cli/types/skill.d.ts +8 -3
- package/dist/gateway/http/nodes.cjs +247 -0
- package/dist/gateway/http/nodes.d.ts +20 -0
- package/dist/gateway/http/nodes.js +210 -0
- package/dist/gateway/node.cjs +10 -1
- package/dist/gateway/node.d.ts +10 -1
- package/dist/gateway/node.js +10 -1
- package/dist/gateway/server.cjs +414 -27
- package/dist/gateway/server.d.ts +34 -0
- package/dist/gateway/server.js +408 -27
- package/dist/gateway/types.d.ts +6 -1
- package/dist/gateway/validation.cjs +2 -0
- package/dist/gateway/validation.d.ts +4 -0
- package/dist/gateway/validation.js +2 -0
- package/dist/skills/activation.cjs +92 -0
- package/dist/skills/activation.d.ts +12 -0
- package/dist/skills/activation.js +58 -0
- package/dist/skills/bin-requirements.cjs +63 -0
- package/dist/skills/bin-requirements.d.ts +3 -0
- package/dist/skills/bin-requirements.js +26 -0
- package/dist/skills/metadata.cjs +141 -0
- package/dist/skills/metadata.d.ts +29 -0
- package/dist/skills/metadata.js +104 -0
- package/dist/skills/overlay.cjs +75 -0
- package/dist/skills/overlay.d.ts +2 -0
- package/dist/skills/overlay.js +38 -0
- package/dist/tests/additionalMessageMiddleware.test.cjs +92 -0
- package/dist/tests/additionalMessageMiddleware.test.js +92 -0
- package/dist/tests/cli-config-loader.test.cjs +7 -3
- package/dist/tests/cli-config-loader.test.js +7 -3
- package/dist/tests/cli-init.test.cjs +54 -0
- package/dist/tests/cli-init.test.js +54 -0
- package/dist/tests/config-json-schema.test.cjs +12 -0
- package/dist/tests/config-json-schema.test.js +12 -0
- package/dist/tests/gateway-http-security.test.cjs +277 -0
- package/dist/tests/gateway-http-security.test.d.ts +1 -0
- package/dist/tests/gateway-http-security.test.js +271 -0
- package/dist/tests/gateway-node-mode.test.cjs +174 -0
- package/dist/tests/gateway-node-mode.test.d.ts +1 -0
- package/dist/tests/gateway-node-mode.test.js +168 -0
- package/dist/tests/gateway-origin-policy.test.cjs +60 -0
- package/dist/tests/gateway-origin-policy.test.d.ts +1 -0
- package/dist/tests/gateway-origin-policy.test.js +54 -0
- package/dist/tests/gateway.test.cjs +1 -0
- package/dist/tests/gateway.test.js +1 -0
- package/dist/tests/node-tools.test.cjs +77 -0
- package/dist/tests/node-tools.test.d.ts +1 -0
- package/dist/tests/node-tools.test.js +71 -0
- package/dist/tests/nodes-api.test.cjs +86 -0
- package/dist/tests/nodes-api.test.d.ts +1 -0
- package/dist/tests/nodes-api.test.js +80 -0
- package/dist/tests/skill-activation.test.cjs +86 -0
- package/dist/tests/skill-activation.test.d.ts +1 -0
- package/dist/tests/skill-activation.test.js +80 -0
- package/dist/tests/skill-metadata.test.cjs +119 -0
- package/dist/tests/skill-metadata.test.d.ts +1 -0
- package/dist/tests/skill-metadata.test.js +113 -0
- package/dist/tests/skill-repository.test.cjs +363 -0
- package/dist/tests/skill-repository.test.js +363 -0
- package/dist/webui/assets/{index-DHbfLOUR.js → index-BMekSELC.js} +106 -106
- package/dist/webui/index.html +1 -1
- package/package.json +4 -4
- package/skills/gog/SKILL.md +1 -1
- package/skills/weather/SKILL.md +1 -1
- package/skills/ui-registry/SKILL.md +0 -35
|
@@ -98,3 +98,366 @@ describe("SkillRepository clawhub provider", ()=>{
|
|
|
98
98
|
expect(fetchMock).toHaveBeenCalledTimes(4);
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
|
+
describe("SkillRepository github provider", ()=>{
|
|
102
|
+
const originalFetch = globalThis.fetch;
|
|
103
|
+
afterEach(()=>{
|
|
104
|
+
globalThis.fetch = originalFetch;
|
|
105
|
+
vi.restoreAllMocks();
|
|
106
|
+
});
|
|
107
|
+
it("merges skills across repositories with later sources overriding earlier ones", async ()=>{
|
|
108
|
+
const encodeSkill = (name, description)=>Buffer.from(`---\nname: ${name}\ndescription: ${description}\n---\n`, "utf-8").toString("base64");
|
|
109
|
+
const fetchMock = vi.fn(async (input)=>{
|
|
110
|
+
const url = input.toString();
|
|
111
|
+
if (url.endsWith("/repos/example-org/community-skills/contents/skills")) return new Response(JSON.stringify([
|
|
112
|
+
{
|
|
113
|
+
name: "gog",
|
|
114
|
+
path: "skills/gog",
|
|
115
|
+
type: "dir",
|
|
116
|
+
url: "https://api.github.com/repos/example-org/community-skills/contents/skills/gog"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "alpha",
|
|
120
|
+
path: "skills/alpha",
|
|
121
|
+
type: "dir",
|
|
122
|
+
url: "https://api.github.com/repos/example-org/community-skills/contents/skills/alpha"
|
|
123
|
+
}
|
|
124
|
+
]), {
|
|
125
|
+
status: 200
|
|
126
|
+
});
|
|
127
|
+
if (url.endsWith("/repos/example-team/custom-skills/contents/skills")) return new Response(JSON.stringify([
|
|
128
|
+
{
|
|
129
|
+
name: "alpha",
|
|
130
|
+
path: "skills/alpha",
|
|
131
|
+
type: "dir",
|
|
132
|
+
url: "https://api.github.com/repos/example-team/custom-skills/contents/skills/alpha"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "wingman-special",
|
|
136
|
+
path: "skills/wingman-special",
|
|
137
|
+
type: "dir",
|
|
138
|
+
url: "https://api.github.com/repos/example-team/custom-skills/contents/skills/wingman-special"
|
|
139
|
+
}
|
|
140
|
+
]), {
|
|
141
|
+
status: 200
|
|
142
|
+
});
|
|
143
|
+
if (url.endsWith("/repos/example-org/community-skills/contents/skills/gog/SKILL.md")) return new Response(JSON.stringify({
|
|
144
|
+
type: "file",
|
|
145
|
+
content: encodeSkill("gog", "Community gog skill")
|
|
146
|
+
}), {
|
|
147
|
+
status: 200
|
|
148
|
+
});
|
|
149
|
+
if (url.endsWith("/repos/example-org/community-skills/contents/skills/alpha/SKILL.md")) return new Response(JSON.stringify({
|
|
150
|
+
type: "file",
|
|
151
|
+
content: encodeSkill("alpha", "Community alpha skill")
|
|
152
|
+
}), {
|
|
153
|
+
status: 200
|
|
154
|
+
});
|
|
155
|
+
if (url.endsWith("/repos/example-team/custom-skills/contents/skills/alpha/SKILL.md")) return new Response(JSON.stringify({
|
|
156
|
+
type: "file",
|
|
157
|
+
content: encodeSkill("alpha", "Custom alpha override")
|
|
158
|
+
}), {
|
|
159
|
+
status: 200
|
|
160
|
+
});
|
|
161
|
+
if (url.endsWith("/repos/example-team/custom-skills/contents/skills/wingman-special/SKILL.md")) return new Response(JSON.stringify({
|
|
162
|
+
type: "file",
|
|
163
|
+
content: encodeSkill("wingman-special", "Custom-only skill")
|
|
164
|
+
}), {
|
|
165
|
+
status: 200
|
|
166
|
+
});
|
|
167
|
+
return new Response("Not Found", {
|
|
168
|
+
status: 404
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
globalThis.fetch = fetchMock;
|
|
172
|
+
const repository = new SkillRepository({
|
|
173
|
+
provider: "github",
|
|
174
|
+
repositories: [
|
|
175
|
+
{
|
|
176
|
+
owner: "example-org",
|
|
177
|
+
name: "community-skills"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
owner: "example-team",
|
|
181
|
+
name: "custom-skills"
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
});
|
|
185
|
+
const skills = await repository.listAvailableSkills();
|
|
186
|
+
expect(skills).toEqual([
|
|
187
|
+
{
|
|
188
|
+
name: "gog",
|
|
189
|
+
description: "Community gog skill",
|
|
190
|
+
path: "skills/gog",
|
|
191
|
+
metadata: {
|
|
192
|
+
name: "gog",
|
|
193
|
+
description: "Community gog skill"
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "alpha",
|
|
198
|
+
description: "Custom alpha override",
|
|
199
|
+
path: "skills/alpha",
|
|
200
|
+
metadata: {
|
|
201
|
+
name: "alpha",
|
|
202
|
+
description: "Custom alpha override"
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "wingman-special",
|
|
207
|
+
description: "Custom-only skill",
|
|
208
|
+
path: "skills/wingman-special",
|
|
209
|
+
metadata: {
|
|
210
|
+
name: "wingman-special",
|
|
211
|
+
description: "Custom-only skill"
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
]);
|
|
215
|
+
});
|
|
216
|
+
it("downloads a skill from the highest-priority matching repository", async ()=>{
|
|
217
|
+
const encodedSkill = Buffer.from("---\nname: alpha\ndescription: Custom alpha override\n---\n", "utf-8").toString("base64");
|
|
218
|
+
const encodedExample = Buffer.from("# Custom Example\n", "utf-8").toString("base64");
|
|
219
|
+
const fetchMock = vi.fn(async (input)=>{
|
|
220
|
+
const url = input.toString();
|
|
221
|
+
if (url.endsWith("/repos/example-team/custom-skills/contents/skills/alpha/SKILL.md")) return new Response(JSON.stringify({
|
|
222
|
+
type: "file",
|
|
223
|
+
content: encodedSkill,
|
|
224
|
+
encoding: "base64"
|
|
225
|
+
}), {
|
|
226
|
+
status: 200
|
|
227
|
+
});
|
|
228
|
+
if (url.endsWith("/repos/example-team/custom-skills/contents/skills/alpha")) return new Response(JSON.stringify([
|
|
229
|
+
{
|
|
230
|
+
type: "file",
|
|
231
|
+
name: "SKILL.md",
|
|
232
|
+
path: "skills/alpha/SKILL.md",
|
|
233
|
+
content: encodedSkill,
|
|
234
|
+
encoding: "base64"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
type: "file",
|
|
238
|
+
name: "examples.md",
|
|
239
|
+
path: "skills/alpha/examples.md",
|
|
240
|
+
content: encodedExample,
|
|
241
|
+
encoding: "base64"
|
|
242
|
+
}
|
|
243
|
+
]), {
|
|
244
|
+
status: 200
|
|
245
|
+
});
|
|
246
|
+
return new Response("Not Found", {
|
|
247
|
+
status: 404
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
globalThis.fetch = fetchMock;
|
|
251
|
+
const repository = new SkillRepository({
|
|
252
|
+
provider: "github",
|
|
253
|
+
repositories: [
|
|
254
|
+
{
|
|
255
|
+
owner: "example-org",
|
|
256
|
+
name: "community-skills"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
owner: "example-team",
|
|
260
|
+
name: "custom-skills"
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
});
|
|
264
|
+
const files = await repository.downloadSkill("alpha");
|
|
265
|
+
expect(files.size).toBe(2);
|
|
266
|
+
expect(files.get("SKILL.md")?.toString("utf-8")).toContain("Custom alpha override");
|
|
267
|
+
expect(files.get("examples.md")?.toString("utf-8")).toContain("Custom Example");
|
|
268
|
+
const requestedUrls = fetchMock.mock.calls.map((call)=>call[0].toString());
|
|
269
|
+
expect(requestedUrls.some((url)=>url.includes("/repos/example-org/community-skills/contents/skills/alpha"))).toBe(false);
|
|
270
|
+
});
|
|
271
|
+
it("uses legacy repositoryOwner/repositoryName when repositories are not provided", async ()=>{
|
|
272
|
+
const fetchMock = vi.fn(async (input)=>{
|
|
273
|
+
const url = input.toString();
|
|
274
|
+
if (url.endsWith("/repos/myorg/myskills/contents/skills")) return new Response(JSON.stringify([
|
|
275
|
+
{
|
|
276
|
+
name: "legacy-skill",
|
|
277
|
+
path: "skills/legacy-skill",
|
|
278
|
+
type: "dir",
|
|
279
|
+
url: "https://api.github.com/repos/myorg/myskills/contents/skills/legacy-skill"
|
|
280
|
+
}
|
|
281
|
+
]), {
|
|
282
|
+
status: 200
|
|
283
|
+
});
|
|
284
|
+
if (url.endsWith("/repos/myorg/myskills/contents/skills/legacy-skill/SKILL.md")) return new Response(JSON.stringify({
|
|
285
|
+
type: "file",
|
|
286
|
+
content: Buffer.from("---\nname: legacy-skill\ndescription: Legacy source\n---\n", "utf-8").toString("base64")
|
|
287
|
+
}), {
|
|
288
|
+
status: 200
|
|
289
|
+
});
|
|
290
|
+
return new Response("Not Found", {
|
|
291
|
+
status: 404
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
globalThis.fetch = fetchMock;
|
|
295
|
+
const repository = new SkillRepository({
|
|
296
|
+
provider: "github",
|
|
297
|
+
repositoryOwner: "myorg",
|
|
298
|
+
repositoryName: "myskills"
|
|
299
|
+
});
|
|
300
|
+
const skills = await repository.listAvailableSkills();
|
|
301
|
+
expect(skills).toHaveLength(1);
|
|
302
|
+
expect(skills[0]?.name).toBe("legacy-skill");
|
|
303
|
+
});
|
|
304
|
+
it("fails clearly when github provider has no configured repositories", async ()=>{
|
|
305
|
+
const repository = new SkillRepository({
|
|
306
|
+
provider: "github"
|
|
307
|
+
});
|
|
308
|
+
await expect(repository.listAvailableSkills()).rejects.toThrow("No GitHub skill repositories configured");
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
describe("SkillRepository hybrid provider", ()=>{
|
|
312
|
+
const originalFetch = globalThis.fetch;
|
|
313
|
+
afterEach(()=>{
|
|
314
|
+
globalThis.fetch = originalFetch;
|
|
315
|
+
vi.restoreAllMocks();
|
|
316
|
+
});
|
|
317
|
+
it("merges ClawHub and GitHub skills, with GitHub overriding conflicts", async ()=>{
|
|
318
|
+
const encodeSkill = (name, description)=>Buffer.from(`---\nname: ${name}\ndescription: ${description}\n---\n`, "utf-8").toString("base64");
|
|
319
|
+
const fetchMock = vi.fn(async (input)=>{
|
|
320
|
+
const url = input.toString();
|
|
321
|
+
if ("https://clawhub.ai/api/v1/skills?sort=downloads&limit=100" === url) return new Response(JSON.stringify({
|
|
322
|
+
items: [
|
|
323
|
+
{
|
|
324
|
+
slug: "alpha",
|
|
325
|
+
summary: "ClawHub alpha skill"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
slug: "weather",
|
|
329
|
+
summary: "ClawHub weather skill"
|
|
330
|
+
}
|
|
331
|
+
],
|
|
332
|
+
nextCursor: null
|
|
333
|
+
}), {
|
|
334
|
+
status: 200
|
|
335
|
+
});
|
|
336
|
+
if (url.endsWith("/repos/example-team/custom-skills/contents/skills")) return new Response(JSON.stringify([
|
|
337
|
+
{
|
|
338
|
+
name: "alpha",
|
|
339
|
+
path: "skills/alpha",
|
|
340
|
+
type: "dir",
|
|
341
|
+
url: "https://api.github.com/repos/example-team/custom-skills/contents/skills/alpha"
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
name: "wingman-special",
|
|
345
|
+
path: "skills/wingman-special",
|
|
346
|
+
type: "dir",
|
|
347
|
+
url: "https://api.github.com/repos/example-team/custom-skills/contents/skills/wingman-special"
|
|
348
|
+
}
|
|
349
|
+
]), {
|
|
350
|
+
status: 200
|
|
351
|
+
});
|
|
352
|
+
if (url.endsWith("/repos/example-team/custom-skills/contents/skills/alpha/SKILL.md")) return new Response(JSON.stringify({
|
|
353
|
+
type: "file",
|
|
354
|
+
content: encodeSkill("alpha", "GitHub alpha override")
|
|
355
|
+
}), {
|
|
356
|
+
status: 200
|
|
357
|
+
});
|
|
358
|
+
if (url.endsWith("/repos/example-team/custom-skills/contents/skills/wingman-special/SKILL.md")) return new Response(JSON.stringify({
|
|
359
|
+
type: "file",
|
|
360
|
+
content: encodeSkill("wingman-special", "GitHub only skill")
|
|
361
|
+
}), {
|
|
362
|
+
status: 200
|
|
363
|
+
});
|
|
364
|
+
return new Response("Not Found", {
|
|
365
|
+
status: 404
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
globalThis.fetch = fetchMock;
|
|
369
|
+
const repository = new SkillRepository({
|
|
370
|
+
provider: "hybrid",
|
|
371
|
+
repositories: [
|
|
372
|
+
{
|
|
373
|
+
owner: "example-team",
|
|
374
|
+
name: "custom-skills"
|
|
375
|
+
}
|
|
376
|
+
],
|
|
377
|
+
clawhubBaseUrl: "https://clawhub.ai"
|
|
378
|
+
});
|
|
379
|
+
const skills = await repository.listAvailableSkills();
|
|
380
|
+
expect(skills).toEqual([
|
|
381
|
+
{
|
|
382
|
+
name: "weather",
|
|
383
|
+
description: "ClawHub weather skill",
|
|
384
|
+
path: "weather",
|
|
385
|
+
metadata: {
|
|
386
|
+
name: "weather",
|
|
387
|
+
description: "ClawHub weather skill"
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: "alpha",
|
|
392
|
+
description: "GitHub alpha override",
|
|
393
|
+
path: "skills/alpha",
|
|
394
|
+
metadata: {
|
|
395
|
+
name: "alpha",
|
|
396
|
+
description: "GitHub alpha override"
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
name: "wingman-special",
|
|
401
|
+
description: "GitHub only skill",
|
|
402
|
+
path: "skills/wingman-special",
|
|
403
|
+
metadata: {
|
|
404
|
+
name: "wingman-special",
|
|
405
|
+
description: "GitHub only skill"
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
]);
|
|
409
|
+
});
|
|
410
|
+
it("falls back to ClawHub when a skill is missing from configured GitHub repos", async ()=>{
|
|
411
|
+
const fetchMock = vi.fn(async (input)=>{
|
|
412
|
+
const url = input.toString();
|
|
413
|
+
if (url.endsWith("/repos/example-team/custom-skills/contents/skills/gog/SKILL.md")) return new Response("Not Found", {
|
|
414
|
+
status: 404
|
|
415
|
+
});
|
|
416
|
+
if ("https://clawhub.ai/api/v1/skills/gog" === url) return new Response(JSON.stringify({
|
|
417
|
+
skill: {
|
|
418
|
+
slug: "gog",
|
|
419
|
+
summary: "Google workspace tooling"
|
|
420
|
+
},
|
|
421
|
+
latestVersion: {
|
|
422
|
+
version: "1.0.0"
|
|
423
|
+
}
|
|
424
|
+
}), {
|
|
425
|
+
status: 200
|
|
426
|
+
});
|
|
427
|
+
if ("https://clawhub.ai/api/v1/skills/gog/versions/1.0.0" === url) return new Response(JSON.stringify({
|
|
428
|
+
version: {
|
|
429
|
+
version: "1.0.0",
|
|
430
|
+
files: [
|
|
431
|
+
{
|
|
432
|
+
path: "SKILL.md"
|
|
433
|
+
}
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
}), {
|
|
437
|
+
status: 200
|
|
438
|
+
});
|
|
439
|
+
if (url.includes("/api/v1/skills/gog/file?")) return new Response("---\nname: gog\ndescription: Test\n---\n", {
|
|
440
|
+
status: 200
|
|
441
|
+
});
|
|
442
|
+
return new Response("Not Found", {
|
|
443
|
+
status: 404
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
globalThis.fetch = fetchMock;
|
|
447
|
+
const repository = new SkillRepository({
|
|
448
|
+
provider: "hybrid",
|
|
449
|
+
repositories: [
|
|
450
|
+
{
|
|
451
|
+
owner: "example-team",
|
|
452
|
+
name: "custom-skills"
|
|
453
|
+
}
|
|
454
|
+
],
|
|
455
|
+
clawhubBaseUrl: "https://clawhub.ai"
|
|
456
|
+
});
|
|
457
|
+
const metadata = await repository.getSkillMetadata("gog");
|
|
458
|
+
expect(metadata.name).toBe("gog");
|
|
459
|
+
expect(metadata.description).toContain("Google workspace tooling");
|
|
460
|
+
const files = await repository.downloadSkill("gog");
|
|
461
|
+
expect(files.has("SKILL.md")).toBe(true);
|
|
462
|
+
});
|
|
463
|
+
});
|