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.
@@ -0,0 +1,1253 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/index.ts
27
+ var import_commander = require("commander");
28
+
29
+ // src/core/constants.ts
30
+ var import_os = require("os");
31
+ var import_path = require("path");
32
+ var SKILLLI_DIR = (0, import_path.join)((0, import_os.homedir)(), ".skillli");
33
+ var LOCAL_INDEX_PATH = (0, import_path.join)(SKILLLI_DIR, "index.json");
34
+ var CONFIG_PATH = (0, import_path.join)(SKILLLI_DIR, "config.json");
35
+ var SKILLS_DIR = (0, import_path.join)(SKILLLI_DIR, "skills");
36
+ var CACHE_DIR = (0, import_path.join)(SKILLLI_DIR, "cache");
37
+ var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/skillli/registry/main/index.json";
38
+ var SKILL_FILENAME = "SKILL.md";
39
+ var MAX_SKILL_SIZE_BYTES = 5 * 1024 * 1024;
40
+ var MAX_SKILL_MD_LINES = 500;
41
+ var VERSION = "0.1.0";
42
+
43
+ // src/cli/commands/init.ts
44
+ var import_promises = require("fs/promises");
45
+ var import_path2 = require("path");
46
+ var import_fs = require("fs");
47
+ var import_chalk = __toESM(require("chalk"), 1);
48
+
49
+ // src/cli/utils/prompts.ts
50
+ var import_inquirer = __toESM(require("inquirer"), 1);
51
+ async function promptInit(defaults) {
52
+ return import_inquirer.default.prompt([
53
+ {
54
+ type: "input",
55
+ name: "name",
56
+ message: "Skill name (lowercase, hyphens):",
57
+ default: defaults?.name,
58
+ validate: (v) => /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(v) || "Must be lowercase alphanumeric with hyphens"
59
+ },
60
+ {
61
+ type: "input",
62
+ name: "version",
63
+ message: "Version:",
64
+ default: defaults?.version ?? "1.0.0"
65
+ },
66
+ {
67
+ type: "input",
68
+ name: "description",
69
+ message: "Description (10-500 chars):",
70
+ default: defaults?.description,
71
+ validate: (v) => v.length >= 10 && v.length <= 500 || "Must be 10-500 characters"
72
+ },
73
+ {
74
+ type: "input",
75
+ name: "author",
76
+ message: "Author (GitHub username):",
77
+ default: defaults?.author
78
+ },
79
+ {
80
+ type: "input",
81
+ name: "license",
82
+ message: "License:",
83
+ default: defaults?.license ?? "MIT"
84
+ },
85
+ {
86
+ type: "input",
87
+ name: "tags",
88
+ message: "Tags (comma-separated):",
89
+ default: defaults?.tags
90
+ },
91
+ {
92
+ type: "list",
93
+ name: "category",
94
+ message: "Category:",
95
+ choices: ["development", "creative", "enterprise", "data", "devops", "other"],
96
+ default: defaults?.category ?? "other"
97
+ }
98
+ ]);
99
+ }
100
+
101
+ // src/cli/commands/init.ts
102
+ var SKILL_TEMPLATE = (answers) => `---
103
+ name: ${answers.name}
104
+ version: ${answers.version}
105
+ description: ${answers.description}
106
+ author: ${answers.author}
107
+ license: ${answers.license}
108
+ tags: [${answers.tags.join(", ")}]
109
+ category: ${answers.category}
110
+ trust-level: community
111
+ user-invocable: true
112
+ ---
113
+
114
+ # ${answers.name}
115
+
116
+ ${answers.description}
117
+
118
+ ## When to Use
119
+
120
+ Describe when this skill should be invoked.
121
+
122
+ ## Instructions
123
+
124
+ Describe what the AI should do when this skill is activated.
125
+
126
+ ## Examples
127
+
128
+ Provide example inputs and expected behavior.
129
+ `;
130
+ function registerInitCommand(program2) {
131
+ program2.command("init [name]").description("Create a new skill from template").option("-d, --dir <path>", "Output directory", ".").action(async (name, options) => {
132
+ try {
133
+ const answers = await promptInit({ name });
134
+ const tags = answers.tags.split(",").map((t) => t.trim()).filter(Boolean);
135
+ const outputDir = (0, import_path2.join)(options.dir, answers.name);
136
+ if ((0, import_fs.existsSync)(outputDir)) {
137
+ console.log(import_chalk.default.red(`Directory ${outputDir} already exists.`));
138
+ process.exit(1);
139
+ }
140
+ await (0, import_promises.mkdir)(outputDir, { recursive: true });
141
+ await (0, import_promises.mkdir)((0, import_path2.join)(outputDir, "scripts"), { recursive: true });
142
+ await (0, import_promises.mkdir)((0, import_path2.join)(outputDir, "references"), { recursive: true });
143
+ const content = SKILL_TEMPLATE({ ...answers, tags });
144
+ await (0, import_promises.writeFile)((0, import_path2.join)(outputDir, "SKILL.md"), content);
145
+ const manifest = {
146
+ name: answers.name,
147
+ version: answers.version,
148
+ description: answers.description,
149
+ author: answers.author,
150
+ license: answers.license,
151
+ tags,
152
+ category: answers.category
153
+ };
154
+ await (0, import_promises.writeFile)((0, import_path2.join)(outputDir, "skillli.json"), JSON.stringify(manifest, null, 2));
155
+ console.log(import_chalk.default.green(`
156
+ Skill "${answers.name}" created at ${outputDir}`));
157
+ console.log(import_chalk.default.gray(" Edit SKILL.md to add your skill instructions."));
158
+ console.log(import_chalk.default.gray(" Run `skillli publish` when ready to share.\n"));
159
+ } catch (error) {
160
+ console.error(import_chalk.default.red("Error:"), error);
161
+ process.exit(1);
162
+ }
163
+ });
164
+ }
165
+
166
+ // src/cli/commands/search.ts
167
+ var import_chalk3 = __toESM(require("chalk"), 1);
168
+ var import_ora = __toESM(require("ora"), 1);
169
+
170
+ // src/core/search.ts
171
+ function tokenize(text) {
172
+ return text.toLowerCase().split(/[\s\-_,./]+/).filter((t) => t.length > 1);
173
+ }
174
+ function textScore(tokens, text) {
175
+ const lower = text.toLowerCase();
176
+ let score = 0;
177
+ for (const token of tokens) {
178
+ if (lower.includes(token)) score += 1;
179
+ }
180
+ return score;
181
+ }
182
+ function scoreSkill(entry, queryTokens, options) {
183
+ let score = 0;
184
+ const matchedOn = [];
185
+ const nameScore = textScore(queryTokens, entry.name);
186
+ if (nameScore > 0) {
187
+ score += nameScore * 5;
188
+ matchedOn.push("name");
189
+ }
190
+ if (entry.name === options.query.toLowerCase()) {
191
+ score += 10;
192
+ }
193
+ const descScore = textScore(queryTokens, entry.description);
194
+ if (descScore > 0) {
195
+ score += descScore * 2;
196
+ matchedOn.push("description");
197
+ }
198
+ for (const token of queryTokens) {
199
+ if (entry.tags.some((t) => t.toLowerCase() === token)) {
200
+ score += 4;
201
+ if (!matchedOn.includes("tags")) matchedOn.push("tags");
202
+ }
203
+ }
204
+ if (options.tags) {
205
+ for (const tag of options.tags) {
206
+ if (entry.tags.includes(tag)) {
207
+ score += 3;
208
+ if (!matchedOn.includes("tags")) matchedOn.push("tags");
209
+ }
210
+ }
211
+ }
212
+ if (options.category && entry.category === options.category) {
213
+ score += 2;
214
+ matchedOn.push("category");
215
+ }
216
+ if (matchedOn.length > 0) {
217
+ if (entry.trustLevel === "official") score += 3;
218
+ if (entry.trustLevel === "verified") score += 1.5;
219
+ score += entry.rating.average * 0.5;
220
+ if (entry.downloads > 0) {
221
+ score += Math.log10(entry.downloads) * 0.5;
222
+ }
223
+ }
224
+ return { score, matchedOn };
225
+ }
226
+ function search(index, options) {
227
+ const queryTokens = tokenize(options.query);
228
+ const results = [];
229
+ for (const entry of Object.values(index.skills)) {
230
+ if (options.category && entry.category !== options.category) continue;
231
+ if (options.trustLevel && entry.trustLevel !== options.trustLevel) continue;
232
+ if (options.minRating && entry.rating.average < options.minRating) continue;
233
+ const { score, matchedOn } = scoreSkill(entry, queryTokens, options);
234
+ if (score > 0) {
235
+ results.push({ skill: entry, relevanceScore: score, matchedOn });
236
+ }
237
+ }
238
+ results.sort((a, b) => b.relevanceScore - a.relevanceScore);
239
+ const offset = options.offset ?? 0;
240
+ const limit = options.limit ?? 20;
241
+ return results.slice(offset, offset + limit);
242
+ }
243
+
244
+ // src/core/local-store.ts
245
+ var import_promises2 = require("fs/promises");
246
+ var import_fs2 = require("fs");
247
+ async function ensureDir() {
248
+ for (const dir of [SKILLLI_DIR, SKILLS_DIR, CACHE_DIR]) {
249
+ if (!(0, import_fs2.existsSync)(dir)) {
250
+ await (0, import_promises2.mkdir)(dir, { recursive: true });
251
+ }
252
+ }
253
+ }
254
+ function defaultConfig() {
255
+ return {
256
+ installedSkills: {},
257
+ registryUrl: DEFAULT_REGISTRY_URL,
258
+ lastSync: ""
259
+ };
260
+ }
261
+ function defaultIndex() {
262
+ return {
263
+ version: "1.0.0",
264
+ lastUpdated: "",
265
+ skills: {}
266
+ };
267
+ }
268
+ async function getConfig() {
269
+ await ensureDir();
270
+ if (!(0, import_fs2.existsSync)(CONFIG_PATH)) {
271
+ const config = defaultConfig();
272
+ await saveConfig(config);
273
+ return config;
274
+ }
275
+ const raw = await (0, import_promises2.readFile)(CONFIG_PATH, "utf-8");
276
+ return JSON.parse(raw);
277
+ }
278
+ async function saveConfig(config) {
279
+ await ensureDir();
280
+ await (0, import_promises2.writeFile)(CONFIG_PATH, JSON.stringify(config, null, 2));
281
+ }
282
+ async function getLocalIndex() {
283
+ await ensureDir();
284
+ if (!(0, import_fs2.existsSync)(LOCAL_INDEX_PATH)) {
285
+ const index = defaultIndex();
286
+ await saveLocalIndex(index);
287
+ return index;
288
+ }
289
+ const raw = await (0, import_promises2.readFile)(LOCAL_INDEX_PATH, "utf-8");
290
+ return JSON.parse(raw);
291
+ }
292
+ async function saveLocalIndex(index) {
293
+ await ensureDir();
294
+ await (0, import_promises2.writeFile)(LOCAL_INDEX_PATH, JSON.stringify(index, null, 2));
295
+ }
296
+ async function getInstalledSkills() {
297
+ const config = await getConfig();
298
+ return Object.values(config.installedSkills);
299
+ }
300
+ async function markInstalled(skill) {
301
+ const config = await getConfig();
302
+ config.installedSkills[skill.name] = skill;
303
+ await saveConfig(config);
304
+ }
305
+ async function markUninstalled(name) {
306
+ const config = await getConfig();
307
+ delete config.installedSkills[name];
308
+ await saveConfig(config);
309
+ }
310
+
311
+ // src/cli/utils/display.ts
312
+ var import_chalk2 = __toESM(require("chalk"), 1);
313
+ function trustBadge(level) {
314
+ switch (level) {
315
+ case "official":
316
+ return import_chalk2.default.green.bold("[OFFICIAL]");
317
+ case "verified":
318
+ return import_chalk2.default.blue.bold("[VERIFIED]");
319
+ default:
320
+ return import_chalk2.default.gray("[COMMUNITY]");
321
+ }
322
+ }
323
+ function stars(rating) {
324
+ const filled = Math.round(rating.average);
325
+ const empty = 5 - filled;
326
+ return import_chalk2.default.yellow("\u2605".repeat(filled)) + import_chalk2.default.gray("\u2606".repeat(empty));
327
+ }
328
+ function displaySearchResults(results) {
329
+ if (results.length === 0) {
330
+ console.log(import_chalk2.default.yellow("No skills found matching your query."));
331
+ return;
332
+ }
333
+ console.log(import_chalk2.default.bold(`
334
+ Found ${results.length} skill(s):
335
+ `));
336
+ for (const { skill, relevanceScore, matchedOn } of results) {
337
+ const badge = trustBadge(skill.trustLevel);
338
+ const rating = stars(skill.rating);
339
+ console.log(
340
+ ` ${import_chalk2.default.cyan.bold(skill.name)} ${import_chalk2.default.gray("v" + skill.version)} ${badge}`
341
+ );
342
+ console.log(` ${skill.description}`);
343
+ console.log(
344
+ ` ${rating} ${import_chalk2.default.gray(`| ${skill.downloads} downloads | by ${skill.author}`)}`
345
+ );
346
+ console.log(
347
+ ` ${import_chalk2.default.gray("Tags:")} ${skill.tags.map((t) => import_chalk2.default.magenta(t)).join(", ")}`
348
+ );
349
+ console.log();
350
+ }
351
+ }
352
+ function displaySkillInfo(skill) {
353
+ console.log(import_chalk2.default.bold.cyan(`
354
+ ${skill.name}`) + import_chalk2.default.gray(` v${skill.version}`));
355
+ console.log(` ${trustBadge(skill.trustLevel)}
356
+ `);
357
+ console.log(` ${skill.description}
358
+ `);
359
+ console.log(` ${import_chalk2.default.bold("Author:")} ${skill.author}`);
360
+ console.log(` ${import_chalk2.default.bold("Category:")} ${skill.category}`);
361
+ console.log(` ${import_chalk2.default.bold("Tags:")} ${skill.tags.join(", ")}`);
362
+ console.log(` ${import_chalk2.default.bold("Rating:")} ${stars(skill.rating)} (${skill.rating.count} ratings)`);
363
+ console.log(` ${import_chalk2.default.bold("Downloads:")} ${skill.downloads}`);
364
+ if (skill.repository) {
365
+ console.log(` ${import_chalk2.default.bold("Repository:")} ${skill.repository}`);
366
+ }
367
+ console.log(` ${import_chalk2.default.bold("Published:")} ${skill.publishedAt}`);
368
+ console.log(` ${import_chalk2.default.bold("Updated:")} ${skill.updatedAt}`);
369
+ console.log();
370
+ }
371
+ function displayTrustScore(score) {
372
+ let color;
373
+ if (score >= 70) color = import_chalk2.default.green;
374
+ else if (score >= 40) color = import_chalk2.default.yellow;
375
+ else color = import_chalk2.default.red;
376
+ const bar = "\u2588".repeat(Math.round(score / 5)) + import_chalk2.default.gray("\u2591".repeat(20 - Math.round(score / 5)));
377
+ console.log(` Trust Score: ${color.bold(score.toString())} / 100 ${bar}`);
378
+ }
379
+ function displaySafeguardReport(result) {
380
+ console.log(import_chalk2.default.bold("\nSafeguard Report:"));
381
+ for (const check of result.checks) {
382
+ const icon = check.passed ? import_chalk2.default.green("\u2713") : check.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("\u26A0");
383
+ console.log(` ${icon} ${check.name}: ${check.message}`);
384
+ }
385
+ displayTrustScore(result.score);
386
+ console.log();
387
+ }
388
+ function displayInstalledSkills(skills) {
389
+ if (skills.length === 0) {
390
+ console.log(import_chalk2.default.yellow("No skills installed."));
391
+ return;
392
+ }
393
+ console.log(import_chalk2.default.bold(`
394
+ Installed skills (${skills.length}):
395
+ `));
396
+ for (const skill of skills) {
397
+ console.log(
398
+ ` ${import_chalk2.default.cyan.bold(skill.name)} ${import_chalk2.default.gray("v" + skill.version)} ${import_chalk2.default.gray(`(${skill.source})`)}`
399
+ );
400
+ console.log(` Installed: ${skill.installedAt}`);
401
+ console.log(` Path: ${skill.path}`);
402
+ console.log();
403
+ }
404
+ }
405
+
406
+ // src/cli/commands/search.ts
407
+ function registerSearchCommand(program2) {
408
+ program2.command("search <query>").description("Search for skills in the registry").option("-t, --tag <tags...>", "Filter by tags").option("-c, --category <category>", "Filter by category").option("--trust <level>", "Filter by trust level").option("--min-rating <n>", "Minimum rating", parseFloat).option("-l, --limit <n>", "Max results", parseInt).action(
409
+ async (query, options) => {
410
+ const spinner = (0, import_ora.default)("Searching skills...").start();
411
+ try {
412
+ const index = await getLocalIndex();
413
+ const results = search(index, {
414
+ query,
415
+ tags: options.tag,
416
+ category: options.category,
417
+ trustLevel: options.trust,
418
+ minRating: options.minRating,
419
+ limit: options.limit
420
+ });
421
+ spinner.stop();
422
+ displaySearchResults(results);
423
+ } catch (error) {
424
+ spinner.fail("Search failed");
425
+ console.error(import_chalk3.default.red(String(error)));
426
+ process.exit(1);
427
+ }
428
+ }
429
+ );
430
+ }
431
+
432
+ // src/cli/commands/install.ts
433
+ var import_chalk4 = __toESM(require("chalk"), 1);
434
+ var import_ora2 = __toESM(require("ora"), 1);
435
+
436
+ // src/core/installer.ts
437
+ var import_promises5 = require("fs/promises");
438
+ var import_fs4 = require("fs");
439
+ var import_path4 = require("path");
440
+ var import_child_process = require("child_process");
441
+
442
+ // src/core/errors.ts
443
+ var SkillliError = class extends Error {
444
+ constructor(message) {
445
+ super(message);
446
+ this.name = "SkillliError";
447
+ }
448
+ };
449
+ var SkillValidationError = class extends SkillliError {
450
+ details;
451
+ constructor(message, details = []) {
452
+ super(message);
453
+ this.name = "SkillValidationError";
454
+ this.details = details;
455
+ }
456
+ };
457
+ var SkillNotFoundError = class extends SkillliError {
458
+ constructor(skillName) {
459
+ super(`Skill not found: ${skillName}`);
460
+ this.name = "SkillNotFoundError";
461
+ }
462
+ };
463
+ var RegistryError = class extends SkillliError {
464
+ constructor(message) {
465
+ super(message);
466
+ this.name = "RegistryError";
467
+ }
468
+ };
469
+ var InstallError = class extends SkillliError {
470
+ constructor(message) {
471
+ super(message);
472
+ this.name = "InstallError";
473
+ }
474
+ };
475
+
476
+ // src/core/registry.ts
477
+ async function fetchIndex(registryUrl) {
478
+ const config = await getConfig();
479
+ const url = registryUrl ?? config.registryUrl ?? DEFAULT_REGISTRY_URL;
480
+ try {
481
+ const res = await fetch(url);
482
+ if (!res.ok) {
483
+ throw new RegistryError(`Failed to fetch registry: ${res.status} ${res.statusText}`);
484
+ }
485
+ const index = await res.json();
486
+ index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
487
+ await saveLocalIndex(index);
488
+ return index;
489
+ } catch (error) {
490
+ if (error instanceof RegistryError) throw error;
491
+ const localIndex = await getLocalIndex();
492
+ if (Object.keys(localIndex.skills).length > 0) {
493
+ return localIndex;
494
+ }
495
+ throw new RegistryError(`Cannot reach registry at ${url}: ${error}`);
496
+ }
497
+ }
498
+ async function getSkillEntry(name) {
499
+ const index = await getLocalIndex();
500
+ const entry = index.skills[name];
501
+ if (!entry) {
502
+ throw new SkillNotFoundError(name);
503
+ }
504
+ return entry;
505
+ }
506
+ async function syncIndex() {
507
+ return fetchIndex();
508
+ }
509
+
510
+ // src/core/parser.ts
511
+ var import_promises3 = require("fs/promises");
512
+ var import_gray_matter = __toESM(require("gray-matter"), 1);
513
+
514
+ // src/core/schema.ts
515
+ var import_zod = require("zod");
516
+ var SkillCategorySchema = import_zod.z.enum([
517
+ "development",
518
+ "creative",
519
+ "enterprise",
520
+ "data",
521
+ "devops",
522
+ "other"
523
+ ]);
524
+ var TrustLevelSchema = import_zod.z.enum(["community", "verified", "official"]);
525
+ var SkillMetadataSchema = import_zod.z.object({
526
+ 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"),
527
+ version: import_zod.z.string().regex(/^\d+\.\d+\.\d+$/, "Must be valid semver (e.g. 1.0.0)"),
528
+ description: import_zod.z.string().min(10).max(500),
529
+ author: import_zod.z.string().min(1),
530
+ license: import_zod.z.string().min(1),
531
+ tags: import_zod.z.array(import_zod.z.string().min(1).max(50)).min(1).max(20),
532
+ category: SkillCategorySchema,
533
+ repository: import_zod.z.string().url().optional(),
534
+ homepage: import_zod.z.string().url().optional(),
535
+ "min-skillli-version": import_zod.z.string().optional(),
536
+ "trust-level": TrustLevelSchema.default("community"),
537
+ checksum: import_zod.z.string().optional(),
538
+ "disable-model-invocation": import_zod.z.boolean().default(false),
539
+ "user-invocable": import_zod.z.boolean().default(true)
540
+ });
541
+
542
+ // src/core/parser.ts
543
+ function normalizeMetadata(raw) {
544
+ return {
545
+ name: raw.name,
546
+ version: raw.version,
547
+ description: raw.description,
548
+ author: raw.author,
549
+ license: raw.license,
550
+ tags: raw.tags,
551
+ category: raw.category,
552
+ repository: raw.repository,
553
+ homepage: raw.homepage,
554
+ minSkillliVersion: raw["min-skillli-version"],
555
+ trustLevel: raw["trust-level"],
556
+ checksum: raw.checksum,
557
+ disableModelInvocation: raw["disable-model-invocation"],
558
+ userInvocable: raw["user-invocable"]
559
+ };
560
+ }
561
+ function validateMetadata(data) {
562
+ const result = SkillMetadataSchema.safeParse(data);
563
+ if (!result.success) {
564
+ const details = result.error.issues.map(
565
+ (issue) => `${issue.path.join(".")}: ${issue.message}`
566
+ );
567
+ throw new SkillValidationError("Invalid skill metadata", details);
568
+ }
569
+ return normalizeMetadata(result.data);
570
+ }
571
+ function parseSkillContent(content, filePath = "<inline>") {
572
+ const { data, content: body, matter: rawFrontmatter } = (0, import_gray_matter.default)(content);
573
+ const metadata = validateMetadata(data);
574
+ return {
575
+ metadata,
576
+ content: body.trim(),
577
+ rawFrontmatter: rawFrontmatter || "",
578
+ filePath
579
+ };
580
+ }
581
+ async function parseSkillFile(filePath) {
582
+ const content = await (0, import_promises3.readFile)(filePath, "utf-8");
583
+ return parseSkillContent(content, filePath);
584
+ }
585
+ function extractManifest(skill) {
586
+ const { metadata } = skill;
587
+ return {
588
+ name: metadata.name,
589
+ version: metadata.version,
590
+ description: metadata.description,
591
+ author: metadata.author,
592
+ license: metadata.license,
593
+ tags: metadata.tags,
594
+ category: metadata.category,
595
+ repository: metadata.repository,
596
+ trust_level: metadata.trustLevel,
597
+ checksum: metadata.checksum,
598
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
599
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
600
+ };
601
+ }
602
+
603
+ // src/core/safeguards.ts
604
+ var import_promises4 = require("fs/promises");
605
+ var import_path3 = require("path");
606
+ var import_fs3 = require("fs");
607
+ var PROHIBITED_PATTERNS = [
608
+ { pattern: /\beval\s*\(/, label: "eval()" },
609
+ { pattern: /\bexec\s*\(/, label: "exec()" },
610
+ { pattern: /\bexecSync\s*\(/, label: "execSync()" },
611
+ { pattern: /rm\s+-rf\s+\//, label: "rm -rf /" },
612
+ { pattern: /\bchild_process\b/, label: "child_process" },
613
+ { pattern: /\bProcess\.kill\b/i, label: "Process.kill" },
614
+ { pattern: /password\s*[:=]\s*['"][^'"]+['"]/, label: "hardcoded password" },
615
+ { pattern: /api[_-]?key\s*[:=]\s*['"][^'"]+['"]/, label: "hardcoded API key" },
616
+ { pattern: /[A-Za-z0-9+/]{100,}={0,2}/, label: "large base64 blob" }
617
+ ];
618
+ var ALLOWED_SCRIPT_EXTENSIONS = [".sh", ".py", ".js", ".ts"];
619
+ function checkSchema(skill) {
620
+ return {
621
+ name: "schema-validation",
622
+ passed: true,
623
+ severity: "info",
624
+ message: "SKILL.md metadata is valid"
625
+ };
626
+ }
627
+ function checkLineCount(content) {
628
+ const lines = content.split("\n").length;
629
+ const passed = lines <= MAX_SKILL_MD_LINES;
630
+ return {
631
+ name: "line-count",
632
+ passed,
633
+ severity: passed ? "info" : "warning",
634
+ message: passed ? `SKILL.md has ${lines} lines (max ${MAX_SKILL_MD_LINES})` : `SKILL.md has ${lines} lines, exceeds max of ${MAX_SKILL_MD_LINES}`
635
+ };
636
+ }
637
+ function checkProhibitedPatterns(content) {
638
+ const found = [];
639
+ for (const { pattern, label } of PROHIBITED_PATTERNS) {
640
+ if (pattern.test(content)) {
641
+ found.push(label);
642
+ }
643
+ }
644
+ const passed = found.length === 0;
645
+ return {
646
+ name: "prohibited-patterns",
647
+ passed,
648
+ severity: passed ? "info" : "error",
649
+ message: passed ? "No prohibited patterns detected" : `Prohibited patterns found: ${found.join(", ")}`
650
+ };
651
+ }
652
+ function checkScriptSafety(skillDir) {
653
+ const scriptsDir = (0, import_path3.join)(skillDir, "scripts");
654
+ if (!(0, import_fs3.existsSync)(scriptsDir)) {
655
+ return {
656
+ name: "script-safety",
657
+ passed: true,
658
+ severity: "info",
659
+ message: "No scripts directory found"
660
+ };
661
+ }
662
+ const badFiles = [];
663
+ const files = (0, import_fs3.readdirSync)(scriptsDir, { recursive: true });
664
+ for (const file of files) {
665
+ const ext = "." + file.split(".").pop();
666
+ if (!ALLOWED_SCRIPT_EXTENSIONS.includes(ext)) {
667
+ badFiles.push(file);
668
+ }
669
+ }
670
+ const passed = badFiles.length === 0;
671
+ return {
672
+ name: "script-safety",
673
+ passed,
674
+ severity: passed ? "info" : "error",
675
+ message: passed ? "All scripts use allowed extensions" : `Disallowed script types: ${badFiles.join(", ")}`
676
+ };
677
+ }
678
+ async function checkFileSize(skillDir) {
679
+ let totalSize = 0;
680
+ const entries = (0, import_fs3.readdirSync)(skillDir, { recursive: true, withFileTypes: true });
681
+ for (const entry of entries) {
682
+ if (entry.isFile()) {
683
+ const fullPath = (0, import_path3.join)(entry.parentPath ?? skillDir, entry.name);
684
+ const s = await (0, import_promises4.stat)(fullPath);
685
+ totalSize += s.size;
686
+ }
687
+ }
688
+ const passed = totalSize <= MAX_SKILL_SIZE_BYTES;
689
+ return {
690
+ name: "file-size",
691
+ passed,
692
+ severity: passed ? "info" : "warning",
693
+ 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`
694
+ };
695
+ }
696
+ async function runSafeguards(skill, skillDir) {
697
+ const checks = [];
698
+ checks.push(checkSchema(skill));
699
+ checks.push(checkLineCount(skill.content));
700
+ checks.push(checkProhibitedPatterns(skill.content + skill.rawFrontmatter));
701
+ if (skillDir) {
702
+ checks.push(checkScriptSafety(skillDir));
703
+ checks.push(await checkFileSize(skillDir));
704
+ }
705
+ const passed = checks.every((c) => c.passed || c.severity !== "error");
706
+ const score = computeTrustScore(skill);
707
+ return { passed, score, checks };
708
+ }
709
+ function computeTrustScore(skill, registryEntry) {
710
+ let score = 0;
711
+ if (skill.metadata.repository) score += 10;
712
+ if (skill.metadata.license) score += 10;
713
+ if (skill.metadata.trustLevel === "verified") score += 15;
714
+ if (skill.metadata.trustLevel === "official") score += 20;
715
+ if (registryEntry && registryEntry.rating.average >= 3.5) score += 15;
716
+ if (registryEntry && registryEntry.downloads > 100) score += 5;
717
+ if (registryEntry && registryEntry.downloads > 1e3) score += 5;
718
+ const patternCheck = checkProhibitedPatterns(skill.content);
719
+ const lineCheck = checkLineCount(skill.content);
720
+ if (patternCheck.passed) score += 20;
721
+ if (lineCheck.passed) score += 15;
722
+ return Math.min(score, 100);
723
+ }
724
+
725
+ // src/core/installer.ts
726
+ async function installFromRegistry(name, version) {
727
+ const entry = await getSkillEntry(name);
728
+ if (!entry.repository) {
729
+ throw new InstallError(`Skill "${name}" has no repository URL`);
730
+ }
731
+ return installFromGithub(entry.repository, name);
732
+ }
733
+ async function installFromGithub(repoUrl, nameOverride) {
734
+ const name = nameOverride ?? repoUrl.split("/").pop()?.replace(/\.git$/, "") ?? "unknown";
735
+ const installPath = (0, import_path4.join)(SKILLS_DIR, name);
736
+ if ((0, import_fs4.existsSync)(installPath)) {
737
+ await (0, import_promises5.rm)(installPath, { recursive: true });
738
+ }
739
+ await (0, import_promises5.mkdir)(installPath, { recursive: true });
740
+ try {
741
+ (0, import_child_process.execSync)(`git clone --depth 1 "${repoUrl}" "${installPath}"`, {
742
+ stdio: "pipe",
743
+ timeout: 3e4
744
+ });
745
+ } catch {
746
+ throw new InstallError(`Failed to clone ${repoUrl}`);
747
+ }
748
+ const skillFile = (0, import_path4.join)(installPath, SKILL_FILENAME);
749
+ if (!(0, import_fs4.existsSync)(skillFile)) {
750
+ await (0, import_promises5.rm)(installPath, { recursive: true });
751
+ throw new InstallError(`No ${SKILL_FILENAME} found in ${repoUrl}`);
752
+ }
753
+ const skill = await parseSkillFile(skillFile);
754
+ const safeguards = await runSafeguards(skill, installPath);
755
+ if (!safeguards.passed) {
756
+ await (0, import_promises5.rm)(installPath, { recursive: true });
757
+ const errors = safeguards.checks.filter((c) => !c.passed).map((c) => c.message);
758
+ throw new InstallError(`Skill failed safety checks: ${errors.join("; ")}`);
759
+ }
760
+ const installed = {
761
+ name: skill.metadata.name,
762
+ version: skill.metadata.version,
763
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
764
+ path: installPath,
765
+ source: "github"
766
+ };
767
+ await markInstalled(installed);
768
+ return installed;
769
+ }
770
+ async function installFromLocal(dirPath) {
771
+ const skillFile = (0, import_path4.join)(dirPath, SKILL_FILENAME);
772
+ if (!(0, import_fs4.existsSync)(skillFile)) {
773
+ throw new InstallError(`No ${SKILL_FILENAME} found in ${dirPath}`);
774
+ }
775
+ const skill = await parseSkillFile(skillFile);
776
+ const safeguards = await runSafeguards(skill, dirPath);
777
+ if (!safeguards.passed) {
778
+ const errors = safeguards.checks.filter((c) => !c.passed).map((c) => c.message);
779
+ throw new InstallError(`Skill failed safety checks: ${errors.join("; ")}`);
780
+ }
781
+ const installPath = (0, import_path4.join)(SKILLS_DIR, skill.metadata.name);
782
+ if ((0, import_fs4.existsSync)(installPath)) {
783
+ await (0, import_promises5.rm)(installPath, { recursive: true });
784
+ }
785
+ (0, import_child_process.execSync)(`cp -r "${dirPath}" "${installPath}"`, { stdio: "pipe" });
786
+ const installed = {
787
+ name: skill.metadata.name,
788
+ version: skill.metadata.version,
789
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
790
+ path: installPath,
791
+ source: "local"
792
+ };
793
+ await markInstalled(installed);
794
+ return installed;
795
+ }
796
+ async function uninstall(name) {
797
+ const installPath = (0, import_path4.join)(SKILLS_DIR, name);
798
+ if ((0, import_fs4.existsSync)(installPath)) {
799
+ await (0, import_promises5.rm)(installPath, { recursive: true });
800
+ }
801
+ await markUninstalled(name);
802
+ }
803
+ async function linkToClaudeSkills(skill, projectDir = process.cwd()) {
804
+ const claudeSkillsDir = (0, import_path4.join)(projectDir, ".claude", "skills");
805
+ await (0, import_promises5.mkdir)(claudeSkillsDir, { recursive: true });
806
+ const linkPath = (0, import_path4.join)(claudeSkillsDir, skill.name);
807
+ if ((0, import_fs4.existsSync)(linkPath)) {
808
+ await (0, import_promises5.rm)(linkPath, { recursive: true });
809
+ }
810
+ await (0, import_promises5.symlink)(skill.path, linkPath, "dir");
811
+ return linkPath;
812
+ }
813
+
814
+ // src/cli/commands/install.ts
815
+ function registerInstallCommand(program2) {
816
+ program2.command("install <skill>").description("Install a skill from the registry, GitHub URL, or local path").option("--link", "Symlink to .claude/skills/ for Claude Code", false).option("--local", "Install from a local directory").action(
817
+ async (skill, options) => {
818
+ const spinner = (0, import_ora2.default)(`Installing ${skill}...`).start();
819
+ try {
820
+ let installed;
821
+ if (options.local) {
822
+ installed = await installFromLocal(skill);
823
+ } else if (skill.startsWith("http") || skill.includes("github.com")) {
824
+ installed = await installFromGithub(skill);
825
+ } else {
826
+ installed = await installFromRegistry(skill);
827
+ }
828
+ spinner.succeed(`Installed ${import_chalk4.default.cyan(installed.name)} v${installed.version}`);
829
+ console.log(` Path: ${installed.path}`);
830
+ if (options.link) {
831
+ const linkPath = await linkToClaudeSkills(installed);
832
+ console.log(` Linked to: ${linkPath}`);
833
+ }
834
+ } catch (error) {
835
+ spinner.fail("Installation failed");
836
+ console.error(import_chalk4.default.red(String(error)));
837
+ process.exit(1);
838
+ }
839
+ }
840
+ );
841
+ }
842
+
843
+ // src/cli/commands/uninstall.ts
844
+ var import_chalk5 = __toESM(require("chalk"), 1);
845
+ var import_ora3 = __toESM(require("ora"), 1);
846
+ function registerUninstallCommand(program2) {
847
+ program2.command("uninstall <skill>").description("Uninstall a skill").action(async (skill) => {
848
+ const spinner = (0, import_ora3.default)(`Uninstalling ${skill}...`).start();
849
+ try {
850
+ await uninstall(skill);
851
+ spinner.succeed(`Uninstalled ${import_chalk5.default.cyan(skill)}`);
852
+ } catch (error) {
853
+ spinner.fail("Uninstall failed");
854
+ console.error(import_chalk5.default.red(String(error)));
855
+ process.exit(1);
856
+ }
857
+ });
858
+ }
859
+
860
+ // src/cli/commands/info.ts
861
+ var import_chalk6 = __toESM(require("chalk"), 1);
862
+ var import_ora4 = __toESM(require("ora"), 1);
863
+ function registerInfoCommand(program2) {
864
+ program2.command("info <skill>").description("Display detailed information about a skill").action(async (skill) => {
865
+ const spinner = (0, import_ora4.default)("Fetching skill info...").start();
866
+ try {
867
+ const entry = await getSkillEntry(skill);
868
+ spinner.stop();
869
+ displaySkillInfo(entry);
870
+ } catch (error) {
871
+ spinner.fail("Failed to get skill info");
872
+ console.error(import_chalk6.default.red(String(error)));
873
+ process.exit(1);
874
+ }
875
+ });
876
+ }
877
+
878
+ // src/cli/commands/list.ts
879
+ var import_chalk7 = __toESM(require("chalk"), 1);
880
+ function registerListCommand(program2) {
881
+ program2.command("list").description("List installed skills").option("--json", "Output as JSON").action(async (options) => {
882
+ try {
883
+ const skills = await getInstalledSkills();
884
+ if (options.json) {
885
+ console.log(JSON.stringify(skills, null, 2));
886
+ } else {
887
+ displayInstalledSkills(skills);
888
+ }
889
+ } catch (error) {
890
+ console.error(import_chalk7.default.red(String(error)));
891
+ process.exit(1);
892
+ }
893
+ });
894
+ }
895
+
896
+ // src/cli/commands/rate.ts
897
+ var import_chalk8 = __toESM(require("chalk"), 1);
898
+
899
+ // src/core/ratings.ts
900
+ async function submitRating(name, rating, userId, comment) {
901
+ const index = await getLocalIndex();
902
+ const entry = index.skills[name];
903
+ if (!entry) {
904
+ throw new SkillNotFoundError(name);
905
+ }
906
+ const current = entry.rating;
907
+ const newCount = current.count + 1;
908
+ const newAverage = (current.average * current.count + rating) / newCount;
909
+ const dist = [...current.distribution];
910
+ dist[rating - 1] += 1;
911
+ entry.rating = {
912
+ average: Math.round(newAverage * 10) / 10,
913
+ count: newCount,
914
+ distribution: dist
915
+ };
916
+ await saveLocalIndex(index);
917
+ return entry.rating;
918
+ }
919
+ function formatRating(rating) {
920
+ const filled = Math.round(rating.average);
921
+ const empty = 5 - filled;
922
+ const stars2 = "\u2605".repeat(filled) + "\u2606".repeat(empty);
923
+ return `${stars2} ${rating.average.toFixed(1)} (${rating.count} ratings)`;
924
+ }
925
+
926
+ // src/cli/commands/rate.ts
927
+ function registerRateCommand(program2) {
928
+ program2.command("rate <skill> <rating>").description("Rate a skill (1-5 stars)").option("-m, --comment <text>", "Add a review comment").action(async (skill, ratingStr, options) => {
929
+ try {
930
+ const rating = parseInt(ratingStr, 10);
931
+ if (isNaN(rating) || rating < 1 || rating > 5) {
932
+ console.error(import_chalk8.default.red("Rating must be 1-5"));
933
+ process.exit(1);
934
+ }
935
+ const config = await getConfig();
936
+ const userId = config.userId ?? "anonymous";
937
+ const updated = await submitRating(skill, rating, userId, options.comment);
938
+ console.log(import_chalk8.default.green(`
939
+ Rated ${import_chalk8.default.cyan(skill)}: ${"\u2605".repeat(rating)}${"\u2606".repeat(5 - rating)}`));
940
+ console.log(` Updated: ${formatRating(updated)}`);
941
+ } catch (error) {
942
+ console.error(import_chalk8.default.red(String(error)));
943
+ process.exit(1);
944
+ }
945
+ });
946
+ }
947
+
948
+ // src/cli/commands/update.ts
949
+ var import_chalk9 = __toESM(require("chalk"), 1);
950
+ var import_ora5 = __toESM(require("ora"), 1);
951
+ function registerUpdateCommand(program2) {
952
+ program2.command("update").description("Update the local skill index from the registry").action(async () => {
953
+ const spinner = (0, import_ora5.default)("Syncing registry index...").start();
954
+ try {
955
+ const index = await syncIndex();
956
+ const count = Object.keys(index.skills).length;
957
+ spinner.succeed(`Index updated: ${count} skills available`);
958
+ } catch (error) {
959
+ spinner.fail("Failed to update index");
960
+ console.error(import_chalk9.default.red(String(error)));
961
+ process.exit(1);
962
+ }
963
+ });
964
+ }
965
+
966
+ // src/cli/commands/publish.ts
967
+ var import_chalk10 = __toESM(require("chalk"), 1);
968
+ var import_ora6 = __toESM(require("ora"), 1);
969
+
970
+ // src/core/publisher.ts
971
+ var import_promises6 = require("fs/promises");
972
+ var import_path5 = require("path");
973
+ var import_crypto = require("crypto");
974
+ var import_fs5 = require("fs");
975
+ async function packageSkill(skillDir) {
976
+ const skillFile = (0, import_path5.join)(skillDir, SKILL_FILENAME);
977
+ if (!(0, import_fs5.existsSync)(skillFile)) {
978
+ throw new SkillValidationError(`No ${SKILL_FILENAME} found in ${skillDir}`);
979
+ }
980
+ const skill = await parseSkillFile(skillFile);
981
+ const safeguards = await runSafeguards(skill, skillDir);
982
+ if (!safeguards.passed) {
983
+ const errors = safeguards.checks.filter((c) => !c.passed).map((c) => `[${c.severity}] ${c.message}`);
984
+ throw new SkillValidationError("Skill failed safety checks", errors);
985
+ }
986
+ const checksum = await computeDirectoryChecksum(skillDir);
987
+ const manifest = extractManifest(skill);
988
+ manifest.checksum = `sha256:${checksum}`;
989
+ manifest.files = listFiles(skillDir);
990
+ manifest.size_bytes = computeDirectorySize(skillDir);
991
+ return { skill, manifest, checksum };
992
+ }
993
+ async function computeDirectoryChecksum(dir) {
994
+ const hash = (0, import_crypto.createHash)("sha256");
995
+ const files = listFiles(dir).sort();
996
+ for (const file of files) {
997
+ const content = await (0, import_promises6.readFile)((0, import_path5.join)(dir, file));
998
+ hash.update(file);
999
+ hash.update(content);
1000
+ }
1001
+ return hash.digest("hex");
1002
+ }
1003
+ function listFiles(dir, prefix = "") {
1004
+ const files = [];
1005
+ const entries = (0, import_fs5.readdirSync)(dir, { withFileTypes: true });
1006
+ for (const entry of entries) {
1007
+ if (entry.name === ".git" || entry.name === "node_modules") continue;
1008
+ const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
1009
+ if (entry.isDirectory()) {
1010
+ files.push(...listFiles((0, import_path5.join)(dir, entry.name), relative));
1011
+ } else {
1012
+ files.push(relative);
1013
+ }
1014
+ }
1015
+ return files;
1016
+ }
1017
+ function computeDirectorySize(dir) {
1018
+ let size = 0;
1019
+ const entries = (0, import_fs5.readdirSync)(dir, { withFileTypes: true });
1020
+ for (const entry of entries) {
1021
+ if (entry.name === ".git" || entry.name === "node_modules") continue;
1022
+ const fullPath = (0, import_path5.join)(dir, entry.name);
1023
+ if (entry.isDirectory()) {
1024
+ size += computeDirectorySize(fullPath);
1025
+ } else {
1026
+ size += (0, import_fs5.statSync)(fullPath).size;
1027
+ }
1028
+ }
1029
+ return size;
1030
+ }
1031
+
1032
+ // src/cli/commands/publish.ts
1033
+ function registerPublishCommand(program2) {
1034
+ program2.command("publish [path]").description("Publish a skill to the registry").option("--dry-run", "Validate without publishing").action(async (path, options) => {
1035
+ const skillDir = path ?? process.cwd();
1036
+ const spinner = (0, import_ora6.default)("Packaging skill...").start();
1037
+ try {
1038
+ const { skill, manifest, checksum } = await packageSkill(skillDir);
1039
+ spinner.succeed(`Packaged ${import_chalk10.default.cyan(skill.metadata.name)} v${skill.metadata.version}`);
1040
+ const safeguards = await runSafeguards(skill, skillDir);
1041
+ displaySafeguardReport(safeguards);
1042
+ console.log(import_chalk10.default.bold("Manifest:"));
1043
+ console.log(JSON.stringify(manifest, null, 2));
1044
+ if (options.dryRun) {
1045
+ console.log(import_chalk10.default.yellow("\n --dry-run: Skill was NOT published."));
1046
+ console.log(import_chalk10.default.gray(" Remove --dry-run to publish for real.\n"));
1047
+ return;
1048
+ }
1049
+ console.log(import_chalk10.default.green("\n Skill validated and ready to publish!"));
1050
+ console.log(import_chalk10.default.gray(" To publish, submit a PR to the skillli registry:"));
1051
+ console.log(import_chalk10.default.gray(" https://github.com/skillli/registry\n"));
1052
+ console.log(import_chalk10.default.gray(` Checksum: sha256:${checksum}`));
1053
+ } catch (error) {
1054
+ spinner.fail("Publish failed");
1055
+ console.error(import_chalk10.default.red(String(error)));
1056
+ process.exit(1);
1057
+ }
1058
+ });
1059
+ }
1060
+
1061
+ // src/cli/commands/trawl.ts
1062
+ var import_chalk11 = __toESM(require("chalk"), 1);
1063
+ var import_ora7 = __toESM(require("ora"), 1);
1064
+
1065
+ // src/trawler/strategies.ts
1066
+ async function searchRegistry(query) {
1067
+ const index = await getLocalIndex();
1068
+ const tokens = query.toLowerCase().split(/\s+/);
1069
+ const results = [];
1070
+ for (const entry of Object.values(index.skills)) {
1071
+ const text = `${entry.name} ${entry.description} ${entry.tags.join(" ")}`.toLowerCase();
1072
+ const matchCount = tokens.filter((t) => text.includes(t)).length;
1073
+ if (matchCount === 0) continue;
1074
+ results.push({
1075
+ source: "registry",
1076
+ skill: entry,
1077
+ confidence: Math.min(matchCount / tokens.length, 1),
1078
+ url: entry.repository || `skillli://registry/${entry.name}`
1079
+ });
1080
+ }
1081
+ return results;
1082
+ }
1083
+ async function searchGithub(query) {
1084
+ const results = [];
1085
+ const searchQuery = encodeURIComponent(`${query} SKILL.md in:path`);
1086
+ const url = `https://api.github.com/search/repositories?q=${searchQuery}&per_page=10`;
1087
+ try {
1088
+ const res = await fetch(url, {
1089
+ headers: { Accept: "application/vnd.github.v3+json" }
1090
+ });
1091
+ if (!res.ok) return results;
1092
+ const data = await res.json();
1093
+ for (const repo of data.items ?? []) {
1094
+ const confidence = Math.min(
1095
+ 0.3 + (repo.stargazers_count > 10 ? 0.2 : 0) + (repo.stargazers_count > 100 ? 0.2 : 0),
1096
+ 0.9
1097
+ );
1098
+ results.push({
1099
+ source: "github",
1100
+ skill: {
1101
+ name: repo.full_name.split("/").pop() ?? repo.full_name,
1102
+ description: repo.description ?? "",
1103
+ author: repo.full_name.split("/")[0],
1104
+ repository: repo.html_url,
1105
+ tags: repo.topics ?? []
1106
+ },
1107
+ confidence,
1108
+ url: repo.html_url
1109
+ });
1110
+ }
1111
+ } catch {
1112
+ }
1113
+ return results;
1114
+ }
1115
+ async function searchNpm(query) {
1116
+ const results = [];
1117
+ const searchQuery = encodeURIComponent(`${query} skill agent claude`);
1118
+ const url = `https://registry.npmjs.org/-/v1/search?text=${searchQuery}&size=10`;
1119
+ try {
1120
+ const res = await fetch(url);
1121
+ if (!res.ok) return results;
1122
+ const data = await res.json();
1123
+ for (const obj of data.objects ?? []) {
1124
+ const pkg = obj.package;
1125
+ results.push({
1126
+ source: "npm",
1127
+ skill: {
1128
+ name: pkg.name,
1129
+ description: pkg.description ?? "",
1130
+ version: pkg.version,
1131
+ repository: pkg.links.repository ?? "",
1132
+ tags: pkg.keywords ?? []
1133
+ },
1134
+ confidence: Math.min(obj.score.final * 0.7, 0.85),
1135
+ url: pkg.links.npm
1136
+ });
1137
+ }
1138
+ } catch {
1139
+ }
1140
+ return results;
1141
+ }
1142
+
1143
+ // src/trawler/ranker.ts
1144
+ function deduplicateResults(results) {
1145
+ const seen = /* @__PURE__ */ new Map();
1146
+ for (const result of results) {
1147
+ const key = result.skill.name?.toLowerCase() ?? result.url;
1148
+ const existing = seen.get(key);
1149
+ if (!existing || result.confidence > existing.confidence) {
1150
+ seen.set(key, result);
1151
+ }
1152
+ }
1153
+ return Array.from(seen.values());
1154
+ }
1155
+ function rankResults(results, query) {
1156
+ const tokens = query.toLowerCase().split(/\s+/);
1157
+ return results.map((result) => {
1158
+ let bonus = 0;
1159
+ if (result.source === "registry") bonus += 0.2;
1160
+ else if (result.source === "github") bonus += 0.05;
1161
+ const name = (result.skill.name ?? "").toLowerCase();
1162
+ for (const token of tokens) {
1163
+ if (name.includes(token)) bonus += 0.15;
1164
+ }
1165
+ const tags = (result.skill.tags ?? []).map((t) => t.toLowerCase());
1166
+ for (const token of tokens) {
1167
+ if (tags.includes(token)) bonus += 0.1;
1168
+ }
1169
+ return {
1170
+ ...result,
1171
+ confidence: Math.min(result.confidence + bonus, 1)
1172
+ };
1173
+ }).sort((a, b) => b.confidence - a.confidence);
1174
+ }
1175
+
1176
+ // src/trawler/index.ts
1177
+ async function trawl(query, options = {}) {
1178
+ const sources = options.sources ?? ["registry", "github"];
1179
+ const maxResults = options.maxResults ?? 10;
1180
+ const searches = [];
1181
+ if (sources.includes("registry")) {
1182
+ searches.push(searchRegistry(query));
1183
+ }
1184
+ if (sources.includes("github")) {
1185
+ searches.push(searchGithub(query));
1186
+ }
1187
+ if (sources.includes("npm")) {
1188
+ searches.push(searchNpm(query));
1189
+ }
1190
+ const allResults = (await Promise.all(searches)).flat();
1191
+ const deduplicated = deduplicateResults(allResults);
1192
+ const ranked = rankResults(deduplicated, query);
1193
+ return ranked.slice(0, maxResults);
1194
+ }
1195
+
1196
+ // src/cli/commands/trawl.ts
1197
+ function registerTrawlCommand(program2) {
1198
+ program2.command("trawl <query>").description("Agentic search across multiple sources for skills").option(
1199
+ "-s, --sources <sources...>",
1200
+ "Sources to search (registry, github, npm)",
1201
+ ["registry", "github"]
1202
+ ).option("-n, --max-results <n>", "Maximum results", parseInt).action(
1203
+ async (query, options) => {
1204
+ const spinner = (0, import_ora7.default)("Trawling for skills...").start();
1205
+ try {
1206
+ const results = await trawl(query, {
1207
+ sources: options.sources,
1208
+ maxResults: options.maxResults
1209
+ });
1210
+ spinner.stop();
1211
+ if (results.length === 0) {
1212
+ console.log(import_chalk11.default.yellow("No skills found across any source."));
1213
+ return;
1214
+ }
1215
+ console.log(import_chalk11.default.bold(`
1216
+ Trawled ${results.length} result(s):
1217
+ `));
1218
+ for (const result of results) {
1219
+ const confidence = Math.round(result.confidence * 100);
1220
+ const confColor = confidence >= 70 ? import_chalk11.default.green : confidence >= 40 ? import_chalk11.default.yellow : import_chalk11.default.gray;
1221
+ console.log(
1222
+ ` ${import_chalk11.default.cyan.bold(result.skill.name ?? "unknown")} ${import_chalk11.default.gray(`[${result.source}]`)} ${confColor(`${confidence}% match`)}`
1223
+ );
1224
+ if (result.skill.description) {
1225
+ console.log(` ${result.skill.description}`);
1226
+ }
1227
+ console.log(` ${import_chalk11.default.gray(result.url)}`);
1228
+ console.log();
1229
+ }
1230
+ } catch (error) {
1231
+ spinner.fail("Trawl failed");
1232
+ console.error(import_chalk11.default.red(String(error)));
1233
+ process.exit(1);
1234
+ }
1235
+ }
1236
+ );
1237
+ }
1238
+
1239
+ // src/cli/index.ts
1240
+ var program = new import_commander.Command();
1241
+ program.name("skillli").description("Discover, publish, rate, and use agentic AI skills packages").version(VERSION);
1242
+ registerInitCommand(program);
1243
+ registerSearchCommand(program);
1244
+ registerInstallCommand(program);
1245
+ registerUninstallCommand(program);
1246
+ registerInfoCommand(program);
1247
+ registerListCommand(program);
1248
+ registerRateCommand(program);
1249
+ registerUpdateCommand(program);
1250
+ registerPublishCommand(program);
1251
+ registerTrawlCommand(program);
1252
+ program.parse();
1253
+ //# sourceMappingURL=index.cjs.map