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