@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/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var __create = Object.create;
|
|
|
4
4
|
var __getProtoOf = Object.getPrototypeOf;
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
9
|
function __accessProp(key) {
|
|
9
10
|
return this[key];
|
|
@@ -30,6 +31,23 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
30
31
|
cache.set(mod, to);
|
|
31
32
|
return to;
|
|
32
33
|
};
|
|
34
|
+
var __toCommonJS = (from) => {
|
|
35
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
36
|
+
if (entry)
|
|
37
|
+
return entry;
|
|
38
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
39
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
40
|
+
for (var key of __getOwnPropNames(from))
|
|
41
|
+
if (!__hasOwnProp.call(entry, key))
|
|
42
|
+
__defProp(entry, key, {
|
|
43
|
+
get: __accessProp.bind(from, key),
|
|
44
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
__moduleCache.set(from, entry);
|
|
48
|
+
return entry;
|
|
49
|
+
};
|
|
50
|
+
var __moduleCache;
|
|
33
51
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
52
|
var __returnValue = (v) => v;
|
|
35
53
|
function __exportSetter(name, newValue) {
|
|
@@ -1896,7 +1914,7 @@ var package_default;
|
|
|
1896
1914
|
var init_package = __esm(() => {
|
|
1897
1915
|
package_default = {
|
|
1898
1916
|
name: "@hasna/skills",
|
|
1899
|
-
version: "0.1.
|
|
1917
|
+
version: "0.1.15",
|
|
1900
1918
|
description: "Skills library for AI coding agents",
|
|
1901
1919
|
type: "module",
|
|
1902
1920
|
bin: {
|
|
@@ -1981,8 +1999,91 @@ var init_package = __esm(() => {
|
|
|
1981
1999
|
});
|
|
1982
2000
|
|
|
1983
2001
|
// src/lib/registry.ts
|
|
2002
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
2003
|
+
import { join } from "path";
|
|
2004
|
+
import { homedir } from "os";
|
|
2005
|
+
function parseSkillMdFrontmatter(content) {
|
|
2006
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2007
|
+
if (!match)
|
|
2008
|
+
return null;
|
|
2009
|
+
const result = {};
|
|
2010
|
+
for (const line of match[1].split(`
|
|
2011
|
+
`)) {
|
|
2012
|
+
const colon = line.indexOf(":");
|
|
2013
|
+
if (colon === -1)
|
|
2014
|
+
continue;
|
|
2015
|
+
const key = line.slice(0, colon).trim();
|
|
2016
|
+
const value = line.slice(colon + 1).trim();
|
|
2017
|
+
if (!key || !value)
|
|
2018
|
+
continue;
|
|
2019
|
+
if (key === "name")
|
|
2020
|
+
result.name = value;
|
|
2021
|
+
else if (key === "description")
|
|
2022
|
+
result.description = value;
|
|
2023
|
+
else if (key === "displayName" || key === "display_name")
|
|
2024
|
+
result.displayName = value;
|
|
2025
|
+
else if (key === "category")
|
|
2026
|
+
result.category = value;
|
|
2027
|
+
else if (key === "tags") {
|
|
2028
|
+
result.tags = value.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean);
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
2032
|
+
}
|
|
2033
|
+
function discoverSkillsInDir(dir) {
|
|
2034
|
+
if (!existsSync(dir))
|
|
2035
|
+
return [];
|
|
2036
|
+
const result = [];
|
|
2037
|
+
try {
|
|
2038
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
2039
|
+
for (const entry of entries) {
|
|
2040
|
+
if (!entry.isDirectory())
|
|
2041
|
+
continue;
|
|
2042
|
+
const skillMdPath = join(dir, entry.name, "SKILL.md");
|
|
2043
|
+
if (!existsSync(skillMdPath))
|
|
2044
|
+
continue;
|
|
2045
|
+
let content;
|
|
2046
|
+
try {
|
|
2047
|
+
content = readFileSync(skillMdPath, "utf-8");
|
|
2048
|
+
} catch {
|
|
2049
|
+
continue;
|
|
2050
|
+
}
|
|
2051
|
+
const fm = parseSkillMdFrontmatter(content);
|
|
2052
|
+
if (!fm?.name)
|
|
2053
|
+
continue;
|
|
2054
|
+
const name = fm.name.replace(/^skill-/, "");
|
|
2055
|
+
result.push({
|
|
2056
|
+
name,
|
|
2057
|
+
displayName: fm.displayName || name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
2058
|
+
description: fm.description || "",
|
|
2059
|
+
category: fm.category || "Development Tools",
|
|
2060
|
+
tags: fm.tags || [],
|
|
2061
|
+
source: "custom"
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
} catch {}
|
|
2065
|
+
return result;
|
|
2066
|
+
}
|
|
2067
|
+
function loadRegistry(cwd) {
|
|
2068
|
+
const now = Date.now();
|
|
2069
|
+
if (_registryCache && now - _registryCacheTime < REGISTRY_CACHE_TTL) {
|
|
2070
|
+
return _registryCache;
|
|
2071
|
+
}
|
|
2072
|
+
const official = SKILLS.map((s) => ({ ...s, source: "official" }));
|
|
2073
|
+
const globalCustom = discoverSkillsInDir(join(homedir(), ".skills"));
|
|
2074
|
+
const projectCustom = discoverSkillsInDir(join(cwd || process.cwd(), ".skills", "custom-skills"));
|
|
2075
|
+
const customNames = new Set([...globalCustom, ...projectCustom].map((s) => s.name));
|
|
2076
|
+
const filtered = official.filter((s) => !customNames.has(s.name));
|
|
2077
|
+
_registryCache = [...filtered, ...globalCustom, ...projectCustom];
|
|
2078
|
+
_registryCacheTime = now;
|
|
2079
|
+
return _registryCache;
|
|
2080
|
+
}
|
|
2081
|
+
function clearRegistryCache() {
|
|
2082
|
+
_registryCache = null;
|
|
2083
|
+
_registryCacheTime = 0;
|
|
2084
|
+
}
|
|
1984
2085
|
function getSkillsByCategory(category) {
|
|
1985
|
-
return
|
|
2086
|
+
return loadRegistry().filter((s) => s.category === category);
|
|
1986
2087
|
}
|
|
1987
2088
|
function editDistance(a, b) {
|
|
1988
2089
|
if (a === b)
|
|
@@ -2028,7 +2129,7 @@ function searchSkills(query) {
|
|
|
2028
2129
|
if (words.length === 0)
|
|
2029
2130
|
return [];
|
|
2030
2131
|
const scored = [];
|
|
2031
|
-
for (const skill of
|
|
2132
|
+
for (const skill of loadRegistry()) {
|
|
2032
2133
|
const nameLower = skill.name.toLowerCase();
|
|
2033
2134
|
const displayNameLower = skill.displayName.toLowerCase();
|
|
2034
2135
|
const descriptionLower = skill.description.toLowerCase();
|
|
@@ -2064,7 +2165,7 @@ function searchSkills(query) {
|
|
|
2064
2165
|
return scored.map((s) => s.skill);
|
|
2065
2166
|
}
|
|
2066
2167
|
function getSkill(name) {
|
|
2067
|
-
return
|
|
2168
|
+
return loadRegistry().find((s) => s.name === name);
|
|
2068
2169
|
}
|
|
2069
2170
|
function levenshtein(a, b) {
|
|
2070
2171
|
const m = a.length, n = b.length;
|
|
@@ -2078,10 +2179,10 @@ function levenshtein(a, b) {
|
|
|
2078
2179
|
}
|
|
2079
2180
|
function findSimilarSkills(query, maxResults = 3) {
|
|
2080
2181
|
const q = query.toLowerCase();
|
|
2081
|
-
const scored =
|
|
2182
|
+
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);
|
|
2082
2183
|
return scored.slice(0, maxResults).map((s) => s.name);
|
|
2083
2184
|
}
|
|
2084
|
-
var CATEGORIES, SKILLS;
|
|
2185
|
+
var CATEGORIES, SKILLS, _registryCache = null, _registryCacheTime = 0, REGISTRY_CACHE_TTL = 5000;
|
|
2085
2186
|
var init_registry = __esm(() => {
|
|
2086
2187
|
CATEGORIES = [
|
|
2087
2188
|
"Development Tools",
|
|
@@ -3119,6 +3220,20 @@ var init_registry = __esm(() => {
|
|
|
3119
3220
|
category: "Design & Branding",
|
|
3120
3221
|
tags: ["testimonials", "graphics", "social-proof", "marketing"]
|
|
3121
3222
|
},
|
|
3223
|
+
{
|
|
3224
|
+
name: "colorextract",
|
|
3225
|
+
displayName: "Color Extract",
|
|
3226
|
+
description: "Extract complete color palettes from screenshots and images using Claude Vision. Outputs open-styles compatible profiles.",
|
|
3227
|
+
category: "Design & Branding",
|
|
3228
|
+
tags: ["colors", "palette", "design", "vision", "screenshot", "extract", "open-styles"]
|
|
3229
|
+
},
|
|
3230
|
+
{
|
|
3231
|
+
name: "siteanalyze",
|
|
3232
|
+
displayName: "Site Analyze",
|
|
3233
|
+
description: "Analyze any website's design system \u2014 detects shadcn/ui, Tailwind, extracts colors, typography, and components via Playwright + Claude Vision.",
|
|
3234
|
+
category: "Design & Branding",
|
|
3235
|
+
tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "open-styles"]
|
|
3236
|
+
},
|
|
3122
3237
|
{
|
|
3123
3238
|
name: "browse",
|
|
3124
3239
|
displayName: "Browse",
|
|
@@ -5162,39 +5277,39 @@ var require_cli_spinners = __commonJS((exports, module) => {
|
|
|
5162
5277
|
});
|
|
5163
5278
|
|
|
5164
5279
|
// src/lib/installer.ts
|
|
5165
|
-
import { existsSync, cpSync, mkdirSync, writeFileSync, rmSync, readdirSync, statSync, readFileSync, accessSync, constants } from "fs";
|
|
5166
|
-
import { join, dirname } from "path";
|
|
5167
|
-
import { homedir } from "os";
|
|
5280
|
+
import { existsSync as existsSync2, cpSync, mkdirSync, writeFileSync, rmSync, readdirSync as readdirSync2, statSync, readFileSync as readFileSync2, accessSync, constants } from "fs";
|
|
5281
|
+
import { join as join2, dirname } from "path";
|
|
5282
|
+
import { homedir as homedir2 } from "os";
|
|
5168
5283
|
import { fileURLToPath } from "url";
|
|
5169
5284
|
function findSkillsDir() {
|
|
5170
5285
|
let dir = __dirname2;
|
|
5171
5286
|
for (let i = 0;i < 5; i++) {
|
|
5172
|
-
const candidate =
|
|
5173
|
-
if (
|
|
5287
|
+
const candidate = join2(dir, "skills");
|
|
5288
|
+
if (existsSync2(candidate) && !dir.includes(".skills")) {
|
|
5174
5289
|
return candidate;
|
|
5175
5290
|
}
|
|
5176
5291
|
dir = dirname(dir);
|
|
5177
5292
|
}
|
|
5178
|
-
return
|
|
5293
|
+
return join2(__dirname2, "..", "skills");
|
|
5179
5294
|
}
|
|
5180
5295
|
function getSkillPath(name) {
|
|
5181
5296
|
const skillName = normalizeSkillName(name);
|
|
5182
|
-
return
|
|
5297
|
+
return join2(SKILLS_DIR, skillName);
|
|
5183
5298
|
}
|
|
5184
5299
|
function installSkill(name, options = {}) {
|
|
5185
5300
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
5186
5301
|
const skillName = normalizeSkillName(name);
|
|
5187
5302
|
const sourcePath = getSkillPath(name);
|
|
5188
|
-
const destDir =
|
|
5189
|
-
const destPath =
|
|
5190
|
-
if (!
|
|
5303
|
+
const destDir = join2(targetDir, ".skills");
|
|
5304
|
+
const destPath = join2(destDir, skillName);
|
|
5305
|
+
if (!existsSync2(sourcePath)) {
|
|
5191
5306
|
return {
|
|
5192
5307
|
skill: name,
|
|
5193
5308
|
success: false,
|
|
5194
5309
|
error: `Skill '${name}' not found`
|
|
5195
5310
|
};
|
|
5196
5311
|
}
|
|
5197
|
-
if (
|
|
5312
|
+
if (existsSync2(destPath) && !overwrite) {
|
|
5198
5313
|
return {
|
|
5199
5314
|
skill: name,
|
|
5200
5315
|
success: false,
|
|
@@ -5203,10 +5318,10 @@ function installSkill(name, options = {}) {
|
|
|
5203
5318
|
};
|
|
5204
5319
|
}
|
|
5205
5320
|
try {
|
|
5206
|
-
if (!
|
|
5321
|
+
if (!existsSync2(destDir)) {
|
|
5207
5322
|
mkdirSync(destDir, { recursive: true });
|
|
5208
5323
|
}
|
|
5209
|
-
if (
|
|
5324
|
+
if (existsSync2(destPath) && overwrite) {
|
|
5210
5325
|
rmSync(destPath, { recursive: true, force: true });
|
|
5211
5326
|
}
|
|
5212
5327
|
cpSync(sourcePath, destPath, {
|
|
@@ -5242,10 +5357,10 @@ function installSkill(name, options = {}) {
|
|
|
5242
5357
|
}
|
|
5243
5358
|
}
|
|
5244
5359
|
function updateSkillsIndex(skillsDir) {
|
|
5245
|
-
const indexPath =
|
|
5360
|
+
const indexPath = join2(skillsDir, "index.ts");
|
|
5246
5361
|
const meta = loadMeta(skillsDir);
|
|
5247
5362
|
const disabledSet = new Set(meta.disabled || []);
|
|
5248
|
-
const skills =
|
|
5363
|
+
const skills = readdirSync2(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
|
|
5249
5364
|
const exports = skills.map((s) => {
|
|
5250
5365
|
const name = s.replace("skill-", "").replace(/-/g, "_");
|
|
5251
5366
|
return `export * as ${name} from './${s}/src/index.js';`;
|
|
@@ -5261,13 +5376,13 @@ ${exports}
|
|
|
5261
5376
|
writeFileSync(indexPath, content);
|
|
5262
5377
|
}
|
|
5263
5378
|
function getMetaPath(skillsDir) {
|
|
5264
|
-
return
|
|
5379
|
+
return join2(skillsDir, ".meta.json");
|
|
5265
5380
|
}
|
|
5266
5381
|
function loadMeta(skillsDir) {
|
|
5267
5382
|
const metaPath = getMetaPath(skillsDir);
|
|
5268
|
-
if (
|
|
5383
|
+
if (existsSync2(metaPath)) {
|
|
5269
5384
|
try {
|
|
5270
|
-
return JSON.parse(
|
|
5385
|
+
return JSON.parse(readFileSync2(metaPath, "utf-8"));
|
|
5271
5386
|
} catch {}
|
|
5272
5387
|
}
|
|
5273
5388
|
return { skills: {} };
|
|
@@ -5280,9 +5395,9 @@ function recordInstall(skillsDir, name) {
|
|
|
5280
5395
|
const skillName = normalizeSkillName(name);
|
|
5281
5396
|
let version = "unknown";
|
|
5282
5397
|
try {
|
|
5283
|
-
const pkgPath =
|
|
5284
|
-
if (
|
|
5285
|
-
const pkg = JSON.parse(
|
|
5398
|
+
const pkgPath = join2(skillsDir, skillName, "package.json");
|
|
5399
|
+
if (existsSync2(pkgPath)) {
|
|
5400
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
5286
5401
|
version = pkg.version || "unknown";
|
|
5287
5402
|
}
|
|
5288
5403
|
} catch {}
|
|
@@ -5295,23 +5410,23 @@ function recordRemove(skillsDir, name) {
|
|
|
5295
5410
|
saveMeta(skillsDir, meta);
|
|
5296
5411
|
}
|
|
5297
5412
|
function getInstallMeta(targetDir = process.cwd()) {
|
|
5298
|
-
return loadMeta(
|
|
5413
|
+
return loadMeta(join2(targetDir, ".skills"));
|
|
5299
5414
|
}
|
|
5300
5415
|
function getInstalledSkills(targetDir = process.cwd()) {
|
|
5301
|
-
const skillsDir =
|
|
5302
|
-
if (!
|
|
5416
|
+
const skillsDir = join2(targetDir, ".skills");
|
|
5417
|
+
if (!existsSync2(skillsDir)) {
|
|
5303
5418
|
return [];
|
|
5304
5419
|
}
|
|
5305
|
-
return
|
|
5306
|
-
const fullPath =
|
|
5420
|
+
return readdirSync2(skillsDir).filter((f) => {
|
|
5421
|
+
const fullPath = join2(skillsDir, f);
|
|
5307
5422
|
return f.startsWith("skill-") && statSync(fullPath).isDirectory();
|
|
5308
5423
|
}).map((f) => f.replace("skill-", ""));
|
|
5309
5424
|
}
|
|
5310
5425
|
function removeSkill(name, targetDir = process.cwd()) {
|
|
5311
5426
|
const skillName = normalizeSkillName(name);
|
|
5312
|
-
const skillsDir =
|
|
5313
|
-
const skillPath =
|
|
5314
|
-
if (!
|
|
5427
|
+
const skillsDir = join2(targetDir, ".skills");
|
|
5428
|
+
const skillPath = join2(skillsDir, skillName);
|
|
5429
|
+
if (!existsSync2(skillPath)) {
|
|
5315
5430
|
return false;
|
|
5316
5431
|
}
|
|
5317
5432
|
rmSync(skillPath, { recursive: true, force: true });
|
|
@@ -5329,27 +5444,31 @@ function resolveAgents(agentArg) {
|
|
|
5329
5444
|
return [agent];
|
|
5330
5445
|
}
|
|
5331
5446
|
function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
5332
|
-
const
|
|
5333
|
-
|
|
5334
|
-
|
|
5447
|
+
const base = projectDir || process.cwd();
|
|
5448
|
+
switch (agent) {
|
|
5449
|
+
case "pi":
|
|
5450
|
+
return scope === "project" ? join2(base, ".pi", "skills") : join2(homedir2(), ".pi", "agent", "skills");
|
|
5451
|
+
case "opencode":
|
|
5452
|
+
return scope === "project" ? join2(base, ".opencode", "skills") : join2(homedir2(), ".opencode", "skills");
|
|
5453
|
+
default:
|
|
5454
|
+
return scope === "project" ? join2(base, `.${agent}`, "skills") : join2(homedir2(), `.${agent}`, "skills");
|
|
5335
5455
|
}
|
|
5336
|
-
return join(homedir(), agentDir, "skills");
|
|
5337
5456
|
}
|
|
5338
5457
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
5339
5458
|
const skillName = normalizeSkillName(name);
|
|
5340
|
-
return
|
|
5459
|
+
return join2(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
5341
5460
|
}
|
|
5342
5461
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
5343
5462
|
const { agent, scope = "global", projectDir } = options;
|
|
5344
5463
|
const skillName = normalizeSkillName(name);
|
|
5345
5464
|
const sourcePath = getSkillPath(name);
|
|
5346
|
-
if (!
|
|
5465
|
+
if (!existsSync2(sourcePath)) {
|
|
5347
5466
|
return { skill: name, success: false, error: `Skill '${name}' not found` };
|
|
5348
5467
|
}
|
|
5349
5468
|
let skillMdContent = null;
|
|
5350
|
-
const skillMdPath =
|
|
5351
|
-
if (
|
|
5352
|
-
skillMdContent =
|
|
5469
|
+
const skillMdPath = join2(sourcePath, "SKILL.md");
|
|
5470
|
+
if (existsSync2(skillMdPath)) {
|
|
5471
|
+
skillMdContent = readFileSync2(skillMdPath, "utf-8");
|
|
5353
5472
|
} else if (generateSkillMd) {
|
|
5354
5473
|
skillMdContent = generateSkillMd(name);
|
|
5355
5474
|
}
|
|
@@ -5358,17 +5477,12 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
5358
5477
|
}
|
|
5359
5478
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
5360
5479
|
if (scope === "global") {
|
|
5361
|
-
const agentBaseDir =
|
|
5362
|
-
if (!
|
|
5363
|
-
const agentLabels = {
|
|
5364
|
-
claude: "Claude Code",
|
|
5365
|
-
codex: "Codex CLI",
|
|
5366
|
-
gemini: "Gemini CLI"
|
|
5367
|
-
};
|
|
5480
|
+
const agentBaseDir = agent === "pi" ? join2(homedir2(), ".pi", "agent") : join2(homedir2(), `.${agent}`);
|
|
5481
|
+
if (!existsSync2(agentBaseDir)) {
|
|
5368
5482
|
return {
|
|
5369
5483
|
skill: name,
|
|
5370
5484
|
success: false,
|
|
5371
|
-
error: `Agent directory ${agentBaseDir} does not exist. Is ${
|
|
5485
|
+
error: `Agent directory ${agentBaseDir} does not exist. Is ${AGENT_LABELS[agent]} installed?`
|
|
5372
5486
|
};
|
|
5373
5487
|
}
|
|
5374
5488
|
try {
|
|
@@ -5383,7 +5497,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
5383
5497
|
}
|
|
5384
5498
|
try {
|
|
5385
5499
|
mkdirSync(destDir, { recursive: true });
|
|
5386
|
-
writeFileSync(
|
|
5500
|
+
writeFileSync(join2(destDir, "SKILL.md"), skillMdContent);
|
|
5387
5501
|
return { skill: name, success: true, path: destDir };
|
|
5388
5502
|
} catch (error) {
|
|
5389
5503
|
return {
|
|
@@ -5396,31 +5510,48 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
5396
5510
|
function removeSkillForAgent(name, options) {
|
|
5397
5511
|
const { agent, scope = "global", projectDir } = options;
|
|
5398
5512
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
5399
|
-
if (!
|
|
5513
|
+
if (!existsSync2(destDir)) {
|
|
5400
5514
|
return false;
|
|
5401
5515
|
}
|
|
5402
5516
|
rmSync(destDir, { recursive: true, force: true });
|
|
5403
5517
|
return true;
|
|
5404
5518
|
}
|
|
5405
|
-
var __dirname2, SKILLS_DIR, AGENT_TARGETS;
|
|
5519
|
+
var __dirname2, SKILLS_DIR, AGENT_TARGETS, AGENT_LABELS;
|
|
5406
5520
|
var init_installer = __esm(() => {
|
|
5407
5521
|
init_registry();
|
|
5408
5522
|
__dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
5409
5523
|
SKILLS_DIR = findSkillsDir();
|
|
5410
|
-
AGENT_TARGETS = ["claude", "codex", "gemini"];
|
|
5524
|
+
AGENT_TARGETS = ["claude", "codex", "gemini", "pi", "opencode"];
|
|
5525
|
+
AGENT_LABELS = {
|
|
5526
|
+
claude: "Claude Code",
|
|
5527
|
+
codex: "Codex CLI",
|
|
5528
|
+
gemini: "Gemini CLI",
|
|
5529
|
+
pi: "pi.dev",
|
|
5530
|
+
opencode: "OpenCode"
|
|
5531
|
+
};
|
|
5411
5532
|
});
|
|
5412
5533
|
|
|
5413
5534
|
// src/lib/skillinfo.ts
|
|
5414
|
-
|
|
5415
|
-
|
|
5535
|
+
var exports_skillinfo = {};
|
|
5536
|
+
__export(exports_skillinfo, {
|
|
5537
|
+
runSkill: () => runSkill,
|
|
5538
|
+
getSkillRequirements: () => getSkillRequirements,
|
|
5539
|
+
getSkillDocs: () => getSkillDocs,
|
|
5540
|
+
getSkillBestDoc: () => getSkillBestDoc,
|
|
5541
|
+
generateSkillMd: () => generateSkillMd,
|
|
5542
|
+
generateEnvExample: () => generateEnvExample,
|
|
5543
|
+
detectProjectSkills: () => detectProjectSkills
|
|
5544
|
+
});
|
|
5545
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync3 } from "fs";
|
|
5546
|
+
import { join as join3 } from "path";
|
|
5416
5547
|
function getSkillDocs(name) {
|
|
5417
5548
|
const skillPath = getSkillPath(name);
|
|
5418
|
-
if (!
|
|
5549
|
+
if (!existsSync3(skillPath))
|
|
5419
5550
|
return null;
|
|
5420
5551
|
return {
|
|
5421
|
-
skillMd: readIfExists(
|
|
5422
|
-
readme: readIfExists(
|
|
5423
|
-
claudeMd: readIfExists(
|
|
5552
|
+
skillMd: readIfExists(join3(skillPath, "SKILL.md")),
|
|
5553
|
+
readme: readIfExists(join3(skillPath, "README.md")),
|
|
5554
|
+
claudeMd: readIfExists(join3(skillPath, "CLAUDE.md"))
|
|
5424
5555
|
};
|
|
5425
5556
|
}
|
|
5426
5557
|
function getSkillBestDoc(name) {
|
|
@@ -5431,11 +5562,11 @@ function getSkillBestDoc(name) {
|
|
|
5431
5562
|
}
|
|
5432
5563
|
function getSkillRequirements(name) {
|
|
5433
5564
|
const skillPath = getSkillPath(name);
|
|
5434
|
-
if (!
|
|
5565
|
+
if (!existsSync3(skillPath))
|
|
5435
5566
|
return null;
|
|
5436
5567
|
const texts = [];
|
|
5437
5568
|
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
|
|
5438
|
-
const content = readIfExists(
|
|
5569
|
+
const content = readIfExists(join3(skillPath, file));
|
|
5439
5570
|
if (content)
|
|
5440
5571
|
texts.push(content);
|
|
5441
5572
|
}
|
|
@@ -5462,10 +5593,10 @@ function getSkillRequirements(name) {
|
|
|
5462
5593
|
}
|
|
5463
5594
|
let cliCommand = null;
|
|
5464
5595
|
let dependencies = {};
|
|
5465
|
-
const pkgPath =
|
|
5466
|
-
if (
|
|
5596
|
+
const pkgPath = join3(skillPath, "package.json");
|
|
5597
|
+
if (existsSync3(pkgPath)) {
|
|
5467
5598
|
try {
|
|
5468
|
-
const pkg = JSON.parse(
|
|
5599
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
5469
5600
|
if (pkg.bin) {
|
|
5470
5601
|
const binKeys = Object.keys(pkg.bin);
|
|
5471
5602
|
if (binKeys.length > 0)
|
|
@@ -5485,25 +5616,25 @@ async function runSkill(name, args, options = {}) {
|
|
|
5485
5616
|
const skillName = normalizeSkillName(name);
|
|
5486
5617
|
let skillPath;
|
|
5487
5618
|
if (options.installed) {
|
|
5488
|
-
skillPath =
|
|
5619
|
+
skillPath = join3(process.cwd(), ".skills", skillName);
|
|
5489
5620
|
} else {
|
|
5490
|
-
const installedPath =
|
|
5491
|
-
if (
|
|
5621
|
+
const installedPath = join3(process.cwd(), ".skills", skillName);
|
|
5622
|
+
if (existsSync3(installedPath)) {
|
|
5492
5623
|
skillPath = installedPath;
|
|
5493
5624
|
} else {
|
|
5494
5625
|
skillPath = getSkillPath(name);
|
|
5495
5626
|
}
|
|
5496
5627
|
}
|
|
5497
|
-
if (!
|
|
5628
|
+
if (!existsSync3(skillPath)) {
|
|
5498
5629
|
return { exitCode: 1, error: `Skill '${name}' not found` };
|
|
5499
5630
|
}
|
|
5500
|
-
const pkgPath =
|
|
5501
|
-
if (!
|
|
5631
|
+
const pkgPath = join3(skillPath, "package.json");
|
|
5632
|
+
if (!existsSync3(pkgPath)) {
|
|
5502
5633
|
return { exitCode: 1, error: `No package.json in skill '${name}'` };
|
|
5503
5634
|
}
|
|
5504
5635
|
let entryPoint;
|
|
5505
5636
|
try {
|
|
5506
|
-
const pkg = JSON.parse(
|
|
5637
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
5507
5638
|
if (pkg.bin) {
|
|
5508
5639
|
const binValues = Object.values(pkg.bin);
|
|
5509
5640
|
entryPoint = binValues[0];
|
|
@@ -5517,12 +5648,12 @@ async function runSkill(name, args, options = {}) {
|
|
|
5517
5648
|
} catch {
|
|
5518
5649
|
return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
|
|
5519
5650
|
}
|
|
5520
|
-
const entryPath =
|
|
5521
|
-
if (!
|
|
5651
|
+
const entryPath = join3(skillPath, entryPoint);
|
|
5652
|
+
if (!existsSync3(entryPath)) {
|
|
5522
5653
|
return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
|
|
5523
5654
|
}
|
|
5524
|
-
const nodeModules =
|
|
5525
|
-
if (!
|
|
5655
|
+
const nodeModules = join3(skillPath, "node_modules");
|
|
5656
|
+
if (!existsSync3(nodeModules)) {
|
|
5526
5657
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
5527
5658
|
cwd: skillPath,
|
|
5528
5659
|
stdout: "pipe",
|
|
@@ -5540,18 +5671,18 @@ async function runSkill(name, args, options = {}) {
|
|
|
5540
5671
|
return { exitCode };
|
|
5541
5672
|
}
|
|
5542
5673
|
function detectProjectSkills(cwd = process.cwd()) {
|
|
5543
|
-
const pkgPath =
|
|
5544
|
-
if (!
|
|
5674
|
+
const pkgPath = join3(cwd, "package.json");
|
|
5675
|
+
if (!existsSync3(pkgPath)) {
|
|
5545
5676
|
const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
|
|
5546
|
-
const recommended2 = alwaysRecommend.map((name) =>
|
|
5677
|
+
const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5547
5678
|
return { detected: [], recommended: recommended2 };
|
|
5548
5679
|
}
|
|
5549
5680
|
let pkg;
|
|
5550
5681
|
try {
|
|
5551
|
-
pkg = JSON.parse(
|
|
5682
|
+
pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
5552
5683
|
} catch {
|
|
5553
5684
|
const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
|
|
5554
|
-
const recommended2 = alwaysRecommend.map((name) =>
|
|
5685
|
+
const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5555
5686
|
return { detected: [], recommended: recommended2 };
|
|
5556
5687
|
}
|
|
5557
5688
|
const allDeps = {
|
|
@@ -5623,15 +5754,66 @@ function detectProjectSkills(cwd = process.cwd()) {
|
|
|
5623
5754
|
}
|
|
5624
5755
|
}
|
|
5625
5756
|
const uniqueDetected = Array.from(new Set(detected));
|
|
5626
|
-
const recommended = Array.from(recommendedNames).map((name) =>
|
|
5757
|
+
const recommended = Array.from(recommendedNames).map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5627
5758
|
return { detected: uniqueDetected, recommended };
|
|
5628
5759
|
}
|
|
5760
|
+
function generateEnvExample(targetDir = process.cwd()) {
|
|
5761
|
+
const skillsDir = join3(targetDir, ".skills");
|
|
5762
|
+
if (!existsSync3(skillsDir))
|
|
5763
|
+
return "";
|
|
5764
|
+
const dirs = readdirSync3(skillsDir).filter((f) => f.startsWith("skill-") && existsSync3(join3(skillsDir, f, "package.json")));
|
|
5765
|
+
const envMap = new Map;
|
|
5766
|
+
for (const dir of dirs) {
|
|
5767
|
+
const skillName = dir.replace("skill-", "");
|
|
5768
|
+
const skillPath = join3(skillsDir, dir);
|
|
5769
|
+
const texts = [];
|
|
5770
|
+
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example"]) {
|
|
5771
|
+
const content = readIfExists(join3(skillPath, file));
|
|
5772
|
+
if (content)
|
|
5773
|
+
texts.push(content);
|
|
5774
|
+
}
|
|
5775
|
+
const allText = texts.join(`
|
|
5776
|
+
`);
|
|
5777
|
+
const foundVars = extractEnvVars(allText);
|
|
5778
|
+
for (const envVar of foundVars) {
|
|
5779
|
+
if (!envMap.has(envVar)) {
|
|
5780
|
+
envMap.set(envVar, []);
|
|
5781
|
+
}
|
|
5782
|
+
if (!envMap.get(envVar).includes(skillName)) {
|
|
5783
|
+
envMap.get(envVar).push(skillName);
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5786
|
+
}
|
|
5787
|
+
if (envMap.size === 0)
|
|
5788
|
+
return "";
|
|
5789
|
+
const lines = [
|
|
5790
|
+
"# Environment variables for installed skills",
|
|
5791
|
+
"# Auto-generated by: skills init",
|
|
5792
|
+
""
|
|
5793
|
+
];
|
|
5794
|
+
const sorted = Array.from(envMap.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
5795
|
+
let lastPrefix = "";
|
|
5796
|
+
for (const [envVar, skills] of sorted) {
|
|
5797
|
+
const prefix = envVar.split("_")[0];
|
|
5798
|
+
if (prefix !== lastPrefix) {
|
|
5799
|
+
if (lastPrefix)
|
|
5800
|
+
lines.push("");
|
|
5801
|
+
lines.push(`# ${prefix}`);
|
|
5802
|
+
lastPrefix = prefix;
|
|
5803
|
+
}
|
|
5804
|
+
lines.push(`# Used by: ${skills.join(", ")}`);
|
|
5805
|
+
lines.push(`${envVar}=`);
|
|
5806
|
+
}
|
|
5807
|
+
return lines.join(`
|
|
5808
|
+
`) + `
|
|
5809
|
+
`;
|
|
5810
|
+
}
|
|
5629
5811
|
function generateSkillMd(name) {
|
|
5630
5812
|
const meta = getSkill(name);
|
|
5631
5813
|
if (!meta)
|
|
5632
5814
|
return null;
|
|
5633
5815
|
const skillPath = getSkillPath(name);
|
|
5634
|
-
if (!
|
|
5816
|
+
if (!existsSync3(skillPath))
|
|
5635
5817
|
return null;
|
|
5636
5818
|
const frontmatter = [
|
|
5637
5819
|
"---",
|
|
@@ -5640,13 +5822,13 @@ function generateSkillMd(name) {
|
|
|
5640
5822
|
"---"
|
|
5641
5823
|
].join(`
|
|
5642
5824
|
`);
|
|
5643
|
-
const readme = readIfExists(
|
|
5644
|
-
const claudeMd = readIfExists(
|
|
5825
|
+
const readme = readIfExists(join3(skillPath, "README.md"));
|
|
5826
|
+
const claudeMd = readIfExists(join3(skillPath, "CLAUDE.md"));
|
|
5645
5827
|
let cliCommand = null;
|
|
5646
|
-
const pkgPath =
|
|
5647
|
-
if (
|
|
5828
|
+
const pkgPath = join3(skillPath, "package.json");
|
|
5829
|
+
if (existsSync3(pkgPath)) {
|
|
5648
5830
|
try {
|
|
5649
|
-
const pkg = JSON.parse(
|
|
5831
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
5650
5832
|
if (pkg.bin) {
|
|
5651
5833
|
const binKeys = Object.keys(pkg.bin);
|
|
5652
5834
|
if (binKeys.length > 0)
|
|
@@ -5719,8 +5901,8 @@ function extractEnvVars(text) {
|
|
|
5719
5901
|
}
|
|
5720
5902
|
function readIfExists(path) {
|
|
5721
5903
|
try {
|
|
5722
|
-
if (
|
|
5723
|
-
return
|
|
5904
|
+
if (existsSync3(path)) {
|
|
5905
|
+
return readFileSync3(path, "utf-8");
|
|
5724
5906
|
}
|
|
5725
5907
|
} catch {}
|
|
5726
5908
|
return null;
|
|
@@ -5733,6 +5915,173 @@ var init_skillinfo = __esm(() => {
|
|
|
5733
5915
|
GENERIC_ENV_PATTERN = /\b((?:OPENAI|ANTHROPIC|GEMINI|XAI|ELEVENLABS|DEEPGRAM|REPLICATE|FAL|STABILITY|EXA|FIRECRAWL|TWILIO|SENDGRID|RESEND|SLACK|DISCORD|NOTION|LINEAR|GITHUB|AWS|GOOGLE|CLOUDFLARE|VERCEL|SUPABASE|STRIPE)_[A-Z_]+)\b/g;
|
|
5734
5916
|
});
|
|
5735
5917
|
|
|
5918
|
+
// src/lib/scheduler.ts
|
|
5919
|
+
var exports_scheduler = {};
|
|
5920
|
+
__export(exports_scheduler, {
|
|
5921
|
+
validateCron: () => validateCron,
|
|
5922
|
+
setScheduleEnabled: () => setScheduleEnabled,
|
|
5923
|
+
removeSchedule: () => removeSchedule,
|
|
5924
|
+
recordScheduleRun: () => recordScheduleRun,
|
|
5925
|
+
listSchedules: () => listSchedules,
|
|
5926
|
+
getNextRun: () => getNextRun,
|
|
5927
|
+
getDueSchedules: () => getDueSchedules,
|
|
5928
|
+
addSchedule: () => addSchedule
|
|
5929
|
+
});
|
|
5930
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
5931
|
+
import { join as join5 } from "path";
|
|
5932
|
+
function getSchedulesPath(targetDir = process.cwd()) {
|
|
5933
|
+
return join5(targetDir, ".skills", "schedules.json");
|
|
5934
|
+
}
|
|
5935
|
+
function loadSchedules(targetDir = process.cwd()) {
|
|
5936
|
+
const path = getSchedulesPath(targetDir);
|
|
5937
|
+
if (existsSync5(path)) {
|
|
5938
|
+
try {
|
|
5939
|
+
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
5940
|
+
} catch {}
|
|
5941
|
+
}
|
|
5942
|
+
return { version: 1, schedules: [] };
|
|
5943
|
+
}
|
|
5944
|
+
function saveSchedules(data, targetDir = process.cwd()) {
|
|
5945
|
+
const path = getSchedulesPath(targetDir);
|
|
5946
|
+
const dir = join5(targetDir, ".skills");
|
|
5947
|
+
if (!existsSync5(dir))
|
|
5948
|
+
mkdirSync3(dir, { recursive: true });
|
|
5949
|
+
writeFileSync3(path, JSON.stringify(data, null, 2));
|
|
5950
|
+
}
|
|
5951
|
+
function validateCron(expr) {
|
|
5952
|
+
const fields = expr.trim().split(/\s+/);
|
|
5953
|
+
if (fields.length !== 5) {
|
|
5954
|
+
return { valid: false, error: `Expected 5 fields, got ${fields.length}. Format: "minute hour day-of-month month day-of-week"` };
|
|
5955
|
+
}
|
|
5956
|
+
return { valid: true };
|
|
5957
|
+
}
|
|
5958
|
+
function getNextRun(cron, from = new Date) {
|
|
5959
|
+
const { valid } = validateCron(cron);
|
|
5960
|
+
if (!valid)
|
|
5961
|
+
return null;
|
|
5962
|
+
const [minuteF, hourF, domF, monthF, dowF] = cron.trim().split(/\s+/);
|
|
5963
|
+
function parseField(f, min, max) {
|
|
5964
|
+
if (f === "*")
|
|
5965
|
+
return Array.from({ length: max - min + 1 }, (_, i) => i + min);
|
|
5966
|
+
if (f.startsWith("*/")) {
|
|
5967
|
+
const step = parseInt(f.slice(2));
|
|
5968
|
+
if (isNaN(step))
|
|
5969
|
+
return [];
|
|
5970
|
+
const vals = [];
|
|
5971
|
+
for (let i = min;i <= max; i += step)
|
|
5972
|
+
vals.push(i);
|
|
5973
|
+
return vals;
|
|
5974
|
+
}
|
|
5975
|
+
return f.split(",").flatMap((part) => {
|
|
5976
|
+
if (part.includes("-")) {
|
|
5977
|
+
const [lo, hi] = part.split("-").map(Number);
|
|
5978
|
+
return Array.from({ length: hi - lo + 1 }, (_, i) => i + lo);
|
|
5979
|
+
}
|
|
5980
|
+
const n = parseInt(part);
|
|
5981
|
+
return isNaN(n) ? [] : [n];
|
|
5982
|
+
});
|
|
5983
|
+
}
|
|
5984
|
+
const minutes = parseField(minuteF, 0, 59);
|
|
5985
|
+
const hours = parseField(hourF, 0, 23);
|
|
5986
|
+
const doms = parseField(domF, 1, 31);
|
|
5987
|
+
const months = parseField(monthF, 1, 12);
|
|
5988
|
+
const dows = parseField(dowF, 0, 6);
|
|
5989
|
+
const candidate = new Date(from);
|
|
5990
|
+
candidate.setSeconds(0, 0);
|
|
5991
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
5992
|
+
const limit = new Date(from);
|
|
5993
|
+
limit.setFullYear(limit.getFullYear() + 1);
|
|
5994
|
+
while (candidate < limit) {
|
|
5995
|
+
const month = candidate.getMonth() + 1;
|
|
5996
|
+
const dom = candidate.getDate();
|
|
5997
|
+
const dow = candidate.getDay();
|
|
5998
|
+
const hour = candidate.getHours();
|
|
5999
|
+
const minute = candidate.getMinutes();
|
|
6000
|
+
if (!months.includes(month)) {
|
|
6001
|
+
candidate.setMonth(candidate.getMonth() + 1, 1);
|
|
6002
|
+
candidate.setHours(0, 0, 0, 0);
|
|
6003
|
+
continue;
|
|
6004
|
+
}
|
|
6005
|
+
if (!doms.includes(dom) || !dows.includes(dow)) {
|
|
6006
|
+
candidate.setDate(candidate.getDate() + 1);
|
|
6007
|
+
candidate.setHours(0, 0, 0, 0);
|
|
6008
|
+
continue;
|
|
6009
|
+
}
|
|
6010
|
+
if (!hours.includes(hour)) {
|
|
6011
|
+
candidate.setHours(candidate.getHours() + 1, 0, 0, 0);
|
|
6012
|
+
continue;
|
|
6013
|
+
}
|
|
6014
|
+
if (!minutes.includes(minute)) {
|
|
6015
|
+
candidate.setMinutes(candidate.getMinutes() + 1, 0, 0);
|
|
6016
|
+
continue;
|
|
6017
|
+
}
|
|
6018
|
+
return new Date(candidate);
|
|
6019
|
+
}
|
|
6020
|
+
return null;
|
|
6021
|
+
}
|
|
6022
|
+
function addSchedule(skill, cron, options = {}) {
|
|
6023
|
+
const { valid, error } = validateCron(cron);
|
|
6024
|
+
if (!valid)
|
|
6025
|
+
return { schedule: null, error };
|
|
6026
|
+
const data = loadSchedules(options.targetDir);
|
|
6027
|
+
const id = `${skill}-${Date.now()}`;
|
|
6028
|
+
const now = new Date;
|
|
6029
|
+
const nextRun = getNextRun(cron, now);
|
|
6030
|
+
const schedule = {
|
|
6031
|
+
id,
|
|
6032
|
+
name: options.name || `${skill} (${cron})`,
|
|
6033
|
+
skill,
|
|
6034
|
+
cron,
|
|
6035
|
+
args: options.args,
|
|
6036
|
+
enabled: true,
|
|
6037
|
+
createdAt: now.toISOString(),
|
|
6038
|
+
nextRun: nextRun?.toISOString()
|
|
6039
|
+
};
|
|
6040
|
+
data.schedules.push(schedule);
|
|
6041
|
+
saveSchedules(data, options.targetDir);
|
|
6042
|
+
return { schedule };
|
|
6043
|
+
}
|
|
6044
|
+
function listSchedules(targetDir) {
|
|
6045
|
+
return loadSchedules(targetDir).schedules;
|
|
6046
|
+
}
|
|
6047
|
+
function removeSchedule(idOrName, targetDir) {
|
|
6048
|
+
const data = loadSchedules(targetDir);
|
|
6049
|
+
const before = data.schedules.length;
|
|
6050
|
+
data.schedules = data.schedules.filter((s) => s.id !== idOrName && s.name !== idOrName);
|
|
6051
|
+
if (data.schedules.length === before)
|
|
6052
|
+
return false;
|
|
6053
|
+
saveSchedules(data, targetDir);
|
|
6054
|
+
return true;
|
|
6055
|
+
}
|
|
6056
|
+
function setScheduleEnabled(idOrName, enabled, targetDir) {
|
|
6057
|
+
const data = loadSchedules(targetDir);
|
|
6058
|
+
const schedule = data.schedules.find((s) => s.id === idOrName || s.name === idOrName);
|
|
6059
|
+
if (!schedule)
|
|
6060
|
+
return false;
|
|
6061
|
+
schedule.enabled = enabled;
|
|
6062
|
+
if (enabled) {
|
|
6063
|
+
schedule.nextRun = getNextRun(schedule.cron)?.toISOString();
|
|
6064
|
+
}
|
|
6065
|
+
saveSchedules(data, targetDir);
|
|
6066
|
+
return true;
|
|
6067
|
+
}
|
|
6068
|
+
function getDueSchedules(targetDir) {
|
|
6069
|
+
const now = new Date;
|
|
6070
|
+
return listSchedules(targetDir).filter((s) => s.enabled && s.nextRun && new Date(s.nextRun) <= now);
|
|
6071
|
+
}
|
|
6072
|
+
function recordScheduleRun(id, status, targetDir) {
|
|
6073
|
+
const data = loadSchedules(targetDir);
|
|
6074
|
+
const schedule = data.schedules.find((s) => s.id === id);
|
|
6075
|
+
if (!schedule)
|
|
6076
|
+
return;
|
|
6077
|
+
const now = new Date;
|
|
6078
|
+
schedule.lastRun = now.toISOString();
|
|
6079
|
+
schedule.lastRunStatus = status;
|
|
6080
|
+
schedule.nextRun = getNextRun(schedule.cron, now)?.toISOString();
|
|
6081
|
+
saveSchedules(data, targetDir);
|
|
6082
|
+
}
|
|
6083
|
+
var init_scheduler = () => {};
|
|
6084
|
+
|
|
5736
6085
|
// ../../../../hasnastudio/hasnastudio-alumia/platform/platformdev/platform-alumia/node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/util.js
|
|
5737
6086
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
5738
6087
|
const t = typeof data;
|
|
@@ -34823,9 +35172,8 @@ var init_stdio2 = __esm(() => {
|
|
|
34823
35172
|
|
|
34824
35173
|
// src/mcp/index.ts
|
|
34825
35174
|
var exports_mcp = {};
|
|
34826
|
-
import { existsSync as
|
|
34827
|
-
import { join as
|
|
34828
|
-
import { homedir as homedir3 } from "os";
|
|
35175
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
|
|
35176
|
+
import { join as join6 } from "path";
|
|
34829
35177
|
function stripNulls(obj) {
|
|
34830
35178
|
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== undefined && !(Array.isArray(v) && v.length === 0)));
|
|
34831
35179
|
}
|
|
@@ -34865,6 +35213,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34865
35213
|
init_registry();
|
|
34866
35214
|
init_installer();
|
|
34867
35215
|
init_skillinfo();
|
|
35216
|
+
init_scheduler();
|
|
34868
35217
|
server = new McpServer({
|
|
34869
35218
|
name: "skills",
|
|
34870
35219
|
version: package_default.version
|
|
@@ -34880,7 +35229,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34880
35229
|
offset: exports_external.number().optional()
|
|
34881
35230
|
}
|
|
34882
35231
|
}, async ({ category, detail, limit, offset }) => {
|
|
34883
|
-
const skills = category ? getSkillsByCategory(category) :
|
|
35232
|
+
const skills = category ? getSkillsByCategory(category) : loadRegistry();
|
|
34884
35233
|
const mapped = detail ? skills : skills.map((s) => ({ name: s.name, category: s.category }));
|
|
34885
35234
|
if (limit !== undefined || offset !== undefined) {
|
|
34886
35235
|
const start = offset || 0;
|
|
@@ -34965,7 +35314,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34965
35314
|
});
|
|
34966
35315
|
server.registerTool("install_skill", {
|
|
34967
35316
|
title: "Install Skill",
|
|
34968
|
-
description: "Install a skill to .skills/ or to an agent dir (for: claude|codex|gemini|all).",
|
|
35317
|
+
description: "Install a skill to .skills/ or to an agent dir (for: claude|codex|gemini|pi|opencode|all).",
|
|
34969
35318
|
inputSchema: {
|
|
34970
35319
|
name: exports_external.string(),
|
|
34971
35320
|
for: exports_external.string().optional(),
|
|
@@ -34977,7 +35326,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34977
35326
|
try {
|
|
34978
35327
|
agents = resolveAgents(agentArg);
|
|
34979
35328
|
} catch (err) {
|
|
34980
|
-
return mcpError("INVALID_AGENT", err.message, [
|
|
35329
|
+
return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
|
|
34981
35330
|
}
|
|
34982
35331
|
const results = agents.map((a) => installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd));
|
|
34983
35332
|
return {
|
|
@@ -35015,7 +35364,7 @@ var init_mcp2 = __esm(() => {
|
|
|
35015
35364
|
try {
|
|
35016
35365
|
agents = resolveAgents(agentArg);
|
|
35017
35366
|
} catch (err) {
|
|
35018
|
-
return mcpError("INVALID_AGENT", err.message, [
|
|
35367
|
+
return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
|
|
35019
35368
|
}
|
|
35020
35369
|
const results2 = [];
|
|
35021
35370
|
for (const name of names) {
|
|
@@ -35049,7 +35398,7 @@ var init_mcp2 = __esm(() => {
|
|
|
35049
35398
|
try {
|
|
35050
35399
|
agents = resolveAgents(agentArg);
|
|
35051
35400
|
} catch (err) {
|
|
35052
|
-
return mcpError("INVALID_AGENT", err.message, [
|
|
35401
|
+
return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
|
|
35053
35402
|
}
|
|
35054
35403
|
const results = agents.map((a) => ({
|
|
35055
35404
|
skill: name,
|
|
@@ -35082,7 +35431,7 @@ var init_mcp2 = __esm(() => {
|
|
|
35082
35431
|
description: "List all unique skill tags with occurrence counts."
|
|
35083
35432
|
}, async () => {
|
|
35084
35433
|
const tagCounts = new Map;
|
|
35085
|
-
for (const skill of
|
|
35434
|
+
for (const skill of loadRegistry()) {
|
|
35086
35435
|
for (const tag of skill.tags) {
|
|
35087
35436
|
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
35088
35437
|
}
|
|
@@ -35156,7 +35505,7 @@ var init_mcp2 = __esm(() => {
|
|
|
35156
35505
|
try {
|
|
35157
35506
|
agents = resolveAgents(agentArg);
|
|
35158
35507
|
} catch (err) {
|
|
35159
|
-
return mcpError("INVALID_AGENT", err.message, [
|
|
35508
|
+
return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
|
|
35160
35509
|
}
|
|
35161
35510
|
for (const name of skillList) {
|
|
35162
35511
|
const agentResults = agents.map((a) => installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd));
|
|
@@ -35184,21 +35533,20 @@ var init_mcp2 = __esm(() => {
|
|
|
35184
35533
|
const version2 = package_default.version;
|
|
35185
35534
|
const cwd = process.cwd();
|
|
35186
35535
|
const installed = getInstalledSkills();
|
|
35187
|
-
const agentNames = ["claude", "codex", "gemini"];
|
|
35188
35536
|
const agents = [];
|
|
35189
|
-
for (const agent of
|
|
35190
|
-
const agentSkillsPath =
|
|
35191
|
-
const exists =
|
|
35537
|
+
for (const agent of AGENT_TARGETS) {
|
|
35538
|
+
const agentSkillsPath = getAgentSkillsDir(agent, "global");
|
|
35539
|
+
const exists = existsSync6(agentSkillsPath);
|
|
35192
35540
|
let skillCount = 0;
|
|
35193
35541
|
if (exists) {
|
|
35194
35542
|
try {
|
|
35195
|
-
skillCount =
|
|
35196
|
-
const full =
|
|
35543
|
+
skillCount = readdirSync4(agentSkillsPath).filter((f) => {
|
|
35544
|
+
const full = join6(agentSkillsPath, f);
|
|
35197
35545
|
return f.startsWith("skill-") && statSync2(full).isDirectory();
|
|
35198
35546
|
}).length;
|
|
35199
35547
|
} catch {}
|
|
35200
35548
|
}
|
|
35201
|
-
agents.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
35549
|
+
agents.push({ agent, label: AGENT_LABELS[agent], path: agentSkillsPath, exists, skillCount });
|
|
35202
35550
|
}
|
|
35203
35551
|
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
35204
35552
|
const result = {
|
|
@@ -35211,12 +35559,107 @@ var init_mcp2 = __esm(() => {
|
|
|
35211
35559
|
};
|
|
35212
35560
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
35213
35561
|
});
|
|
35562
|
+
server.registerTool("schedule_skill", {
|
|
35563
|
+
title: "Schedule Skill",
|
|
35564
|
+
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).",
|
|
35565
|
+
inputSchema: {
|
|
35566
|
+
skill: exports_external.string(),
|
|
35567
|
+
cron: exports_external.string(),
|
|
35568
|
+
name: exports_external.string().optional(),
|
|
35569
|
+
args: exports_external.array(exports_external.string()).optional()
|
|
35570
|
+
}
|
|
35571
|
+
}, async ({ skill, cron, name, args }) => {
|
|
35572
|
+
const { schedule, error: error48 } = addSchedule(skill, cron, { name, args });
|
|
35573
|
+
if (error48 || !schedule) {
|
|
35574
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error48 || "Failed to add schedule" }) }] };
|
|
35575
|
+
}
|
|
35576
|
+
return { content: [{ type: "text", text: JSON.stringify(schedule, null, 2) }] };
|
|
35577
|
+
});
|
|
35578
|
+
server.registerTool("list_schedules", {
|
|
35579
|
+
title: "List Schedules",
|
|
35580
|
+
description: "List all scheduled skill runs.",
|
|
35581
|
+
inputSchema: {}
|
|
35582
|
+
}, async () => {
|
|
35583
|
+
const schedules = listSchedules();
|
|
35584
|
+
return { content: [{ type: "text", text: JSON.stringify(schedules, null, 2) }] };
|
|
35585
|
+
});
|
|
35586
|
+
server.registerTool("remove_schedule", {
|
|
35587
|
+
title: "Remove Schedule",
|
|
35588
|
+
description: "Remove a schedule by its ID or name.",
|
|
35589
|
+
inputSchema: {
|
|
35590
|
+
id_or_name: exports_external.string()
|
|
35591
|
+
}
|
|
35592
|
+
}, async ({ id_or_name }) => {
|
|
35593
|
+
const removed = removeSchedule(id_or_name);
|
|
35594
|
+
return { content: [{ type: "text", text: JSON.stringify({ removed, id_or_name }) }] };
|
|
35595
|
+
});
|
|
35596
|
+
server.registerTool("detect_project_skills", {
|
|
35597
|
+
title: "Detect Project Skills",
|
|
35598
|
+
description: "Detect project type from package.json and return recommended skills based on dependencies.",
|
|
35599
|
+
inputSchema: {
|
|
35600
|
+
directory: exports_external.string().optional()
|
|
35601
|
+
}
|
|
35602
|
+
}, async ({ directory }) => {
|
|
35603
|
+
const cwd = directory || process.cwd();
|
|
35604
|
+
const { detected, recommended } = detectProjectSkills(cwd);
|
|
35605
|
+
return {
|
|
35606
|
+
content: [{
|
|
35607
|
+
type: "text",
|
|
35608
|
+
text: JSON.stringify({
|
|
35609
|
+
directory: cwd,
|
|
35610
|
+
detected,
|
|
35611
|
+
recommended: recommended.map((s) => ({ name: s.name, displayName: s.displayName, description: s.description, category: s.category }))
|
|
35612
|
+
}, null, 2)
|
|
35613
|
+
}]
|
|
35614
|
+
};
|
|
35615
|
+
});
|
|
35616
|
+
server.registerTool("validate_skill", {
|
|
35617
|
+
title: "Validate Skill",
|
|
35618
|
+
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.",
|
|
35619
|
+
inputSchema: {
|
|
35620
|
+
name: exports_external.string()
|
|
35621
|
+
}
|
|
35622
|
+
}, async ({ name }) => {
|
|
35623
|
+
const skillPath = getSkillPath(name);
|
|
35624
|
+
const issues = [];
|
|
35625
|
+
if (!existsSync6(skillPath)) {
|
|
35626
|
+
return {
|
|
35627
|
+
content: [{ type: "text", text: JSON.stringify({ name, valid: false, issues: [`Skill directory not found: ${skillPath}`] }) }]
|
|
35628
|
+
};
|
|
35629
|
+
}
|
|
35630
|
+
if (!existsSync6(join6(skillPath, "SKILL.md")))
|
|
35631
|
+
issues.push("Missing SKILL.md");
|
|
35632
|
+
if (!existsSync6(join6(skillPath, "tsconfig.json")))
|
|
35633
|
+
issues.push("Missing tsconfig.json");
|
|
35634
|
+
const pkgPath = join6(skillPath, "package.json");
|
|
35635
|
+
if (!existsSync6(pkgPath)) {
|
|
35636
|
+
issues.push("Missing package.json");
|
|
35637
|
+
} else {
|
|
35638
|
+
try {
|
|
35639
|
+
const pkg = JSON.parse(__require("fs").readFileSync(pkgPath, "utf-8"));
|
|
35640
|
+
if (!pkg.bin || Object.keys(pkg.bin).length === 0)
|
|
35641
|
+
issues.push("package.json missing 'bin' entry");
|
|
35642
|
+
} catch {
|
|
35643
|
+
issues.push("package.json is invalid JSON");
|
|
35644
|
+
}
|
|
35645
|
+
}
|
|
35646
|
+
const srcDir = join6(skillPath, "src");
|
|
35647
|
+
if (!existsSync6(srcDir)) {
|
|
35648
|
+
issues.push("Missing src/ directory");
|
|
35649
|
+
} else if (!existsSync6(join6(srcDir, "index.ts"))) {
|
|
35650
|
+
issues.push("Missing src/index.ts");
|
|
35651
|
+
}
|
|
35652
|
+
const valid = issues.length === 0;
|
|
35653
|
+
return {
|
|
35654
|
+
content: [{ type: "text", text: JSON.stringify({ name, valid, path: skillPath, issues }) }]
|
|
35655
|
+
};
|
|
35656
|
+
});
|
|
35214
35657
|
server.registerResource("Skills Registry", "skills://registry", {
|
|
35215
35658
|
description: "Compact skill list [{name,category}]. Use skills://{name} for detail."
|
|
35216
35659
|
}, async () => ({
|
|
35217
35660
|
contents: [{
|
|
35218
35661
|
uri: "skills://registry",
|
|
35219
|
-
text: JSON.stringify(
|
|
35662
|
+
text: JSON.stringify(loadRegistry().map((s) => ({ name: s.name, category: s.category }))),
|
|
35220
35663
|
mimeType: "application/json"
|
|
35221
35664
|
}]
|
|
35222
35665
|
}));
|
|
@@ -35325,16 +35768,16 @@ __export(exports_serve, {
|
|
|
35325
35768
|
startServer: () => startServer,
|
|
35326
35769
|
createFetchHandler: () => createFetchHandler
|
|
35327
35770
|
});
|
|
35328
|
-
import { existsSync as
|
|
35329
|
-
import { join as
|
|
35771
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
35772
|
+
import { join as join7, dirname as dirname3, extname } from "path";
|
|
35330
35773
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
35331
35774
|
function getPackageJson() {
|
|
35332
35775
|
try {
|
|
35333
35776
|
const scriptDir = dirname3(fileURLToPath2(import.meta.url));
|
|
35334
35777
|
for (const rel of ["../..", ".."]) {
|
|
35335
|
-
const pkgPath =
|
|
35336
|
-
if (
|
|
35337
|
-
const pkg = JSON.parse(
|
|
35778
|
+
const pkgPath = join7(scriptDir, rel, "package.json");
|
|
35779
|
+
if (existsSync7(pkgPath)) {
|
|
35780
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
35338
35781
|
return { version: pkg.version || "unknown", name: pkg.name || "skills" };
|
|
35339
35782
|
}
|
|
35340
35783
|
}
|
|
@@ -35345,20 +35788,20 @@ function resolveDashboardDir() {
|
|
|
35345
35788
|
const candidates = [];
|
|
35346
35789
|
try {
|
|
35347
35790
|
const scriptDir = dirname3(fileURLToPath2(import.meta.url));
|
|
35348
|
-
candidates.push(
|
|
35349
|
-
candidates.push(
|
|
35791
|
+
candidates.push(join7(scriptDir, "..", "dashboard", "dist"));
|
|
35792
|
+
candidates.push(join7(scriptDir, "..", "..", "dashboard", "dist"));
|
|
35350
35793
|
} catch {}
|
|
35351
35794
|
if (process.argv[1]) {
|
|
35352
35795
|
const mainDir = dirname3(process.argv[1]);
|
|
35353
|
-
candidates.push(
|
|
35354
|
-
candidates.push(
|
|
35796
|
+
candidates.push(join7(mainDir, "..", "dashboard", "dist"));
|
|
35797
|
+
candidates.push(join7(mainDir, "..", "..", "dashboard", "dist"));
|
|
35355
35798
|
}
|
|
35356
|
-
candidates.push(
|
|
35799
|
+
candidates.push(join7(process.cwd(), "dashboard", "dist"));
|
|
35357
35800
|
for (const candidate of candidates) {
|
|
35358
|
-
if (
|
|
35801
|
+
if (existsSync7(candidate))
|
|
35359
35802
|
return candidate;
|
|
35360
35803
|
}
|
|
35361
|
-
return
|
|
35804
|
+
return join7(process.cwd(), "dashboard", "dist");
|
|
35362
35805
|
}
|
|
35363
35806
|
function json2(data, status = 200) {
|
|
35364
35807
|
return new Response(JSON.stringify(data), {
|
|
@@ -35387,7 +35830,7 @@ function parseFields(searchParams) {
|
|
|
35387
35830
|
}
|
|
35388
35831
|
function getAllSkillsWithStatus() {
|
|
35389
35832
|
const installed = new Set(getInstalledSkills());
|
|
35390
|
-
return
|
|
35833
|
+
return loadRegistry().map((meta3) => {
|
|
35391
35834
|
const reqs = getSkillRequirements(meta3.name);
|
|
35392
35835
|
const envVars = reqs?.envVars || [];
|
|
35393
35836
|
return {
|
|
@@ -35400,12 +35843,13 @@ function getAllSkillsWithStatus() {
|
|
|
35400
35843
|
envVars,
|
|
35401
35844
|
envVarsSet: envVars.filter((v) => !!process.env[v]),
|
|
35402
35845
|
systemDeps: reqs?.systemDeps || [],
|
|
35403
|
-
cliCommand: reqs?.cliCommand || null
|
|
35846
|
+
cliCommand: reqs?.cliCommand || null,
|
|
35847
|
+
source: meta3.source ?? "official"
|
|
35404
35848
|
};
|
|
35405
35849
|
});
|
|
35406
35850
|
}
|
|
35407
35851
|
function serveStaticFile(filePath) {
|
|
35408
|
-
if (!
|
|
35852
|
+
if (!existsSync7(filePath))
|
|
35409
35853
|
return null;
|
|
35410
35854
|
const ext = extname(filePath);
|
|
35411
35855
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -35415,7 +35859,7 @@ function serveStaticFile(filePath) {
|
|
|
35415
35859
|
}
|
|
35416
35860
|
function createFetchHandler(options) {
|
|
35417
35861
|
const dashboardDir = options?.dashboardDir ?? resolveDashboardDir();
|
|
35418
|
-
const dashboardExists = options?.dashboardExists ??
|
|
35862
|
+
const dashboardExists = options?.dashboardExists ?? existsSync7(dashboardDir);
|
|
35419
35863
|
return async function fetchHandler(req) {
|
|
35420
35864
|
const url2 = new URL(req.url);
|
|
35421
35865
|
const path = url2.pathname.replace(/^\/api\/v1\//, "/api/");
|
|
@@ -35426,7 +35870,7 @@ function createFetchHandler(options) {
|
|
|
35426
35870
|
status: "ok",
|
|
35427
35871
|
version: pkg.version,
|
|
35428
35872
|
uptime: Math.floor(process.uptime()),
|
|
35429
|
-
skillCount:
|
|
35873
|
+
skillCount: loadRegistry().length
|
|
35430
35874
|
});
|
|
35431
35875
|
}
|
|
35432
35876
|
if (path === "/api/skills" && method === "GET") {
|
|
@@ -35467,7 +35911,7 @@ function createFetchHandler(options) {
|
|
|
35467
35911
|
}
|
|
35468
35912
|
if (path === "/api/tags" && method === "GET") {
|
|
35469
35913
|
const tagCounts = new Map;
|
|
35470
|
-
for (const skill of
|
|
35914
|
+
for (const skill of loadRegistry()) {
|
|
35471
35915
|
for (const tag of skill.tags) {
|
|
35472
35916
|
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
35473
35917
|
}
|
|
@@ -35699,12 +36143,12 @@ function createFetchHandler(options) {
|
|
|
35699
36143
|
}
|
|
35700
36144
|
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
35701
36145
|
if (path !== "/") {
|
|
35702
|
-
const filePath =
|
|
36146
|
+
const filePath = join7(dashboardDir, path);
|
|
35703
36147
|
const res2 = serveStaticFile(filePath);
|
|
35704
36148
|
if (res2)
|
|
35705
36149
|
return res2;
|
|
35706
36150
|
}
|
|
35707
|
-
const indexPath =
|
|
36151
|
+
const indexPath = join7(dashboardDir, "index.html");
|
|
35708
36152
|
const res = serveStaticFile(indexPath);
|
|
35709
36153
|
if (res)
|
|
35710
36154
|
return res;
|
|
@@ -35715,7 +36159,7 @@ function createFetchHandler(options) {
|
|
|
35715
36159
|
async function startServer(port = 0, options) {
|
|
35716
36160
|
const shouldOpen = options?.open ?? true;
|
|
35717
36161
|
const dashboardDir = resolveDashboardDir();
|
|
35718
|
-
const dashboardExists =
|
|
36162
|
+
const dashboardExists = existsSync7(dashboardDir);
|
|
35719
36163
|
if (!dashboardExists) {
|
|
35720
36164
|
console.error(`
|
|
35721
36165
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -35795,8 +36239,8 @@ var {
|
|
|
35795
36239
|
// src/cli/index.tsx
|
|
35796
36240
|
init_package();
|
|
35797
36241
|
import chalk2 from "chalk";
|
|
35798
|
-
import { existsSync as
|
|
35799
|
-
import { join as
|
|
36242
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync4, appendFileSync, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
36243
|
+
import { join as join8 } from "path";
|
|
35800
36244
|
|
|
35801
36245
|
// src/cli/components/App.tsx
|
|
35802
36246
|
import { useState as useState7 } from "react";
|
|
@@ -36922,9 +37366,9 @@ init_installer();
|
|
|
36922
37366
|
init_skillinfo();
|
|
36923
37367
|
|
|
36924
37368
|
// src/lib/config.ts
|
|
36925
|
-
import { existsSync as
|
|
36926
|
-
import { join as
|
|
36927
|
-
import { homedir as
|
|
37369
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
37370
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
37371
|
+
import { homedir as homedir3 } from "os";
|
|
36928
37372
|
var VALID_KEYS = {
|
|
36929
37373
|
defaultAgent: ["claude", "codex", "gemini", "all"],
|
|
36930
37374
|
defaultScope: ["global", "project"],
|
|
@@ -36932,15 +37376,15 @@ var VALID_KEYS = {
|
|
|
36932
37376
|
};
|
|
36933
37377
|
function getConfigPath(scope) {
|
|
36934
37378
|
if (scope === "global") {
|
|
36935
|
-
return
|
|
37379
|
+
return join4(homedir3(), ".skillsrc");
|
|
36936
37380
|
}
|
|
36937
|
-
return
|
|
37381
|
+
return join4(process.cwd(), "skills.config.json");
|
|
36938
37382
|
}
|
|
36939
37383
|
function readConfigFile(path) {
|
|
36940
|
-
if (!
|
|
37384
|
+
if (!existsSync4(path))
|
|
36941
37385
|
return {};
|
|
36942
37386
|
try {
|
|
36943
|
-
const raw =
|
|
37387
|
+
const raw = readFileSync4(path, "utf-8");
|
|
36944
37388
|
const parsed = JSON.parse(raw);
|
|
36945
37389
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
36946
37390
|
return {};
|
|
@@ -36971,9 +37415,9 @@ function saveConfig(key, value, scope = "project") {
|
|
|
36971
37415
|
}
|
|
36972
37416
|
const filePath = getConfigPath(scope);
|
|
36973
37417
|
let existing = {};
|
|
36974
|
-
if (
|
|
37418
|
+
if (existsSync4(filePath)) {
|
|
36975
37419
|
try {
|
|
36976
|
-
existing = JSON.parse(
|
|
37420
|
+
existing = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
36977
37421
|
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
|
|
36978
37422
|
existing = {};
|
|
36979
37423
|
}
|
|
@@ -36982,7 +37426,7 @@ function saveConfig(key, value, scope = "project") {
|
|
|
36982
37426
|
}
|
|
36983
37427
|
} else {
|
|
36984
37428
|
const dir = dirname2(filePath);
|
|
36985
|
-
if (!
|
|
37429
|
+
if (!existsSync4(dir)) {
|
|
36986
37430
|
mkdirSync2(dir, { recursive: true });
|
|
36987
37431
|
}
|
|
36988
37432
|
}
|
|
@@ -36992,6 +37436,7 @@ function saveConfig(key, value, scope = "project") {
|
|
|
36992
37436
|
}
|
|
36993
37437
|
|
|
36994
37438
|
// src/cli/index.tsx
|
|
37439
|
+
init_scheduler();
|
|
36995
37440
|
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
36996
37441
|
var isTTY = (process.stdout.isTTY ?? false) && (process.stdin.isTTY ?? false);
|
|
36997
37442
|
if (process.argv.includes("--no-color")) {
|
|
@@ -37024,7 +37469,7 @@ program2.command("interactive", { isDefault: true }).alias("i").description("Int
|
|
|
37024
37469
|
}
|
|
37025
37470
|
render(/* @__PURE__ */ jsxDEV7(App, {}, undefined, false, undefined, this));
|
|
37026
37471
|
});
|
|
37027
|
-
program2.command("install").alias("add").argument("[skills...]", "Skills to install").option("--verbose", "Enable verbose debug logging", false).option("-o, --overwrite", "Overwrite existing skills", false).option("--json", "Output results as JSON", false).option("--for <agent>", "Install for agent: claude, codex, gemini, or all").option("--scope <scope>", "Install scope: global or project", "global").option("--dry-run", "Print what would happen without actually installing", false).option("--category <category>", "Install all skills in a category (case-insensitive)").description("Install one or more skills").action((skills, options) => {
|
|
37472
|
+
program2.command("install").alias("add").argument("[skills...]", "Skills to install").option("--verbose", "Enable verbose debug logging", false).option("-o, --overwrite", "Overwrite existing skills", false).option("--json", "Output results as JSON", false).option("--for <agent>", "Install for agent: claude, codex, gemini, pi, opencode, or all").option("--scope <scope>", "Install scope: global or project", "global").option("--dry-run", "Print what would happen without actually installing", false).option("--category <category>", "Install all skills in a category (case-insensitive)").description("Install one or more skills").action((skills, options) => {
|
|
37028
37473
|
const config2 = loadConfig();
|
|
37029
37474
|
if (!options.for && config2.defaultAgent) {
|
|
37030
37475
|
options.for = config2.defaultAgent;
|
|
@@ -37165,7 +37610,14 @@ program2.command("list").alias("ls").option("-c, --category <category>", "Filter
|
|
|
37165
37610
|
if (options.installed) {
|
|
37166
37611
|
const installed = getInstalledSkills();
|
|
37167
37612
|
if (options.json) {
|
|
37168
|
-
|
|
37613
|
+
const meta4 = getInstallMeta();
|
|
37614
|
+
const registry3 = loadRegistry();
|
|
37615
|
+
const output = installed.map((name) => {
|
|
37616
|
+
const m = meta4.skills[name];
|
|
37617
|
+
const s = registry3.find((r) => r.name === name);
|
|
37618
|
+
return { name, version: m?.version ?? null, installedAt: m?.installedAt ?? null, source: s?.source ?? "official" };
|
|
37619
|
+
});
|
|
37620
|
+
console.log(JSON.stringify(output));
|
|
37169
37621
|
return;
|
|
37170
37622
|
}
|
|
37171
37623
|
if (installed.length === 0) {
|
|
@@ -37173,16 +37625,22 @@ program2.command("list").alias("ls").option("-c, --category <category>", "Filter
|
|
|
37173
37625
|
return;
|
|
37174
37626
|
}
|
|
37175
37627
|
if (brief) {
|
|
37176
|
-
for (const name of installed)
|
|
37628
|
+
for (const name of installed)
|
|
37177
37629
|
console.log(name);
|
|
37178
|
-
}
|
|
37179
37630
|
return;
|
|
37180
37631
|
}
|
|
37632
|
+
const meta3 = getInstallMeta();
|
|
37633
|
+
const registry2 = loadRegistry();
|
|
37181
37634
|
console.log(chalk2.bold(`
|
|
37182
37635
|
Installed skills (${installed.length}):
|
|
37183
37636
|
`));
|
|
37184
37637
|
for (const name of installed) {
|
|
37185
|
-
|
|
37638
|
+
const m = meta3.skills[name];
|
|
37639
|
+
const s = registry2.find((r) => r.name === name);
|
|
37640
|
+
const version2 = m?.version ? chalk2.dim(`v${m.version}`) : "";
|
|
37641
|
+
const installedAt = m?.installedAt ? chalk2.dim(new Date(m.installedAt).toLocaleDateString()) : "";
|
|
37642
|
+
const source = s?.source === "custom" ? chalk2.yellow(" [custom]") : "";
|
|
37643
|
+
console.log(` ${chalk2.cyan(name)}${source} ${version2} ${installedAt}`);
|
|
37186
37644
|
}
|
|
37187
37645
|
return;
|
|
37188
37646
|
}
|
|
@@ -37218,7 +37676,7 @@ ${category} (${skills.length}):
|
|
|
37218
37676
|
return;
|
|
37219
37677
|
}
|
|
37220
37678
|
if (tagFilter) {
|
|
37221
|
-
const skills =
|
|
37679
|
+
const skills = loadRegistry().filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
|
|
37222
37680
|
if (options.json) {
|
|
37223
37681
|
console.log(JSON.stringify(skills, null, 2));
|
|
37224
37682
|
return;
|
|
@@ -37233,29 +37691,31 @@ ${category} (${skills.length}):
|
|
|
37233
37691
|
Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
37234
37692
|
`));
|
|
37235
37693
|
for (const s of skills) {
|
|
37236
|
-
|
|
37694
|
+
const customBadge = s.source === "custom" ? chalk2.yellow(" [custom]") : "";
|
|
37695
|
+
console.log(` ${chalk2.cyan(s.name)}${customBadge} ${chalk2.dim(`[${s.category}]`)} - ${s.description}`);
|
|
37237
37696
|
}
|
|
37238
37697
|
return;
|
|
37239
37698
|
}
|
|
37699
|
+
const allSkills = loadRegistry();
|
|
37240
37700
|
if (options.json) {
|
|
37241
|
-
console.log(JSON.stringify(
|
|
37701
|
+
console.log(JSON.stringify(allSkills, null, 2));
|
|
37242
37702
|
return;
|
|
37243
37703
|
}
|
|
37244
37704
|
if (fmt === "compact") {
|
|
37245
|
-
for (const s of
|
|
37705
|
+
for (const s of allSkills)
|
|
37246
37706
|
console.log(s.name);
|
|
37247
37707
|
return;
|
|
37248
37708
|
}
|
|
37249
37709
|
if (fmt === "csv") {
|
|
37250
|
-
console.log("name,category,description");
|
|
37251
|
-
for (const s of
|
|
37710
|
+
console.log("name,category,description,source");
|
|
37711
|
+
for (const s of allSkills) {
|
|
37252
37712
|
const desc = s.description.replace(/"/g, '""');
|
|
37253
|
-
console.log(`${s.name},${s.category},"${desc}"`);
|
|
37713
|
+
console.log(`${s.name},${s.category},"${desc}",${s.source ?? "official"}`);
|
|
37254
37714
|
}
|
|
37255
37715
|
return;
|
|
37256
37716
|
}
|
|
37257
37717
|
if (brief) {
|
|
37258
|
-
const sorted = [...
|
|
37718
|
+
const sorted = [...allSkills].sort((a, b) => {
|
|
37259
37719
|
const catCmp = a.category.localeCompare(b.category);
|
|
37260
37720
|
return catCmp !== 0 ? catCmp : a.name.localeCompare(b.name);
|
|
37261
37721
|
});
|
|
@@ -37265,16 +37725,26 @@ Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
|
37265
37725
|
return;
|
|
37266
37726
|
}
|
|
37267
37727
|
console.log(chalk2.bold(`
|
|
37268
|
-
Available skills (${
|
|
37728
|
+
Available skills (${allSkills.length}):
|
|
37269
37729
|
`));
|
|
37270
37730
|
for (const category of CATEGORIES) {
|
|
37271
37731
|
const skills = getSkillsByCategory(category);
|
|
37732
|
+
if (skills.length === 0)
|
|
37733
|
+
continue;
|
|
37272
37734
|
console.log(chalk2.bold(`${category} (${skills.length}):`));
|
|
37273
37735
|
for (const s of skills) {
|
|
37274
|
-
|
|
37736
|
+
const customBadge = s.source === "custom" ? chalk2.yellow(" [custom]") : "";
|
|
37737
|
+
console.log(` ${chalk2.cyan(s.name)}${customBadge} - ${s.description}`);
|
|
37275
37738
|
}
|
|
37276
37739
|
console.log();
|
|
37277
37740
|
}
|
|
37741
|
+
const customUncategorized = allSkills.filter((s) => s.source === "custom" && !CATEGORIES.includes(s.category));
|
|
37742
|
+
if (customUncategorized.length > 0) {
|
|
37743
|
+
console.log(chalk2.bold(`Custom (${customUncategorized.length}):`));
|
|
37744
|
+
for (const s of customUncategorized) {
|
|
37745
|
+
console.log(` ${chalk2.yellow(s.name)} - ${s.description}`);
|
|
37746
|
+
}
|
|
37747
|
+
}
|
|
37278
37748
|
});
|
|
37279
37749
|
program2.command("search").alias("s").argument("<query>", "Search term").option("--verbose", "Enable verbose debug logging", false).option("--json", "Output as JSON", false).option("--brief", "One line per skill: name \u2014 description [category]", false).option("--format <format>", "Output format: compact (names only) or csv (name,category,description)").option("-c, --category <category>", "Filter results by category").option("-t, --tags <tags>", "Filter results by comma-separated tags (OR logic, case-insensitive)").description("Search for skills").action((query, options) => {
|
|
37280
37750
|
let results = searchSkills(query);
|
|
@@ -37350,8 +37820,17 @@ program2.command("info").argument("<skill>", "Skill name").option("--json", "Out
|
|
|
37350
37820
|
console.log(`${skill.name} \u2014 ${skill.description} [${skill.category}] (tags: ${skill.tags.join(", ")})`);
|
|
37351
37821
|
return;
|
|
37352
37822
|
}
|
|
37823
|
+
const { execSync: execSyncInfo } = __require("child_process");
|
|
37824
|
+
function cmdAvailable(cmd) {
|
|
37825
|
+
try {
|
|
37826
|
+
execSyncInfo(`which ${cmd}`, { stdio: "ignore" });
|
|
37827
|
+
return true;
|
|
37828
|
+
} catch {
|
|
37829
|
+
return false;
|
|
37830
|
+
}
|
|
37831
|
+
}
|
|
37353
37832
|
console.log(`
|
|
37354
|
-
${chalk2.bold(skill.displayName)}`);
|
|
37833
|
+
${chalk2.bold(skill.displayName)}${skill.source === "custom" ? chalk2.yellow(" [custom]") : ""}`);
|
|
37355
37834
|
console.log(`${skill.description}`);
|
|
37356
37835
|
console.log(`${chalk2.dim("Category:")} ${skill.category}`);
|
|
37357
37836
|
console.log(`${chalk2.dim("Tags:")} ${skill.tags.join(", ")}`);
|
|
@@ -37359,10 +37838,18 @@ ${chalk2.bold(skill.displayName)}`);
|
|
|
37359
37838
|
console.log(`${chalk2.dim("CLI:")} ${reqs.cliCommand}`);
|
|
37360
37839
|
}
|
|
37361
37840
|
if (reqs?.envVars.length) {
|
|
37362
|
-
console.log(`${chalk2.dim("Env vars:")}
|
|
37841
|
+
console.log(`${chalk2.dim("Env vars:")}`);
|
|
37842
|
+
for (const v of reqs.envVars) {
|
|
37843
|
+
const set3 = !!process.env[v];
|
|
37844
|
+
console.log(` ${set3 ? chalk2.green("\u2713") : chalk2.red("\u2717")} ${v}${set3 ? "" : chalk2.dim(" (not set)")}`);
|
|
37845
|
+
}
|
|
37363
37846
|
}
|
|
37364
37847
|
if (reqs?.systemDeps.length) {
|
|
37365
|
-
console.log(`${chalk2.dim("System deps:")}
|
|
37848
|
+
console.log(`${chalk2.dim("System deps:")}`);
|
|
37849
|
+
for (const d of reqs.systemDeps) {
|
|
37850
|
+
const avail = cmdAvailable(d);
|
|
37851
|
+
console.log(` ${avail ? chalk2.green("\u2713") : chalk2.red("\u2717")} ${d}${avail ? "" : chalk2.dim(" (not found)")}`);
|
|
37852
|
+
}
|
|
37366
37853
|
}
|
|
37367
37854
|
console.log(`${chalk2.dim("Install:")} skills install ${skill.name}`);
|
|
37368
37855
|
});
|
|
@@ -37466,7 +37953,7 @@ program2.command("run").argument("<skill>", "Skill name").argument("[args...]",
|
|
|
37466
37953
|
}
|
|
37467
37954
|
process.exitCode = result.exitCode;
|
|
37468
37955
|
});
|
|
37469
|
-
program2.command("init").option("--json", "Output as JSON", false).option("--for <agent>", "Detect project type and install recommended skills for agent: claude, codex, gemini, or all").option("--scope <scope>", "Install scope: global or project", "global").description("Initialize project for installed skills (.env.example, .gitignore)").action((options) => {
|
|
37956
|
+
program2.command("init").option("--json", "Output as JSON", false).option("--for <agent>", "Detect project type and install recommended skills for agent: claude, codex, gemini, pi, opencode, or all").option("--scope <scope>", "Install scope: global or project", "global").description("Initialize project for installed skills (.env.example, .gitignore)").action((options) => {
|
|
37470
37957
|
const cwd = process.cwd();
|
|
37471
37958
|
if (options.for) {
|
|
37472
37959
|
let agents;
|
|
@@ -37580,8 +38067,8 @@ Installing recommended skills for ${options.for} (${options.scope})...
|
|
|
37580
38067
|
const envContent = lines.join(`
|
|
37581
38068
|
`) + `
|
|
37582
38069
|
`;
|
|
37583
|
-
const envPath =
|
|
37584
|
-
|
|
38070
|
+
const envPath = join8(cwd, ".env.example");
|
|
38071
|
+
writeFileSync4(envPath, envContent);
|
|
37585
38072
|
envVarCount = envMap.size;
|
|
37586
38073
|
if (!options.json) {
|
|
37587
38074
|
console.log(chalk2.green(`\u2713 Generated .env.example (${envVarCount} variables from ${installed.length} skills)`));
|
|
@@ -37591,11 +38078,11 @@ Installing recommended skills for ${options.for} (${options.scope})...
|
|
|
37591
38078
|
console.log(chalk2.dim(" No environment variables detected across installed skills"));
|
|
37592
38079
|
}
|
|
37593
38080
|
}
|
|
37594
|
-
const gitignorePath =
|
|
38081
|
+
const gitignorePath = join8(cwd, ".gitignore");
|
|
37595
38082
|
const gitignoreEntry = ".skills/";
|
|
37596
38083
|
let gitignoreContent = "";
|
|
37597
|
-
if (
|
|
37598
|
-
gitignoreContent =
|
|
38084
|
+
if (existsSync8(gitignorePath)) {
|
|
38085
|
+
gitignoreContent = readFileSync7(gitignorePath, "utf-8");
|
|
37599
38086
|
}
|
|
37600
38087
|
let gitignoreUpdated = false;
|
|
37601
38088
|
if (!gitignoreContent.includes(gitignoreEntry)) {
|
|
@@ -37641,7 +38128,7 @@ Initialized for ${installed.length} installed skill(s)`));
|
|
|
37641
38128
|
}
|
|
37642
38129
|
}
|
|
37643
38130
|
});
|
|
37644
|
-
program2.command("remove").alias("rm").argument("<skill>", "Skill to remove").option("--verbose", "Enable verbose debug logging", false).option("--json", "Output as JSON", false).option("--for <agent>", "Remove from agent: claude, codex, gemini, or all").option("--scope <scope>", "Remove scope: global or project", "global").option("--dry-run", "Print what would happen without actually removing", false).option("-y, --yes", "Skip confirmation prompt", false).description("Remove an installed skill").action(async (skill, options) => {
|
|
38131
|
+
program2.command("remove").alias("rm").argument("<skill>", "Skill to remove").option("--verbose", "Enable verbose debug logging", false).option("--json", "Output as JSON", false).option("--for <agent>", "Remove from agent: claude, codex, gemini, pi, opencode, or all").option("--scope <scope>", "Remove scope: global or project", "global").option("--dry-run", "Print what would happen without actually removing", false).option("-y, --yes", "Skip confirmation prompt", false).description("Remove an installed skill").action(async (skill, options) => {
|
|
37645
38132
|
debug(`remove: skill=${skill} for=${options.for ?? "none"} scope=${options.scope} dryRun=${options.dryRun}`);
|
|
37646
38133
|
if (!options.yes && !options.dryRun && isTTY) {
|
|
37647
38134
|
const skillName = normalizeSkillName(skill);
|
|
@@ -37724,10 +38211,10 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
37724
38211
|
}
|
|
37725
38212
|
function collectFiles(dir, base = "") {
|
|
37726
38213
|
const files = new Set;
|
|
37727
|
-
if (!
|
|
38214
|
+
if (!existsSync8(dir))
|
|
37728
38215
|
return files;
|
|
37729
|
-
for (const entry of
|
|
37730
|
-
const full =
|
|
38216
|
+
for (const entry of readdirSync5(dir)) {
|
|
38217
|
+
const full = join8(dir, entry);
|
|
37731
38218
|
const rel = base ? `${base}/${entry}` : entry;
|
|
37732
38219
|
if (statSync3(full).isDirectory()) {
|
|
37733
38220
|
for (const f of collectFiles(full, rel))
|
|
@@ -37741,7 +38228,7 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
37741
38228
|
const updateResults = [];
|
|
37742
38229
|
for (const name of toUpdate) {
|
|
37743
38230
|
const skillName = normalizeSkillName(name);
|
|
37744
|
-
const destPath =
|
|
38231
|
+
const destPath = join8(process.cwd(), ".skills", skillName);
|
|
37745
38232
|
const beforeFiles = collectFiles(destPath);
|
|
37746
38233
|
const result = installSkill(name, { overwrite: true });
|
|
37747
38234
|
const afterFiles = collectFiles(destPath);
|
|
@@ -37819,10 +38306,11 @@ Tags:
|
|
|
37819
38306
|
console.log(` ${chalk2.cyan(name)} (${count})`);
|
|
37820
38307
|
}
|
|
37821
38308
|
});
|
|
37822
|
-
program2.command("mcp").option("--register <agent>", "Register MCP server with agent: claude, codex, gemini, or all").description("Start MCP server (stdio) or register with an agent").action(async (options) => {
|
|
38309
|
+
program2.command("mcp").option("--register <agent>", "Register MCP server with agent: claude, codex, gemini, pi, opencode, or all").description("Start MCP server (stdio) or register with an agent").action(async (options) => {
|
|
37823
38310
|
if (options.register) {
|
|
37824
|
-
const agents = options.register === "all" ? [
|
|
37825
|
-
const binPath =
|
|
38311
|
+
const agents = options.register === "all" ? [...AGENT_TARGETS] : [options.register];
|
|
38312
|
+
const binPath = join8(import.meta.dir, "..", "mcp", "index.ts");
|
|
38313
|
+
const { homedir: hd } = await import("os");
|
|
37826
38314
|
for (const agent of agents) {
|
|
37827
38315
|
if (agent === "claude") {
|
|
37828
38316
|
try {
|
|
@@ -37836,8 +38324,7 @@ program2.command("mcp").option("--register <agent>", "Register MCP server with a
|
|
|
37836
38324
|
console.log(chalk2.yellow(`Manual registration: claude mcp add skills -- bun run ${binPath}`));
|
|
37837
38325
|
}
|
|
37838
38326
|
} else if (agent === "codex") {
|
|
37839
|
-
const
|
|
37840
|
-
const configPath = join6(homedir4(), ".codex", "config.toml");
|
|
38327
|
+
const configPath = join8(hd(), ".codex", "config.toml");
|
|
37841
38328
|
console.log(chalk2.bold(`
|
|
37842
38329
|
Add to ${configPath}:`));
|
|
37843
38330
|
console.log(chalk2.dim(`[mcp_servers.skills]
|
|
@@ -37845,16 +38332,25 @@ command = "bun"
|
|
|
37845
38332
|
args = ["run", "${binPath}"]`));
|
|
37846
38333
|
console.log(chalk2.green(`\u2713 Codex MCP config shown above`));
|
|
37847
38334
|
} else if (agent === "gemini") {
|
|
37848
|
-
const
|
|
37849
|
-
const configPath = join6(homedir4(), ".gemini", "settings.json");
|
|
38335
|
+
const configPath = join8(hd(), ".gemini", "settings.json");
|
|
37850
38336
|
console.log(chalk2.bold(`
|
|
37851
38337
|
Add to ${configPath} mcpServers:`));
|
|
37852
|
-
console.log(chalk2.dim(JSON.stringify({
|
|
37853
|
-
skills: { command: "bun", args: ["run", binPath] }
|
|
37854
|
-
}, null, 2)));
|
|
38338
|
+
console.log(chalk2.dim(JSON.stringify({ skills: { command: "bun", args: ["run", binPath] } }, null, 2)));
|
|
37855
38339
|
console.log(chalk2.green(`\u2713 Gemini MCP config shown above`));
|
|
38340
|
+
} else if (agent === "pi") {
|
|
38341
|
+
const configPath = join8(hd(), ".pi", "agent", "mcp.json");
|
|
38342
|
+
console.log(chalk2.bold(`
|
|
38343
|
+
Add to ${configPath}:`));
|
|
38344
|
+
console.log(chalk2.dim(JSON.stringify({ skills: { command: "bun", args: ["run", binPath] } }, null, 2)));
|
|
38345
|
+
console.log(chalk2.green(`\u2713 pi.dev MCP config shown above`));
|
|
38346
|
+
} else if (agent === "opencode") {
|
|
38347
|
+
const configPath = join8(hd(), ".opencode", "config.json");
|
|
38348
|
+
console.log(chalk2.bold(`
|
|
38349
|
+
Add to ${configPath} mcp section:`));
|
|
38350
|
+
console.log(chalk2.dim(JSON.stringify({ skills: { command: "bun", args: ["run", binPath] } }, null, 2)));
|
|
38351
|
+
console.log(chalk2.green(`\u2713 OpenCode MCP config shown above`));
|
|
37856
38352
|
} else {
|
|
37857
|
-
console.error(chalk2.red(`Unknown agent: ${agent}. Available:
|
|
38353
|
+
console.error(chalk2.red(`Unknown agent: ${agent}. Available: ${AGENT_TARGETS.join(", ")}, all`));
|
|
37858
38354
|
process.exitCode = 1;
|
|
37859
38355
|
}
|
|
37860
38356
|
}
|
|
@@ -38040,18 +38536,18 @@ program2.command("export").option("--json", "Output as JSON (default behavior)",
|
|
|
38040
38536
|
};
|
|
38041
38537
|
console.log(JSON.stringify(payload, null, 2));
|
|
38042
38538
|
});
|
|
38043
|
-
program2.command("import").argument("<file>", "JSON file to import (use - for stdin)").option("--json", "Output results as JSON", false).option("--for <agent>", "Install for agent: claude, codex, gemini, or all").option("--scope <scope>", "Install scope: global or project", "global").option("--dry-run", "Show what would be installed without actually installing", false).description("Import and install skills from a JSON export file").action(async (file2, options) => {
|
|
38539
|
+
program2.command("import").argument("<file>", "JSON file to import (use - for stdin)").option("--json", "Output results as JSON", false).option("--for <agent>", "Install for agent: claude, codex, gemini, pi, opencode, or all").option("--scope <scope>", "Install scope: global or project", "global").option("--dry-run", "Show what would be installed without actually installing", false).description("Import and install skills from a JSON export file").action(async (file2, options) => {
|
|
38044
38540
|
let raw;
|
|
38045
38541
|
try {
|
|
38046
38542
|
if (file2 === "-") {
|
|
38047
38543
|
raw = await new Response(process.stdin).text();
|
|
38048
38544
|
} else {
|
|
38049
|
-
if (!
|
|
38545
|
+
if (!existsSync8(file2)) {
|
|
38050
38546
|
console.error(chalk2.red(`File not found: ${file2}`));
|
|
38051
38547
|
process.exitCode = 1;
|
|
38052
38548
|
return;
|
|
38053
38549
|
}
|
|
38054
|
-
raw =
|
|
38550
|
+
raw = readFileSync7(file2, "utf-8");
|
|
38055
38551
|
}
|
|
38056
38552
|
} catch (err) {
|
|
38057
38553
|
console.error(chalk2.red(`Failed to read file: ${err.message}`));
|
|
@@ -38142,7 +38638,8 @@ Imported ${succeeded}/${total} skill(s)${failed > 0 ? ` (${failed} failed)` : ""
|
|
|
38142
38638
|
process.exitCode = 1;
|
|
38143
38639
|
}
|
|
38144
38640
|
});
|
|
38145
|
-
program2.command("doctor").option("--json", "Output as JSON", false).description("Check
|
|
38641
|
+
program2.command("doctor").option("--json", "Output as JSON", false).description("Check env vars, system deps, and install health for installed skills").action((options) => {
|
|
38642
|
+
const { execSync } = __require("child_process");
|
|
38146
38643
|
const installed = getInstalledSkills();
|
|
38147
38644
|
if (installed.length === 0) {
|
|
38148
38645
|
if (options.json) {
|
|
@@ -38152,37 +38649,53 @@ program2.command("doctor").option("--json", "Output as JSON", false).description
|
|
|
38152
38649
|
}
|
|
38153
38650
|
return;
|
|
38154
38651
|
}
|
|
38652
|
+
function isCommandAvailable(cmd) {
|
|
38653
|
+
try {
|
|
38654
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
38655
|
+
return true;
|
|
38656
|
+
} catch {
|
|
38657
|
+
return false;
|
|
38658
|
+
}
|
|
38659
|
+
}
|
|
38155
38660
|
const report = [];
|
|
38156
38661
|
for (const name of installed) {
|
|
38157
38662
|
const reqs = getSkillRequirements(name);
|
|
38158
|
-
const envVars = (reqs?.envVars ?? []).map((v) => ({
|
|
38159
|
-
|
|
38160
|
-
|
|
38161
|
-
})
|
|
38162
|
-
report.push({ skill: name, envVars });
|
|
38663
|
+
const envVars = (reqs?.envVars ?? []).map((v) => ({ name: v, set: !!process.env[v] }));
|
|
38664
|
+
const systemDeps = (reqs?.systemDeps ?? []).map((d) => ({ name: d, available: isCommandAvailable(d) }));
|
|
38665
|
+
const healthy = envVars.every((v) => v.set) && systemDeps.every((d) => d.available);
|
|
38666
|
+
report.push({ skill: name, envVars, systemDeps, healthy });
|
|
38163
38667
|
}
|
|
38164
38668
|
if (options.json) {
|
|
38165
38669
|
console.log(JSON.stringify(report, null, 2));
|
|
38166
38670
|
return;
|
|
38167
38671
|
}
|
|
38672
|
+
const issues = report.filter((r) => !r.healthy);
|
|
38168
38673
|
console.log(chalk2.bold(`
|
|
38169
|
-
Skills Doctor
|
|
38674
|
+
Skills Doctor \u2014 ${installed.length} installed, ${issues.length} with issues:
|
|
38170
38675
|
`));
|
|
38171
38676
|
for (const entry of report) {
|
|
38172
|
-
|
|
38173
|
-
|
|
38174
|
-
|
|
38175
|
-
|
|
38176
|
-
|
|
38177
|
-
|
|
38178
|
-
|
|
38179
|
-
|
|
38677
|
+
const icon = entry.healthy ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
38678
|
+
console.log(` ${icon} ${chalk2.bold(entry.skill)}`);
|
|
38679
|
+
for (const v of entry.envVars) {
|
|
38680
|
+
const status = v.set ? chalk2.green("set") : chalk2.red("missing");
|
|
38681
|
+
console.log(` ${v.name} [${status}]`);
|
|
38682
|
+
}
|
|
38683
|
+
for (const d of entry.systemDeps) {
|
|
38684
|
+
const status = d.available ? chalk2.green("available") : chalk2.red("not found");
|
|
38685
|
+
console.log(` ${d.name} [${status}]`);
|
|
38180
38686
|
}
|
|
38687
|
+
if (entry.envVars.length === 0 && entry.systemDeps.length === 0) {
|
|
38688
|
+
console.log(chalk2.dim(" No requirements"));
|
|
38689
|
+
}
|
|
38690
|
+
}
|
|
38691
|
+
if (issues.length === 0) {
|
|
38692
|
+
console.log(chalk2.green(`
|
|
38693
|
+
All skills healthy! \u2713`));
|
|
38181
38694
|
}
|
|
38182
38695
|
});
|
|
38183
38696
|
program2.command("auth").argument("[skill]", "Skill name (omit to check all installed skills)").option("--set <assignment>", "Set an env var in .env file (format: KEY=VALUE)").option("--json", "Output as JSON", false).description("Show auth/env var status for a skill or all installed skills").action((name, options) => {
|
|
38184
38697
|
const cwd = process.cwd();
|
|
38185
|
-
const envFilePath =
|
|
38698
|
+
const envFilePath = join8(cwd, ".env");
|
|
38186
38699
|
if (options.set) {
|
|
38187
38700
|
const eqIdx = options.set.indexOf("=");
|
|
38188
38701
|
if (eqIdx === -1) {
|
|
@@ -38198,8 +38711,8 @@ program2.command("auth").argument("[skill]", "Skill name (omit to check all inst
|
|
|
38198
38711
|
return;
|
|
38199
38712
|
}
|
|
38200
38713
|
let existing = "";
|
|
38201
|
-
if (
|
|
38202
|
-
existing =
|
|
38714
|
+
if (existsSync8(envFilePath)) {
|
|
38715
|
+
existing = readFileSync7(envFilePath, "utf-8");
|
|
38203
38716
|
}
|
|
38204
38717
|
const keyPattern = new RegExp(`^${key}=.*$`, "m");
|
|
38205
38718
|
let updated;
|
|
@@ -38212,7 +38725,7 @@ program2.command("auth").argument("[skill]", "Skill name (omit to check all inst
|
|
|
38212
38725
|
${key}=${value}
|
|
38213
38726
|
`;
|
|
38214
38727
|
}
|
|
38215
|
-
|
|
38728
|
+
writeFileSync4(envFilePath, updated, "utf-8");
|
|
38216
38729
|
console.log(chalk2.green(`Set ${key} in ${envFilePath}`));
|
|
38217
38730
|
return;
|
|
38218
38731
|
}
|
|
@@ -38282,21 +38795,20 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
|
|
|
38282
38795
|
const version2 = package_default.version;
|
|
38283
38796
|
const cwd = process.cwd();
|
|
38284
38797
|
const installed = getInstalledSkills();
|
|
38285
|
-
const agentNames = ["claude", "codex", "gemini"];
|
|
38286
38798
|
const agentConfigs = [];
|
|
38287
|
-
for (const agent of
|
|
38288
|
-
const agentSkillsPath =
|
|
38289
|
-
const exists =
|
|
38799
|
+
for (const agent of AGENT_TARGETS) {
|
|
38800
|
+
const agentSkillsPath = getAgentSkillsDir(agent, "global");
|
|
38801
|
+
const exists = existsSync8(agentSkillsPath);
|
|
38290
38802
|
let skillCount = 0;
|
|
38291
38803
|
if (exists) {
|
|
38292
38804
|
try {
|
|
38293
|
-
skillCount =
|
|
38294
|
-
const full =
|
|
38805
|
+
skillCount = readdirSync5(agentSkillsPath).filter((f) => {
|
|
38806
|
+
const full = join8(agentSkillsPath, f);
|
|
38295
38807
|
return f.startsWith("skill-") && statSync3(full).isDirectory();
|
|
38296
38808
|
}).length;
|
|
38297
38809
|
} catch {}
|
|
38298
38810
|
}
|
|
38299
|
-
agentConfigs.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
38811
|
+
agentConfigs.push({ agent, label: AGENT_LABELS[agent], path: agentSkillsPath, exists, skillCount });
|
|
38300
38812
|
}
|
|
38301
38813
|
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
38302
38814
|
if (options.json) {
|
|
@@ -38436,19 +38948,19 @@ program2.command("outdated").option("--json", "Output as JSON", false).descripti
|
|
|
38436
38948
|
const upToDate = [];
|
|
38437
38949
|
for (const name of installed) {
|
|
38438
38950
|
const skillName = normalizeSkillName(name);
|
|
38439
|
-
const installedPkgPath =
|
|
38951
|
+
const installedPkgPath = join8(cwd, ".skills", skillName, "package.json");
|
|
38440
38952
|
let installedVersion = "unknown";
|
|
38441
|
-
if (
|
|
38953
|
+
if (existsSync8(installedPkgPath)) {
|
|
38442
38954
|
try {
|
|
38443
|
-
installedVersion = JSON.parse(
|
|
38955
|
+
installedVersion = JSON.parse(readFileSync7(installedPkgPath, "utf-8")).version || "unknown";
|
|
38444
38956
|
} catch {}
|
|
38445
38957
|
}
|
|
38446
38958
|
const registryPath = getSkillPath(name);
|
|
38447
|
-
const registryPkgPath =
|
|
38959
|
+
const registryPkgPath = join8(registryPath, "package.json");
|
|
38448
38960
|
let registryVersion = "unknown";
|
|
38449
|
-
if (
|
|
38961
|
+
if (existsSync8(registryPkgPath)) {
|
|
38450
38962
|
try {
|
|
38451
|
-
registryVersion = JSON.parse(
|
|
38963
|
+
registryVersion = JSON.parse(readFileSync7(registryPkgPath, "utf-8")).version || "unknown";
|
|
38452
38964
|
} catch {}
|
|
38453
38965
|
}
|
|
38454
38966
|
if (installedVersion !== registryVersion) {
|
|
@@ -38513,7 +39025,470 @@ configCmd.command("get <key>").description("Get a specific configuration value")
|
|
|
38513
39025
|
configCmd.command("path").description("Show configuration file paths").action(() => {
|
|
38514
39026
|
const globalPath = getConfigPath("global");
|
|
38515
39027
|
const projectPath = getConfigPath("project");
|
|
38516
|
-
console.log(`${chalk2.cyan("global")}: ${globalPath}${
|
|
38517
|
-
console.log(`${chalk2.cyan("project")}: ${projectPath}${
|
|
39028
|
+
console.log(`${chalk2.cyan("global")}: ${globalPath}${existsSync8(globalPath) ? chalk2.green(" (exists)") : chalk2.dim(" (not found)")}`);
|
|
39029
|
+
console.log(`${chalk2.cyan("project")}: ${projectPath}${existsSync8(projectPath) ? chalk2.green(" (exists)") : chalk2.dim(" (not found)")}`);
|
|
39030
|
+
});
|
|
39031
|
+
program2.command("create").argument("<name>", "Skill name (e.g. my-tool)").option("--category <category>", "Skill category", "Development Tools").option("--description <description>", "Short description of what the skill does").option("--tags <tags>", "Comma-separated tags (e.g. api,testing,automation)").option("--global", "Create in ~/.skills/ instead of .skills/custom-skills/", false).option("--json", "Output result as JSON", false).description("Scaffold a new custom skill directory").action((name, options) => {
|
|
39032
|
+
const { homedir: homedir4 } = __require("os");
|
|
39033
|
+
const bare = name.replace(/^skill-/, "");
|
|
39034
|
+
const dirName = `skill-${bare}`;
|
|
39035
|
+
const baseDir = options.global ? join8(homedir4(), ".skills") : join8(process.cwd(), ".skills", "custom-skills");
|
|
39036
|
+
const skillDir = join8(baseDir, dirName);
|
|
39037
|
+
if (existsSync8(skillDir)) {
|
|
39038
|
+
if (options.json) {
|
|
39039
|
+
console.log(JSON.stringify({ error: `Skill '${bare}' already exists at ${skillDir}` }));
|
|
39040
|
+
} else {
|
|
39041
|
+
console.error(chalk2.red(`Skill '${bare}' already exists at ${skillDir}`));
|
|
39042
|
+
}
|
|
39043
|
+
process.exitCode = 1;
|
|
39044
|
+
return;
|
|
39045
|
+
}
|
|
39046
|
+
const description = options.description || `${bare} skill`;
|
|
39047
|
+
const tags = options.tags ? options.tags.split(",").map((t) => t.trim()).filter(Boolean) : [bare];
|
|
39048
|
+
const displayName = bare.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
39049
|
+
const category = options.category;
|
|
39050
|
+
mkdirSync4(join8(skillDir, "src"), { recursive: true });
|
|
39051
|
+
writeFileSync4(join8(skillDir, "SKILL.md"), [
|
|
39052
|
+
"---",
|
|
39053
|
+
`name: ${bare}`,
|
|
39054
|
+
`description: ${description}`,
|
|
39055
|
+
`displayName: ${displayName}`,
|
|
39056
|
+
`category: ${category}`,
|
|
39057
|
+
`tags: [${tags.join(", ")}]`,
|
|
39058
|
+
"---",
|
|
39059
|
+
"",
|
|
39060
|
+
`# ${displayName}`,
|
|
39061
|
+
"",
|
|
39062
|
+
description,
|
|
39063
|
+
"",
|
|
39064
|
+
"## Usage",
|
|
39065
|
+
"",
|
|
39066
|
+
"```bash",
|
|
39067
|
+
`${bare} --help`,
|
|
39068
|
+
"```",
|
|
39069
|
+
""
|
|
39070
|
+
].join(`
|
|
39071
|
+
`));
|
|
39072
|
+
writeFileSync4(join8(skillDir, "src", "index.ts"), [
|
|
39073
|
+
`#!/usr/bin/env bun`,
|
|
39074
|
+
`/**`,
|
|
39075
|
+
` * ${displayName} \u2014 ${description}`,
|
|
39076
|
+
` */`,
|
|
39077
|
+
"",
|
|
39078
|
+
`console.log("${displayName}");`,
|
|
39079
|
+
""
|
|
39080
|
+
].join(`
|
|
39081
|
+
`));
|
|
39082
|
+
writeFileSync4(join8(skillDir, "package.json"), JSON.stringify({
|
|
39083
|
+
name: `skill-${bare}`,
|
|
39084
|
+
version: "0.1.0",
|
|
39085
|
+
description,
|
|
39086
|
+
bin: { [bare]: "./src/index.ts" },
|
|
39087
|
+
scripts: { dev: `bun src/index.ts` },
|
|
39088
|
+
dependencies: {}
|
|
39089
|
+
}, null, 2) + `
|
|
39090
|
+
`);
|
|
39091
|
+
writeFileSync4(join8(skillDir, "tsconfig.json"), JSON.stringify({
|
|
39092
|
+
compilerOptions: {
|
|
39093
|
+
target: "ES2022",
|
|
39094
|
+
module: "ESNext",
|
|
39095
|
+
moduleResolution: "bundler",
|
|
39096
|
+
strict: true,
|
|
39097
|
+
outDir: "dist"
|
|
39098
|
+
},
|
|
39099
|
+
include: ["src/**/*.ts"]
|
|
39100
|
+
}, null, 2) + `
|
|
39101
|
+
`);
|
|
39102
|
+
clearRegistryCache();
|
|
39103
|
+
if (options.json) {
|
|
39104
|
+
console.log(JSON.stringify({ created: true, name: bare, path: skillDir, category, tags }));
|
|
39105
|
+
} else {
|
|
39106
|
+
console.log(chalk2.green(`\u2713 Created custom skill '${bare}' at ${skillDir}`));
|
|
39107
|
+
console.log(chalk2.dim(` Category: ${category}`));
|
|
39108
|
+
console.log(chalk2.dim(` Tags: ${tags.join(", ")}`));
|
|
39109
|
+
console.log("");
|
|
39110
|
+
console.log(` ${chalk2.cyan("Edit:")} ${join8(skillDir, "src", "index.ts")}`);
|
|
39111
|
+
console.log(` ${chalk2.cyan("Run:")} bun ${join8(skillDir, "src", "index.ts")}`);
|
|
39112
|
+
console.log(` ${chalk2.cyan("Docs:")} ${join8(skillDir, "SKILL.md")}`);
|
|
39113
|
+
}
|
|
39114
|
+
});
|
|
39115
|
+
program2.command("sync").option("--to <agent>", "Push custom skills to agent: claude, codex, gemini, pi, opencode, or all").option("--from <agent>", "List agent skills and show which are unknown to the registry").option("--register", "With --from: copy unknown agent skills into ~/.skills/ to add them to the registry", false).option("--scope <scope>", "Agent install scope: global or project", "global").option("--json", "Output as JSON", false).description("Sync custom skills with agent directories (--to or --from)").action((options) => {
|
|
39116
|
+
const { homedir: homedir4 } = __require("os");
|
|
39117
|
+
if (!options.to && !options.from) {
|
|
39118
|
+
console.error(chalk2.red("Specify --to <agent> or --from <agent>"));
|
|
39119
|
+
process.exitCode = 1;
|
|
39120
|
+
return;
|
|
39121
|
+
}
|
|
39122
|
+
if (options.from) {
|
|
39123
|
+
const agentName = options.from;
|
|
39124
|
+
if (!AGENT_TARGETS.includes(agentName)) {
|
|
39125
|
+
console.error(chalk2.red(`Unknown agent: ${agentName}. Available: ${AGENT_TARGETS.join(", ")}`));
|
|
39126
|
+
process.exitCode = 1;
|
|
39127
|
+
return;
|
|
39128
|
+
}
|
|
39129
|
+
const agentDir = getAgentSkillsDir(agentName, options.scope);
|
|
39130
|
+
if (!existsSync8(agentDir)) {
|
|
39131
|
+
if (options.json) {
|
|
39132
|
+
console.log(JSON.stringify({ agentDir, skills: [], message: "Directory not found" }));
|
|
39133
|
+
} else {
|
|
39134
|
+
console.log(chalk2.dim(`No skills directory found at ${agentDir}`));
|
|
39135
|
+
}
|
|
39136
|
+
return;
|
|
39137
|
+
}
|
|
39138
|
+
const registry2 = loadRegistry();
|
|
39139
|
+
const registryNames = new Set(registry2.map((s) => s.name));
|
|
39140
|
+
const found = [];
|
|
39141
|
+
for (const entry of readdirSync5(agentDir, { withFileTypes: true })) {
|
|
39142
|
+
if (!entry.isDirectory())
|
|
39143
|
+
continue;
|
|
39144
|
+
const bare = entry.name.replace(/^skill-/, "");
|
|
39145
|
+
found.push({
|
|
39146
|
+
name: bare,
|
|
39147
|
+
path: join8(agentDir, entry.name),
|
|
39148
|
+
inRegistry: registryNames.has(bare)
|
|
39149
|
+
});
|
|
39150
|
+
}
|
|
39151
|
+
const unknown3 = found.filter((s) => !s.inRegistry);
|
|
39152
|
+
if (options.register && unknown3.length > 0) {
|
|
39153
|
+
const globalSkillsDir = join8(homedir4(), ".skills");
|
|
39154
|
+
const registered = [];
|
|
39155
|
+
for (const s of unknown3) {
|
|
39156
|
+
const srcSkillMd = join8(s.path, "SKILL.md");
|
|
39157
|
+
if (!existsSync8(srcSkillMd))
|
|
39158
|
+
continue;
|
|
39159
|
+
const destDir = join8(globalSkillsDir, `skill-${s.name}`);
|
|
39160
|
+
if (!existsSync8(destDir)) {
|
|
39161
|
+
mkdirSync4(destDir, { recursive: true });
|
|
39162
|
+
}
|
|
39163
|
+
writeFileSync4(join8(destDir, "SKILL.md"), readFileSync7(srcSkillMd, "utf-8"));
|
|
39164
|
+
registered.push(s.name);
|
|
39165
|
+
}
|
|
39166
|
+
clearRegistryCache();
|
|
39167
|
+
if (options.json) {
|
|
39168
|
+
console.log(JSON.stringify({ agentDir, skills: found, registered }));
|
|
39169
|
+
} else {
|
|
39170
|
+
for (const name of registered) {
|
|
39171
|
+
console.log(chalk2.green(`\u2713 Registered '${name}' into ~/.skills/ (global custom)`));
|
|
39172
|
+
}
|
|
39173
|
+
if (registered.length === 0)
|
|
39174
|
+
console.log(chalk2.dim("No new skills to register (all SKILL.md files missing)"));
|
|
39175
|
+
}
|
|
39176
|
+
return;
|
|
39177
|
+
}
|
|
39178
|
+
if (options.json) {
|
|
39179
|
+
console.log(JSON.stringify({ agentDir, skills: found }));
|
|
39180
|
+
} else {
|
|
39181
|
+
console.log(chalk2.bold(`
|
|
39182
|
+
Agent skills in ~/.${agentName}/skills/ (${found.length} found):
|
|
39183
|
+
`));
|
|
39184
|
+
for (const s of found) {
|
|
39185
|
+
const label = s.inRegistry ? chalk2.green("\u2713 in registry") : chalk2.yellow("\u2717 not in registry");
|
|
39186
|
+
console.log(` ${chalk2.cyan(s.name)} \u2014 ${label}`);
|
|
39187
|
+
}
|
|
39188
|
+
if (unknown3.length > 0) {
|
|
39189
|
+
console.log("");
|
|
39190
|
+
console.log(chalk2.dim(`Tip: ${unknown3.length} skill(s) not in registry. Run with --register to add them to ~/.skills/.`));
|
|
39191
|
+
}
|
|
39192
|
+
}
|
|
39193
|
+
return;
|
|
39194
|
+
}
|
|
39195
|
+
if (options.to) {
|
|
39196
|
+
let agents;
|
|
39197
|
+
try {
|
|
39198
|
+
agents = resolveAgents(options.to);
|
|
39199
|
+
} catch (err) {
|
|
39200
|
+
console.error(chalk2.red(err.message));
|
|
39201
|
+
process.exitCode = 1;
|
|
39202
|
+
return;
|
|
39203
|
+
}
|
|
39204
|
+
const registry2 = loadRegistry();
|
|
39205
|
+
const customSkills = registry2.filter((s) => s.source === "custom");
|
|
39206
|
+
if (customSkills.length === 0) {
|
|
39207
|
+
if (options.json) {
|
|
39208
|
+
console.log(JSON.stringify({ pushed: 0, message: "No custom skills found" }));
|
|
39209
|
+
} else {
|
|
39210
|
+
console.log(chalk2.dim("No custom skills found. Use 'skills create <name>' to scaffold one."));
|
|
39211
|
+
}
|
|
39212
|
+
return;
|
|
39213
|
+
}
|
|
39214
|
+
const results = [];
|
|
39215
|
+
for (const skill of customSkills) {
|
|
39216
|
+
for (const agent of agents) {
|
|
39217
|
+
const result = installSkillForAgent(skill.name, {
|
|
39218
|
+
agent,
|
|
39219
|
+
scope: options.scope
|
|
39220
|
+
}, generateSkillMd);
|
|
39221
|
+
results.push({ skill: skill.name, agent, success: result.success, error: result.error });
|
|
39222
|
+
}
|
|
39223
|
+
}
|
|
39224
|
+
if (options.json) {
|
|
39225
|
+
console.log(JSON.stringify({ pushed: results.filter((r) => r.success).length, results }));
|
|
39226
|
+
} else {
|
|
39227
|
+
for (const r of results) {
|
|
39228
|
+
if (r.success) {
|
|
39229
|
+
console.log(chalk2.green(`\u2713 ${r.skill} \u2192 ${r.agent}`));
|
|
39230
|
+
} else {
|
|
39231
|
+
console.log(chalk2.red(`\u2717 ${r.skill} \u2192 ${r.agent}: ${r.error}`));
|
|
39232
|
+
}
|
|
39233
|
+
}
|
|
39234
|
+
}
|
|
39235
|
+
}
|
|
39236
|
+
});
|
|
39237
|
+
program2.command("validate").argument("<name>", "Skill name to validate").option("--json", "Output as JSON", false).description("Validate a skill's directory structure (SKILL.md, package.json, src/index.ts, tsconfig.json)").action((name, options) => {
|
|
39238
|
+
const skillPath = getSkillPath(name);
|
|
39239
|
+
const issues = [];
|
|
39240
|
+
if (!existsSync8(skillPath)) {
|
|
39241
|
+
if (options.json) {
|
|
39242
|
+
console.log(JSON.stringify({ name, valid: false, issues: [`Skill directory not found: ${skillPath}`] }));
|
|
39243
|
+
} else {
|
|
39244
|
+
console.error(chalk2.red(`Skill '${name}' not found at ${skillPath}`));
|
|
39245
|
+
}
|
|
39246
|
+
process.exitCode = 1;
|
|
39247
|
+
return;
|
|
39248
|
+
}
|
|
39249
|
+
if (!existsSync8(join8(skillPath, "SKILL.md")))
|
|
39250
|
+
issues.push("Missing SKILL.md");
|
|
39251
|
+
if (!existsSync8(join8(skillPath, "tsconfig.json")))
|
|
39252
|
+
issues.push("Missing tsconfig.json");
|
|
39253
|
+
const pkgPath = join8(skillPath, "package.json");
|
|
39254
|
+
if (!existsSync8(pkgPath)) {
|
|
39255
|
+
issues.push("Missing package.json");
|
|
39256
|
+
} else {
|
|
39257
|
+
try {
|
|
39258
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
39259
|
+
if (!pkg.bin || Object.keys(pkg.bin).length === 0)
|
|
39260
|
+
issues.push("package.json missing 'bin' entry");
|
|
39261
|
+
} catch {
|
|
39262
|
+
issues.push("package.json is invalid JSON");
|
|
39263
|
+
}
|
|
39264
|
+
}
|
|
39265
|
+
if (!existsSync8(join8(skillPath, "src"))) {
|
|
39266
|
+
issues.push("Missing src/ directory");
|
|
39267
|
+
} else if (!existsSync8(join8(skillPath, "src", "index.ts"))) {
|
|
39268
|
+
issues.push("Missing src/index.ts");
|
|
39269
|
+
}
|
|
39270
|
+
const valid = issues.length === 0;
|
|
39271
|
+
if (options.json) {
|
|
39272
|
+
console.log(JSON.stringify({ name, valid, path: skillPath, issues }));
|
|
39273
|
+
} else if (valid) {
|
|
39274
|
+
console.log(chalk2.green(`\u2713 ${name} \u2014 all checks passed`));
|
|
39275
|
+
} else {
|
|
39276
|
+
console.log(chalk2.red(`\u2717 ${name} \u2014 ${issues.length} issue(s):`));
|
|
39277
|
+
for (const issue2 of issues)
|
|
39278
|
+
console.log(chalk2.red(` \u2022 ${issue2}`));
|
|
39279
|
+
process.exitCode = 1;
|
|
39280
|
+
}
|
|
39281
|
+
});
|
|
39282
|
+
program2.command("diff").argument("<name>", "Skill name to diff").option("--json", "Output as JSON", false).description("Show files that differ between installed version and source (preview before update)").action((name, options) => {
|
|
39283
|
+
const bare = name.replace(/^skill-/, "");
|
|
39284
|
+
const skillName = `skill-${bare}`;
|
|
39285
|
+
const destPath = join8(process.cwd(), ".skills", skillName);
|
|
39286
|
+
const sourcePath = getSkillPath(bare);
|
|
39287
|
+
if (!existsSync8(sourcePath)) {
|
|
39288
|
+
if (options.json) {
|
|
39289
|
+
console.log(JSON.stringify({ error: `Skill '${bare}' not found in registry` }));
|
|
39290
|
+
} else {
|
|
39291
|
+
skillNotFound(bare);
|
|
39292
|
+
}
|
|
39293
|
+
process.exitCode = 1;
|
|
39294
|
+
return;
|
|
39295
|
+
}
|
|
39296
|
+
if (!existsSync8(destPath)) {
|
|
39297
|
+
if (options.json) {
|
|
39298
|
+
console.log(JSON.stringify({ installed: false, message: `'${bare}' is not installed locally` }));
|
|
39299
|
+
} else {
|
|
39300
|
+
console.log(chalk2.dim(`'${bare}' is not installed. Run: skills install ${bare}`));
|
|
39301
|
+
}
|
|
39302
|
+
return;
|
|
39303
|
+
}
|
|
39304
|
+
function collectFiles(dir, base = "") {
|
|
39305
|
+
const files = new Map;
|
|
39306
|
+
if (!existsSync8(dir))
|
|
39307
|
+
return files;
|
|
39308
|
+
for (const entry of readdirSync5(dir)) {
|
|
39309
|
+
const full = join8(dir, entry);
|
|
39310
|
+
const rel = base ? `${base}/${entry}` : entry;
|
|
39311
|
+
if (statSync3(full).isDirectory()) {
|
|
39312
|
+
for (const [k, v] of collectFiles(full, rel))
|
|
39313
|
+
files.set(k, v);
|
|
39314
|
+
} else {
|
|
39315
|
+
try {
|
|
39316
|
+
files.set(rel, readFileSync7(full, "utf-8"));
|
|
39317
|
+
} catch {
|
|
39318
|
+
files.set(rel, "");
|
|
39319
|
+
}
|
|
39320
|
+
}
|
|
39321
|
+
}
|
|
39322
|
+
return files;
|
|
39323
|
+
}
|
|
39324
|
+
const installed = collectFiles(destPath);
|
|
39325
|
+
const source = collectFiles(sourcePath);
|
|
39326
|
+
const changed = [];
|
|
39327
|
+
const added = [];
|
|
39328
|
+
const removed = [];
|
|
39329
|
+
for (const [file2, content] of source) {
|
|
39330
|
+
if (!installed.has(file2))
|
|
39331
|
+
added.push(file2);
|
|
39332
|
+
else if (installed.get(file2) !== content)
|
|
39333
|
+
changed.push(file2);
|
|
39334
|
+
}
|
|
39335
|
+
for (const file2 of installed.keys()) {
|
|
39336
|
+
if (!source.has(file2))
|
|
39337
|
+
removed.push(file2);
|
|
39338
|
+
}
|
|
39339
|
+
if (options.json) {
|
|
39340
|
+
console.log(JSON.stringify({ name: bare, changed, added, removed, upToDate: changed.length === 0 && added.length === 0 && removed.length === 0 }));
|
|
39341
|
+
return;
|
|
39342
|
+
}
|
|
39343
|
+
if (changed.length === 0 && added.length === 0 && removed.length === 0) {
|
|
39344
|
+
console.log(chalk2.green(`\u2713 ${bare} \u2014 up to date`));
|
|
39345
|
+
return;
|
|
39346
|
+
}
|
|
39347
|
+
console.log(chalk2.bold(`
|
|
39348
|
+
Diff for '${bare}':
|
|
39349
|
+
`));
|
|
39350
|
+
for (const f of changed)
|
|
39351
|
+
console.log(chalk2.yellow(` ~ ${f}`));
|
|
39352
|
+
for (const f of added)
|
|
39353
|
+
console.log(chalk2.green(` + ${f}`));
|
|
39354
|
+
for (const f of removed)
|
|
39355
|
+
console.log(chalk2.red(` - ${f}`));
|
|
39356
|
+
console.log(`
|
|
39357
|
+
${chalk2.dim(`Run 'skills update ${bare}' to apply changes`)}`);
|
|
39358
|
+
});
|
|
39359
|
+
var scheduleCmd = program2.command("schedule").description("Manage scheduled skill runs (cron-based)");
|
|
39360
|
+
scheduleCmd.command("add").argument("<skill>", "Skill to schedule (bare name, e.g. image)").argument("<cron>", '5-field cron expression (e.g. "0 9 * * *" = daily at 9am)').option("--name <label>", "Human-readable label for this schedule").option("--args <args>", "Space-separated args to pass to the skill").option("--json", "Output as JSON", false).description("Add a cron schedule for a skill").action((skill, cron, options) => {
|
|
39361
|
+
const args = options.args ? options.args.split(" ").filter(Boolean) : undefined;
|
|
39362
|
+
const { schedule, error: error48 } = addSchedule(skill, cron, { name: options.name, args });
|
|
39363
|
+
if (options.json) {
|
|
39364
|
+
console.log(JSON.stringify(schedule ? { schedule } : { error: error48 }));
|
|
39365
|
+
return;
|
|
39366
|
+
}
|
|
39367
|
+
if (error48 || !schedule) {
|
|
39368
|
+
console.error(chalk2.red(`\u2717 ${error48 || "Failed to add schedule"}`));
|
|
39369
|
+
process.exitCode = 1;
|
|
39370
|
+
return;
|
|
39371
|
+
}
|
|
39372
|
+
console.log(chalk2.green(`\u2713 Scheduled '${schedule.name}'`));
|
|
39373
|
+
console.log(chalk2.dim(` Cron: ${schedule.cron}`));
|
|
39374
|
+
if (schedule.nextRun) {
|
|
39375
|
+
console.log(chalk2.dim(` Next run: ${new Date(schedule.nextRun).toLocaleString()}`));
|
|
39376
|
+
}
|
|
39377
|
+
console.log(chalk2.dim(` ID: ${schedule.id}`));
|
|
39378
|
+
});
|
|
39379
|
+
scheduleCmd.command("list").option("--json", "Output as JSON", false).description("List all scheduled skills").action((options) => {
|
|
39380
|
+
const schedules = listSchedules();
|
|
39381
|
+
if (options.json) {
|
|
39382
|
+
console.log(JSON.stringify(schedules));
|
|
39383
|
+
return;
|
|
39384
|
+
}
|
|
39385
|
+
if (schedules.length === 0) {
|
|
39386
|
+
console.log(chalk2.dim("No schedules. Run: skills schedule add <skill> <cron>"));
|
|
39387
|
+
return;
|
|
39388
|
+
}
|
|
39389
|
+
console.log(chalk2.bold(`
|
|
39390
|
+
Scheduled skills (${schedules.length}):
|
|
39391
|
+
`));
|
|
39392
|
+
for (const s of schedules) {
|
|
39393
|
+
const status = s.enabled ? chalk2.green("enabled") : chalk2.dim("disabled");
|
|
39394
|
+
const last = s.lastRun ? `last: ${new Date(s.lastRun).toLocaleString()} [${s.lastRunStatus ?? "?"}]` : "never run";
|
|
39395
|
+
const next = s.nextRun ? `next: ${new Date(s.nextRun).toLocaleString()}` : "";
|
|
39396
|
+
console.log(` ${chalk2.cyan(s.name)} [${status}]`);
|
|
39397
|
+
console.log(chalk2.dim(` skill: ${s.skill} cron: ${s.cron} ${last} ${next}`));
|
|
39398
|
+
}
|
|
39399
|
+
});
|
|
39400
|
+
scheduleCmd.command("remove").argument("<id-or-name>", "Schedule ID or name to remove").option("--json", "Output as JSON", false).description("Remove a schedule").action((idOrName, options) => {
|
|
39401
|
+
const removed = removeSchedule(idOrName);
|
|
39402
|
+
if (options.json) {
|
|
39403
|
+
console.log(JSON.stringify({ removed, idOrName }));
|
|
39404
|
+
return;
|
|
39405
|
+
}
|
|
39406
|
+
if (removed) {
|
|
39407
|
+
console.log(chalk2.green(`\u2713 Removed schedule '${idOrName}'`));
|
|
39408
|
+
} else {
|
|
39409
|
+
console.error(chalk2.red(`Schedule '${idOrName}' not found`));
|
|
39410
|
+
process.exitCode = 1;
|
|
39411
|
+
}
|
|
39412
|
+
});
|
|
39413
|
+
scheduleCmd.command("enable").argument("<id-or-name>", "Schedule ID or name").description("Enable a disabled schedule").action((idOrName) => {
|
|
39414
|
+
const ok = setScheduleEnabled(idOrName, true);
|
|
39415
|
+
if (ok)
|
|
39416
|
+
console.log(chalk2.green(`\u2713 Enabled '${idOrName}'`));
|
|
39417
|
+
else {
|
|
39418
|
+
console.error(chalk2.red(`Schedule '${idOrName}' not found`));
|
|
39419
|
+
process.exitCode = 1;
|
|
39420
|
+
}
|
|
39421
|
+
});
|
|
39422
|
+
scheduleCmd.command("disable").argument("<id-or-name>", "Schedule ID or name").description("Disable a schedule without removing it").action((idOrName) => {
|
|
39423
|
+
const ok = setScheduleEnabled(idOrName, false);
|
|
39424
|
+
if (ok)
|
|
39425
|
+
console.log(chalk2.green(`\u2713 Disabled '${idOrName}'`));
|
|
39426
|
+
else {
|
|
39427
|
+
console.error(chalk2.red(`Schedule '${idOrName}' not found`));
|
|
39428
|
+
process.exitCode = 1;
|
|
39429
|
+
}
|
|
39430
|
+
});
|
|
39431
|
+
scheduleCmd.command("run").option("--dry-run", "Show which schedules are due without running them", false).option("--json", "Output as JSON", false).description("Execute all due schedules now").action(async (options) => {
|
|
39432
|
+
const due = getDueSchedules();
|
|
39433
|
+
if (due.length === 0) {
|
|
39434
|
+
if (options.json)
|
|
39435
|
+
console.log(JSON.stringify({ ran: 0, schedules: [] }));
|
|
39436
|
+
else
|
|
39437
|
+
console.log(chalk2.dim("No schedules are due."));
|
|
39438
|
+
return;
|
|
39439
|
+
}
|
|
39440
|
+
if (options.dryRun) {
|
|
39441
|
+
if (options.json)
|
|
39442
|
+
console.log(JSON.stringify({ due: due.map((s) => s.name) }));
|
|
39443
|
+
else {
|
|
39444
|
+
console.log(chalk2.bold(`${due.length} schedule(s) due:
|
|
39445
|
+
`));
|
|
39446
|
+
for (const s of due)
|
|
39447
|
+
console.log(` ${chalk2.cyan(s.name)} \u2014 ${s.skill} (${s.cron})`);
|
|
39448
|
+
}
|
|
39449
|
+
return;
|
|
39450
|
+
}
|
|
39451
|
+
const results = [];
|
|
39452
|
+
for (const s of due) {
|
|
39453
|
+
try {
|
|
39454
|
+
const { runSkill: runSkill2 } = await Promise.resolve().then(() => (init_skillinfo(), exports_skillinfo));
|
|
39455
|
+
await runSkill2(s.skill, s.args ?? []);
|
|
39456
|
+
recordScheduleRun(s.id, "success");
|
|
39457
|
+
results.push({ name: s.name, skill: s.skill, status: "success" });
|
|
39458
|
+
} catch (err) {
|
|
39459
|
+
recordScheduleRun(s.id, "error");
|
|
39460
|
+
results.push({ name: s.name, skill: s.skill, status: "error", error: err.message });
|
|
39461
|
+
}
|
|
39462
|
+
}
|
|
39463
|
+
if (options.json) {
|
|
39464
|
+
console.log(JSON.stringify({ ran: results.length, results }));
|
|
39465
|
+
} else {
|
|
39466
|
+
for (const r of results) {
|
|
39467
|
+
const icon = r.status === "success" ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
39468
|
+
console.log(`${icon} ${r.name} (${r.skill})`);
|
|
39469
|
+
if (r.error)
|
|
39470
|
+
console.log(chalk2.dim(` ${r.error}`));
|
|
39471
|
+
}
|
|
39472
|
+
}
|
|
39473
|
+
});
|
|
39474
|
+
scheduleCmd.command("validate").argument("<cron>", "Cron expression to validate").description("Validate a cron expression and show the next 5 run times").action((cron) => {
|
|
39475
|
+
const { getNextRun: getNextRun2 } = (init_scheduler(), __toCommonJS(exports_scheduler));
|
|
39476
|
+
const { valid, error: error48 } = validateCron(cron);
|
|
39477
|
+
if (!valid) {
|
|
39478
|
+
console.error(chalk2.red(`Invalid cron: ${error48}`));
|
|
39479
|
+
process.exitCode = 1;
|
|
39480
|
+
return;
|
|
39481
|
+
}
|
|
39482
|
+
console.log(chalk2.green(`\u2713 Valid cron: "${cron}"`));
|
|
39483
|
+
console.log(chalk2.dim(`
|
|
39484
|
+
Next 5 run times:`));
|
|
39485
|
+
let d = new Date;
|
|
39486
|
+
for (let i = 0;i < 5; i++) {
|
|
39487
|
+
const next = getNextRun2(cron, d);
|
|
39488
|
+
if (!next)
|
|
39489
|
+
break;
|
|
39490
|
+
console.log(` ${next.toLocaleString()}`);
|
|
39491
|
+
d = next;
|
|
39492
|
+
}
|
|
38518
39493
|
});
|
|
38519
39494
|
program2.parse();
|