skillli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1025 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ SkillMetadataSchema: () => SkillMetadataSchema,
34
+ VERSION: () => VERSION,
35
+ computeTrustScore: () => computeTrustScore,
36
+ createSkillliMcpServer: () => createSkillliMcpServer,
37
+ extractManifest: () => extractManifest,
38
+ fetchIndex: () => fetchIndex,
39
+ formatRating: () => formatRating,
40
+ getConfig: () => getConfig,
41
+ getInstalledSkills: () => getInstalledSkills,
42
+ getLocalIndex: () => getLocalIndex,
43
+ getRatings: () => getRatings,
44
+ getSkillEntry: () => getSkillEntry,
45
+ installFromGithub: () => installFromGithub,
46
+ installFromLocal: () => installFromLocal,
47
+ installFromRegistry: () => installFromRegistry,
48
+ linkToClaudeSkills: () => linkToClaudeSkills,
49
+ packageSkill: () => packageSkill,
50
+ parseSkillContent: () => parseSkillContent,
51
+ parseSkillFile: () => parseSkillFile,
52
+ runSafeguards: () => runSafeguards,
53
+ search: () => search,
54
+ searchByCategory: () => searchByCategory,
55
+ searchByTags: () => searchByTags,
56
+ submitRating: () => submitRating,
57
+ syncIndex: () => syncIndex,
58
+ trawl: () => trawl,
59
+ uninstall: () => uninstall,
60
+ validateMetadata: () => validateMetadata
61
+ });
62
+ module.exports = __toCommonJS(src_exports);
63
+
64
+ // src/core/parser.ts
65
+ var import_promises = require("fs/promises");
66
+ var import_gray_matter = __toESM(require("gray-matter"), 1);
67
+
68
+ // src/core/schema.ts
69
+ var import_zod = require("zod");
70
+ var SkillCategorySchema = import_zod.z.enum([
71
+ "development",
72
+ "creative",
73
+ "enterprise",
74
+ "data",
75
+ "devops",
76
+ "other"
77
+ ]);
78
+ var TrustLevelSchema = import_zod.z.enum(["community", "verified", "official"]);
79
+ var SkillMetadataSchema = import_zod.z.object({
80
+ name: import_zod.z.string().min(1).max(100).regex(/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/, "Must be lowercase alphanumeric with hyphens, not starting/ending with hyphen"),
81
+ version: import_zod.z.string().regex(/^\d+\.\d+\.\d+$/, "Must be valid semver (e.g. 1.0.0)"),
82
+ description: import_zod.z.string().min(10).max(500),
83
+ author: import_zod.z.string().min(1),
84
+ license: import_zod.z.string().min(1),
85
+ tags: import_zod.z.array(import_zod.z.string().min(1).max(50)).min(1).max(20),
86
+ category: SkillCategorySchema,
87
+ repository: import_zod.z.string().url().optional(),
88
+ homepage: import_zod.z.string().url().optional(),
89
+ "min-skillli-version": import_zod.z.string().optional(),
90
+ "trust-level": TrustLevelSchema.default("community"),
91
+ checksum: import_zod.z.string().optional(),
92
+ "disable-model-invocation": import_zod.z.boolean().default(false),
93
+ "user-invocable": import_zod.z.boolean().default(true)
94
+ });
95
+
96
+ // src/core/errors.ts
97
+ var SkillliError = class extends Error {
98
+ constructor(message) {
99
+ super(message);
100
+ this.name = "SkillliError";
101
+ }
102
+ };
103
+ var SkillValidationError = class extends SkillliError {
104
+ details;
105
+ constructor(message, details = []) {
106
+ super(message);
107
+ this.name = "SkillValidationError";
108
+ this.details = details;
109
+ }
110
+ };
111
+ var SkillNotFoundError = class extends SkillliError {
112
+ constructor(skillName) {
113
+ super(`Skill not found: ${skillName}`);
114
+ this.name = "SkillNotFoundError";
115
+ }
116
+ };
117
+ var RegistryError = class extends SkillliError {
118
+ constructor(message) {
119
+ super(message);
120
+ this.name = "RegistryError";
121
+ }
122
+ };
123
+ var InstallError = class extends SkillliError {
124
+ constructor(message) {
125
+ super(message);
126
+ this.name = "InstallError";
127
+ }
128
+ };
129
+
130
+ // src/core/parser.ts
131
+ function normalizeMetadata(raw) {
132
+ return {
133
+ name: raw.name,
134
+ version: raw.version,
135
+ description: raw.description,
136
+ author: raw.author,
137
+ license: raw.license,
138
+ tags: raw.tags,
139
+ category: raw.category,
140
+ repository: raw.repository,
141
+ homepage: raw.homepage,
142
+ minSkillliVersion: raw["min-skillli-version"],
143
+ trustLevel: raw["trust-level"],
144
+ checksum: raw.checksum,
145
+ disableModelInvocation: raw["disable-model-invocation"],
146
+ userInvocable: raw["user-invocable"]
147
+ };
148
+ }
149
+ function validateMetadata(data) {
150
+ const result = SkillMetadataSchema.safeParse(data);
151
+ if (!result.success) {
152
+ const details = result.error.issues.map(
153
+ (issue) => `${issue.path.join(".")}: ${issue.message}`
154
+ );
155
+ throw new SkillValidationError("Invalid skill metadata", details);
156
+ }
157
+ return normalizeMetadata(result.data);
158
+ }
159
+ function parseSkillContent(content, filePath = "<inline>") {
160
+ const { data, content: body, matter: rawFrontmatter } = (0, import_gray_matter.default)(content);
161
+ const metadata = validateMetadata(data);
162
+ return {
163
+ metadata,
164
+ content: body.trim(),
165
+ rawFrontmatter: rawFrontmatter || "",
166
+ filePath
167
+ };
168
+ }
169
+ async function parseSkillFile(filePath) {
170
+ const content = await (0, import_promises.readFile)(filePath, "utf-8");
171
+ return parseSkillContent(content, filePath);
172
+ }
173
+ function extractManifest(skill) {
174
+ const { metadata } = skill;
175
+ return {
176
+ name: metadata.name,
177
+ version: metadata.version,
178
+ description: metadata.description,
179
+ author: metadata.author,
180
+ license: metadata.license,
181
+ tags: metadata.tags,
182
+ category: metadata.category,
183
+ repository: metadata.repository,
184
+ trust_level: metadata.trustLevel,
185
+ checksum: metadata.checksum,
186
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
187
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
188
+ };
189
+ }
190
+
191
+ // src/core/search.ts
192
+ function tokenize(text) {
193
+ return text.toLowerCase().split(/[\s\-_,./]+/).filter((t) => t.length > 1);
194
+ }
195
+ function textScore(tokens, text) {
196
+ const lower = text.toLowerCase();
197
+ let score = 0;
198
+ for (const token of tokens) {
199
+ if (lower.includes(token)) score += 1;
200
+ }
201
+ return score;
202
+ }
203
+ function scoreSkill(entry, queryTokens, options) {
204
+ let score = 0;
205
+ const matchedOn = [];
206
+ const nameScore = textScore(queryTokens, entry.name);
207
+ if (nameScore > 0) {
208
+ score += nameScore * 5;
209
+ matchedOn.push("name");
210
+ }
211
+ if (entry.name === options.query.toLowerCase()) {
212
+ score += 10;
213
+ }
214
+ const descScore = textScore(queryTokens, entry.description);
215
+ if (descScore > 0) {
216
+ score += descScore * 2;
217
+ matchedOn.push("description");
218
+ }
219
+ for (const token of queryTokens) {
220
+ if (entry.tags.some((t) => t.toLowerCase() === token)) {
221
+ score += 4;
222
+ if (!matchedOn.includes("tags")) matchedOn.push("tags");
223
+ }
224
+ }
225
+ if (options.tags) {
226
+ for (const tag of options.tags) {
227
+ if (entry.tags.includes(tag)) {
228
+ score += 3;
229
+ if (!matchedOn.includes("tags")) matchedOn.push("tags");
230
+ }
231
+ }
232
+ }
233
+ if (options.category && entry.category === options.category) {
234
+ score += 2;
235
+ matchedOn.push("category");
236
+ }
237
+ if (matchedOn.length > 0) {
238
+ if (entry.trustLevel === "official") score += 3;
239
+ if (entry.trustLevel === "verified") score += 1.5;
240
+ score += entry.rating.average * 0.5;
241
+ if (entry.downloads > 0) {
242
+ score += Math.log10(entry.downloads) * 0.5;
243
+ }
244
+ }
245
+ return { score, matchedOn };
246
+ }
247
+ function search(index, options) {
248
+ const queryTokens = tokenize(options.query);
249
+ const results = [];
250
+ for (const entry of Object.values(index.skills)) {
251
+ if (options.category && entry.category !== options.category) continue;
252
+ if (options.trustLevel && entry.trustLevel !== options.trustLevel) continue;
253
+ if (options.minRating && entry.rating.average < options.minRating) continue;
254
+ const { score, matchedOn } = scoreSkill(entry, queryTokens, options);
255
+ if (score > 0) {
256
+ results.push({ skill: entry, relevanceScore: score, matchedOn });
257
+ }
258
+ }
259
+ results.sort((a, b) => b.relevanceScore - a.relevanceScore);
260
+ const offset = options.offset ?? 0;
261
+ const limit = options.limit ?? 20;
262
+ return results.slice(offset, offset + limit);
263
+ }
264
+ function searchByTags(index, tags) {
265
+ return search(index, { query: tags.join(" "), tags });
266
+ }
267
+ function searchByCategory(index, category) {
268
+ return search(index, { query: "", category });
269
+ }
270
+
271
+ // src/core/installer.ts
272
+ var import_promises4 = require("fs/promises");
273
+ var import_fs3 = require("fs");
274
+ var import_path3 = require("path");
275
+ var import_child_process = require("child_process");
276
+
277
+ // src/core/constants.ts
278
+ var import_os = require("os");
279
+ var import_path = require("path");
280
+ var SKILLLI_DIR = (0, import_path.join)((0, import_os.homedir)(), ".skillli");
281
+ var LOCAL_INDEX_PATH = (0, import_path.join)(SKILLLI_DIR, "index.json");
282
+ var CONFIG_PATH = (0, import_path.join)(SKILLLI_DIR, "config.json");
283
+ var SKILLS_DIR = (0, import_path.join)(SKILLLI_DIR, "skills");
284
+ var CACHE_DIR = (0, import_path.join)(SKILLLI_DIR, "cache");
285
+ var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/skillli/registry/main/index.json";
286
+ var SKILL_FILENAME = "SKILL.md";
287
+ var MAX_SKILL_SIZE_BYTES = 5 * 1024 * 1024;
288
+ var MAX_SKILL_MD_LINES = 500;
289
+ var VERSION = "0.1.0";
290
+
291
+ // src/core/local-store.ts
292
+ var import_promises2 = require("fs/promises");
293
+ var import_fs = require("fs");
294
+ async function ensureDir() {
295
+ for (const dir of [SKILLLI_DIR, SKILLS_DIR, CACHE_DIR]) {
296
+ if (!(0, import_fs.existsSync)(dir)) {
297
+ await (0, import_promises2.mkdir)(dir, { recursive: true });
298
+ }
299
+ }
300
+ }
301
+ function defaultConfig() {
302
+ return {
303
+ installedSkills: {},
304
+ registryUrl: DEFAULT_REGISTRY_URL,
305
+ lastSync: ""
306
+ };
307
+ }
308
+ function defaultIndex() {
309
+ return {
310
+ version: "1.0.0",
311
+ lastUpdated: "",
312
+ skills: {}
313
+ };
314
+ }
315
+ async function getConfig() {
316
+ await ensureDir();
317
+ if (!(0, import_fs.existsSync)(CONFIG_PATH)) {
318
+ const config = defaultConfig();
319
+ await saveConfig(config);
320
+ return config;
321
+ }
322
+ const raw = await (0, import_promises2.readFile)(CONFIG_PATH, "utf-8");
323
+ return JSON.parse(raw);
324
+ }
325
+ async function saveConfig(config) {
326
+ await ensureDir();
327
+ await (0, import_promises2.writeFile)(CONFIG_PATH, JSON.stringify(config, null, 2));
328
+ }
329
+ async function getLocalIndex() {
330
+ await ensureDir();
331
+ if (!(0, import_fs.existsSync)(LOCAL_INDEX_PATH)) {
332
+ const index = defaultIndex();
333
+ await saveLocalIndex(index);
334
+ return index;
335
+ }
336
+ const raw = await (0, import_promises2.readFile)(LOCAL_INDEX_PATH, "utf-8");
337
+ return JSON.parse(raw);
338
+ }
339
+ async function saveLocalIndex(index) {
340
+ await ensureDir();
341
+ await (0, import_promises2.writeFile)(LOCAL_INDEX_PATH, JSON.stringify(index, null, 2));
342
+ }
343
+ async function getInstalledSkills() {
344
+ const config = await getConfig();
345
+ return Object.values(config.installedSkills);
346
+ }
347
+ async function markInstalled(skill) {
348
+ const config = await getConfig();
349
+ config.installedSkills[skill.name] = skill;
350
+ await saveConfig(config);
351
+ }
352
+ async function markUninstalled(name) {
353
+ const config = await getConfig();
354
+ delete config.installedSkills[name];
355
+ await saveConfig(config);
356
+ }
357
+
358
+ // src/core/registry.ts
359
+ async function fetchIndex(registryUrl) {
360
+ const config = await getConfig();
361
+ const url = registryUrl ?? config.registryUrl ?? DEFAULT_REGISTRY_URL;
362
+ try {
363
+ const res = await fetch(url);
364
+ if (!res.ok) {
365
+ throw new RegistryError(`Failed to fetch registry: ${res.status} ${res.statusText}`);
366
+ }
367
+ const index = await res.json();
368
+ index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
369
+ await saveLocalIndex(index);
370
+ return index;
371
+ } catch (error) {
372
+ if (error instanceof RegistryError) throw error;
373
+ const localIndex = await getLocalIndex();
374
+ if (Object.keys(localIndex.skills).length > 0) {
375
+ return localIndex;
376
+ }
377
+ throw new RegistryError(`Cannot reach registry at ${url}: ${error}`);
378
+ }
379
+ }
380
+ async function getSkillEntry(name) {
381
+ const index = await getLocalIndex();
382
+ const entry = index.skills[name];
383
+ if (!entry) {
384
+ throw new SkillNotFoundError(name);
385
+ }
386
+ return entry;
387
+ }
388
+ async function syncIndex() {
389
+ return fetchIndex();
390
+ }
391
+
392
+ // src/core/safeguards.ts
393
+ var import_promises3 = require("fs/promises");
394
+ var import_path2 = require("path");
395
+ var import_fs2 = require("fs");
396
+ var PROHIBITED_PATTERNS = [
397
+ { pattern: /\beval\s*\(/, label: "eval()" },
398
+ { pattern: /\bexec\s*\(/, label: "exec()" },
399
+ { pattern: /\bexecSync\s*\(/, label: "execSync()" },
400
+ { pattern: /rm\s+-rf\s+\//, label: "rm -rf /" },
401
+ { pattern: /\bchild_process\b/, label: "child_process" },
402
+ { pattern: /\bProcess\.kill\b/i, label: "Process.kill" },
403
+ { pattern: /password\s*[:=]\s*['"][^'"]+['"]/, label: "hardcoded password" },
404
+ { pattern: /api[_-]?key\s*[:=]\s*['"][^'"]+['"]/, label: "hardcoded API key" },
405
+ { pattern: /[A-Za-z0-9+/]{100,}={0,2}/, label: "large base64 blob" }
406
+ ];
407
+ var ALLOWED_SCRIPT_EXTENSIONS = [".sh", ".py", ".js", ".ts"];
408
+ function checkSchema(skill) {
409
+ return {
410
+ name: "schema-validation",
411
+ passed: true,
412
+ severity: "info",
413
+ message: "SKILL.md metadata is valid"
414
+ };
415
+ }
416
+ function checkLineCount(content) {
417
+ const lines = content.split("\n").length;
418
+ const passed = lines <= MAX_SKILL_MD_LINES;
419
+ return {
420
+ name: "line-count",
421
+ passed,
422
+ severity: passed ? "info" : "warning",
423
+ message: passed ? `SKILL.md has ${lines} lines (max ${MAX_SKILL_MD_LINES})` : `SKILL.md has ${lines} lines, exceeds max of ${MAX_SKILL_MD_LINES}`
424
+ };
425
+ }
426
+ function checkProhibitedPatterns(content) {
427
+ const found = [];
428
+ for (const { pattern, label } of PROHIBITED_PATTERNS) {
429
+ if (pattern.test(content)) {
430
+ found.push(label);
431
+ }
432
+ }
433
+ const passed = found.length === 0;
434
+ return {
435
+ name: "prohibited-patterns",
436
+ passed,
437
+ severity: passed ? "info" : "error",
438
+ message: passed ? "No prohibited patterns detected" : `Prohibited patterns found: ${found.join(", ")}`
439
+ };
440
+ }
441
+ function checkScriptSafety(skillDir) {
442
+ const scriptsDir = (0, import_path2.join)(skillDir, "scripts");
443
+ if (!(0, import_fs2.existsSync)(scriptsDir)) {
444
+ return {
445
+ name: "script-safety",
446
+ passed: true,
447
+ severity: "info",
448
+ message: "No scripts directory found"
449
+ };
450
+ }
451
+ const badFiles = [];
452
+ const files = (0, import_fs2.readdirSync)(scriptsDir, { recursive: true });
453
+ for (const file of files) {
454
+ const ext = "." + file.split(".").pop();
455
+ if (!ALLOWED_SCRIPT_EXTENSIONS.includes(ext)) {
456
+ badFiles.push(file);
457
+ }
458
+ }
459
+ const passed = badFiles.length === 0;
460
+ return {
461
+ name: "script-safety",
462
+ passed,
463
+ severity: passed ? "info" : "error",
464
+ message: passed ? "All scripts use allowed extensions" : `Disallowed script types: ${badFiles.join(", ")}`
465
+ };
466
+ }
467
+ async function checkFileSize(skillDir) {
468
+ let totalSize = 0;
469
+ const entries = (0, import_fs2.readdirSync)(skillDir, { recursive: true, withFileTypes: true });
470
+ for (const entry of entries) {
471
+ if (entry.isFile()) {
472
+ const fullPath = (0, import_path2.join)(entry.parentPath ?? skillDir, entry.name);
473
+ const s = await (0, import_promises3.stat)(fullPath);
474
+ totalSize += s.size;
475
+ }
476
+ }
477
+ const passed = totalSize <= MAX_SKILL_SIZE_BYTES;
478
+ return {
479
+ name: "file-size",
480
+ passed,
481
+ severity: passed ? "info" : "warning",
482
+ message: passed ? `Total size: ${(totalSize / 1024).toFixed(1)}KB (max ${MAX_SKILL_SIZE_BYTES / 1024 / 1024}MB)` : `Total size ${(totalSize / 1024 / 1024).toFixed(1)}MB exceeds max of ${MAX_SKILL_SIZE_BYTES / 1024 / 1024}MB`
483
+ };
484
+ }
485
+ async function runSafeguards(skill, skillDir) {
486
+ const checks = [];
487
+ checks.push(checkSchema(skill));
488
+ checks.push(checkLineCount(skill.content));
489
+ checks.push(checkProhibitedPatterns(skill.content + skill.rawFrontmatter));
490
+ if (skillDir) {
491
+ checks.push(checkScriptSafety(skillDir));
492
+ checks.push(await checkFileSize(skillDir));
493
+ }
494
+ const passed = checks.every((c) => c.passed || c.severity !== "error");
495
+ const score = computeTrustScore(skill);
496
+ return { passed, score, checks };
497
+ }
498
+ function computeTrustScore(skill, registryEntry) {
499
+ let score = 0;
500
+ if (skill.metadata.repository) score += 10;
501
+ if (skill.metadata.license) score += 10;
502
+ if (skill.metadata.trustLevel === "verified") score += 15;
503
+ if (skill.metadata.trustLevel === "official") score += 20;
504
+ if (registryEntry && registryEntry.rating.average >= 3.5) score += 15;
505
+ if (registryEntry && registryEntry.downloads > 100) score += 5;
506
+ if (registryEntry && registryEntry.downloads > 1e3) score += 5;
507
+ const patternCheck = checkProhibitedPatterns(skill.content);
508
+ const lineCheck = checkLineCount(skill.content);
509
+ if (patternCheck.passed) score += 20;
510
+ if (lineCheck.passed) score += 15;
511
+ return Math.min(score, 100);
512
+ }
513
+
514
+ // src/core/installer.ts
515
+ async function installFromRegistry(name, version) {
516
+ const entry = await getSkillEntry(name);
517
+ if (!entry.repository) {
518
+ throw new InstallError(`Skill "${name}" has no repository URL`);
519
+ }
520
+ return installFromGithub(entry.repository, name);
521
+ }
522
+ async function installFromGithub(repoUrl, nameOverride) {
523
+ const name = nameOverride ?? repoUrl.split("/").pop()?.replace(/\.git$/, "") ?? "unknown";
524
+ const installPath = (0, import_path3.join)(SKILLS_DIR, name);
525
+ if ((0, import_fs3.existsSync)(installPath)) {
526
+ await (0, import_promises4.rm)(installPath, { recursive: true });
527
+ }
528
+ await (0, import_promises4.mkdir)(installPath, { recursive: true });
529
+ try {
530
+ (0, import_child_process.execSync)(`git clone --depth 1 "${repoUrl}" "${installPath}"`, {
531
+ stdio: "pipe",
532
+ timeout: 3e4
533
+ });
534
+ } catch {
535
+ throw new InstallError(`Failed to clone ${repoUrl}`);
536
+ }
537
+ const skillFile = (0, import_path3.join)(installPath, SKILL_FILENAME);
538
+ if (!(0, import_fs3.existsSync)(skillFile)) {
539
+ await (0, import_promises4.rm)(installPath, { recursive: true });
540
+ throw new InstallError(`No ${SKILL_FILENAME} found in ${repoUrl}`);
541
+ }
542
+ const skill = await parseSkillFile(skillFile);
543
+ const safeguards = await runSafeguards(skill, installPath);
544
+ if (!safeguards.passed) {
545
+ await (0, import_promises4.rm)(installPath, { recursive: true });
546
+ const errors = safeguards.checks.filter((c) => !c.passed).map((c) => c.message);
547
+ throw new InstallError(`Skill failed safety checks: ${errors.join("; ")}`);
548
+ }
549
+ const installed = {
550
+ name: skill.metadata.name,
551
+ version: skill.metadata.version,
552
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
553
+ path: installPath,
554
+ source: "github"
555
+ };
556
+ await markInstalled(installed);
557
+ return installed;
558
+ }
559
+ async function installFromLocal(dirPath) {
560
+ const skillFile = (0, import_path3.join)(dirPath, SKILL_FILENAME);
561
+ if (!(0, import_fs3.existsSync)(skillFile)) {
562
+ throw new InstallError(`No ${SKILL_FILENAME} found in ${dirPath}`);
563
+ }
564
+ const skill = await parseSkillFile(skillFile);
565
+ const safeguards = await runSafeguards(skill, dirPath);
566
+ if (!safeguards.passed) {
567
+ const errors = safeguards.checks.filter((c) => !c.passed).map((c) => c.message);
568
+ throw new InstallError(`Skill failed safety checks: ${errors.join("; ")}`);
569
+ }
570
+ const installPath = (0, import_path3.join)(SKILLS_DIR, skill.metadata.name);
571
+ if ((0, import_fs3.existsSync)(installPath)) {
572
+ await (0, import_promises4.rm)(installPath, { recursive: true });
573
+ }
574
+ (0, import_child_process.execSync)(`cp -r "${dirPath}" "${installPath}"`, { stdio: "pipe" });
575
+ const installed = {
576
+ name: skill.metadata.name,
577
+ version: skill.metadata.version,
578
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
579
+ path: installPath,
580
+ source: "local"
581
+ };
582
+ await markInstalled(installed);
583
+ return installed;
584
+ }
585
+ async function uninstall(name) {
586
+ const installPath = (0, import_path3.join)(SKILLS_DIR, name);
587
+ if ((0, import_fs3.existsSync)(installPath)) {
588
+ await (0, import_promises4.rm)(installPath, { recursive: true });
589
+ }
590
+ await markUninstalled(name);
591
+ }
592
+ async function linkToClaudeSkills(skill, projectDir = process.cwd()) {
593
+ const claudeSkillsDir = (0, import_path3.join)(projectDir, ".claude", "skills");
594
+ await (0, import_promises4.mkdir)(claudeSkillsDir, { recursive: true });
595
+ const linkPath = (0, import_path3.join)(claudeSkillsDir, skill.name);
596
+ if ((0, import_fs3.existsSync)(linkPath)) {
597
+ await (0, import_promises4.rm)(linkPath, { recursive: true });
598
+ }
599
+ await (0, import_promises4.symlink)(skill.path, linkPath, "dir");
600
+ return linkPath;
601
+ }
602
+
603
+ // src/core/ratings.ts
604
+ async function getRatings(name) {
605
+ const index = await getLocalIndex();
606
+ const entry = index.skills[name];
607
+ if (!entry) {
608
+ throw new SkillNotFoundError(name);
609
+ }
610
+ return entry.rating;
611
+ }
612
+ async function submitRating(name, rating, userId, comment) {
613
+ const index = await getLocalIndex();
614
+ const entry = index.skills[name];
615
+ if (!entry) {
616
+ throw new SkillNotFoundError(name);
617
+ }
618
+ const current = entry.rating;
619
+ const newCount = current.count + 1;
620
+ const newAverage = (current.average * current.count + rating) / newCount;
621
+ const dist = [...current.distribution];
622
+ dist[rating - 1] += 1;
623
+ entry.rating = {
624
+ average: Math.round(newAverage * 10) / 10,
625
+ count: newCount,
626
+ distribution: dist
627
+ };
628
+ await saveLocalIndex(index);
629
+ return entry.rating;
630
+ }
631
+ function formatRating(rating) {
632
+ const filled = Math.round(rating.average);
633
+ const empty = 5 - filled;
634
+ const stars = "\u2605".repeat(filled) + "\u2606".repeat(empty);
635
+ return `${stars} ${rating.average.toFixed(1)} (${rating.count} ratings)`;
636
+ }
637
+
638
+ // src/core/publisher.ts
639
+ var import_promises5 = require("fs/promises");
640
+ var import_path4 = require("path");
641
+ var import_crypto = require("crypto");
642
+ var import_fs4 = require("fs");
643
+ async function packageSkill(skillDir) {
644
+ const skillFile = (0, import_path4.join)(skillDir, SKILL_FILENAME);
645
+ if (!(0, import_fs4.existsSync)(skillFile)) {
646
+ throw new SkillValidationError(`No ${SKILL_FILENAME} found in ${skillDir}`);
647
+ }
648
+ const skill = await parseSkillFile(skillFile);
649
+ const safeguards = await runSafeguards(skill, skillDir);
650
+ if (!safeguards.passed) {
651
+ const errors = safeguards.checks.filter((c) => !c.passed).map((c) => `[${c.severity}] ${c.message}`);
652
+ throw new SkillValidationError("Skill failed safety checks", errors);
653
+ }
654
+ const checksum = await computeDirectoryChecksum(skillDir);
655
+ const manifest = extractManifest(skill);
656
+ manifest.checksum = `sha256:${checksum}`;
657
+ manifest.files = listFiles(skillDir);
658
+ manifest.size_bytes = computeDirectorySize(skillDir);
659
+ return { skill, manifest, checksum };
660
+ }
661
+ async function computeDirectoryChecksum(dir) {
662
+ const hash = (0, import_crypto.createHash)("sha256");
663
+ const files = listFiles(dir).sort();
664
+ for (const file of files) {
665
+ const content = await (0, import_promises5.readFile)((0, import_path4.join)(dir, file));
666
+ hash.update(file);
667
+ hash.update(content);
668
+ }
669
+ return hash.digest("hex");
670
+ }
671
+ function listFiles(dir, prefix = "") {
672
+ const files = [];
673
+ const entries = (0, import_fs4.readdirSync)(dir, { withFileTypes: true });
674
+ for (const entry of entries) {
675
+ if (entry.name === ".git" || entry.name === "node_modules") continue;
676
+ const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
677
+ if (entry.isDirectory()) {
678
+ files.push(...listFiles((0, import_path4.join)(dir, entry.name), relative));
679
+ } else {
680
+ files.push(relative);
681
+ }
682
+ }
683
+ return files;
684
+ }
685
+ function computeDirectorySize(dir) {
686
+ let size = 0;
687
+ const entries = (0, import_fs4.readdirSync)(dir, { withFileTypes: true });
688
+ for (const entry of entries) {
689
+ if (entry.name === ".git" || entry.name === "node_modules") continue;
690
+ const fullPath = (0, import_path4.join)(dir, entry.name);
691
+ if (entry.isDirectory()) {
692
+ size += computeDirectorySize(fullPath);
693
+ } else {
694
+ size += (0, import_fs4.statSync)(fullPath).size;
695
+ }
696
+ }
697
+ return size;
698
+ }
699
+
700
+ // src/trawler/strategies.ts
701
+ async function searchRegistry(query) {
702
+ const index = await getLocalIndex();
703
+ const tokens = query.toLowerCase().split(/\s+/);
704
+ const results = [];
705
+ for (const entry of Object.values(index.skills)) {
706
+ const text = `${entry.name} ${entry.description} ${entry.tags.join(" ")}`.toLowerCase();
707
+ const matchCount = tokens.filter((t) => text.includes(t)).length;
708
+ if (matchCount === 0) continue;
709
+ results.push({
710
+ source: "registry",
711
+ skill: entry,
712
+ confidence: Math.min(matchCount / tokens.length, 1),
713
+ url: entry.repository || `skillli://registry/${entry.name}`
714
+ });
715
+ }
716
+ return results;
717
+ }
718
+ async function searchGithub(query) {
719
+ const results = [];
720
+ const searchQuery = encodeURIComponent(`${query} SKILL.md in:path`);
721
+ const url = `https://api.github.com/search/repositories?q=${searchQuery}&per_page=10`;
722
+ try {
723
+ const res = await fetch(url, {
724
+ headers: { Accept: "application/vnd.github.v3+json" }
725
+ });
726
+ if (!res.ok) return results;
727
+ const data = await res.json();
728
+ for (const repo of data.items ?? []) {
729
+ const confidence = Math.min(
730
+ 0.3 + (repo.stargazers_count > 10 ? 0.2 : 0) + (repo.stargazers_count > 100 ? 0.2 : 0),
731
+ 0.9
732
+ );
733
+ results.push({
734
+ source: "github",
735
+ skill: {
736
+ name: repo.full_name.split("/").pop() ?? repo.full_name,
737
+ description: repo.description ?? "",
738
+ author: repo.full_name.split("/")[0],
739
+ repository: repo.html_url,
740
+ tags: repo.topics ?? []
741
+ },
742
+ confidence,
743
+ url: repo.html_url
744
+ });
745
+ }
746
+ } catch {
747
+ }
748
+ return results;
749
+ }
750
+ async function searchNpm(query) {
751
+ const results = [];
752
+ const searchQuery = encodeURIComponent(`${query} skill agent claude`);
753
+ const url = `https://registry.npmjs.org/-/v1/search?text=${searchQuery}&size=10`;
754
+ try {
755
+ const res = await fetch(url);
756
+ if (!res.ok) return results;
757
+ const data = await res.json();
758
+ for (const obj of data.objects ?? []) {
759
+ const pkg = obj.package;
760
+ results.push({
761
+ source: "npm",
762
+ skill: {
763
+ name: pkg.name,
764
+ description: pkg.description ?? "",
765
+ version: pkg.version,
766
+ repository: pkg.links.repository ?? "",
767
+ tags: pkg.keywords ?? []
768
+ },
769
+ confidence: Math.min(obj.score.final * 0.7, 0.85),
770
+ url: pkg.links.npm
771
+ });
772
+ }
773
+ } catch {
774
+ }
775
+ return results;
776
+ }
777
+
778
+ // src/trawler/ranker.ts
779
+ function deduplicateResults(results) {
780
+ const seen = /* @__PURE__ */ new Map();
781
+ for (const result of results) {
782
+ const key = result.skill.name?.toLowerCase() ?? result.url;
783
+ const existing = seen.get(key);
784
+ if (!existing || result.confidence > existing.confidence) {
785
+ seen.set(key, result);
786
+ }
787
+ }
788
+ return Array.from(seen.values());
789
+ }
790
+ function rankResults(results, query) {
791
+ const tokens = query.toLowerCase().split(/\s+/);
792
+ return results.map((result) => {
793
+ let bonus = 0;
794
+ if (result.source === "registry") bonus += 0.2;
795
+ else if (result.source === "github") bonus += 0.05;
796
+ const name = (result.skill.name ?? "").toLowerCase();
797
+ for (const token of tokens) {
798
+ if (name.includes(token)) bonus += 0.15;
799
+ }
800
+ const tags = (result.skill.tags ?? []).map((t) => t.toLowerCase());
801
+ for (const token of tokens) {
802
+ if (tags.includes(token)) bonus += 0.1;
803
+ }
804
+ return {
805
+ ...result,
806
+ confidence: Math.min(result.confidence + bonus, 1)
807
+ };
808
+ }).sort((a, b) => b.confidence - a.confidence);
809
+ }
810
+
811
+ // src/trawler/index.ts
812
+ async function trawl(query, options = {}) {
813
+ const sources = options.sources ?? ["registry", "github"];
814
+ const maxResults = options.maxResults ?? 10;
815
+ const searches = [];
816
+ if (sources.includes("registry")) {
817
+ searches.push(searchRegistry(query));
818
+ }
819
+ if (sources.includes("github")) {
820
+ searches.push(searchGithub(query));
821
+ }
822
+ if (sources.includes("npm")) {
823
+ searches.push(searchNpm(query));
824
+ }
825
+ const allResults = (await Promise.all(searches)).flat();
826
+ const deduplicated = deduplicateResults(allResults);
827
+ const ranked = rankResults(deduplicated, query);
828
+ return ranked.slice(0, maxResults);
829
+ }
830
+
831
+ // src/mcp/server.ts
832
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
833
+ var import_zod2 = require("zod");
834
+ function createSkillliMcpServer() {
835
+ const server = new import_mcp.McpServer({
836
+ name: "skillli",
837
+ version: "0.1.0"
838
+ });
839
+ server.tool(
840
+ "search_skills",
841
+ "Search the skillli registry for agentic AI skills",
842
+ {
843
+ query: import_zod2.z.string().describe("Search query"),
844
+ tags: import_zod2.z.array(import_zod2.z.string()).optional().describe("Filter by tags"),
845
+ category: import_zod2.z.string().optional().describe("Filter by category"),
846
+ limit: import_zod2.z.number().optional().default(10).describe("Max results")
847
+ },
848
+ async ({ query, tags, category, limit }) => {
849
+ const index = await getLocalIndex();
850
+ const results = search(index, {
851
+ query,
852
+ tags,
853
+ category,
854
+ limit
855
+ });
856
+ const text = results.length === 0 ? "No skills found matching your query." : results.map((r) => {
857
+ const s = r.skill;
858
+ return `**${s.name}** v${s.version} [${s.trustLevel.toUpperCase()}]
859
+ ${s.description}
860
+ Rating: ${s.rating.average}/5 (${s.rating.count}) | Downloads: ${s.downloads}
861
+ Tags: ${s.tags.join(", ")}`;
862
+ }).join("\n\n");
863
+ return { content: [{ type: "text", text }] };
864
+ }
865
+ );
866
+ server.tool(
867
+ "install_skill",
868
+ "Install an agentic skill from the skillli registry",
869
+ {
870
+ name: import_zod2.z.string().describe("Skill name to install"),
871
+ link: import_zod2.z.boolean().optional().default(true).describe("Link to .claude/skills/")
872
+ },
873
+ async ({ name, link }) => {
874
+ try {
875
+ const installed = await installFromRegistry(name);
876
+ let text = `Installed ${installed.name} v${installed.version} at ${installed.path}`;
877
+ if (link) {
878
+ const linkPath = await linkToClaudeSkills(installed);
879
+ text += `
880
+ Linked to ${linkPath}`;
881
+ }
882
+ return { content: [{ type: "text", text }] };
883
+ } catch (error) {
884
+ return { content: [{ type: "text", text: `Install failed: ${error}` }], isError: true };
885
+ }
886
+ }
887
+ );
888
+ server.tool(
889
+ "get_skill_info",
890
+ "Get detailed information about a skill including trust score",
891
+ {
892
+ name: import_zod2.z.string().describe("Skill name")
893
+ },
894
+ async ({ name }) => {
895
+ try {
896
+ const entry = await getSkillEntry(name);
897
+ const text = [
898
+ `**${entry.name}** v${entry.version} [${entry.trustLevel.toUpperCase()}]`,
899
+ `${entry.description}`,
900
+ `Author: ${entry.author}`,
901
+ `Category: ${entry.category}`,
902
+ `Tags: ${entry.tags.join(", ")}`,
903
+ `Rating: ${entry.rating.average}/5 (${entry.rating.count} ratings)`,
904
+ `Downloads: ${entry.downloads}`,
905
+ entry.repository ? `Repository: ${entry.repository}` : null,
906
+ `Published: ${entry.publishedAt}`,
907
+ `Updated: ${entry.updatedAt}`
908
+ ].filter(Boolean).join("\n");
909
+ return { content: [{ type: "text", text }] };
910
+ } catch (error) {
911
+ return { content: [{ type: "text", text: `Error: ${error}` }], isError: true };
912
+ }
913
+ }
914
+ );
915
+ server.tool(
916
+ "trawl_skills",
917
+ "Agentic search across multiple sources (registry, GitHub, npm) for skills matching a need",
918
+ {
919
+ query: import_zod2.z.string().describe("What kind of skill you need"),
920
+ sources: import_zod2.z.array(import_zod2.z.enum(["registry", "github", "npm"])).optional().default(["registry", "github"]),
921
+ maxResults: import_zod2.z.number().optional().default(5)
922
+ },
923
+ async ({ query, sources, maxResults }) => {
924
+ const results = await trawl(query, { sources, maxResults });
925
+ if (results.length === 0) {
926
+ return { content: [{ type: "text", text: "No skills found across any source." }] };
927
+ }
928
+ const text = results.map((r) => {
929
+ const conf = Math.round(r.confidence * 100);
930
+ return `**${r.skill.name ?? "unknown"}** [${r.source}] (${conf}% match)
931
+ ${r.skill.description ?? ""}
932
+ ${r.url}`;
933
+ }).join("\n\n");
934
+ return { content: [{ type: "text", text }] };
935
+ }
936
+ );
937
+ server.tool(
938
+ "rate_skill",
939
+ "Rate an installed skill (1-5 stars)",
940
+ {
941
+ name: import_zod2.z.string().describe("Skill name to rate"),
942
+ rating: import_zod2.z.number().min(1).max(5).describe("Rating 1-5"),
943
+ comment: import_zod2.z.string().optional().describe("Optional review comment")
944
+ },
945
+ async ({ name, rating, comment }) => {
946
+ try {
947
+ const updated = await submitRating(name, rating, "mcp-user", comment);
948
+ return {
949
+ content: [
950
+ { type: "text", text: `Rated ${name}: ${formatRating(updated)}` }
951
+ ]
952
+ };
953
+ } catch (error) {
954
+ return { content: [{ type: "text", text: `Rating failed: ${error}` }], isError: true };
955
+ }
956
+ }
957
+ );
958
+ server.resource(
959
+ "installed-skills",
960
+ "skillli://installed",
961
+ { description: "List of all currently installed skillli skills" },
962
+ async () => {
963
+ const skills = await getInstalledSkills();
964
+ return {
965
+ contents: [
966
+ {
967
+ uri: "skillli://installed",
968
+ mimeType: "application/json",
969
+ text: JSON.stringify(skills, null, 2)
970
+ }
971
+ ]
972
+ };
973
+ }
974
+ );
975
+ server.resource(
976
+ "skill-index",
977
+ "skillli://index",
978
+ { description: "The full skillli registry index" },
979
+ async () => {
980
+ const index = await getLocalIndex();
981
+ return {
982
+ contents: [
983
+ {
984
+ uri: "skillli://index",
985
+ mimeType: "application/json",
986
+ text: JSON.stringify(index, null, 2)
987
+ }
988
+ ]
989
+ };
990
+ }
991
+ );
992
+ return server;
993
+ }
994
+ // Annotate the CommonJS export names for ESM import in node:
995
+ 0 && (module.exports = {
996
+ SkillMetadataSchema,
997
+ VERSION,
998
+ computeTrustScore,
999
+ createSkillliMcpServer,
1000
+ extractManifest,
1001
+ fetchIndex,
1002
+ formatRating,
1003
+ getConfig,
1004
+ getInstalledSkills,
1005
+ getLocalIndex,
1006
+ getRatings,
1007
+ getSkillEntry,
1008
+ installFromGithub,
1009
+ installFromLocal,
1010
+ installFromRegistry,
1011
+ linkToClaudeSkills,
1012
+ packageSkill,
1013
+ parseSkillContent,
1014
+ parseSkillFile,
1015
+ runSafeguards,
1016
+ search,
1017
+ searchByCategory,
1018
+ searchByTags,
1019
+ submitRating,
1020
+ syncIndex,
1021
+ trawl,
1022
+ uninstall,
1023
+ validateMetadata
1024
+ });
1025
+ //# sourceMappingURL=index.cjs.map