@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.
Files changed (104) hide show
  1. package/README.md +29 -111
  2. package/dist/agent/config/agentConfig.cjs +2 -0
  3. package/dist/agent/config/agentConfig.d.ts +6 -0
  4. package/dist/agent/config/agentConfig.js +2 -0
  5. package/dist/agent/config/agentLoader.cjs +21 -18
  6. package/dist/agent/config/agentLoader.js +22 -19
  7. package/dist/agent/config/toolRegistry.cjs +19 -0
  8. package/dist/agent/config/toolRegistry.d.ts +4 -0
  9. package/dist/agent/config/toolRegistry.js +17 -1
  10. package/dist/agent/middleware/additional-messages.cjs +115 -11
  11. package/dist/agent/middleware/additional-messages.d.ts +9 -0
  12. package/dist/agent/middleware/additional-messages.js +115 -11
  13. package/dist/agent/tests/agentLoader.test.cjs +45 -0
  14. package/dist/agent/tests/agentLoader.test.js +45 -0
  15. package/dist/agent/tests/toolRegistry.test.cjs +2 -0
  16. package/dist/agent/tests/toolRegistry.test.js +2 -0
  17. package/dist/agent/tools/node_invoke.cjs +146 -0
  18. package/dist/agent/tools/node_invoke.d.ts +86 -0
  19. package/dist/agent/tools/node_invoke.js +109 -0
  20. package/dist/cli/commands/gateway.cjs +1 -1
  21. package/dist/cli/commands/gateway.js +1 -1
  22. package/dist/cli/commands/init.cjs +135 -1
  23. package/dist/cli/commands/init.js +136 -2
  24. package/dist/cli/commands/skill.cjs +7 -3
  25. package/dist/cli/commands/skill.js +7 -3
  26. package/dist/cli/config/loader.cjs +7 -3
  27. package/dist/cli/config/loader.js +7 -3
  28. package/dist/cli/config/schema.cjs +27 -9
  29. package/dist/cli/config/schema.d.ts +18 -4
  30. package/dist/cli/config/schema.js +23 -8
  31. package/dist/cli/core/agentInvoker.cjs +70 -14
  32. package/dist/cli/core/agentInvoker.d.ts +10 -0
  33. package/dist/cli/core/agentInvoker.js +70 -14
  34. package/dist/cli/services/skillRepository.cjs +155 -69
  35. package/dist/cli/services/skillRepository.d.ts +7 -2
  36. package/dist/cli/services/skillRepository.js +155 -69
  37. package/dist/cli/services/skillService.cjs +93 -26
  38. package/dist/cli/services/skillService.d.ts +7 -0
  39. package/dist/cli/services/skillService.js +96 -29
  40. package/dist/cli/types/skill.d.ts +8 -3
  41. package/dist/gateway/http/nodes.cjs +247 -0
  42. package/dist/gateway/http/nodes.d.ts +20 -0
  43. package/dist/gateway/http/nodes.js +210 -0
  44. package/dist/gateway/node.cjs +10 -1
  45. package/dist/gateway/node.d.ts +10 -1
  46. package/dist/gateway/node.js +10 -1
  47. package/dist/gateway/server.cjs +414 -27
  48. package/dist/gateway/server.d.ts +34 -0
  49. package/dist/gateway/server.js +408 -27
  50. package/dist/gateway/types.d.ts +6 -1
  51. package/dist/gateway/validation.cjs +2 -0
  52. package/dist/gateway/validation.d.ts +4 -0
  53. package/dist/gateway/validation.js +2 -0
  54. package/dist/skills/activation.cjs +92 -0
  55. package/dist/skills/activation.d.ts +12 -0
  56. package/dist/skills/activation.js +58 -0
  57. package/dist/skills/bin-requirements.cjs +63 -0
  58. package/dist/skills/bin-requirements.d.ts +3 -0
  59. package/dist/skills/bin-requirements.js +26 -0
  60. package/dist/skills/metadata.cjs +141 -0
  61. package/dist/skills/metadata.d.ts +29 -0
  62. package/dist/skills/metadata.js +104 -0
  63. package/dist/skills/overlay.cjs +75 -0
  64. package/dist/skills/overlay.d.ts +2 -0
  65. package/dist/skills/overlay.js +38 -0
  66. package/dist/tests/additionalMessageMiddleware.test.cjs +92 -0
  67. package/dist/tests/additionalMessageMiddleware.test.js +92 -0
  68. package/dist/tests/cli-config-loader.test.cjs +7 -3
  69. package/dist/tests/cli-config-loader.test.js +7 -3
  70. package/dist/tests/cli-init.test.cjs +54 -0
  71. package/dist/tests/cli-init.test.js +54 -0
  72. package/dist/tests/config-json-schema.test.cjs +12 -0
  73. package/dist/tests/config-json-schema.test.js +12 -0
  74. package/dist/tests/gateway-http-security.test.cjs +277 -0
  75. package/dist/tests/gateway-http-security.test.d.ts +1 -0
  76. package/dist/tests/gateway-http-security.test.js +271 -0
  77. package/dist/tests/gateway-node-mode.test.cjs +174 -0
  78. package/dist/tests/gateway-node-mode.test.d.ts +1 -0
  79. package/dist/tests/gateway-node-mode.test.js +168 -0
  80. package/dist/tests/gateway-origin-policy.test.cjs +60 -0
  81. package/dist/tests/gateway-origin-policy.test.d.ts +1 -0
  82. package/dist/tests/gateway-origin-policy.test.js +54 -0
  83. package/dist/tests/gateway.test.cjs +1 -0
  84. package/dist/tests/gateway.test.js +1 -0
  85. package/dist/tests/node-tools.test.cjs +77 -0
  86. package/dist/tests/node-tools.test.d.ts +1 -0
  87. package/dist/tests/node-tools.test.js +71 -0
  88. package/dist/tests/nodes-api.test.cjs +86 -0
  89. package/dist/tests/nodes-api.test.d.ts +1 -0
  90. package/dist/tests/nodes-api.test.js +80 -0
  91. package/dist/tests/skill-activation.test.cjs +86 -0
  92. package/dist/tests/skill-activation.test.d.ts +1 -0
  93. package/dist/tests/skill-activation.test.js +80 -0
  94. package/dist/tests/skill-metadata.test.cjs +119 -0
  95. package/dist/tests/skill-metadata.test.d.ts +1 -0
  96. package/dist/tests/skill-metadata.test.js +113 -0
  97. package/dist/tests/skill-repository.test.cjs +363 -0
  98. package/dist/tests/skill-repository.test.js +363 -0
  99. package/dist/webui/assets/{index-DHbfLOUR.js → index-BMekSELC.js} +106 -106
  100. package/dist/webui/index.html +1 -1
  101. package/package.json +4 -4
  102. package/skills/gog/SKILL.md +1 -1
  103. package/skills/weather/SKILL.md +1 -1
  104. 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
+ });