@hasna/skills 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/mcp.js CHANGED
@@ -44,6 +44,7 @@ var __export = (target, all) => {
44
44
  set: __exportSetter.bind(all, name)
45
45
  });
46
46
  };
47
+ var __require = import.meta.require;
47
48
 
48
49
  // node_modules/ajv/dist/compile/codegen/code.js
49
50
  var require_code = __commonJS((exports) => {
@@ -28560,13 +28561,12 @@ class StdioServerTransport {
28560
28561
  }
28561
28562
 
28562
28563
  // src/mcp/index.ts
28563
- import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
28564
- import { join as join3 } from "path";
28565
- import { homedir as homedir2 } from "os";
28564
+ import { existsSync as existsSync5, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
28565
+ import { join as join5 } from "path";
28566
28566
  // package.json
28567
28567
  var package_default = {
28568
28568
  name: "@hasna/skills",
28569
- version: "0.1.14",
28569
+ version: "0.1.16",
28570
28570
  description: "Skills library for AI coding agents",
28571
28571
  type: "module",
28572
28572
  bin: {
@@ -28626,6 +28626,7 @@ var package_default = {
28626
28626
  typescript: "^5"
28627
28627
  },
28628
28628
  dependencies: {
28629
+ "@hasna/cloud": "^0.1.0",
28629
28630
  "@modelcontextprotocol/sdk": "^1.26.0",
28630
28631
  chalk: "^5.3.0",
28631
28632
  commander: "^12.1.0",
@@ -28650,6 +28651,9 @@ var package_default = {
28650
28651
  };
28651
28652
 
28652
28653
  // src/lib/registry.ts
28654
+ import { existsSync, readFileSync, readdirSync } from "fs";
28655
+ import { join } from "path";
28656
+ import { homedir } from "os";
28653
28657
  var CATEGORIES = [
28654
28658
  "Development Tools",
28655
28659
  "Business & Marketing",
@@ -29686,6 +29690,20 @@ var SKILLS = [
29686
29690
  category: "Design & Branding",
29687
29691
  tags: ["testimonials", "graphics", "social-proof", "marketing"]
29688
29692
  },
29693
+ {
29694
+ name: "colorextract",
29695
+ displayName: "Color Extract",
29696
+ description: "Extract complete color palettes from screenshots and images using Claude Vision. Outputs open-styles compatible profiles.",
29697
+ category: "Design & Branding",
29698
+ tags: ["colors", "palette", "design", "vision", "screenshot", "extract", "open-styles"]
29699
+ },
29700
+ {
29701
+ name: "siteanalyze",
29702
+ displayName: "Site Analyze",
29703
+ description: "Analyze any website's design system \u2014 detects shadcn/ui, Tailwind, extracts colors, typography, and components via Playwright + Claude Vision.",
29704
+ category: "Design & Branding",
29705
+ tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "open-styles"]
29706
+ },
29689
29707
  {
29690
29708
  name: "browse",
29691
29709
  displayName: "Browse",
@@ -30086,8 +30104,90 @@ var SKILLS = [
30086
30104
  tags: ["seating", "chart", "events", "venues"]
30087
30105
  }
30088
30106
  ];
30107
+ function parseSkillMdFrontmatter(content) {
30108
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
30109
+ if (!match)
30110
+ return null;
30111
+ const result = {};
30112
+ for (const line of match[1].split(`
30113
+ `)) {
30114
+ const colon = line.indexOf(":");
30115
+ if (colon === -1)
30116
+ continue;
30117
+ const key = line.slice(0, colon).trim();
30118
+ const value = line.slice(colon + 1).trim();
30119
+ if (!key || !value)
30120
+ continue;
30121
+ if (key === "name")
30122
+ result.name = value;
30123
+ else if (key === "description")
30124
+ result.description = value;
30125
+ else if (key === "displayName" || key === "display_name")
30126
+ result.displayName = value;
30127
+ else if (key === "category")
30128
+ result.category = value;
30129
+ else if (key === "tags") {
30130
+ result.tags = value.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean);
30131
+ }
30132
+ }
30133
+ return Object.keys(result).length > 0 ? result : null;
30134
+ }
30135
+ function discoverSkillsInDir(dir) {
30136
+ if (!existsSync(dir))
30137
+ return [];
30138
+ const result = [];
30139
+ try {
30140
+ const entries = readdirSync(dir, { withFileTypes: true });
30141
+ for (const entry of entries) {
30142
+ if (!entry.isDirectory())
30143
+ continue;
30144
+ const skillMdPath = join(dir, entry.name, "SKILL.md");
30145
+ if (!existsSync(skillMdPath))
30146
+ continue;
30147
+ let content;
30148
+ try {
30149
+ content = readFileSync(skillMdPath, "utf-8");
30150
+ } catch {
30151
+ continue;
30152
+ }
30153
+ const fm = parseSkillMdFrontmatter(content);
30154
+ if (!fm?.name)
30155
+ continue;
30156
+ const name = fm.name.replace(/^skill-/, "");
30157
+ result.push({
30158
+ name,
30159
+ displayName: fm.displayName || name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
30160
+ description: fm.description || "",
30161
+ category: fm.category || "Development Tools",
30162
+ tags: fm.tags || [],
30163
+ source: "custom"
30164
+ });
30165
+ }
30166
+ } catch {}
30167
+ return result;
30168
+ }
30169
+ var _registryCache = null;
30170
+ var _registryCacheTime = 0;
30171
+ var REGISTRY_CACHE_TTL = 5000;
30172
+ function loadRegistry(cwd) {
30173
+ const now = Date.now();
30174
+ if (_registryCache && now - _registryCacheTime < REGISTRY_CACHE_TTL) {
30175
+ return _registryCache;
30176
+ }
30177
+ const official = SKILLS.map((s) => ({ ...s, source: "official" }));
30178
+ const globalCustomNew = discoverSkillsInDir(join(homedir(), ".hasna", "skills", "custom"));
30179
+ const globalCustomOld = discoverSkillsInDir(join(homedir(), ".skills"));
30180
+ const oldNames = new Set(globalCustomNew.map((s) => s.name));
30181
+ const globalCustom = [...globalCustomNew, ...globalCustomOld.filter((s) => !oldNames.has(s.name))];
30182
+ const projectCustom = discoverSkillsInDir(join(cwd || process.cwd(), ".skills", "custom-skills"));
30183
+ const customNames = new Set([...globalCustom, ...projectCustom].map((s) => s.name));
30184
+ const filtered = official.filter((s) => !customNames.has(s.name));
30185
+ _registryCache = [...filtered, ...globalCustom, ...projectCustom];
30186
+ _registryCacheTime = now;
30187
+ return _registryCache;
30188
+ }
30089
30189
  function getSkillsByCategory(category) {
30090
- return SKILLS.filter((s) => s.category === category);
30190
+ return loadRegistry().filter((s) => s.category === category);
30091
30191
  }
30092
30192
  function editDistance(a, b) {
30093
30193
  if (a === b)
@@ -30133,7 +30233,7 @@ function searchSkills(query) {
30133
30233
  if (words.length === 0)
30134
30234
  return [];
30135
30235
  const scored = [];
30136
- for (const skill of SKILLS) {
30236
+ for (const skill of loadRegistry()) {
30137
30237
  const nameLower = skill.name.toLowerCase();
30138
30238
  const displayNameLower = skill.displayName.toLowerCase();
30139
30239
  const descriptionLower = skill.description.toLowerCase();
@@ -30169,7 +30269,7 @@ function searchSkills(query) {
30169
30269
  return scored.map((s) => s.skill);
30170
30270
  }
30171
30271
  function getSkill(name) {
30172
- return SKILLS.find((s) => s.name === name);
30272
+ return loadRegistry().find((s) => s.name === name);
30173
30273
  }
30174
30274
  function levenshtein(a, b) {
30175
30275
  const m = a.length, n = b.length;
@@ -30183,14 +30283,14 @@ function levenshtein(a, b) {
30183
30283
  }
30184
30284
  function findSimilarSkills(query, maxResults = 3) {
30185
30285
  const q = query.toLowerCase();
30186
- const scored = SKILLS.map((s) => ({ name: s.name, dist: levenshtein(q, s.name.toLowerCase()) })).filter((s) => s.dist <= Math.max(3, Math.floor(q.length / 2))).sort((a, b) => a.dist - b.dist);
30286
+ const scored = loadRegistry().map((s) => ({ name: s.name, dist: levenshtein(q, s.name.toLowerCase()) })).filter((s) => s.dist <= Math.max(3, Math.floor(q.length / 2))).sort((a, b) => a.dist - b.dist);
30187
30287
  return scored.slice(0, maxResults).map((s) => s.name);
30188
30288
  }
30189
30289
 
30190
30290
  // src/lib/installer.ts
30191
- import { existsSync, cpSync, mkdirSync, writeFileSync, rmSync, readdirSync, statSync, readFileSync, accessSync, constants } from "fs";
30192
- import { join, dirname } from "path";
30193
- import { homedir } from "os";
30291
+ import { existsSync as existsSync2, cpSync, mkdirSync, writeFileSync, rmSync, readdirSync as readdirSync2, statSync, readFileSync as readFileSync2, accessSync, constants } from "fs";
30292
+ import { join as join2, dirname } from "path";
30293
+ import { homedir as homedir2 } from "os";
30194
30294
  import { fileURLToPath } from "url";
30195
30295
 
30196
30296
  // src/lib/utils.ts
@@ -30203,33 +30303,33 @@ var __dirname2 = dirname(fileURLToPath(import.meta.url));
30203
30303
  function findSkillsDir() {
30204
30304
  let dir = __dirname2;
30205
30305
  for (let i = 0;i < 5; i++) {
30206
- const candidate = join(dir, "skills");
30207
- if (existsSync(candidate)) {
30306
+ const candidate = join2(dir, "skills");
30307
+ if (existsSync2(candidate) && !dir.includes(".skills")) {
30208
30308
  return candidate;
30209
30309
  }
30210
30310
  dir = dirname(dir);
30211
30311
  }
30212
- return join(__dirname2, "..", "skills");
30312
+ return join2(__dirname2, "..", "skills");
30213
30313
  }
30214
30314
  var SKILLS_DIR = findSkillsDir();
30215
30315
  function getSkillPath(name) {
30216
30316
  const skillName = normalizeSkillName(name);
30217
- return join(SKILLS_DIR, skillName);
30317
+ return join2(SKILLS_DIR, skillName);
30218
30318
  }
30219
30319
  function installSkill(name, options = {}) {
30220
30320
  const { targetDir = process.cwd(), overwrite = false } = options;
30221
30321
  const skillName = normalizeSkillName(name);
30222
30322
  const sourcePath = getSkillPath(name);
30223
- const destDir = join(targetDir, ".skills");
30224
- const destPath = join(destDir, skillName);
30225
- if (!existsSync(sourcePath)) {
30323
+ const destDir = join2(targetDir, ".skills");
30324
+ const destPath = join2(destDir, skillName);
30325
+ if (!existsSync2(sourcePath)) {
30226
30326
  return {
30227
30327
  skill: name,
30228
30328
  success: false,
30229
30329
  error: `Skill '${name}' not found`
30230
30330
  };
30231
30331
  }
30232
- if (existsSync(destPath) && !overwrite) {
30332
+ if (existsSync2(destPath) && !overwrite) {
30233
30333
  return {
30234
30334
  skill: name,
30235
30335
  success: false,
@@ -30238,10 +30338,10 @@ function installSkill(name, options = {}) {
30238
30338
  };
30239
30339
  }
30240
30340
  try {
30241
- if (!existsSync(destDir)) {
30341
+ if (!existsSync2(destDir)) {
30242
30342
  mkdirSync(destDir, { recursive: true });
30243
30343
  }
30244
- if (existsSync(destPath) && overwrite) {
30344
+ if (existsSync2(destPath) && overwrite) {
30245
30345
  rmSync(destPath, { recursive: true, force: true });
30246
30346
  }
30247
30347
  cpSync(sourcePath, destPath, {
@@ -30277,10 +30377,10 @@ function installSkill(name, options = {}) {
30277
30377
  }
30278
30378
  }
30279
30379
  function updateSkillsIndex(skillsDir) {
30280
- const indexPath = join(skillsDir, "index.ts");
30380
+ const indexPath = join2(skillsDir, "index.ts");
30281
30381
  const meta3 = loadMeta(skillsDir);
30282
30382
  const disabledSet = new Set(meta3.disabled || []);
30283
- const skills = readdirSync(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
30383
+ const skills = readdirSync2(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
30284
30384
  const exports = skills.map((s) => {
30285
30385
  const name = s.replace("skill-", "").replace(/-/g, "_");
30286
30386
  return `export * as ${name} from './${s}/src/index.js';`;
@@ -30296,13 +30396,13 @@ ${exports}
30296
30396
  writeFileSync(indexPath, content);
30297
30397
  }
30298
30398
  function getMetaPath(skillsDir) {
30299
- return join(skillsDir, ".meta.json");
30399
+ return join2(skillsDir, ".meta.json");
30300
30400
  }
30301
30401
  function loadMeta(skillsDir) {
30302
30402
  const metaPath = getMetaPath(skillsDir);
30303
- if (existsSync(metaPath)) {
30403
+ if (existsSync2(metaPath)) {
30304
30404
  try {
30305
- return JSON.parse(readFileSync(metaPath, "utf-8"));
30405
+ return JSON.parse(readFileSync2(metaPath, "utf-8"));
30306
30406
  } catch {}
30307
30407
  }
30308
30408
  return { skills: {} };
@@ -30315,9 +30415,9 @@ function recordInstall(skillsDir, name) {
30315
30415
  const skillName = normalizeSkillName(name);
30316
30416
  let version2 = "unknown";
30317
30417
  try {
30318
- const pkgPath = join(skillsDir, skillName, "package.json");
30319
- if (existsSync(pkgPath)) {
30320
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
30418
+ const pkgPath = join2(skillsDir, skillName, "package.json");
30419
+ if (existsSync2(pkgPath)) {
30420
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
30321
30421
  version2 = pkg.version || "unknown";
30322
30422
  }
30323
30423
  } catch {}
@@ -30330,20 +30430,20 @@ function recordRemove(skillsDir, name) {
30330
30430
  saveMeta(skillsDir, meta3);
30331
30431
  }
30332
30432
  function getInstalledSkills(targetDir = process.cwd()) {
30333
- const skillsDir = join(targetDir, ".skills");
30334
- if (!existsSync(skillsDir)) {
30433
+ const skillsDir = join2(targetDir, ".skills");
30434
+ if (!existsSync2(skillsDir)) {
30335
30435
  return [];
30336
30436
  }
30337
- return readdirSync(skillsDir).filter((f) => {
30338
- const fullPath = join(skillsDir, f);
30437
+ return readdirSync2(skillsDir).filter((f) => {
30438
+ const fullPath = join2(skillsDir, f);
30339
30439
  return f.startsWith("skill-") && statSync(fullPath).isDirectory();
30340
30440
  }).map((f) => f.replace("skill-", ""));
30341
30441
  }
30342
30442
  function removeSkill(name, targetDir = process.cwd()) {
30343
30443
  const skillName = normalizeSkillName(name);
30344
- const skillsDir = join(targetDir, ".skills");
30345
- const skillPath = join(skillsDir, skillName);
30346
- if (!existsSync(skillPath)) {
30444
+ const skillsDir = join2(targetDir, ".skills");
30445
+ const skillPath = join2(skillsDir, skillName);
30446
+ if (!existsSync2(skillPath)) {
30347
30447
  return false;
30348
30448
  }
30349
30449
  rmSync(skillPath, { recursive: true, force: true });
@@ -30351,7 +30451,14 @@ function removeSkill(name, targetDir = process.cwd()) {
30351
30451
  recordRemove(skillsDir, name);
30352
30452
  return true;
30353
30453
  }
30354
- var AGENT_TARGETS = ["claude", "codex", "gemini"];
30454
+ var AGENT_TARGETS = ["claude", "codex", "gemini", "pi", "opencode"];
30455
+ var AGENT_LABELS = {
30456
+ claude: "Claude Code",
30457
+ codex: "Codex CLI",
30458
+ gemini: "Gemini CLI",
30459
+ pi: "pi.dev",
30460
+ opencode: "OpenCode"
30461
+ };
30355
30462
  function resolveAgents(agentArg) {
30356
30463
  if (agentArg === "all")
30357
30464
  return [...AGENT_TARGETS];
@@ -30362,27 +30469,31 @@ function resolveAgents(agentArg) {
30362
30469
  return [agent];
30363
30470
  }
30364
30471
  function getAgentSkillsDir(agent, scope = "global", projectDir) {
30365
- const agentDir = `.${agent}`;
30366
- if (scope === "project") {
30367
- return join(projectDir || process.cwd(), agentDir, "skills");
30472
+ const base = projectDir || process.cwd();
30473
+ switch (agent) {
30474
+ case "pi":
30475
+ return scope === "project" ? join2(base, ".pi", "skills") : join2(homedir2(), ".pi", "agent", "skills");
30476
+ case "opencode":
30477
+ return scope === "project" ? join2(base, ".opencode", "skills") : join2(homedir2(), ".opencode", "skills");
30478
+ default:
30479
+ return scope === "project" ? join2(base, `.${agent}`, "skills") : join2(homedir2(), `.${agent}`, "skills");
30368
30480
  }
30369
- return join(homedir(), agentDir, "skills");
30370
30481
  }
30371
30482
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
30372
30483
  const skillName = normalizeSkillName(name);
30373
- return join(getAgentSkillsDir(agent, scope, projectDir), skillName);
30484
+ return join2(getAgentSkillsDir(agent, scope, projectDir), skillName);
30374
30485
  }
30375
30486
  function installSkillForAgent(name, options, generateSkillMd) {
30376
30487
  const { agent, scope = "global", projectDir } = options;
30377
30488
  const skillName = normalizeSkillName(name);
30378
30489
  const sourcePath = getSkillPath(name);
30379
- if (!existsSync(sourcePath)) {
30490
+ if (!existsSync2(sourcePath)) {
30380
30491
  return { skill: name, success: false, error: `Skill '${name}' not found` };
30381
30492
  }
30382
30493
  let skillMdContent = null;
30383
- const skillMdPath = join(sourcePath, "SKILL.md");
30384
- if (existsSync(skillMdPath)) {
30385
- skillMdContent = readFileSync(skillMdPath, "utf-8");
30494
+ const skillMdPath = join2(sourcePath, "SKILL.md");
30495
+ if (existsSync2(skillMdPath)) {
30496
+ skillMdContent = readFileSync2(skillMdPath, "utf-8");
30386
30497
  } else if (generateSkillMd) {
30387
30498
  skillMdContent = generateSkillMd(name);
30388
30499
  }
@@ -30391,17 +30502,12 @@ function installSkillForAgent(name, options, generateSkillMd) {
30391
30502
  }
30392
30503
  const destDir = getAgentSkillPath(name, agent, scope, projectDir);
30393
30504
  if (scope === "global") {
30394
- const agentBaseDir = join(homedir(), `.${agent}`);
30395
- if (!existsSync(agentBaseDir)) {
30396
- const agentLabels = {
30397
- claude: "Claude Code",
30398
- codex: "Codex CLI",
30399
- gemini: "Gemini CLI"
30400
- };
30505
+ const agentBaseDir = agent === "pi" ? join2(homedir2(), ".pi", "agent") : join2(homedir2(), `.${agent}`);
30506
+ if (!existsSync2(agentBaseDir)) {
30401
30507
  return {
30402
30508
  skill: name,
30403
30509
  success: false,
30404
- error: `Agent directory ${agentBaseDir} does not exist. Is ${agentLabels[agent]} installed?`
30510
+ error: `Agent directory ${agentBaseDir} does not exist. Is ${AGENT_LABELS[agent]} installed?`
30405
30511
  };
30406
30512
  }
30407
30513
  try {
@@ -30416,7 +30522,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
30416
30522
  }
30417
30523
  try {
30418
30524
  mkdirSync(destDir, { recursive: true });
30419
- writeFileSync(join(destDir, "SKILL.md"), skillMdContent);
30525
+ writeFileSync(join2(destDir, "SKILL.md"), skillMdContent);
30420
30526
  return { skill: name, success: true, path: destDir };
30421
30527
  } catch (error48) {
30422
30528
  return {
@@ -30429,7 +30535,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
30429
30535
  function removeSkillForAgent(name, options) {
30430
30536
  const { agent, scope = "global", projectDir } = options;
30431
30537
  const destDir = getAgentSkillPath(name, agent, scope, projectDir);
30432
- if (!existsSync(destDir)) {
30538
+ if (!existsSync2(destDir)) {
30433
30539
  return false;
30434
30540
  }
30435
30541
  rmSync(destDir, { recursive: true, force: true });
@@ -30437,16 +30543,16 @@ function removeSkillForAgent(name, options) {
30437
30543
  }
30438
30544
 
30439
30545
  // src/lib/skillinfo.ts
30440
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
30441
- import { join as join2 } from "path";
30546
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync3 } from "fs";
30547
+ import { join as join3 } from "path";
30442
30548
  function getSkillDocs(name) {
30443
30549
  const skillPath = getSkillPath(name);
30444
- if (!existsSync2(skillPath))
30550
+ if (!existsSync3(skillPath))
30445
30551
  return null;
30446
30552
  return {
30447
- skillMd: readIfExists(join2(skillPath, "SKILL.md")),
30448
- readme: readIfExists(join2(skillPath, "README.md")),
30449
- claudeMd: readIfExists(join2(skillPath, "CLAUDE.md"))
30553
+ skillMd: readIfExists(join3(skillPath, "SKILL.md")),
30554
+ readme: readIfExists(join3(skillPath, "README.md")),
30555
+ claudeMd: readIfExists(join3(skillPath, "CLAUDE.md"))
30450
30556
  };
30451
30557
  }
30452
30558
  function getSkillBestDoc(name) {
@@ -30457,11 +30563,11 @@ function getSkillBestDoc(name) {
30457
30563
  }
30458
30564
  function getSkillRequirements(name) {
30459
30565
  const skillPath = getSkillPath(name);
30460
- if (!existsSync2(skillPath))
30566
+ if (!existsSync3(skillPath))
30461
30567
  return null;
30462
30568
  const texts = [];
30463
30569
  for (const file2 of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
30464
- const content = readIfExists(join2(skillPath, file2));
30570
+ const content = readIfExists(join3(skillPath, file2));
30465
30571
  if (content)
30466
30572
  texts.push(content);
30467
30573
  }
@@ -30488,10 +30594,10 @@ function getSkillRequirements(name) {
30488
30594
  }
30489
30595
  let cliCommand = null;
30490
30596
  let dependencies = {};
30491
- const pkgPath = join2(skillPath, "package.json");
30492
- if (existsSync2(pkgPath)) {
30597
+ const pkgPath = join3(skillPath, "package.json");
30598
+ if (existsSync3(pkgPath)) {
30493
30599
  try {
30494
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
30600
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
30495
30601
  if (pkg.bin) {
30496
30602
  const binKeys = Object.keys(pkg.bin);
30497
30603
  if (binKeys.length > 0)
@@ -30511,25 +30617,25 @@ async function runSkill(name, args, options = {}) {
30511
30617
  const skillName = normalizeSkillName(name);
30512
30618
  let skillPath;
30513
30619
  if (options.installed) {
30514
- skillPath = join2(process.cwd(), ".skills", skillName);
30620
+ skillPath = join3(process.cwd(), ".skills", skillName);
30515
30621
  } else {
30516
- const installedPath = join2(process.cwd(), ".skills", skillName);
30517
- if (existsSync2(installedPath)) {
30622
+ const installedPath = join3(process.cwd(), ".skills", skillName);
30623
+ if (existsSync3(installedPath)) {
30518
30624
  skillPath = installedPath;
30519
30625
  } else {
30520
30626
  skillPath = getSkillPath(name);
30521
30627
  }
30522
30628
  }
30523
- if (!existsSync2(skillPath)) {
30629
+ if (!existsSync3(skillPath)) {
30524
30630
  return { exitCode: 1, error: `Skill '${name}' not found` };
30525
30631
  }
30526
- const pkgPath = join2(skillPath, "package.json");
30527
- if (!existsSync2(pkgPath)) {
30632
+ const pkgPath = join3(skillPath, "package.json");
30633
+ if (!existsSync3(pkgPath)) {
30528
30634
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
30529
30635
  }
30530
30636
  let entryPoint;
30531
30637
  try {
30532
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
30638
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
30533
30639
  if (pkg.bin) {
30534
30640
  const binValues = Object.values(pkg.bin);
30535
30641
  entryPoint = binValues[0];
@@ -30543,12 +30649,12 @@ async function runSkill(name, args, options = {}) {
30543
30649
  } catch {
30544
30650
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
30545
30651
  }
30546
- const entryPath = join2(skillPath, entryPoint);
30547
- if (!existsSync2(entryPath)) {
30652
+ const entryPath = join3(skillPath, entryPoint);
30653
+ if (!existsSync3(entryPath)) {
30548
30654
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
30549
30655
  }
30550
- const nodeModules = join2(skillPath, "node_modules");
30551
- if (!existsSync2(nodeModules)) {
30656
+ const nodeModules = join3(skillPath, "node_modules");
30657
+ if (!existsSync3(nodeModules)) {
30552
30658
  const install = Bun.spawn(["bun", "install", "--no-save"], {
30553
30659
  cwd: skillPath,
30554
30660
  stdout: "pipe",
@@ -30565,12 +30671,99 @@ async function runSkill(name, args, options = {}) {
30565
30671
  const exitCode = await proc.exited;
30566
30672
  return { exitCode };
30567
30673
  }
30674
+ function detectProjectSkills(cwd = process.cwd()) {
30675
+ const pkgPath = join3(cwd, "package.json");
30676
+ if (!existsSync3(pkgPath)) {
30677
+ const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
30678
+ const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
30679
+ return { detected: [], recommended: recommended2 };
30680
+ }
30681
+ let pkg;
30682
+ try {
30683
+ pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
30684
+ } catch {
30685
+ const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
30686
+ const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
30687
+ return { detected: [], recommended: recommended2 };
30688
+ }
30689
+ const allDeps = {
30690
+ ...pkg.dependencies,
30691
+ ...pkg.devDependencies
30692
+ };
30693
+ const depNames = Object.keys(allDeps);
30694
+ const detected = [];
30695
+ const recommendedNames = new Set;
30696
+ for (const name of ["implementation-plan", "write", "deepresearch"]) {
30697
+ recommendedNames.add(name);
30698
+ }
30699
+ const frontendDeps = ["next", "react", "vue", "svelte", "nuxt", "@nuxtjs/nuxt"];
30700
+ for (const dep of frontendDeps) {
30701
+ if (depNames.some((d) => d === dep || d.startsWith(`${dep}/`))) {
30702
+ detected.push(dep);
30703
+ for (const name of ["image", "generate-favicon", "seo-brief-builder"]) {
30704
+ recommendedNames.add(name);
30705
+ }
30706
+ break;
30707
+ }
30708
+ }
30709
+ const backendDeps = ["express", "fastify", "hono", "koa", "@hono/hono"];
30710
+ for (const dep of backendDeps) {
30711
+ if (depNames.some((d) => d === dep || d.startsWith(`${dep}/`))) {
30712
+ detected.push(dep);
30713
+ for (const name of ["api-test-suite", "apidocs"]) {
30714
+ recommendedNames.add(name);
30715
+ }
30716
+ break;
30717
+ }
30718
+ }
30719
+ const aiDeps = ["@anthropic-ai/sdk", "openai", "@openai/openai", "anthropic"];
30720
+ for (const dep of aiDeps) {
30721
+ if (depNames.includes(dep)) {
30722
+ detected.push(dep);
30723
+ for (const name of ["deepresearch", "webcrawling"]) {
30724
+ recommendedNames.add(name);
30725
+ }
30726
+ break;
30727
+ }
30728
+ }
30729
+ if (depNames.includes("stripe")) {
30730
+ detected.push("stripe");
30731
+ recommendedNames.add("invoice");
30732
+ }
30733
+ const emailDeps = ["nodemailer", "@sendgrid/mail", "@sendgrid/client"];
30734
+ for (const dep of emailDeps) {
30735
+ if (depNames.includes(dep)) {
30736
+ detected.push(dep);
30737
+ for (const name of ["gmail", "email-campaign"]) {
30738
+ recommendedNames.add(name);
30739
+ }
30740
+ break;
30741
+ }
30742
+ }
30743
+ const testDeps = ["vitest", "jest", "mocha", "@jest/core"];
30744
+ for (const dep of testDeps) {
30745
+ if (depNames.includes(dep)) {
30746
+ detected.push(dep);
30747
+ recommendedNames.add("api-test-suite");
30748
+ break;
30749
+ }
30750
+ }
30751
+ if (depNames.includes("typescript")) {
30752
+ detected.push("typescript");
30753
+ for (const name of ["scaffold-project", "deploy"]) {
30754
+ recommendedNames.add(name);
30755
+ }
30756
+ }
30757
+ const uniqueDetected = Array.from(new Set(detected));
30758
+ const recommended = Array.from(recommendedNames).map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
30759
+ return { detected: uniqueDetected, recommended };
30760
+ }
30568
30761
  function generateSkillMd(name) {
30569
30762
  const meta3 = getSkill(name);
30570
30763
  if (!meta3)
30571
30764
  return null;
30572
30765
  const skillPath = getSkillPath(name);
30573
- if (!existsSync2(skillPath))
30766
+ if (!existsSync3(skillPath))
30574
30767
  return null;
30575
30768
  const frontmatter = [
30576
30769
  "---",
@@ -30579,13 +30772,13 @@ function generateSkillMd(name) {
30579
30772
  "---"
30580
30773
  ].join(`
30581
30774
  `);
30582
- const readme = readIfExists(join2(skillPath, "README.md"));
30583
- const claudeMd = readIfExists(join2(skillPath, "CLAUDE.md"));
30775
+ const readme = readIfExists(join3(skillPath, "README.md"));
30776
+ const claudeMd = readIfExists(join3(skillPath, "CLAUDE.md"));
30584
30777
  let cliCommand = null;
30585
- const pkgPath = join2(skillPath, "package.json");
30586
- if (existsSync2(pkgPath)) {
30778
+ const pkgPath = join3(skillPath, "package.json");
30779
+ if (existsSync3(pkgPath)) {
30587
30780
  try {
30588
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
30781
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
30589
30782
  if (pkg.bin) {
30590
30783
  const binKeys = Object.keys(pkg.bin);
30591
30784
  if (binKeys.length > 0)
@@ -30660,13 +30853,141 @@ function extractEnvVars(text) {
30660
30853
  }
30661
30854
  function readIfExists(path) {
30662
30855
  try {
30663
- if (existsSync2(path)) {
30664
- return readFileSync2(path, "utf-8");
30856
+ if (existsSync3(path)) {
30857
+ return readFileSync3(path, "utf-8");
30665
30858
  }
30666
30859
  } catch {}
30667
30860
  return null;
30668
30861
  }
30669
30862
 
30863
+ // src/lib/scheduler.ts
30864
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
30865
+ import { join as join4 } from "path";
30866
+ function getSchedulesPath(targetDir = process.cwd()) {
30867
+ return join4(targetDir, ".skills", "schedules.json");
30868
+ }
30869
+ function loadSchedules(targetDir = process.cwd()) {
30870
+ const path = getSchedulesPath(targetDir);
30871
+ if (existsSync4(path)) {
30872
+ try {
30873
+ return JSON.parse(readFileSync4(path, "utf-8"));
30874
+ } catch {}
30875
+ }
30876
+ return { version: 1, schedules: [] };
30877
+ }
30878
+ function saveSchedules(data, targetDir = process.cwd()) {
30879
+ const path = getSchedulesPath(targetDir);
30880
+ const dir = join4(targetDir, ".skills");
30881
+ if (!existsSync4(dir))
30882
+ mkdirSync2(dir, { recursive: true });
30883
+ writeFileSync2(path, JSON.stringify(data, null, 2));
30884
+ }
30885
+ function validateCron(expr) {
30886
+ const fields = expr.trim().split(/\s+/);
30887
+ if (fields.length !== 5) {
30888
+ return { valid: false, error: `Expected 5 fields, got ${fields.length}. Format: "minute hour day-of-month month day-of-week"` };
30889
+ }
30890
+ return { valid: true };
30891
+ }
30892
+ function getNextRun(cron, from = new Date) {
30893
+ const { valid } = validateCron(cron);
30894
+ if (!valid)
30895
+ return null;
30896
+ const [minuteF, hourF, domF, monthF, dowF] = cron.trim().split(/\s+/);
30897
+ function parseField(f, min, max) {
30898
+ if (f === "*")
30899
+ return Array.from({ length: max - min + 1 }, (_, i) => i + min);
30900
+ if (f.startsWith("*/")) {
30901
+ const step = parseInt(f.slice(2));
30902
+ if (isNaN(step))
30903
+ return [];
30904
+ const vals = [];
30905
+ for (let i = min;i <= max; i += step)
30906
+ vals.push(i);
30907
+ return vals;
30908
+ }
30909
+ return f.split(",").flatMap((part) => {
30910
+ if (part.includes("-")) {
30911
+ const [lo, hi] = part.split("-").map(Number);
30912
+ return Array.from({ length: hi - lo + 1 }, (_, i) => i + lo);
30913
+ }
30914
+ const n = parseInt(part);
30915
+ return isNaN(n) ? [] : [n];
30916
+ });
30917
+ }
30918
+ const minutes = parseField(minuteF, 0, 59);
30919
+ const hours = parseField(hourF, 0, 23);
30920
+ const doms = parseField(domF, 1, 31);
30921
+ const months = parseField(monthF, 1, 12);
30922
+ const dows = parseField(dowF, 0, 6);
30923
+ const candidate = new Date(from);
30924
+ candidate.setSeconds(0, 0);
30925
+ candidate.setMinutes(candidate.getMinutes() + 1);
30926
+ const limit = new Date(from);
30927
+ limit.setFullYear(limit.getFullYear() + 1);
30928
+ while (candidate < limit) {
30929
+ const month = candidate.getMonth() + 1;
30930
+ const dom = candidate.getDate();
30931
+ const dow = candidate.getDay();
30932
+ const hour = candidate.getHours();
30933
+ const minute = candidate.getMinutes();
30934
+ if (!months.includes(month)) {
30935
+ candidate.setMonth(candidate.getMonth() + 1, 1);
30936
+ candidate.setHours(0, 0, 0, 0);
30937
+ continue;
30938
+ }
30939
+ if (!doms.includes(dom) || !dows.includes(dow)) {
30940
+ candidate.setDate(candidate.getDate() + 1);
30941
+ candidate.setHours(0, 0, 0, 0);
30942
+ continue;
30943
+ }
30944
+ if (!hours.includes(hour)) {
30945
+ candidate.setHours(candidate.getHours() + 1, 0, 0, 0);
30946
+ continue;
30947
+ }
30948
+ if (!minutes.includes(minute)) {
30949
+ candidate.setMinutes(candidate.getMinutes() + 1, 0, 0);
30950
+ continue;
30951
+ }
30952
+ return new Date(candidate);
30953
+ }
30954
+ return null;
30955
+ }
30956
+ function addSchedule(skill, cron, options = {}) {
30957
+ const { valid, error: error48 } = validateCron(cron);
30958
+ if (!valid)
30959
+ return { schedule: null, error: error48 };
30960
+ const data = loadSchedules(options.targetDir);
30961
+ const id = `${skill}-${Date.now()}`;
30962
+ const now = new Date;
30963
+ const nextRun = getNextRun(cron, now);
30964
+ const schedule = {
30965
+ id,
30966
+ name: options.name || `${skill} (${cron})`,
30967
+ skill,
30968
+ cron,
30969
+ args: options.args,
30970
+ enabled: true,
30971
+ createdAt: now.toISOString(),
30972
+ nextRun: nextRun?.toISOString()
30973
+ };
30974
+ data.schedules.push(schedule);
30975
+ saveSchedules(data, options.targetDir);
30976
+ return { schedule };
30977
+ }
30978
+ function listSchedules(targetDir) {
30979
+ return loadSchedules(targetDir).schedules;
30980
+ }
30981
+ function removeSchedule(idOrName, targetDir) {
30982
+ const data = loadSchedules(targetDir);
30983
+ const before = data.schedules.length;
30984
+ data.schedules = data.schedules.filter((s) => s.id !== idOrName && s.name !== idOrName);
30985
+ if (data.schedules.length === before)
30986
+ return false;
30987
+ saveSchedules(data, targetDir);
30988
+ return true;
30989
+ }
30990
+
30670
30991
  // src/mcp/index.ts
30671
30992
  var server = new McpServer({
30672
30993
  name: "skills",
@@ -30710,7 +31031,7 @@ server.registerTool("list_skills", {
30710
31031
  offset: exports_external.number().optional()
30711
31032
  }
30712
31033
  }, async ({ category, detail, limit, offset }) => {
30713
- const skills = category ? getSkillsByCategory(category) : SKILLS;
31034
+ const skills = category ? getSkillsByCategory(category) : loadRegistry();
30714
31035
  const mapped = detail ? skills : skills.map((s) => ({ name: s.name, category: s.category }));
30715
31036
  if (limit !== undefined || offset !== undefined) {
30716
31037
  const start = offset || 0;
@@ -30795,7 +31116,7 @@ server.registerTool("get_skill_docs", {
30795
31116
  });
30796
31117
  server.registerTool("install_skill", {
30797
31118
  title: "Install Skill",
30798
- description: "Install a skill to .skills/ or to an agent dir (for: claude|codex|gemini|all).",
31119
+ description: "Install a skill to .skills/ or to an agent dir (for: claude|codex|gemini|pi|opencode|all).",
30799
31120
  inputSchema: {
30800
31121
  name: exports_external.string(),
30801
31122
  for: exports_external.string().optional(),
@@ -30807,7 +31128,7 @@ server.registerTool("install_skill", {
30807
31128
  try {
30808
31129
  agents = resolveAgents(agentArg);
30809
31130
  } catch (err) {
30810
- return mcpError("INVALID_AGENT", err.message, ["claude", "codex", "gemini", "all"]);
31131
+ return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
30811
31132
  }
30812
31133
  const results = agents.map((a) => installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd));
30813
31134
  return {
@@ -30845,7 +31166,7 @@ server.registerTool("install_category", {
30845
31166
  try {
30846
31167
  agents = resolveAgents(agentArg);
30847
31168
  } catch (err) {
30848
- return mcpError("INVALID_AGENT", err.message, ["claude", "codex", "gemini", "all"]);
31169
+ return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
30849
31170
  }
30850
31171
  const results2 = [];
30851
31172
  for (const name of names) {
@@ -30879,7 +31200,7 @@ server.registerTool("remove_skill", {
30879
31200
  try {
30880
31201
  agents = resolveAgents(agentArg);
30881
31202
  } catch (err) {
30882
- return mcpError("INVALID_AGENT", err.message, ["claude", "codex", "gemini", "all"]);
31203
+ return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
30883
31204
  }
30884
31205
  const results = agents.map((a) => ({
30885
31206
  skill: name,
@@ -30912,7 +31233,7 @@ server.registerTool("list_tags", {
30912
31233
  description: "List all unique skill tags with occurrence counts."
30913
31234
  }, async () => {
30914
31235
  const tagCounts = new Map;
30915
- for (const skill of SKILLS) {
31236
+ for (const skill of loadRegistry()) {
30916
31237
  for (const tag of skill.tags) {
30917
31238
  tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
30918
31239
  }
@@ -30986,7 +31307,7 @@ server.registerTool("import_skills", {
30986
31307
  try {
30987
31308
  agents = resolveAgents(agentArg);
30988
31309
  } catch (err) {
30989
- return mcpError("INVALID_AGENT", err.message, ["claude", "codex", "gemini", "all"]);
31310
+ return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
30990
31311
  }
30991
31312
  for (const name of skillList) {
30992
31313
  const agentResults = agents.map((a) => installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd));
@@ -31014,21 +31335,20 @@ server.registerTool("whoami", {
31014
31335
  const version2 = package_default.version;
31015
31336
  const cwd = process.cwd();
31016
31337
  const installed = getInstalledSkills();
31017
- const agentNames = ["claude", "codex", "gemini"];
31018
31338
  const agents = [];
31019
- for (const agent of agentNames) {
31020
- const agentSkillsPath = join3(homedir2(), `.${agent}`, "skills");
31021
- const exists = existsSync3(agentSkillsPath);
31339
+ for (const agent of AGENT_TARGETS) {
31340
+ const agentSkillsPath = getAgentSkillsDir(agent, "global");
31341
+ const exists = existsSync5(agentSkillsPath);
31022
31342
  let skillCount = 0;
31023
31343
  if (exists) {
31024
31344
  try {
31025
- skillCount = readdirSync3(agentSkillsPath).filter((f) => {
31026
- const full = join3(agentSkillsPath, f);
31345
+ skillCount = readdirSync4(agentSkillsPath).filter((f) => {
31346
+ const full = join5(agentSkillsPath, f);
31027
31347
  return f.startsWith("skill-") && statSync2(full).isDirectory();
31028
31348
  }).length;
31029
31349
  } catch {}
31030
31350
  }
31031
- agents.push({ agent, path: agentSkillsPath, exists, skillCount });
31351
+ agents.push({ agent, label: AGENT_LABELS[agent], path: agentSkillsPath, exists, skillCount });
31032
31352
  }
31033
31353
  const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
31034
31354
  const result = {
@@ -31041,12 +31361,107 @@ server.registerTool("whoami", {
31041
31361
  };
31042
31362
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
31043
31363
  });
31364
+ server.registerTool("schedule_skill", {
31365
+ title: "Schedule Skill",
31366
+ description: "Add a cron schedule to run a skill at a recurring time. Cron format: 'minute hour dom month dow' (e.g. '0 9 * * *' = daily at 9am).",
31367
+ inputSchema: {
31368
+ skill: exports_external.string(),
31369
+ cron: exports_external.string(),
31370
+ name: exports_external.string().optional(),
31371
+ args: exports_external.array(exports_external.string()).optional()
31372
+ }
31373
+ }, async ({ skill, cron, name, args }) => {
31374
+ const { schedule, error: error48 } = addSchedule(skill, cron, { name, args });
31375
+ if (error48 || !schedule) {
31376
+ return { content: [{ type: "text", text: JSON.stringify({ error: error48 || "Failed to add schedule" }) }] };
31377
+ }
31378
+ return { content: [{ type: "text", text: JSON.stringify(schedule, null, 2) }] };
31379
+ });
31380
+ server.registerTool("list_schedules", {
31381
+ title: "List Schedules",
31382
+ description: "List all scheduled skill runs.",
31383
+ inputSchema: {}
31384
+ }, async () => {
31385
+ const schedules = listSchedules();
31386
+ return { content: [{ type: "text", text: JSON.stringify(schedules, null, 2) }] };
31387
+ });
31388
+ server.registerTool("remove_schedule", {
31389
+ title: "Remove Schedule",
31390
+ description: "Remove a schedule by its ID or name.",
31391
+ inputSchema: {
31392
+ id_or_name: exports_external.string()
31393
+ }
31394
+ }, async ({ id_or_name }) => {
31395
+ const removed = removeSchedule(id_or_name);
31396
+ return { content: [{ type: "text", text: JSON.stringify({ removed, id_or_name }) }] };
31397
+ });
31398
+ server.registerTool("detect_project_skills", {
31399
+ title: "Detect Project Skills",
31400
+ description: "Detect project type from package.json and return recommended skills based on dependencies.",
31401
+ inputSchema: {
31402
+ directory: exports_external.string().optional()
31403
+ }
31404
+ }, async ({ directory }) => {
31405
+ const cwd = directory || process.cwd();
31406
+ const { detected, recommended } = detectProjectSkills(cwd);
31407
+ return {
31408
+ content: [{
31409
+ type: "text",
31410
+ text: JSON.stringify({
31411
+ directory: cwd,
31412
+ detected,
31413
+ recommended: recommended.map((s) => ({ name: s.name, displayName: s.displayName, description: s.description, category: s.category }))
31414
+ }, null, 2)
31415
+ }]
31416
+ };
31417
+ });
31418
+ server.registerTool("validate_skill", {
31419
+ title: "Validate Skill",
31420
+ description: "Check a skill's structure: SKILL.md, package.json with bin entry, tsconfig.json, src/index.ts. Returns validation result with list of issues.",
31421
+ inputSchema: {
31422
+ name: exports_external.string()
31423
+ }
31424
+ }, async ({ name }) => {
31425
+ const skillPath = getSkillPath(name);
31426
+ const issues = [];
31427
+ if (!existsSync5(skillPath)) {
31428
+ return {
31429
+ content: [{ type: "text", text: JSON.stringify({ name, valid: false, issues: [`Skill directory not found: ${skillPath}`] }) }]
31430
+ };
31431
+ }
31432
+ if (!existsSync5(join5(skillPath, "SKILL.md")))
31433
+ issues.push("Missing SKILL.md");
31434
+ if (!existsSync5(join5(skillPath, "tsconfig.json")))
31435
+ issues.push("Missing tsconfig.json");
31436
+ const pkgPath = join5(skillPath, "package.json");
31437
+ if (!existsSync5(pkgPath)) {
31438
+ issues.push("Missing package.json");
31439
+ } else {
31440
+ try {
31441
+ const pkg = JSON.parse(__require("fs").readFileSync(pkgPath, "utf-8"));
31442
+ if (!pkg.bin || Object.keys(pkg.bin).length === 0)
31443
+ issues.push("package.json missing 'bin' entry");
31444
+ } catch {
31445
+ issues.push("package.json is invalid JSON");
31446
+ }
31447
+ }
31448
+ const srcDir = join5(skillPath, "src");
31449
+ if (!existsSync5(srcDir)) {
31450
+ issues.push("Missing src/ directory");
31451
+ } else if (!existsSync5(join5(srcDir, "index.ts"))) {
31452
+ issues.push("Missing src/index.ts");
31453
+ }
31454
+ const valid = issues.length === 0;
31455
+ return {
31456
+ content: [{ type: "text", text: JSON.stringify({ name, valid, path: skillPath, issues }) }]
31457
+ };
31458
+ });
31044
31459
  server.registerResource("Skills Registry", "skills://registry", {
31045
31460
  description: "Compact skill list [{name,category}]. Use skills://{name} for detail."
31046
31461
  }, async () => ({
31047
31462
  contents: [{
31048
31463
  uri: "skills://registry",
31049
- text: JSON.stringify(SKILLS.map((s) => ({ name: s.name, category: s.category }))),
31464
+ text: JSON.stringify(loadRegistry().map((s) => ({ name: s.name, category: s.category }))),
31050
31465
  mimeType: "application/json"
31051
31466
  }]
31052
31467
  }));