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