@wingman-ai/gateway 0.3.2 → 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 (51) 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/mcpClientManager.test.cjs +50 -0
  5. package/dist/agent/tests/mcpClientManager.test.js +50 -0
  6. package/dist/cli/commands/skill.cjs +12 -4
  7. package/dist/cli/commands/skill.js +12 -4
  8. package/dist/cli/config/jsonSchema.cjs +55 -0
  9. package/dist/cli/config/jsonSchema.d.ts +2 -0
  10. package/dist/cli/config/jsonSchema.js +18 -0
  11. package/dist/cli/config/loader.cjs +33 -1
  12. package/dist/cli/config/loader.js +33 -1
  13. package/dist/cli/config/schema.cjs +119 -2
  14. package/dist/cli/config/schema.d.ts +40 -0
  15. package/dist/cli/config/schema.js +119 -2
  16. package/dist/cli/core/agentInvoker.cjs +4 -1
  17. package/dist/cli/core/agentInvoker.d.ts +3 -0
  18. package/dist/cli/core/agentInvoker.js +4 -1
  19. package/dist/cli/services/skillRepository.cjs +138 -20
  20. package/dist/cli/services/skillRepository.d.ts +10 -2
  21. package/dist/cli/services/skillRepository.js +138 -20
  22. package/dist/cli/services/skillSecurityScanner.cjs +158 -0
  23. package/dist/cli/services/skillSecurityScanner.d.ts +28 -0
  24. package/dist/cli/services/skillSecurityScanner.js +121 -0
  25. package/dist/cli/services/skillService.cjs +44 -12
  26. package/dist/cli/services/skillService.d.ts +2 -0
  27. package/dist/cli/services/skillService.js +46 -14
  28. package/dist/cli/types/skill.d.ts +9 -0
  29. package/dist/gateway/server.cjs +5 -1
  30. package/dist/gateway/server.js +5 -1
  31. package/dist/gateway/types.d.ts +9 -0
  32. package/dist/tests/cli-config-loader.test.cjs +33 -1
  33. package/dist/tests/cli-config-loader.test.js +33 -1
  34. package/dist/tests/config-json-schema.test.cjs +25 -0
  35. package/dist/tests/config-json-schema.test.d.ts +1 -0
  36. package/dist/tests/config-json-schema.test.js +19 -0
  37. package/dist/tests/skill-repository.test.cjs +106 -0
  38. package/dist/tests/skill-repository.test.d.ts +1 -0
  39. package/dist/tests/skill-repository.test.js +100 -0
  40. package/dist/tests/skill-security-scanner.test.cjs +126 -0
  41. package/dist/tests/skill-security-scanner.test.d.ts +1 -0
  42. package/dist/tests/skill-security-scanner.test.js +120 -0
  43. package/dist/tests/uv.test.cjs +47 -0
  44. package/dist/tests/uv.test.d.ts +1 -0
  45. package/dist/tests/uv.test.js +41 -0
  46. package/dist/utils/uv.cjs +64 -0
  47. package/dist/utils/uv.d.ts +3 -0
  48. package/dist/utils/uv.js +24 -0
  49. package/package.json +2 -1
  50. package/skills/gog/SKILL.md +36 -0
  51. package/skills/weather/SKILL.md +49 -0
@@ -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 };
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ __skillSecurityScanner: ()=>__skillSecurityScanner,
28
+ scanSkillDirectory: ()=>scanSkillDirectory
29
+ });
30
+ const external_node_child_process_namespaceObject = require("node:child_process");
31
+ const uv_cjs_namespaceObject = require("../../utils/uv.cjs");
32
+ const DEFAULT_SCANNER_COMMAND = "uvx";
33
+ const DEFAULT_SCANNER_ARGS = [
34
+ "--from",
35
+ "mcp-scan>=0.4,<0.5",
36
+ "mcp-scan",
37
+ "--json",
38
+ "--skills"
39
+ ];
40
+ const DEFAULT_BLOCKED_CODES = [
41
+ "MCP501",
42
+ "MCP506",
43
+ "MCP507",
44
+ "MCP508",
45
+ "MCP509",
46
+ "MCP510",
47
+ "MCP511"
48
+ ];
49
+ function getScannerCommand(security) {
50
+ return security?.scannerCommand?.trim() || DEFAULT_SCANNER_COMMAND;
51
+ }
52
+ function getScannerArgs(security) {
53
+ if (Array.isArray(security?.scannerArgs) && security.scannerArgs.length > 0) return security.scannerArgs;
54
+ return DEFAULT_SCANNER_ARGS;
55
+ }
56
+ function getBlockedIssueCodes(security) {
57
+ const configured = security?.blockIssueCodes || DEFAULT_BLOCKED_CODES;
58
+ return new Set(configured.map((code)=>code.trim().toUpperCase()).filter(Boolean));
59
+ }
60
+ function parseScanResult(stdout) {
61
+ try {
62
+ return JSON.parse(stdout);
63
+ } catch {
64
+ const firstBrace = stdout.indexOf("{");
65
+ const lastBrace = stdout.lastIndexOf("}");
66
+ if (-1 === firstBrace || -1 === lastBrace || lastBrace < firstBrace) throw new Error("Scanner output did not include JSON");
67
+ const jsonPayload = stdout.slice(firstBrace, lastBrace + 1);
68
+ return JSON.parse(jsonPayload);
69
+ }
70
+ }
71
+ async function runCommand(command, args) {
72
+ return await new Promise((resolve, reject)=>{
73
+ const child = (0, external_node_child_process_namespaceObject.spawn)(command, args, {
74
+ stdio: [
75
+ "ignore",
76
+ "pipe",
77
+ "pipe"
78
+ ]
79
+ });
80
+ let stdout = "";
81
+ let stderr = "";
82
+ child.stdout.on("data", (chunk)=>{
83
+ stdout += chunk.toString();
84
+ });
85
+ child.stderr.on("data", (chunk)=>{
86
+ stderr += chunk.toString();
87
+ });
88
+ child.on("error", reject);
89
+ child.on("close", (exitCode)=>{
90
+ resolve({
91
+ exitCode,
92
+ stdout,
93
+ stderr
94
+ });
95
+ });
96
+ });
97
+ }
98
+ async function scanSkillDirectory(skillPath, logger, security) {
99
+ const scanOnInstall = security?.scanOnInstall ?? true;
100
+ if (!scanOnInstall) return;
101
+ const command = getScannerCommand(security);
102
+ (0, uv_cjs_namespaceObject.ensureUvAvailableForFeature)(command, "skills.security.scanOnInstall");
103
+ const args = [
104
+ ...getScannerArgs(security),
105
+ skillPath
106
+ ];
107
+ logger.info(`Running skill security scan: ${command} ${args.join(" ")}`);
108
+ const result = await runCommand(command, args);
109
+ if (0 !== result.exitCode) {
110
+ const details = result.stderr.trim() || result.stdout.trim();
111
+ throw new Error(`Skill security scan failed with exit code ${result.exitCode ?? "unknown"}${details ? `: ${details}` : ""}`);
112
+ }
113
+ const parsed = parseScanResult(result.stdout);
114
+ const failedPaths = Object.entries(parsed).filter(([, value])=>Boolean(value.error && false !== value.error.is_failure));
115
+ if (failedPaths.length > 0) {
116
+ const formatted = failedPaths.map(([path, value])=>{
117
+ const category = value.error?.category ? ` (${value.error.category})` : "";
118
+ return `${path}: ${value.error?.message || "unknown scan error"}${category}`;
119
+ }).join("; ");
120
+ throw new Error(`Skill security scan reported errors: ${formatted}`);
121
+ }
122
+ const blockedCodes = getBlockedIssueCodes(security);
123
+ const blockingIssues = [];
124
+ const nonBlockingIssues = [];
125
+ for (const value of Object.values(parsed))for (const issue of value.issues || []){
126
+ const code = (issue.code || "").trim().toUpperCase();
127
+ if (!code) continue;
128
+ const issueDetails = {
129
+ code,
130
+ message: issue.message || ""
131
+ };
132
+ if (blockedCodes.has(code)) blockingIssues.push(issueDetails);
133
+ else nonBlockingIssues.push(issueDetails);
134
+ }
135
+ if (nonBlockingIssues.length > 0) {
136
+ const codes = Array.from(new Set(nonBlockingIssues.map((issue)=>issue.code)));
137
+ logger.warn(`Skill security scan returned non-blocking issues: ${codes.join(", ")}`);
138
+ }
139
+ if (blockingIssues.length > 0) {
140
+ const codes = Array.from(new Set(blockingIssues.map((issue)=>issue.code)));
141
+ throw new Error(`Skill security scan blocked installation due to issue codes: ${codes.join(", ")}`);
142
+ }
143
+ }
144
+ const __skillSecurityScanner = {
145
+ parseScanResult,
146
+ getScannerArgs,
147
+ getScannerCommand,
148
+ getBlockedIssueCodes
149
+ };
150
+ exports.__skillSecurityScanner = __webpack_exports__.__skillSecurityScanner;
151
+ exports.scanSkillDirectory = __webpack_exports__.scanSkillDirectory;
152
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
153
+ "__skillSecurityScanner",
154
+ "scanSkillDirectory"
155
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
156
+ Object.defineProperty(exports, '__esModule', {
157
+ value: true
158
+ });
@@ -0,0 +1,28 @@
1
+ import type { Logger } from "@/logger.js";
2
+ import type { SkillSecurityOptions } from "../types/skill.js";
3
+ type ScanIssue = {
4
+ code?: string;
5
+ message?: string;
6
+ };
7
+ type ScanError = {
8
+ message?: string;
9
+ is_failure?: boolean;
10
+ category?: string;
11
+ };
12
+ type ScanPathResult = {
13
+ issues?: ScanIssue[];
14
+ error?: ScanError | null;
15
+ };
16
+ type ScanResultMap = Record<string, ScanPathResult>;
17
+ declare function getScannerCommand(security?: SkillSecurityOptions): string;
18
+ declare function getScannerArgs(security?: SkillSecurityOptions): string[];
19
+ declare function getBlockedIssueCodes(security?: SkillSecurityOptions): Set<string>;
20
+ declare function parseScanResult(stdout: string): ScanResultMap;
21
+ export declare function scanSkillDirectory(skillPath: string, logger: Logger, security?: SkillSecurityOptions): Promise<void>;
22
+ export declare const __skillSecurityScanner: {
23
+ parseScanResult: typeof parseScanResult;
24
+ getScannerArgs: typeof getScannerArgs;
25
+ getScannerCommand: typeof getScannerCommand;
26
+ getBlockedIssueCodes: typeof getBlockedIssueCodes;
27
+ };
28
+ export {};