@wingman-ai/gateway 0.3.1 → 0.4.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.
Files changed (59) hide show
  1. package/dist/agent/config/mcpClientManager.cjs +48 -9
  2. package/dist/agent/config/mcpClientManager.d.ts +12 -0
  3. package/dist/agent/config/mcpClientManager.js +48 -9
  4. package/dist/agent/tests/internet_search.test.cjs +22 -28
  5. package/dist/agent/tests/internet_search.test.js +22 -28
  6. package/dist/agent/tests/mcpClientManager.test.cjs +50 -0
  7. package/dist/agent/tests/mcpClientManager.test.js +50 -0
  8. package/dist/agent/tools/internet_search.cjs +9 -5
  9. package/dist/agent/tools/internet_search.js +9 -5
  10. package/dist/cli/commands/skill.cjs +12 -4
  11. package/dist/cli/commands/skill.js +12 -4
  12. package/dist/cli/config/jsonSchema.cjs +55 -0
  13. package/dist/cli/config/jsonSchema.d.ts +2 -0
  14. package/dist/cli/config/jsonSchema.js +18 -0
  15. package/dist/cli/config/loader.cjs +33 -1
  16. package/dist/cli/config/loader.js +33 -1
  17. package/dist/cli/config/schema.cjs +119 -2
  18. package/dist/cli/config/schema.d.ts +40 -0
  19. package/dist/cli/config/schema.js +119 -2
  20. package/dist/cli/core/agentInvoker.cjs +4 -1
  21. package/dist/cli/core/agentInvoker.d.ts +3 -0
  22. package/dist/cli/core/agentInvoker.js +4 -1
  23. package/dist/cli/services/skillRepository.cjs +138 -20
  24. package/dist/cli/services/skillRepository.d.ts +10 -2
  25. package/dist/cli/services/skillRepository.js +138 -20
  26. package/dist/cli/services/skillSecurityScanner.cjs +158 -0
  27. package/dist/cli/services/skillSecurityScanner.d.ts +28 -0
  28. package/dist/cli/services/skillSecurityScanner.js +121 -0
  29. package/dist/cli/services/skillService.cjs +44 -12
  30. package/dist/cli/services/skillService.d.ts +2 -0
  31. package/dist/cli/services/skillService.js +46 -14
  32. package/dist/cli/types/skill.d.ts +9 -0
  33. package/dist/gateway/server.cjs +5 -1
  34. package/dist/gateway/server.js +5 -1
  35. package/dist/gateway/types.d.ts +9 -0
  36. package/dist/tests/cli-config-loader.test.cjs +33 -1
  37. package/dist/tests/cli-config-loader.test.js +33 -1
  38. package/dist/tests/config-json-schema.test.cjs +25 -0
  39. package/dist/tests/config-json-schema.test.d.ts +1 -0
  40. package/dist/tests/config-json-schema.test.js +19 -0
  41. package/dist/tests/skill-repository.test.cjs +106 -0
  42. package/dist/tests/skill-repository.test.d.ts +1 -0
  43. package/dist/tests/skill-repository.test.js +100 -0
  44. package/dist/tests/skill-security-scanner.test.cjs +126 -0
  45. package/dist/tests/skill-security-scanner.test.d.ts +1 -0
  46. package/dist/tests/skill-security-scanner.test.js +120 -0
  47. package/dist/tests/uv.test.cjs +47 -0
  48. package/dist/tests/uv.test.d.ts +1 -0
  49. package/dist/tests/uv.test.js +41 -0
  50. package/dist/utils/uv.cjs +64 -0
  51. package/dist/utils/uv.d.ts +3 -0
  52. package/dist/utils/uv.js +24 -0
  53. package/dist/webui/assets/index-Cwkg4DKj.css +11 -0
  54. package/dist/webui/assets/{index-C8-oboEC.js → index-DHbfLOUR.js} +21 -19
  55. package/dist/webui/index.html +2 -2
  56. package/package.json +2 -3
  57. package/skills/gog/SKILL.md +36 -0
  58. package/skills/weather/SKILL.md +49 -0
  59. package/dist/webui/assets/index-BW9nM0J2.css +0 -11
@@ -1,4 +1,5 @@
1
1
  import type { WingmanAgentConfig } from "@/agent/config/agentConfig.js";
2
+ import { type MCPProxyConfig } from "@/agent/config/mcpClientManager.js";
2
3
  import { type TerminalSessionManager } from "@/agent/tools/terminal_session_manager.js";
3
4
  import type { WingmanAgent } from "@/types/agents.js";
4
5
  import type { Logger } from "../../logger.js";
@@ -14,6 +15,7 @@ export interface AgentInvokerOptions {
14
15
  terminalSessionManager?: TerminalSessionManager;
15
16
  workdir?: string | null;
16
17
  defaultOutputDir?: string | null;
18
+ mcpProxyConfig?: MCPProxyConfig;
17
19
  }
18
20
  export interface InvokeAgentOptions {
19
21
  signal?: AbortSignal;
@@ -149,6 +151,7 @@ export declare class AgentInvoker {
149
151
  private terminalSessionManager;
150
152
  private workdir;
151
153
  private defaultOutputDir;
154
+ private mcpProxyConfig;
152
155
  constructor(options: AgentInvokerOptions);
153
156
  findAllAgents(): WingmanAgentConfig[];
154
157
  /**
@@ -296,7 +296,8 @@ class AgentInvoker {
296
296
  if (mcpConfigs.length > 0) {
297
297
  this.logger.debug("Initializing MCP client for agent invocation");
298
298
  this.mcpManager = new MCPClientManager(mcpConfigs, this.logger, {
299
- executionWorkspace
299
+ executionWorkspace,
300
+ proxyConfig: this.mcpProxyConfig
300
301
  });
301
302
  await this.mcpManager.initialize();
302
303
  const mcpTools = await this.mcpManager.getTools();
@@ -551,6 +552,7 @@ class AgentInvoker {
551
552
  _define_property(this, "terminalSessionManager", void 0);
552
553
  _define_property(this, "workdir", null);
553
554
  _define_property(this, "defaultOutputDir", null);
555
+ _define_property(this, "mcpProxyConfig", void 0);
554
556
  this.outputManager = options.outputManager;
555
557
  this.logger = options.logger;
556
558
  this.workspace = options.workspace || process.cwd();
@@ -559,6 +561,7 @@ class AgentInvoker {
559
561
  this.terminalSessionManager = options.terminalSessionManager || getSharedTerminalSessionManager();
560
562
  this.workdir = options.workdir || null;
561
563
  this.defaultOutputDir = options.defaultOutputDir || null;
564
+ this.mcpProxyConfig = options.mcpProxyConfig;
562
565
  const configLoader = new WingmanConfigLoader(this.configDir, this.workspace);
563
566
  this.wingmanConfig = configLoader.loadConfig();
564
567
  this.loader = new AgentLoader(this.configDir, this.workspace, this.wingmanConfig);
@@ -39,8 +39,8 @@ function _define_property(obj, key, value) {
39
39
  }
40
40
  const logger = (0, external_logger_cjs_namespaceObject.createLogger)();
41
41
  class SkillRepository {
42
- async fetch(path) {
43
- const url = `${this.baseUrl}${path}`;
42
+ async fetchGitHub(path) {
43
+ const url = `${this.githubBaseUrl}${path}`;
44
44
  const headers = {
45
45
  Accept: "application/vnd.github.v3+json",
46
46
  "User-Agent": "wingman-cli"
@@ -62,29 +62,33 @@ class SkillRepository {
62
62
  }
63
63
  async listAvailableSkills() {
64
64
  try {
65
- const contents = await this.fetch(`/repos/${this.owner}/${this.repo}/contents/skills`);
66
- const skills = [];
67
- for (const item of contents)if ("dir" === item.type) try {
68
- const metadata = await this.getSkillMetadata(item.name);
69
- skills.push({
70
- name: item.name,
71
- description: metadata.description || "No description",
72
- path: item.path,
73
- metadata
74
- });
75
- } catch (error) {
76
- logger.warn(`Could not read skill ${item.name}`, error instanceof Error ? error.message : String(error));
77
- }
78
- return skills;
65
+ if ("clawhub" === this.provider) return await this.listSkillsFromClawhub();
66
+ return await this.listSkillsFromGitHub();
79
67
  } catch (error) {
80
68
  if (error instanceof Error) throw new Error(`Failed to list skills: ${error.message}`);
81
69
  throw error;
82
70
  }
83
71
  }
72
+ async fetchJson(url, options) {
73
+ const response = await fetch(url, {
74
+ headers: options?.headers
75
+ });
76
+ if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);
77
+ return await response.json();
78
+ }
79
+ async fetchBinary(url, options) {
80
+ const response = await fetch(url, {
81
+ headers: options?.headers
82
+ });
83
+ if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);
84
+ const arrayBuffer = await response.arrayBuffer();
85
+ return Buffer.from(arrayBuffer);
86
+ }
84
87
  async getSkillMetadata(skillName) {
88
+ if ("clawhub" === this.provider) return await this.getClawhubSkillMetadata(skillName);
85
89
  try {
86
90
  const skillMdPath = `/repos/${this.owner}/${this.repo}/contents/skills/${skillName}/SKILL.md`;
87
- const skillMd = await this.fetch(skillMdPath);
91
+ const skillMd = await this.fetchGitHub(skillMdPath);
88
92
  if ("file" !== skillMd.type || !skillMd.content) throw new Error("SKILL.md not found or invalid");
89
93
  const content = Buffer.from(skillMd.content, "base64").toString("utf-8");
90
94
  const metadata = this.parseSkillMetadata(content);
@@ -94,6 +98,35 @@ class SkillRepository {
94
98
  throw error;
95
99
  }
96
100
  }
101
+ async getClawhubSkillMetadata(skillName) {
102
+ try {
103
+ const detail = await this.fetchJson(`${this.clawhubBaseUrl}/api/v1/skills/${encodeURIComponent(skillName)}`, {
104
+ headers: {
105
+ Accept: "application/json",
106
+ "User-Agent": "wingman-cli"
107
+ }
108
+ });
109
+ const slug = detail.skill?.slug?.trim() || skillName.trim();
110
+ const description = detail.skill?.summary?.trim() || detail.skill?.displayName?.trim() || "No description";
111
+ const nameRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/;
112
+ if (!nameRegex.test(slug)) throw new Error(`Invalid skill name '${slug}': must be lowercase alphanumeric with hyphens only`);
113
+ return {
114
+ name: slug,
115
+ description,
116
+ metadata: {
117
+ ...detail.latestVersion?.version ? {
118
+ version: detail.latestVersion.version
119
+ } : {},
120
+ ...detail.owner?.handle ? {
121
+ owner: detail.owner.handle
122
+ } : {}
123
+ }
124
+ };
125
+ } catch (error) {
126
+ if (error instanceof Error) throw new Error(`Failed to fetch skill metadata for ${skillName}: ${error.message}`);
127
+ throw error;
128
+ }
129
+ }
97
130
  parseSkillMetadata(content) {
98
131
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---/;
99
132
  const match = content.match(frontmatterRegex);
@@ -134,6 +167,7 @@ class SkillRepository {
134
167
  return metadata;
135
168
  }
136
169
  async downloadSkill(skillName) {
170
+ if ("clawhub" === this.provider) return await this.downloadSkillFromClawhub(skillName);
137
171
  try {
138
172
  const files = new Map();
139
173
  await this.downloadDirectory(`skills/${skillName}`, files, skillName);
@@ -143,14 +177,51 @@ class SkillRepository {
143
177
  throw error;
144
178
  }
145
179
  }
180
+ async downloadSkillFromClawhub(skillName) {
181
+ try {
182
+ const detail = await this.fetchJson(`${this.clawhubBaseUrl}/api/v1/skills/${encodeURIComponent(skillName)}`, {
183
+ headers: {
184
+ Accept: "application/json",
185
+ "User-Agent": "wingman-cli"
186
+ }
187
+ });
188
+ const slug = detail.skill?.slug || skillName;
189
+ const version = detail.latestVersion?.version;
190
+ if (!version) throw new Error("No latest version available");
191
+ const filesResponse = await this.fetchJson(`${this.clawhubBaseUrl}/api/v1/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}`, {
192
+ headers: {
193
+ Accept: "application/json",
194
+ "User-Agent": "wingman-cli"
195
+ }
196
+ });
197
+ const files = filesResponse.version?.files || [];
198
+ const output = new Map();
199
+ for (const file of files){
200
+ const fileUrl = new URL(`${this.clawhubBaseUrl}/api/v1/skills/${encodeURIComponent(slug)}/file`);
201
+ fileUrl.searchParams.set("path", file.path);
202
+ fileUrl.searchParams.set("version", version);
203
+ const content = await this.fetchBinary(fileUrl.toString(), {
204
+ headers: {
205
+ Accept: "text/plain, application/octet-stream",
206
+ "User-Agent": "wingman-cli"
207
+ }
208
+ });
209
+ output.set(file.path, content);
210
+ }
211
+ return output;
212
+ } catch (error) {
213
+ if (error instanceof Error) throw new Error(`Failed to download skill ${skillName}: ${error.message}`);
214
+ throw error;
215
+ }
216
+ }
146
217
  async downloadDirectory(path, files, skillName) {
147
- const contents = await this.fetch(`/repos/${this.owner}/${this.repo}/contents/${path}`);
218
+ const contents = await this.fetchGitHub(`/repos/${this.owner}/${this.repo}/contents/${path}`);
148
219
  for (const item of contents)if ("file" === item.type) if (item.content) {
149
220
  const content = Buffer.from(item.content, "base64");
150
221
  const relativePath = item.path.replace(`skills/${skillName}/`, "");
151
222
  files.set(relativePath, content);
152
223
  } else {
153
- const fileData = await this.fetch(item.url.replace(this.baseUrl, ""));
224
+ const fileData = await this.fetchGitHub(item.url.replace(this.githubBaseUrl, ""));
154
225
  if (fileData.content && "base64" === fileData.encoding) {
155
226
  const content = Buffer.from(fileData.content, "base64");
156
227
  const relativePath = item.path.replace(`skills/${skillName}/`, "");
@@ -159,14 +230,61 @@ class SkillRepository {
159
230
  }
160
231
  else if ("dir" === item.type) await this.downloadDirectory(item.path, files, skillName);
161
232
  }
233
+ async listSkillsFromClawhub() {
234
+ const allSkills = [];
235
+ let cursor = null;
236
+ do {
237
+ const url = new URL(`${this.clawhubBaseUrl}/api/v1/skills`);
238
+ url.searchParams.set("sort", "downloads");
239
+ url.searchParams.set("limit", "100");
240
+ if (cursor) url.searchParams.set("cursor", cursor);
241
+ const response = await this.fetchJson(url.toString(), {
242
+ headers: {
243
+ Accept: "application/json",
244
+ "User-Agent": "wingman-cli"
245
+ }
246
+ });
247
+ for (const item of response.items || [])allSkills.push({
248
+ name: item.slug,
249
+ description: item.summary?.trim() || item.displayName?.trim() || "No description",
250
+ path: item.slug,
251
+ metadata: {
252
+ name: item.slug,
253
+ description: item.summary?.trim() || item.displayName?.trim() || "No description"
254
+ }
255
+ });
256
+ cursor = response.nextCursor || null;
257
+ }while (cursor);
258
+ return allSkills;
259
+ }
260
+ async listSkillsFromGitHub() {
261
+ const contents = await this.fetchGitHub(`/repos/${this.owner}/${this.repo}/contents/skills`);
262
+ const skills = [];
263
+ for (const item of contents)if ("dir" === item.type) try {
264
+ const metadata = await this.getSkillMetadata(item.name);
265
+ skills.push({
266
+ name: item.name,
267
+ description: metadata.description || "No description",
268
+ path: item.path,
269
+ metadata
270
+ });
271
+ } catch (error) {
272
+ logger.warn(`Could not read skill ${item.name}`, error instanceof Error ? error.message : String(error));
273
+ }
274
+ return skills;
275
+ }
162
276
  constructor(options = {}){
163
- _define_property(this, "baseUrl", "https://api.github.com");
277
+ _define_property(this, "githubBaseUrl", "https://api.github.com");
164
278
  _define_property(this, "owner", void 0);
165
279
  _define_property(this, "repo", void 0);
166
280
  _define_property(this, "token", void 0);
281
+ _define_property(this, "provider", void 0);
282
+ _define_property(this, "clawhubBaseUrl", void 0);
283
+ this.provider = options.provider || "github";
167
284
  this.owner = options.repositoryOwner || "anthropics";
168
285
  this.repo = options.repositoryName || "skills";
169
286
  this.token = options.githubToken || process.env.GITHUB_TOKEN || void 0;
287
+ this.clawhubBaseUrl = (options.clawhubBaseUrl || "https://clawhub.ai").replace(/\/+$/, "");
170
288
  }
171
289
  }
172
290
  exports.SkillRepository = __webpack_exports__.SkillRepository;
@@ -3,23 +3,28 @@ import type { SkillInfo, SkillMetadata, SkillRepositoryOptions } from "../types/
3
3
  * GitHub API client for interacting with the skills repository
4
4
  */
5
5
  export declare class SkillRepository {
6
- private readonly baseUrl;
6
+ private readonly githubBaseUrl;
7
7
  private readonly owner;
8
8
  private readonly repo;
9
9
  private readonly token?;
10
+ private readonly provider;
11
+ private readonly clawhubBaseUrl;
10
12
  constructor(options?: SkillRepositoryOptions);
11
13
  /**
12
14
  * Fetch data from GitHub API
13
15
  */
14
- private fetch;
16
+ private fetchGitHub;
15
17
  /**
16
18
  * List available skills from the repository
17
19
  */
18
20
  listAvailableSkills(): Promise<SkillInfo[]>;
21
+ private fetchJson;
22
+ private fetchBinary;
19
23
  /**
20
24
  * Get skill metadata by fetching and parsing SKILL.md
21
25
  */
22
26
  getSkillMetadata(skillName: string): Promise<SkillMetadata>;
27
+ private getClawhubSkillMetadata;
23
28
  /**
24
29
  * Parse SKILL.md content to extract YAML frontmatter
25
30
  */
@@ -28,8 +33,11 @@ export declare class SkillRepository {
28
33
  * Download all files for a skill
29
34
  */
30
35
  downloadSkill(skillName: string): Promise<Map<string, string | Buffer>>;
36
+ private downloadSkillFromClawhub;
31
37
  /**
32
38
  * Recursively download all files in a directory
33
39
  */
34
40
  private downloadDirectory;
41
+ private listSkillsFromClawhub;
42
+ private listSkillsFromGitHub;
35
43
  }
@@ -11,8 +11,8 @@ function _define_property(obj, key, value) {
11
11
  }
12
12
  const logger = createLogger();
13
13
  class SkillRepository {
14
- async fetch(path) {
15
- const url = `${this.baseUrl}${path}`;
14
+ async fetchGitHub(path) {
15
+ const url = `${this.githubBaseUrl}${path}`;
16
16
  const headers = {
17
17
  Accept: "application/vnd.github.v3+json",
18
18
  "User-Agent": "wingman-cli"
@@ -34,29 +34,33 @@ class SkillRepository {
34
34
  }
35
35
  async listAvailableSkills() {
36
36
  try {
37
- const contents = await this.fetch(`/repos/${this.owner}/${this.repo}/contents/skills`);
38
- const skills = [];
39
- for (const item of contents)if ("dir" === item.type) try {
40
- const metadata = await this.getSkillMetadata(item.name);
41
- skills.push({
42
- name: item.name,
43
- description: metadata.description || "No description",
44
- path: item.path,
45
- metadata
46
- });
47
- } catch (error) {
48
- logger.warn(`Could not read skill ${item.name}`, error instanceof Error ? error.message : String(error));
49
- }
50
- return skills;
37
+ if ("clawhub" === this.provider) return await this.listSkillsFromClawhub();
38
+ return await this.listSkillsFromGitHub();
51
39
  } catch (error) {
52
40
  if (error instanceof Error) throw new Error(`Failed to list skills: ${error.message}`);
53
41
  throw error;
54
42
  }
55
43
  }
44
+ async fetchJson(url, options) {
45
+ const response = await fetch(url, {
46
+ headers: options?.headers
47
+ });
48
+ if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);
49
+ return await response.json();
50
+ }
51
+ async fetchBinary(url, options) {
52
+ const response = await fetch(url, {
53
+ headers: options?.headers
54
+ });
55
+ if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);
56
+ const arrayBuffer = await response.arrayBuffer();
57
+ return Buffer.from(arrayBuffer);
58
+ }
56
59
  async getSkillMetadata(skillName) {
60
+ if ("clawhub" === this.provider) return await this.getClawhubSkillMetadata(skillName);
57
61
  try {
58
62
  const skillMdPath = `/repos/${this.owner}/${this.repo}/contents/skills/${skillName}/SKILL.md`;
59
- const skillMd = await this.fetch(skillMdPath);
63
+ const skillMd = await this.fetchGitHub(skillMdPath);
60
64
  if ("file" !== skillMd.type || !skillMd.content) throw new Error("SKILL.md not found or invalid");
61
65
  const content = Buffer.from(skillMd.content, "base64").toString("utf-8");
62
66
  const metadata = this.parseSkillMetadata(content);
@@ -66,6 +70,35 @@ class SkillRepository {
66
70
  throw error;
67
71
  }
68
72
  }
73
+ async getClawhubSkillMetadata(skillName) {
74
+ try {
75
+ const detail = await this.fetchJson(`${this.clawhubBaseUrl}/api/v1/skills/${encodeURIComponent(skillName)}`, {
76
+ headers: {
77
+ Accept: "application/json",
78
+ "User-Agent": "wingman-cli"
79
+ }
80
+ });
81
+ const slug = detail.skill?.slug?.trim() || skillName.trim();
82
+ const description = detail.skill?.summary?.trim() || detail.skill?.displayName?.trim() || "No description";
83
+ const nameRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/;
84
+ if (!nameRegex.test(slug)) throw new Error(`Invalid skill name '${slug}': must be lowercase alphanumeric with hyphens only`);
85
+ return {
86
+ name: slug,
87
+ description,
88
+ metadata: {
89
+ ...detail.latestVersion?.version ? {
90
+ version: detail.latestVersion.version
91
+ } : {},
92
+ ...detail.owner?.handle ? {
93
+ owner: detail.owner.handle
94
+ } : {}
95
+ }
96
+ };
97
+ } catch (error) {
98
+ if (error instanceof Error) throw new Error(`Failed to fetch skill metadata for ${skillName}: ${error.message}`);
99
+ throw error;
100
+ }
101
+ }
69
102
  parseSkillMetadata(content) {
70
103
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---/;
71
104
  const match = content.match(frontmatterRegex);
@@ -106,6 +139,7 @@ class SkillRepository {
106
139
  return metadata;
107
140
  }
108
141
  async downloadSkill(skillName) {
142
+ if ("clawhub" === this.provider) return await this.downloadSkillFromClawhub(skillName);
109
143
  try {
110
144
  const files = new Map();
111
145
  await this.downloadDirectory(`skills/${skillName}`, files, skillName);
@@ -115,14 +149,51 @@ class SkillRepository {
115
149
  throw error;
116
150
  }
117
151
  }
152
+ async downloadSkillFromClawhub(skillName) {
153
+ try {
154
+ const detail = await this.fetchJson(`${this.clawhubBaseUrl}/api/v1/skills/${encodeURIComponent(skillName)}`, {
155
+ headers: {
156
+ Accept: "application/json",
157
+ "User-Agent": "wingman-cli"
158
+ }
159
+ });
160
+ const slug = detail.skill?.slug || skillName;
161
+ const version = detail.latestVersion?.version;
162
+ if (!version) throw new Error("No latest version available");
163
+ const filesResponse = await this.fetchJson(`${this.clawhubBaseUrl}/api/v1/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}`, {
164
+ headers: {
165
+ Accept: "application/json",
166
+ "User-Agent": "wingman-cli"
167
+ }
168
+ });
169
+ const files = filesResponse.version?.files || [];
170
+ const output = new Map();
171
+ for (const file of files){
172
+ const fileUrl = new URL(`${this.clawhubBaseUrl}/api/v1/skills/${encodeURIComponent(slug)}/file`);
173
+ fileUrl.searchParams.set("path", file.path);
174
+ fileUrl.searchParams.set("version", version);
175
+ const content = await this.fetchBinary(fileUrl.toString(), {
176
+ headers: {
177
+ Accept: "text/plain, application/octet-stream",
178
+ "User-Agent": "wingman-cli"
179
+ }
180
+ });
181
+ output.set(file.path, content);
182
+ }
183
+ return output;
184
+ } catch (error) {
185
+ if (error instanceof Error) throw new Error(`Failed to download skill ${skillName}: ${error.message}`);
186
+ throw error;
187
+ }
188
+ }
118
189
  async downloadDirectory(path, files, skillName) {
119
- const contents = await this.fetch(`/repos/${this.owner}/${this.repo}/contents/${path}`);
190
+ const contents = await this.fetchGitHub(`/repos/${this.owner}/${this.repo}/contents/${path}`);
120
191
  for (const item of contents)if ("file" === item.type) if (item.content) {
121
192
  const content = Buffer.from(item.content, "base64");
122
193
  const relativePath = item.path.replace(`skills/${skillName}/`, "");
123
194
  files.set(relativePath, content);
124
195
  } else {
125
- const fileData = await this.fetch(item.url.replace(this.baseUrl, ""));
196
+ const fileData = await this.fetchGitHub(item.url.replace(this.githubBaseUrl, ""));
126
197
  if (fileData.content && "base64" === fileData.encoding) {
127
198
  const content = Buffer.from(fileData.content, "base64");
128
199
  const relativePath = item.path.replace(`skills/${skillName}/`, "");
@@ -131,14 +202,61 @@ class SkillRepository {
131
202
  }
132
203
  else if ("dir" === item.type) await this.downloadDirectory(item.path, files, skillName);
133
204
  }
205
+ async listSkillsFromClawhub() {
206
+ const allSkills = [];
207
+ let cursor = null;
208
+ do {
209
+ const url = new URL(`${this.clawhubBaseUrl}/api/v1/skills`);
210
+ url.searchParams.set("sort", "downloads");
211
+ url.searchParams.set("limit", "100");
212
+ if (cursor) url.searchParams.set("cursor", cursor);
213
+ const response = await this.fetchJson(url.toString(), {
214
+ headers: {
215
+ Accept: "application/json",
216
+ "User-Agent": "wingman-cli"
217
+ }
218
+ });
219
+ for (const item of response.items || [])allSkills.push({
220
+ name: item.slug,
221
+ description: item.summary?.trim() || item.displayName?.trim() || "No description",
222
+ path: item.slug,
223
+ metadata: {
224
+ name: item.slug,
225
+ description: item.summary?.trim() || item.displayName?.trim() || "No description"
226
+ }
227
+ });
228
+ cursor = response.nextCursor || null;
229
+ }while (cursor);
230
+ return allSkills;
231
+ }
232
+ async listSkillsFromGitHub() {
233
+ const contents = await this.fetchGitHub(`/repos/${this.owner}/${this.repo}/contents/skills`);
234
+ const skills = [];
235
+ for (const item of contents)if ("dir" === item.type) try {
236
+ const metadata = await this.getSkillMetadata(item.name);
237
+ skills.push({
238
+ name: item.name,
239
+ description: metadata.description || "No description",
240
+ path: item.path,
241
+ metadata
242
+ });
243
+ } catch (error) {
244
+ logger.warn(`Could not read skill ${item.name}`, error instanceof Error ? error.message : String(error));
245
+ }
246
+ return skills;
247
+ }
134
248
  constructor(options = {}){
135
- _define_property(this, "baseUrl", "https://api.github.com");
249
+ _define_property(this, "githubBaseUrl", "https://api.github.com");
136
250
  _define_property(this, "owner", void 0);
137
251
  _define_property(this, "repo", void 0);
138
252
  _define_property(this, "token", void 0);
253
+ _define_property(this, "provider", void 0);
254
+ _define_property(this, "clawhubBaseUrl", void 0);
255
+ this.provider = options.provider || "github";
139
256
  this.owner = options.repositoryOwner || "anthropics";
140
257
  this.repo = options.repositoryName || "skills";
141
258
  this.token = options.githubToken || process.env.GITHUB_TOKEN || void 0;
259
+ this.clawhubBaseUrl = (options.clawhubBaseUrl || "https://clawhub.ai").replace(/\/+$/, "");
142
260
  }
143
261
  }
144
262
  export { SkillRepository };