@hasna/skills 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +1212 -220
- package/bin/mcp.js +516 -101
- package/dist/index.d.ts +2 -1
- package/dist/index.js +391 -105
- package/dist/lib/config.d.ts +8 -1
- package/dist/lib/installer.d.ts +11 -2
- package/dist/lib/registry.d.ts +13 -0
- package/dist/lib/scheduler.d.ts +47 -0
- package/package.json +2 -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.16",
|
|
1900
1918
|
description: "Skills library for AI coding agents",
|
|
1901
1919
|
type: "module",
|
|
1902
1920
|
bin: {
|
|
@@ -1956,6 +1974,7 @@ var init_package = __esm(() => {
|
|
|
1956
1974
|
typescript: "^5"
|
|
1957
1975
|
},
|
|
1958
1976
|
dependencies: {
|
|
1977
|
+
"@hasna/cloud": "^0.1.0",
|
|
1959
1978
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
1960
1979
|
chalk: "^5.3.0",
|
|
1961
1980
|
commander: "^12.1.0",
|
|
@@ -1981,8 +2000,94 @@ var init_package = __esm(() => {
|
|
|
1981
2000
|
});
|
|
1982
2001
|
|
|
1983
2002
|
// src/lib/registry.ts
|
|
2003
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
2004
|
+
import { join } from "path";
|
|
2005
|
+
import { homedir } from "os";
|
|
2006
|
+
function parseSkillMdFrontmatter(content) {
|
|
2007
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2008
|
+
if (!match)
|
|
2009
|
+
return null;
|
|
2010
|
+
const result = {};
|
|
2011
|
+
for (const line of match[1].split(`
|
|
2012
|
+
`)) {
|
|
2013
|
+
const colon = line.indexOf(":");
|
|
2014
|
+
if (colon === -1)
|
|
2015
|
+
continue;
|
|
2016
|
+
const key = line.slice(0, colon).trim();
|
|
2017
|
+
const value = line.slice(colon + 1).trim();
|
|
2018
|
+
if (!key || !value)
|
|
2019
|
+
continue;
|
|
2020
|
+
if (key === "name")
|
|
2021
|
+
result.name = value;
|
|
2022
|
+
else if (key === "description")
|
|
2023
|
+
result.description = value;
|
|
2024
|
+
else if (key === "displayName" || key === "display_name")
|
|
2025
|
+
result.displayName = value;
|
|
2026
|
+
else if (key === "category")
|
|
2027
|
+
result.category = value;
|
|
2028
|
+
else if (key === "tags") {
|
|
2029
|
+
result.tags = value.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
2033
|
+
}
|
|
2034
|
+
function discoverSkillsInDir(dir) {
|
|
2035
|
+
if (!existsSync(dir))
|
|
2036
|
+
return [];
|
|
2037
|
+
const result = [];
|
|
2038
|
+
try {
|
|
2039
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
2040
|
+
for (const entry of entries) {
|
|
2041
|
+
if (!entry.isDirectory())
|
|
2042
|
+
continue;
|
|
2043
|
+
const skillMdPath = join(dir, entry.name, "SKILL.md");
|
|
2044
|
+
if (!existsSync(skillMdPath))
|
|
2045
|
+
continue;
|
|
2046
|
+
let content;
|
|
2047
|
+
try {
|
|
2048
|
+
content = readFileSync(skillMdPath, "utf-8");
|
|
2049
|
+
} catch {
|
|
2050
|
+
continue;
|
|
2051
|
+
}
|
|
2052
|
+
const fm = parseSkillMdFrontmatter(content);
|
|
2053
|
+
if (!fm?.name)
|
|
2054
|
+
continue;
|
|
2055
|
+
const name = fm.name.replace(/^skill-/, "");
|
|
2056
|
+
result.push({
|
|
2057
|
+
name,
|
|
2058
|
+
displayName: fm.displayName || name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
2059
|
+
description: fm.description || "",
|
|
2060
|
+
category: fm.category || "Development Tools",
|
|
2061
|
+
tags: fm.tags || [],
|
|
2062
|
+
source: "custom"
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
} catch {}
|
|
2066
|
+
return result;
|
|
2067
|
+
}
|
|
2068
|
+
function loadRegistry(cwd) {
|
|
2069
|
+
const now = Date.now();
|
|
2070
|
+
if (_registryCache && now - _registryCacheTime < REGISTRY_CACHE_TTL) {
|
|
2071
|
+
return _registryCache;
|
|
2072
|
+
}
|
|
2073
|
+
const official = SKILLS.map((s) => ({ ...s, source: "official" }));
|
|
2074
|
+
const globalCustomNew = discoverSkillsInDir(join(homedir(), ".hasna", "skills", "custom"));
|
|
2075
|
+
const globalCustomOld = discoverSkillsInDir(join(homedir(), ".skills"));
|
|
2076
|
+
const oldNames = new Set(globalCustomNew.map((s) => s.name));
|
|
2077
|
+
const globalCustom = [...globalCustomNew, ...globalCustomOld.filter((s) => !oldNames.has(s.name))];
|
|
2078
|
+
const projectCustom = discoverSkillsInDir(join(cwd || process.cwd(), ".skills", "custom-skills"));
|
|
2079
|
+
const customNames = new Set([...globalCustom, ...projectCustom].map((s) => s.name));
|
|
2080
|
+
const filtered = official.filter((s) => !customNames.has(s.name));
|
|
2081
|
+
_registryCache = [...filtered, ...globalCustom, ...projectCustom];
|
|
2082
|
+
_registryCacheTime = now;
|
|
2083
|
+
return _registryCache;
|
|
2084
|
+
}
|
|
2085
|
+
function clearRegistryCache() {
|
|
2086
|
+
_registryCache = null;
|
|
2087
|
+
_registryCacheTime = 0;
|
|
2088
|
+
}
|
|
1984
2089
|
function getSkillsByCategory(category) {
|
|
1985
|
-
return
|
|
2090
|
+
return loadRegistry().filter((s) => s.category === category);
|
|
1986
2091
|
}
|
|
1987
2092
|
function editDistance(a, b) {
|
|
1988
2093
|
if (a === b)
|
|
@@ -2028,7 +2133,7 @@ function searchSkills(query) {
|
|
|
2028
2133
|
if (words.length === 0)
|
|
2029
2134
|
return [];
|
|
2030
2135
|
const scored = [];
|
|
2031
|
-
for (const skill of
|
|
2136
|
+
for (const skill of loadRegistry()) {
|
|
2032
2137
|
const nameLower = skill.name.toLowerCase();
|
|
2033
2138
|
const displayNameLower = skill.displayName.toLowerCase();
|
|
2034
2139
|
const descriptionLower = skill.description.toLowerCase();
|
|
@@ -2064,7 +2169,7 @@ function searchSkills(query) {
|
|
|
2064
2169
|
return scored.map((s) => s.skill);
|
|
2065
2170
|
}
|
|
2066
2171
|
function getSkill(name) {
|
|
2067
|
-
return
|
|
2172
|
+
return loadRegistry().find((s) => s.name === name);
|
|
2068
2173
|
}
|
|
2069
2174
|
function levenshtein(a, b) {
|
|
2070
2175
|
const m = a.length, n = b.length;
|
|
@@ -2078,10 +2183,10 @@ function levenshtein(a, b) {
|
|
|
2078
2183
|
}
|
|
2079
2184
|
function findSimilarSkills(query, maxResults = 3) {
|
|
2080
2185
|
const q = query.toLowerCase();
|
|
2081
|
-
const scored =
|
|
2186
|
+
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
2187
|
return scored.slice(0, maxResults).map((s) => s.name);
|
|
2083
2188
|
}
|
|
2084
|
-
var CATEGORIES, SKILLS;
|
|
2189
|
+
var CATEGORIES, SKILLS, _registryCache = null, _registryCacheTime = 0, REGISTRY_CACHE_TTL = 5000;
|
|
2085
2190
|
var init_registry = __esm(() => {
|
|
2086
2191
|
CATEGORIES = [
|
|
2087
2192
|
"Development Tools",
|
|
@@ -3119,6 +3224,20 @@ var init_registry = __esm(() => {
|
|
|
3119
3224
|
category: "Design & Branding",
|
|
3120
3225
|
tags: ["testimonials", "graphics", "social-proof", "marketing"]
|
|
3121
3226
|
},
|
|
3227
|
+
{
|
|
3228
|
+
name: "colorextract",
|
|
3229
|
+
displayName: "Color Extract",
|
|
3230
|
+
description: "Extract complete color palettes from screenshots and images using Claude Vision. Outputs open-styles compatible profiles.",
|
|
3231
|
+
category: "Design & Branding",
|
|
3232
|
+
tags: ["colors", "palette", "design", "vision", "screenshot", "extract", "open-styles"]
|
|
3233
|
+
},
|
|
3234
|
+
{
|
|
3235
|
+
name: "siteanalyze",
|
|
3236
|
+
displayName: "Site Analyze",
|
|
3237
|
+
description: "Analyze any website's design system \u2014 detects shadcn/ui, Tailwind, extracts colors, typography, and components via Playwright + Claude Vision.",
|
|
3238
|
+
category: "Design & Branding",
|
|
3239
|
+
tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "open-styles"]
|
|
3240
|
+
},
|
|
3122
3241
|
{
|
|
3123
3242
|
name: "browse",
|
|
3124
3243
|
displayName: "Browse",
|
|
@@ -5162,39 +5281,39 @@ var require_cli_spinners = __commonJS((exports, module) => {
|
|
|
5162
5281
|
});
|
|
5163
5282
|
|
|
5164
5283
|
// 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";
|
|
5284
|
+
import { existsSync as existsSync2, cpSync, mkdirSync, writeFileSync, rmSync, readdirSync as readdirSync2, statSync, readFileSync as readFileSync2, accessSync, constants } from "fs";
|
|
5285
|
+
import { join as join2, dirname } from "path";
|
|
5286
|
+
import { homedir as homedir2 } from "os";
|
|
5168
5287
|
import { fileURLToPath } from "url";
|
|
5169
5288
|
function findSkillsDir() {
|
|
5170
5289
|
let dir = __dirname2;
|
|
5171
5290
|
for (let i = 0;i < 5; i++) {
|
|
5172
|
-
const candidate =
|
|
5173
|
-
if (
|
|
5291
|
+
const candidate = join2(dir, "skills");
|
|
5292
|
+
if (existsSync2(candidate) && !dir.includes(".skills")) {
|
|
5174
5293
|
return candidate;
|
|
5175
5294
|
}
|
|
5176
5295
|
dir = dirname(dir);
|
|
5177
5296
|
}
|
|
5178
|
-
return
|
|
5297
|
+
return join2(__dirname2, "..", "skills");
|
|
5179
5298
|
}
|
|
5180
5299
|
function getSkillPath(name) {
|
|
5181
5300
|
const skillName = normalizeSkillName(name);
|
|
5182
|
-
return
|
|
5301
|
+
return join2(SKILLS_DIR, skillName);
|
|
5183
5302
|
}
|
|
5184
5303
|
function installSkill(name, options = {}) {
|
|
5185
5304
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
5186
5305
|
const skillName = normalizeSkillName(name);
|
|
5187
5306
|
const sourcePath = getSkillPath(name);
|
|
5188
|
-
const destDir =
|
|
5189
|
-
const destPath =
|
|
5190
|
-
if (!
|
|
5307
|
+
const destDir = join2(targetDir, ".skills");
|
|
5308
|
+
const destPath = join2(destDir, skillName);
|
|
5309
|
+
if (!existsSync2(sourcePath)) {
|
|
5191
5310
|
return {
|
|
5192
5311
|
skill: name,
|
|
5193
5312
|
success: false,
|
|
5194
5313
|
error: `Skill '${name}' not found`
|
|
5195
5314
|
};
|
|
5196
5315
|
}
|
|
5197
|
-
if (
|
|
5316
|
+
if (existsSync2(destPath) && !overwrite) {
|
|
5198
5317
|
return {
|
|
5199
5318
|
skill: name,
|
|
5200
5319
|
success: false,
|
|
@@ -5203,10 +5322,10 @@ function installSkill(name, options = {}) {
|
|
|
5203
5322
|
};
|
|
5204
5323
|
}
|
|
5205
5324
|
try {
|
|
5206
|
-
if (!
|
|
5325
|
+
if (!existsSync2(destDir)) {
|
|
5207
5326
|
mkdirSync(destDir, { recursive: true });
|
|
5208
5327
|
}
|
|
5209
|
-
if (
|
|
5328
|
+
if (existsSync2(destPath) && overwrite) {
|
|
5210
5329
|
rmSync(destPath, { recursive: true, force: true });
|
|
5211
5330
|
}
|
|
5212
5331
|
cpSync(sourcePath, destPath, {
|
|
@@ -5242,10 +5361,10 @@ function installSkill(name, options = {}) {
|
|
|
5242
5361
|
}
|
|
5243
5362
|
}
|
|
5244
5363
|
function updateSkillsIndex(skillsDir) {
|
|
5245
|
-
const indexPath =
|
|
5364
|
+
const indexPath = join2(skillsDir, "index.ts");
|
|
5246
5365
|
const meta = loadMeta(skillsDir);
|
|
5247
5366
|
const disabledSet = new Set(meta.disabled || []);
|
|
5248
|
-
const skills =
|
|
5367
|
+
const skills = readdirSync2(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
|
|
5249
5368
|
const exports = skills.map((s) => {
|
|
5250
5369
|
const name = s.replace("skill-", "").replace(/-/g, "_");
|
|
5251
5370
|
return `export * as ${name} from './${s}/src/index.js';`;
|
|
@@ -5261,13 +5380,13 @@ ${exports}
|
|
|
5261
5380
|
writeFileSync(indexPath, content);
|
|
5262
5381
|
}
|
|
5263
5382
|
function getMetaPath(skillsDir) {
|
|
5264
|
-
return
|
|
5383
|
+
return join2(skillsDir, ".meta.json");
|
|
5265
5384
|
}
|
|
5266
5385
|
function loadMeta(skillsDir) {
|
|
5267
5386
|
const metaPath = getMetaPath(skillsDir);
|
|
5268
|
-
if (
|
|
5387
|
+
if (existsSync2(metaPath)) {
|
|
5269
5388
|
try {
|
|
5270
|
-
return JSON.parse(
|
|
5389
|
+
return JSON.parse(readFileSync2(metaPath, "utf-8"));
|
|
5271
5390
|
} catch {}
|
|
5272
5391
|
}
|
|
5273
5392
|
return { skills: {} };
|
|
@@ -5280,9 +5399,9 @@ function recordInstall(skillsDir, name) {
|
|
|
5280
5399
|
const skillName = normalizeSkillName(name);
|
|
5281
5400
|
let version = "unknown";
|
|
5282
5401
|
try {
|
|
5283
|
-
const pkgPath =
|
|
5284
|
-
if (
|
|
5285
|
-
const pkg = JSON.parse(
|
|
5402
|
+
const pkgPath = join2(skillsDir, skillName, "package.json");
|
|
5403
|
+
if (existsSync2(pkgPath)) {
|
|
5404
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
5286
5405
|
version = pkg.version || "unknown";
|
|
5287
5406
|
}
|
|
5288
5407
|
} catch {}
|
|
@@ -5295,23 +5414,23 @@ function recordRemove(skillsDir, name) {
|
|
|
5295
5414
|
saveMeta(skillsDir, meta);
|
|
5296
5415
|
}
|
|
5297
5416
|
function getInstallMeta(targetDir = process.cwd()) {
|
|
5298
|
-
return loadMeta(
|
|
5417
|
+
return loadMeta(join2(targetDir, ".skills"));
|
|
5299
5418
|
}
|
|
5300
5419
|
function getInstalledSkills(targetDir = process.cwd()) {
|
|
5301
|
-
const skillsDir =
|
|
5302
|
-
if (!
|
|
5420
|
+
const skillsDir = join2(targetDir, ".skills");
|
|
5421
|
+
if (!existsSync2(skillsDir)) {
|
|
5303
5422
|
return [];
|
|
5304
5423
|
}
|
|
5305
|
-
return
|
|
5306
|
-
const fullPath =
|
|
5424
|
+
return readdirSync2(skillsDir).filter((f) => {
|
|
5425
|
+
const fullPath = join2(skillsDir, f);
|
|
5307
5426
|
return f.startsWith("skill-") && statSync(fullPath).isDirectory();
|
|
5308
5427
|
}).map((f) => f.replace("skill-", ""));
|
|
5309
5428
|
}
|
|
5310
5429
|
function removeSkill(name, targetDir = process.cwd()) {
|
|
5311
5430
|
const skillName = normalizeSkillName(name);
|
|
5312
|
-
const skillsDir =
|
|
5313
|
-
const skillPath =
|
|
5314
|
-
if (!
|
|
5431
|
+
const skillsDir = join2(targetDir, ".skills");
|
|
5432
|
+
const skillPath = join2(skillsDir, skillName);
|
|
5433
|
+
if (!existsSync2(skillPath)) {
|
|
5315
5434
|
return false;
|
|
5316
5435
|
}
|
|
5317
5436
|
rmSync(skillPath, { recursive: true, force: true });
|
|
@@ -5329,27 +5448,31 @@ function resolveAgents(agentArg) {
|
|
|
5329
5448
|
return [agent];
|
|
5330
5449
|
}
|
|
5331
5450
|
function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
5332
|
-
const
|
|
5333
|
-
|
|
5334
|
-
|
|
5451
|
+
const base = projectDir || process.cwd();
|
|
5452
|
+
switch (agent) {
|
|
5453
|
+
case "pi":
|
|
5454
|
+
return scope === "project" ? join2(base, ".pi", "skills") : join2(homedir2(), ".pi", "agent", "skills");
|
|
5455
|
+
case "opencode":
|
|
5456
|
+
return scope === "project" ? join2(base, ".opencode", "skills") : join2(homedir2(), ".opencode", "skills");
|
|
5457
|
+
default:
|
|
5458
|
+
return scope === "project" ? join2(base, `.${agent}`, "skills") : join2(homedir2(), `.${agent}`, "skills");
|
|
5335
5459
|
}
|
|
5336
|
-
return join(homedir(), agentDir, "skills");
|
|
5337
5460
|
}
|
|
5338
5461
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
5339
5462
|
const skillName = normalizeSkillName(name);
|
|
5340
|
-
return
|
|
5463
|
+
return join2(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
5341
5464
|
}
|
|
5342
5465
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
5343
5466
|
const { agent, scope = "global", projectDir } = options;
|
|
5344
5467
|
const skillName = normalizeSkillName(name);
|
|
5345
5468
|
const sourcePath = getSkillPath(name);
|
|
5346
|
-
if (!
|
|
5469
|
+
if (!existsSync2(sourcePath)) {
|
|
5347
5470
|
return { skill: name, success: false, error: `Skill '${name}' not found` };
|
|
5348
5471
|
}
|
|
5349
5472
|
let skillMdContent = null;
|
|
5350
|
-
const skillMdPath =
|
|
5351
|
-
if (
|
|
5352
|
-
skillMdContent =
|
|
5473
|
+
const skillMdPath = join2(sourcePath, "SKILL.md");
|
|
5474
|
+
if (existsSync2(skillMdPath)) {
|
|
5475
|
+
skillMdContent = readFileSync2(skillMdPath, "utf-8");
|
|
5353
5476
|
} else if (generateSkillMd) {
|
|
5354
5477
|
skillMdContent = generateSkillMd(name);
|
|
5355
5478
|
}
|
|
@@ -5358,17 +5481,12 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
5358
5481
|
}
|
|
5359
5482
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
5360
5483
|
if (scope === "global") {
|
|
5361
|
-
const agentBaseDir =
|
|
5362
|
-
if (!
|
|
5363
|
-
const agentLabels = {
|
|
5364
|
-
claude: "Claude Code",
|
|
5365
|
-
codex: "Codex CLI",
|
|
5366
|
-
gemini: "Gemini CLI"
|
|
5367
|
-
};
|
|
5484
|
+
const agentBaseDir = agent === "pi" ? join2(homedir2(), ".pi", "agent") : join2(homedir2(), `.${agent}`);
|
|
5485
|
+
if (!existsSync2(agentBaseDir)) {
|
|
5368
5486
|
return {
|
|
5369
5487
|
skill: name,
|
|
5370
5488
|
success: false,
|
|
5371
|
-
error: `Agent directory ${agentBaseDir} does not exist. Is ${
|
|
5489
|
+
error: `Agent directory ${agentBaseDir} does not exist. Is ${AGENT_LABELS[agent]} installed?`
|
|
5372
5490
|
};
|
|
5373
5491
|
}
|
|
5374
5492
|
try {
|
|
@@ -5383,7 +5501,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
5383
5501
|
}
|
|
5384
5502
|
try {
|
|
5385
5503
|
mkdirSync(destDir, { recursive: true });
|
|
5386
|
-
writeFileSync(
|
|
5504
|
+
writeFileSync(join2(destDir, "SKILL.md"), skillMdContent);
|
|
5387
5505
|
return { skill: name, success: true, path: destDir };
|
|
5388
5506
|
} catch (error) {
|
|
5389
5507
|
return {
|
|
@@ -5396,31 +5514,48 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
5396
5514
|
function removeSkillForAgent(name, options) {
|
|
5397
5515
|
const { agent, scope = "global", projectDir } = options;
|
|
5398
5516
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
5399
|
-
if (!
|
|
5517
|
+
if (!existsSync2(destDir)) {
|
|
5400
5518
|
return false;
|
|
5401
5519
|
}
|
|
5402
5520
|
rmSync(destDir, { recursive: true, force: true });
|
|
5403
5521
|
return true;
|
|
5404
5522
|
}
|
|
5405
|
-
var __dirname2, SKILLS_DIR, AGENT_TARGETS;
|
|
5523
|
+
var __dirname2, SKILLS_DIR, AGENT_TARGETS, AGENT_LABELS;
|
|
5406
5524
|
var init_installer = __esm(() => {
|
|
5407
5525
|
init_registry();
|
|
5408
5526
|
__dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
5409
5527
|
SKILLS_DIR = findSkillsDir();
|
|
5410
|
-
AGENT_TARGETS = ["claude", "codex", "gemini"];
|
|
5528
|
+
AGENT_TARGETS = ["claude", "codex", "gemini", "pi", "opencode"];
|
|
5529
|
+
AGENT_LABELS = {
|
|
5530
|
+
claude: "Claude Code",
|
|
5531
|
+
codex: "Codex CLI",
|
|
5532
|
+
gemini: "Gemini CLI",
|
|
5533
|
+
pi: "pi.dev",
|
|
5534
|
+
opencode: "OpenCode"
|
|
5535
|
+
};
|
|
5411
5536
|
});
|
|
5412
5537
|
|
|
5413
5538
|
// src/lib/skillinfo.ts
|
|
5414
|
-
|
|
5415
|
-
|
|
5539
|
+
var exports_skillinfo = {};
|
|
5540
|
+
__export(exports_skillinfo, {
|
|
5541
|
+
runSkill: () => runSkill,
|
|
5542
|
+
getSkillRequirements: () => getSkillRequirements,
|
|
5543
|
+
getSkillDocs: () => getSkillDocs,
|
|
5544
|
+
getSkillBestDoc: () => getSkillBestDoc,
|
|
5545
|
+
generateSkillMd: () => generateSkillMd,
|
|
5546
|
+
generateEnvExample: () => generateEnvExample,
|
|
5547
|
+
detectProjectSkills: () => detectProjectSkills
|
|
5548
|
+
});
|
|
5549
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync3 } from "fs";
|
|
5550
|
+
import { join as join3 } from "path";
|
|
5416
5551
|
function getSkillDocs(name) {
|
|
5417
5552
|
const skillPath = getSkillPath(name);
|
|
5418
|
-
if (!
|
|
5553
|
+
if (!existsSync3(skillPath))
|
|
5419
5554
|
return null;
|
|
5420
5555
|
return {
|
|
5421
|
-
skillMd: readIfExists(
|
|
5422
|
-
readme: readIfExists(
|
|
5423
|
-
claudeMd: readIfExists(
|
|
5556
|
+
skillMd: readIfExists(join3(skillPath, "SKILL.md")),
|
|
5557
|
+
readme: readIfExists(join3(skillPath, "README.md")),
|
|
5558
|
+
claudeMd: readIfExists(join3(skillPath, "CLAUDE.md"))
|
|
5424
5559
|
};
|
|
5425
5560
|
}
|
|
5426
5561
|
function getSkillBestDoc(name) {
|
|
@@ -5431,11 +5566,11 @@ function getSkillBestDoc(name) {
|
|
|
5431
5566
|
}
|
|
5432
5567
|
function getSkillRequirements(name) {
|
|
5433
5568
|
const skillPath = getSkillPath(name);
|
|
5434
|
-
if (!
|
|
5569
|
+
if (!existsSync3(skillPath))
|
|
5435
5570
|
return null;
|
|
5436
5571
|
const texts = [];
|
|
5437
5572
|
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
|
|
5438
|
-
const content = readIfExists(
|
|
5573
|
+
const content = readIfExists(join3(skillPath, file));
|
|
5439
5574
|
if (content)
|
|
5440
5575
|
texts.push(content);
|
|
5441
5576
|
}
|
|
@@ -5462,10 +5597,10 @@ function getSkillRequirements(name) {
|
|
|
5462
5597
|
}
|
|
5463
5598
|
let cliCommand = null;
|
|
5464
5599
|
let dependencies = {};
|
|
5465
|
-
const pkgPath =
|
|
5466
|
-
if (
|
|
5600
|
+
const pkgPath = join3(skillPath, "package.json");
|
|
5601
|
+
if (existsSync3(pkgPath)) {
|
|
5467
5602
|
try {
|
|
5468
|
-
const pkg = JSON.parse(
|
|
5603
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
5469
5604
|
if (pkg.bin) {
|
|
5470
5605
|
const binKeys = Object.keys(pkg.bin);
|
|
5471
5606
|
if (binKeys.length > 0)
|
|
@@ -5485,25 +5620,25 @@ async function runSkill(name, args, options = {}) {
|
|
|
5485
5620
|
const skillName = normalizeSkillName(name);
|
|
5486
5621
|
let skillPath;
|
|
5487
5622
|
if (options.installed) {
|
|
5488
|
-
skillPath =
|
|
5623
|
+
skillPath = join3(process.cwd(), ".skills", skillName);
|
|
5489
5624
|
} else {
|
|
5490
|
-
const installedPath =
|
|
5491
|
-
if (
|
|
5625
|
+
const installedPath = join3(process.cwd(), ".skills", skillName);
|
|
5626
|
+
if (existsSync3(installedPath)) {
|
|
5492
5627
|
skillPath = installedPath;
|
|
5493
5628
|
} else {
|
|
5494
5629
|
skillPath = getSkillPath(name);
|
|
5495
5630
|
}
|
|
5496
5631
|
}
|
|
5497
|
-
if (!
|
|
5632
|
+
if (!existsSync3(skillPath)) {
|
|
5498
5633
|
return { exitCode: 1, error: `Skill '${name}' not found` };
|
|
5499
5634
|
}
|
|
5500
|
-
const pkgPath =
|
|
5501
|
-
if (!
|
|
5635
|
+
const pkgPath = join3(skillPath, "package.json");
|
|
5636
|
+
if (!existsSync3(pkgPath)) {
|
|
5502
5637
|
return { exitCode: 1, error: `No package.json in skill '${name}'` };
|
|
5503
5638
|
}
|
|
5504
5639
|
let entryPoint;
|
|
5505
5640
|
try {
|
|
5506
|
-
const pkg = JSON.parse(
|
|
5641
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
5507
5642
|
if (pkg.bin) {
|
|
5508
5643
|
const binValues = Object.values(pkg.bin);
|
|
5509
5644
|
entryPoint = binValues[0];
|
|
@@ -5517,12 +5652,12 @@ async function runSkill(name, args, options = {}) {
|
|
|
5517
5652
|
} catch {
|
|
5518
5653
|
return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
|
|
5519
5654
|
}
|
|
5520
|
-
const entryPath =
|
|
5521
|
-
if (!
|
|
5655
|
+
const entryPath = join3(skillPath, entryPoint);
|
|
5656
|
+
if (!existsSync3(entryPath)) {
|
|
5522
5657
|
return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
|
|
5523
5658
|
}
|
|
5524
|
-
const nodeModules =
|
|
5525
|
-
if (!
|
|
5659
|
+
const nodeModules = join3(skillPath, "node_modules");
|
|
5660
|
+
if (!existsSync3(nodeModules)) {
|
|
5526
5661
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
5527
5662
|
cwd: skillPath,
|
|
5528
5663
|
stdout: "pipe",
|
|
@@ -5540,18 +5675,18 @@ async function runSkill(name, args, options = {}) {
|
|
|
5540
5675
|
return { exitCode };
|
|
5541
5676
|
}
|
|
5542
5677
|
function detectProjectSkills(cwd = process.cwd()) {
|
|
5543
|
-
const pkgPath =
|
|
5544
|
-
if (!
|
|
5678
|
+
const pkgPath = join3(cwd, "package.json");
|
|
5679
|
+
if (!existsSync3(pkgPath)) {
|
|
5545
5680
|
const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
|
|
5546
|
-
const recommended2 = alwaysRecommend.map((name) =>
|
|
5681
|
+
const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5547
5682
|
return { detected: [], recommended: recommended2 };
|
|
5548
5683
|
}
|
|
5549
5684
|
let pkg;
|
|
5550
5685
|
try {
|
|
5551
|
-
pkg = JSON.parse(
|
|
5686
|
+
pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
5552
5687
|
} catch {
|
|
5553
5688
|
const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
|
|
5554
|
-
const recommended2 = alwaysRecommend.map((name) =>
|
|
5689
|
+
const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5555
5690
|
return { detected: [], recommended: recommended2 };
|
|
5556
5691
|
}
|
|
5557
5692
|
const allDeps = {
|
|
@@ -5623,15 +5758,66 @@ function detectProjectSkills(cwd = process.cwd()) {
|
|
|
5623
5758
|
}
|
|
5624
5759
|
}
|
|
5625
5760
|
const uniqueDetected = Array.from(new Set(detected));
|
|
5626
|
-
const recommended = Array.from(recommendedNames).map((name) =>
|
|
5761
|
+
const recommended = Array.from(recommendedNames).map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5627
5762
|
return { detected: uniqueDetected, recommended };
|
|
5628
5763
|
}
|
|
5764
|
+
function generateEnvExample(targetDir = process.cwd()) {
|
|
5765
|
+
const skillsDir = join3(targetDir, ".skills");
|
|
5766
|
+
if (!existsSync3(skillsDir))
|
|
5767
|
+
return "";
|
|
5768
|
+
const dirs = readdirSync3(skillsDir).filter((f) => f.startsWith("skill-") && existsSync3(join3(skillsDir, f, "package.json")));
|
|
5769
|
+
const envMap = new Map;
|
|
5770
|
+
for (const dir of dirs) {
|
|
5771
|
+
const skillName = dir.replace("skill-", "");
|
|
5772
|
+
const skillPath = join3(skillsDir, dir);
|
|
5773
|
+
const texts = [];
|
|
5774
|
+
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example"]) {
|
|
5775
|
+
const content = readIfExists(join3(skillPath, file));
|
|
5776
|
+
if (content)
|
|
5777
|
+
texts.push(content);
|
|
5778
|
+
}
|
|
5779
|
+
const allText = texts.join(`
|
|
5780
|
+
`);
|
|
5781
|
+
const foundVars = extractEnvVars(allText);
|
|
5782
|
+
for (const envVar of foundVars) {
|
|
5783
|
+
if (!envMap.has(envVar)) {
|
|
5784
|
+
envMap.set(envVar, []);
|
|
5785
|
+
}
|
|
5786
|
+
if (!envMap.get(envVar).includes(skillName)) {
|
|
5787
|
+
envMap.get(envVar).push(skillName);
|
|
5788
|
+
}
|
|
5789
|
+
}
|
|
5790
|
+
}
|
|
5791
|
+
if (envMap.size === 0)
|
|
5792
|
+
return "";
|
|
5793
|
+
const lines = [
|
|
5794
|
+
"# Environment variables for installed skills",
|
|
5795
|
+
"# Auto-generated by: skills init",
|
|
5796
|
+
""
|
|
5797
|
+
];
|
|
5798
|
+
const sorted = Array.from(envMap.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
5799
|
+
let lastPrefix = "";
|
|
5800
|
+
for (const [envVar, skills] of sorted) {
|
|
5801
|
+
const prefix = envVar.split("_")[0];
|
|
5802
|
+
if (prefix !== lastPrefix) {
|
|
5803
|
+
if (lastPrefix)
|
|
5804
|
+
lines.push("");
|
|
5805
|
+
lines.push(`# ${prefix}`);
|
|
5806
|
+
lastPrefix = prefix;
|
|
5807
|
+
}
|
|
5808
|
+
lines.push(`# Used by: ${skills.join(", ")}`);
|
|
5809
|
+
lines.push(`${envVar}=`);
|
|
5810
|
+
}
|
|
5811
|
+
return lines.join(`
|
|
5812
|
+
`) + `
|
|
5813
|
+
`;
|
|
5814
|
+
}
|
|
5629
5815
|
function generateSkillMd(name) {
|
|
5630
5816
|
const meta = getSkill(name);
|
|
5631
5817
|
if (!meta)
|
|
5632
5818
|
return null;
|
|
5633
5819
|
const skillPath = getSkillPath(name);
|
|
5634
|
-
if (!
|
|
5820
|
+
if (!existsSync3(skillPath))
|
|
5635
5821
|
return null;
|
|
5636
5822
|
const frontmatter = [
|
|
5637
5823
|
"---",
|
|
@@ -5640,13 +5826,13 @@ function generateSkillMd(name) {
|
|
|
5640
5826
|
"---"
|
|
5641
5827
|
].join(`
|
|
5642
5828
|
`);
|
|
5643
|
-
const readme = readIfExists(
|
|
5644
|
-
const claudeMd = readIfExists(
|
|
5829
|
+
const readme = readIfExists(join3(skillPath, "README.md"));
|
|
5830
|
+
const claudeMd = readIfExists(join3(skillPath, "CLAUDE.md"));
|
|
5645
5831
|
let cliCommand = null;
|
|
5646
|
-
const pkgPath =
|
|
5647
|
-
if (
|
|
5832
|
+
const pkgPath = join3(skillPath, "package.json");
|
|
5833
|
+
if (existsSync3(pkgPath)) {
|
|
5648
5834
|
try {
|
|
5649
|
-
const pkg = JSON.parse(
|
|
5835
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
5650
5836
|
if (pkg.bin) {
|
|
5651
5837
|
const binKeys = Object.keys(pkg.bin);
|
|
5652
5838
|
if (binKeys.length > 0)
|
|
@@ -5719,8 +5905,8 @@ function extractEnvVars(text) {
|
|
|
5719
5905
|
}
|
|
5720
5906
|
function readIfExists(path) {
|
|
5721
5907
|
try {
|
|
5722
|
-
if (
|
|
5723
|
-
return
|
|
5908
|
+
if (existsSync3(path)) {
|
|
5909
|
+
return readFileSync3(path, "utf-8");
|
|
5724
5910
|
}
|
|
5725
5911
|
} catch {}
|
|
5726
5912
|
return null;
|
|
@@ -5733,6 +5919,173 @@ var init_skillinfo = __esm(() => {
|
|
|
5733
5919
|
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
5920
|
});
|
|
5735
5921
|
|
|
5922
|
+
// src/lib/scheduler.ts
|
|
5923
|
+
var exports_scheduler = {};
|
|
5924
|
+
__export(exports_scheduler, {
|
|
5925
|
+
validateCron: () => validateCron,
|
|
5926
|
+
setScheduleEnabled: () => setScheduleEnabled,
|
|
5927
|
+
removeSchedule: () => removeSchedule,
|
|
5928
|
+
recordScheduleRun: () => recordScheduleRun,
|
|
5929
|
+
listSchedules: () => listSchedules,
|
|
5930
|
+
getNextRun: () => getNextRun,
|
|
5931
|
+
getDueSchedules: () => getDueSchedules,
|
|
5932
|
+
addSchedule: () => addSchedule
|
|
5933
|
+
});
|
|
5934
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
5935
|
+
import { join as join5 } from "path";
|
|
5936
|
+
function getSchedulesPath(targetDir = process.cwd()) {
|
|
5937
|
+
return join5(targetDir, ".skills", "schedules.json");
|
|
5938
|
+
}
|
|
5939
|
+
function loadSchedules(targetDir = process.cwd()) {
|
|
5940
|
+
const path = getSchedulesPath(targetDir);
|
|
5941
|
+
if (existsSync5(path)) {
|
|
5942
|
+
try {
|
|
5943
|
+
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
5944
|
+
} catch {}
|
|
5945
|
+
}
|
|
5946
|
+
return { version: 1, schedules: [] };
|
|
5947
|
+
}
|
|
5948
|
+
function saveSchedules(data, targetDir = process.cwd()) {
|
|
5949
|
+
const path = getSchedulesPath(targetDir);
|
|
5950
|
+
const dir = join5(targetDir, ".skills");
|
|
5951
|
+
if (!existsSync5(dir))
|
|
5952
|
+
mkdirSync3(dir, { recursive: true });
|
|
5953
|
+
writeFileSync3(path, JSON.stringify(data, null, 2));
|
|
5954
|
+
}
|
|
5955
|
+
function validateCron(expr) {
|
|
5956
|
+
const fields = expr.trim().split(/\s+/);
|
|
5957
|
+
if (fields.length !== 5) {
|
|
5958
|
+
return { valid: false, error: `Expected 5 fields, got ${fields.length}. Format: "minute hour day-of-month month day-of-week"` };
|
|
5959
|
+
}
|
|
5960
|
+
return { valid: true };
|
|
5961
|
+
}
|
|
5962
|
+
function getNextRun(cron, from = new Date) {
|
|
5963
|
+
const { valid } = validateCron(cron);
|
|
5964
|
+
if (!valid)
|
|
5965
|
+
return null;
|
|
5966
|
+
const [minuteF, hourF, domF, monthF, dowF] = cron.trim().split(/\s+/);
|
|
5967
|
+
function parseField(f, min, max) {
|
|
5968
|
+
if (f === "*")
|
|
5969
|
+
return Array.from({ length: max - min + 1 }, (_, i) => i + min);
|
|
5970
|
+
if (f.startsWith("*/")) {
|
|
5971
|
+
const step = parseInt(f.slice(2));
|
|
5972
|
+
if (isNaN(step))
|
|
5973
|
+
return [];
|
|
5974
|
+
const vals = [];
|
|
5975
|
+
for (let i = min;i <= max; i += step)
|
|
5976
|
+
vals.push(i);
|
|
5977
|
+
return vals;
|
|
5978
|
+
}
|
|
5979
|
+
return f.split(",").flatMap((part) => {
|
|
5980
|
+
if (part.includes("-")) {
|
|
5981
|
+
const [lo, hi] = part.split("-").map(Number);
|
|
5982
|
+
return Array.from({ length: hi - lo + 1 }, (_, i) => i + lo);
|
|
5983
|
+
}
|
|
5984
|
+
const n = parseInt(part);
|
|
5985
|
+
return isNaN(n) ? [] : [n];
|
|
5986
|
+
});
|
|
5987
|
+
}
|
|
5988
|
+
const minutes = parseField(minuteF, 0, 59);
|
|
5989
|
+
const hours = parseField(hourF, 0, 23);
|
|
5990
|
+
const doms = parseField(domF, 1, 31);
|
|
5991
|
+
const months = parseField(monthF, 1, 12);
|
|
5992
|
+
const dows = parseField(dowF, 0, 6);
|
|
5993
|
+
const candidate = new Date(from);
|
|
5994
|
+
candidate.setSeconds(0, 0);
|
|
5995
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
5996
|
+
const limit = new Date(from);
|
|
5997
|
+
limit.setFullYear(limit.getFullYear() + 1);
|
|
5998
|
+
while (candidate < limit) {
|
|
5999
|
+
const month = candidate.getMonth() + 1;
|
|
6000
|
+
const dom = candidate.getDate();
|
|
6001
|
+
const dow = candidate.getDay();
|
|
6002
|
+
const hour = candidate.getHours();
|
|
6003
|
+
const minute = candidate.getMinutes();
|
|
6004
|
+
if (!months.includes(month)) {
|
|
6005
|
+
candidate.setMonth(candidate.getMonth() + 1, 1);
|
|
6006
|
+
candidate.setHours(0, 0, 0, 0);
|
|
6007
|
+
continue;
|
|
6008
|
+
}
|
|
6009
|
+
if (!doms.includes(dom) || !dows.includes(dow)) {
|
|
6010
|
+
candidate.setDate(candidate.getDate() + 1);
|
|
6011
|
+
candidate.setHours(0, 0, 0, 0);
|
|
6012
|
+
continue;
|
|
6013
|
+
}
|
|
6014
|
+
if (!hours.includes(hour)) {
|
|
6015
|
+
candidate.setHours(candidate.getHours() + 1, 0, 0, 0);
|
|
6016
|
+
continue;
|
|
6017
|
+
}
|
|
6018
|
+
if (!minutes.includes(minute)) {
|
|
6019
|
+
candidate.setMinutes(candidate.getMinutes() + 1, 0, 0);
|
|
6020
|
+
continue;
|
|
6021
|
+
}
|
|
6022
|
+
return new Date(candidate);
|
|
6023
|
+
}
|
|
6024
|
+
return null;
|
|
6025
|
+
}
|
|
6026
|
+
function addSchedule(skill, cron, options = {}) {
|
|
6027
|
+
const { valid, error } = validateCron(cron);
|
|
6028
|
+
if (!valid)
|
|
6029
|
+
return { schedule: null, error };
|
|
6030
|
+
const data = loadSchedules(options.targetDir);
|
|
6031
|
+
const id = `${skill}-${Date.now()}`;
|
|
6032
|
+
const now = new Date;
|
|
6033
|
+
const nextRun = getNextRun(cron, now);
|
|
6034
|
+
const schedule = {
|
|
6035
|
+
id,
|
|
6036
|
+
name: options.name || `${skill} (${cron})`,
|
|
6037
|
+
skill,
|
|
6038
|
+
cron,
|
|
6039
|
+
args: options.args,
|
|
6040
|
+
enabled: true,
|
|
6041
|
+
createdAt: now.toISOString(),
|
|
6042
|
+
nextRun: nextRun?.toISOString()
|
|
6043
|
+
};
|
|
6044
|
+
data.schedules.push(schedule);
|
|
6045
|
+
saveSchedules(data, options.targetDir);
|
|
6046
|
+
return { schedule };
|
|
6047
|
+
}
|
|
6048
|
+
function listSchedules(targetDir) {
|
|
6049
|
+
return loadSchedules(targetDir).schedules;
|
|
6050
|
+
}
|
|
6051
|
+
function removeSchedule(idOrName, targetDir) {
|
|
6052
|
+
const data = loadSchedules(targetDir);
|
|
6053
|
+
const before = data.schedules.length;
|
|
6054
|
+
data.schedules = data.schedules.filter((s) => s.id !== idOrName && s.name !== idOrName);
|
|
6055
|
+
if (data.schedules.length === before)
|
|
6056
|
+
return false;
|
|
6057
|
+
saveSchedules(data, targetDir);
|
|
6058
|
+
return true;
|
|
6059
|
+
}
|
|
6060
|
+
function setScheduleEnabled(idOrName, enabled, targetDir) {
|
|
6061
|
+
const data = loadSchedules(targetDir);
|
|
6062
|
+
const schedule = data.schedules.find((s) => s.id === idOrName || s.name === idOrName);
|
|
6063
|
+
if (!schedule)
|
|
6064
|
+
return false;
|
|
6065
|
+
schedule.enabled = enabled;
|
|
6066
|
+
if (enabled) {
|
|
6067
|
+
schedule.nextRun = getNextRun(schedule.cron)?.toISOString();
|
|
6068
|
+
}
|
|
6069
|
+
saveSchedules(data, targetDir);
|
|
6070
|
+
return true;
|
|
6071
|
+
}
|
|
6072
|
+
function getDueSchedules(targetDir) {
|
|
6073
|
+
const now = new Date;
|
|
6074
|
+
return listSchedules(targetDir).filter((s) => s.enabled && s.nextRun && new Date(s.nextRun) <= now);
|
|
6075
|
+
}
|
|
6076
|
+
function recordScheduleRun(id, status, targetDir) {
|
|
6077
|
+
const data = loadSchedules(targetDir);
|
|
6078
|
+
const schedule = data.schedules.find((s) => s.id === id);
|
|
6079
|
+
if (!schedule)
|
|
6080
|
+
return;
|
|
6081
|
+
const now = new Date;
|
|
6082
|
+
schedule.lastRun = now.toISOString();
|
|
6083
|
+
schedule.lastRunStatus = status;
|
|
6084
|
+
schedule.nextRun = getNextRun(schedule.cron, now)?.toISOString();
|
|
6085
|
+
saveSchedules(data, targetDir);
|
|
6086
|
+
}
|
|
6087
|
+
var init_scheduler = () => {};
|
|
6088
|
+
|
|
5736
6089
|
// ../../../../hasnastudio/hasnastudio-alumia/platform/platformdev/platform-alumia/node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/util.js
|
|
5737
6090
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
5738
6091
|
const t = typeof data;
|
|
@@ -34823,9 +35176,8 @@ var init_stdio2 = __esm(() => {
|
|
|
34823
35176
|
|
|
34824
35177
|
// src/mcp/index.ts
|
|
34825
35178
|
var exports_mcp = {};
|
|
34826
|
-
import { existsSync as
|
|
34827
|
-
import { join as
|
|
34828
|
-
import { homedir as homedir3 } from "os";
|
|
35179
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
|
|
35180
|
+
import { join as join6 } from "path";
|
|
34829
35181
|
function stripNulls(obj) {
|
|
34830
35182
|
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== undefined && !(Array.isArray(v) && v.length === 0)));
|
|
34831
35183
|
}
|
|
@@ -34865,6 +35217,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34865
35217
|
init_registry();
|
|
34866
35218
|
init_installer();
|
|
34867
35219
|
init_skillinfo();
|
|
35220
|
+
init_scheduler();
|
|
34868
35221
|
server = new McpServer({
|
|
34869
35222
|
name: "skills",
|
|
34870
35223
|
version: package_default.version
|
|
@@ -34880,7 +35233,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34880
35233
|
offset: exports_external.number().optional()
|
|
34881
35234
|
}
|
|
34882
35235
|
}, async ({ category, detail, limit, offset }) => {
|
|
34883
|
-
const skills = category ? getSkillsByCategory(category) :
|
|
35236
|
+
const skills = category ? getSkillsByCategory(category) : loadRegistry();
|
|
34884
35237
|
const mapped = detail ? skills : skills.map((s) => ({ name: s.name, category: s.category }));
|
|
34885
35238
|
if (limit !== undefined || offset !== undefined) {
|
|
34886
35239
|
const start = offset || 0;
|
|
@@ -34965,7 +35318,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34965
35318
|
});
|
|
34966
35319
|
server.registerTool("install_skill", {
|
|
34967
35320
|
title: "Install Skill",
|
|
34968
|
-
description: "Install a skill to .skills/ or to an agent dir (for: claude|codex|gemini|all).",
|
|
35321
|
+
description: "Install a skill to .skills/ or to an agent dir (for: claude|codex|gemini|pi|opencode|all).",
|
|
34969
35322
|
inputSchema: {
|
|
34970
35323
|
name: exports_external.string(),
|
|
34971
35324
|
for: exports_external.string().optional(),
|
|
@@ -34977,7 +35330,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34977
35330
|
try {
|
|
34978
35331
|
agents = resolveAgents(agentArg);
|
|
34979
35332
|
} catch (err) {
|
|
34980
|
-
return mcpError("INVALID_AGENT", err.message, [
|
|
35333
|
+
return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
|
|
34981
35334
|
}
|
|
34982
35335
|
const results = agents.map((a) => installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd));
|
|
34983
35336
|
return {
|
|
@@ -35015,7 +35368,7 @@ var init_mcp2 = __esm(() => {
|
|
|
35015
35368
|
try {
|
|
35016
35369
|
agents = resolveAgents(agentArg);
|
|
35017
35370
|
} catch (err) {
|
|
35018
|
-
return mcpError("INVALID_AGENT", err.message, [
|
|
35371
|
+
return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
|
|
35019
35372
|
}
|
|
35020
35373
|
const results2 = [];
|
|
35021
35374
|
for (const name of names) {
|
|
@@ -35049,7 +35402,7 @@ var init_mcp2 = __esm(() => {
|
|
|
35049
35402
|
try {
|
|
35050
35403
|
agents = resolveAgents(agentArg);
|
|
35051
35404
|
} catch (err) {
|
|
35052
|
-
return mcpError("INVALID_AGENT", err.message, [
|
|
35405
|
+
return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
|
|
35053
35406
|
}
|
|
35054
35407
|
const results = agents.map((a) => ({
|
|
35055
35408
|
skill: name,
|
|
@@ -35082,7 +35435,7 @@ var init_mcp2 = __esm(() => {
|
|
|
35082
35435
|
description: "List all unique skill tags with occurrence counts."
|
|
35083
35436
|
}, async () => {
|
|
35084
35437
|
const tagCounts = new Map;
|
|
35085
|
-
for (const skill of
|
|
35438
|
+
for (const skill of loadRegistry()) {
|
|
35086
35439
|
for (const tag of skill.tags) {
|
|
35087
35440
|
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
35088
35441
|
}
|
|
@@ -35156,7 +35509,7 @@ var init_mcp2 = __esm(() => {
|
|
|
35156
35509
|
try {
|
|
35157
35510
|
agents = resolveAgents(agentArg);
|
|
35158
35511
|
} catch (err) {
|
|
35159
|
-
return mcpError("INVALID_AGENT", err.message, [
|
|
35512
|
+
return mcpError("INVALID_AGENT", err.message, [...AGENT_TARGETS, "all"]);
|
|
35160
35513
|
}
|
|
35161
35514
|
for (const name of skillList) {
|
|
35162
35515
|
const agentResults = agents.map((a) => installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd));
|
|
@@ -35184,21 +35537,20 @@ var init_mcp2 = __esm(() => {
|
|
|
35184
35537
|
const version2 = package_default.version;
|
|
35185
35538
|
const cwd = process.cwd();
|
|
35186
35539
|
const installed = getInstalledSkills();
|
|
35187
|
-
const agentNames = ["claude", "codex", "gemini"];
|
|
35188
35540
|
const agents = [];
|
|
35189
|
-
for (const agent of
|
|
35190
|
-
const agentSkillsPath =
|
|
35191
|
-
const exists =
|
|
35541
|
+
for (const agent of AGENT_TARGETS) {
|
|
35542
|
+
const agentSkillsPath = getAgentSkillsDir(agent, "global");
|
|
35543
|
+
const exists = existsSync6(agentSkillsPath);
|
|
35192
35544
|
let skillCount = 0;
|
|
35193
35545
|
if (exists) {
|
|
35194
35546
|
try {
|
|
35195
|
-
skillCount =
|
|
35196
|
-
const full =
|
|
35547
|
+
skillCount = readdirSync4(agentSkillsPath).filter((f) => {
|
|
35548
|
+
const full = join6(agentSkillsPath, f);
|
|
35197
35549
|
return f.startsWith("skill-") && statSync2(full).isDirectory();
|
|
35198
35550
|
}).length;
|
|
35199
35551
|
} catch {}
|
|
35200
35552
|
}
|
|
35201
|
-
agents.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
35553
|
+
agents.push({ agent, label: AGENT_LABELS[agent], path: agentSkillsPath, exists, skillCount });
|
|
35202
35554
|
}
|
|
35203
35555
|
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
35204
35556
|
const result = {
|
|
@@ -35211,12 +35563,107 @@ var init_mcp2 = __esm(() => {
|
|
|
35211
35563
|
};
|
|
35212
35564
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
35213
35565
|
});
|
|
35566
|
+
server.registerTool("schedule_skill", {
|
|
35567
|
+
title: "Schedule Skill",
|
|
35568
|
+
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).",
|
|
35569
|
+
inputSchema: {
|
|
35570
|
+
skill: exports_external.string(),
|
|
35571
|
+
cron: exports_external.string(),
|
|
35572
|
+
name: exports_external.string().optional(),
|
|
35573
|
+
args: exports_external.array(exports_external.string()).optional()
|
|
35574
|
+
}
|
|
35575
|
+
}, async ({ skill, cron, name, args }) => {
|
|
35576
|
+
const { schedule, error: error48 } = addSchedule(skill, cron, { name, args });
|
|
35577
|
+
if (error48 || !schedule) {
|
|
35578
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error48 || "Failed to add schedule" }) }] };
|
|
35579
|
+
}
|
|
35580
|
+
return { content: [{ type: "text", text: JSON.stringify(schedule, null, 2) }] };
|
|
35581
|
+
});
|
|
35582
|
+
server.registerTool("list_schedules", {
|
|
35583
|
+
title: "List Schedules",
|
|
35584
|
+
description: "List all scheduled skill runs.",
|
|
35585
|
+
inputSchema: {}
|
|
35586
|
+
}, async () => {
|
|
35587
|
+
const schedules = listSchedules();
|
|
35588
|
+
return { content: [{ type: "text", text: JSON.stringify(schedules, null, 2) }] };
|
|
35589
|
+
});
|
|
35590
|
+
server.registerTool("remove_schedule", {
|
|
35591
|
+
title: "Remove Schedule",
|
|
35592
|
+
description: "Remove a schedule by its ID or name.",
|
|
35593
|
+
inputSchema: {
|
|
35594
|
+
id_or_name: exports_external.string()
|
|
35595
|
+
}
|
|
35596
|
+
}, async ({ id_or_name }) => {
|
|
35597
|
+
const removed = removeSchedule(id_or_name);
|
|
35598
|
+
return { content: [{ type: "text", text: JSON.stringify({ removed, id_or_name }) }] };
|
|
35599
|
+
});
|
|
35600
|
+
server.registerTool("detect_project_skills", {
|
|
35601
|
+
title: "Detect Project Skills",
|
|
35602
|
+
description: "Detect project type from package.json and return recommended skills based on dependencies.",
|
|
35603
|
+
inputSchema: {
|
|
35604
|
+
directory: exports_external.string().optional()
|
|
35605
|
+
}
|
|
35606
|
+
}, async ({ directory }) => {
|
|
35607
|
+
const cwd = directory || process.cwd();
|
|
35608
|
+
const { detected, recommended } = detectProjectSkills(cwd);
|
|
35609
|
+
return {
|
|
35610
|
+
content: [{
|
|
35611
|
+
type: "text",
|
|
35612
|
+
text: JSON.stringify({
|
|
35613
|
+
directory: cwd,
|
|
35614
|
+
detected,
|
|
35615
|
+
recommended: recommended.map((s) => ({ name: s.name, displayName: s.displayName, description: s.description, category: s.category }))
|
|
35616
|
+
}, null, 2)
|
|
35617
|
+
}]
|
|
35618
|
+
};
|
|
35619
|
+
});
|
|
35620
|
+
server.registerTool("validate_skill", {
|
|
35621
|
+
title: "Validate Skill",
|
|
35622
|
+
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.",
|
|
35623
|
+
inputSchema: {
|
|
35624
|
+
name: exports_external.string()
|
|
35625
|
+
}
|
|
35626
|
+
}, async ({ name }) => {
|
|
35627
|
+
const skillPath = getSkillPath(name);
|
|
35628
|
+
const issues = [];
|
|
35629
|
+
if (!existsSync6(skillPath)) {
|
|
35630
|
+
return {
|
|
35631
|
+
content: [{ type: "text", text: JSON.stringify({ name, valid: false, issues: [`Skill directory not found: ${skillPath}`] }) }]
|
|
35632
|
+
};
|
|
35633
|
+
}
|
|
35634
|
+
if (!existsSync6(join6(skillPath, "SKILL.md")))
|
|
35635
|
+
issues.push("Missing SKILL.md");
|
|
35636
|
+
if (!existsSync6(join6(skillPath, "tsconfig.json")))
|
|
35637
|
+
issues.push("Missing tsconfig.json");
|
|
35638
|
+
const pkgPath = join6(skillPath, "package.json");
|
|
35639
|
+
if (!existsSync6(pkgPath)) {
|
|
35640
|
+
issues.push("Missing package.json");
|
|
35641
|
+
} else {
|
|
35642
|
+
try {
|
|
35643
|
+
const pkg = JSON.parse(__require("fs").readFileSync(pkgPath, "utf-8"));
|
|
35644
|
+
if (!pkg.bin || Object.keys(pkg.bin).length === 0)
|
|
35645
|
+
issues.push("package.json missing 'bin' entry");
|
|
35646
|
+
} catch {
|
|
35647
|
+
issues.push("package.json is invalid JSON");
|
|
35648
|
+
}
|
|
35649
|
+
}
|
|
35650
|
+
const srcDir = join6(skillPath, "src");
|
|
35651
|
+
if (!existsSync6(srcDir)) {
|
|
35652
|
+
issues.push("Missing src/ directory");
|
|
35653
|
+
} else if (!existsSync6(join6(srcDir, "index.ts"))) {
|
|
35654
|
+
issues.push("Missing src/index.ts");
|
|
35655
|
+
}
|
|
35656
|
+
const valid = issues.length === 0;
|
|
35657
|
+
return {
|
|
35658
|
+
content: [{ type: "text", text: JSON.stringify({ name, valid, path: skillPath, issues }) }]
|
|
35659
|
+
};
|
|
35660
|
+
});
|
|
35214
35661
|
server.registerResource("Skills Registry", "skills://registry", {
|
|
35215
35662
|
description: "Compact skill list [{name,category}]. Use skills://{name} for detail."
|
|
35216
35663
|
}, async () => ({
|
|
35217
35664
|
contents: [{
|
|
35218
35665
|
uri: "skills://registry",
|
|
35219
|
-
text: JSON.stringify(
|
|
35666
|
+
text: JSON.stringify(loadRegistry().map((s) => ({ name: s.name, category: s.category }))),
|
|
35220
35667
|
mimeType: "application/json"
|
|
35221
35668
|
}]
|
|
35222
35669
|
}));
|
|
@@ -35325,16 +35772,16 @@ __export(exports_serve, {
|
|
|
35325
35772
|
startServer: () => startServer,
|
|
35326
35773
|
createFetchHandler: () => createFetchHandler
|
|
35327
35774
|
});
|
|
35328
|
-
import { existsSync as
|
|
35329
|
-
import { join as
|
|
35775
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
35776
|
+
import { join as join7, dirname as dirname3, extname } from "path";
|
|
35330
35777
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
35331
35778
|
function getPackageJson() {
|
|
35332
35779
|
try {
|
|
35333
35780
|
const scriptDir = dirname3(fileURLToPath2(import.meta.url));
|
|
35334
35781
|
for (const rel of ["../..", ".."]) {
|
|
35335
|
-
const pkgPath =
|
|
35336
|
-
if (
|
|
35337
|
-
const pkg = JSON.parse(
|
|
35782
|
+
const pkgPath = join7(scriptDir, rel, "package.json");
|
|
35783
|
+
if (existsSync7(pkgPath)) {
|
|
35784
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
35338
35785
|
return { version: pkg.version || "unknown", name: pkg.name || "skills" };
|
|
35339
35786
|
}
|
|
35340
35787
|
}
|
|
@@ -35345,20 +35792,20 @@ function resolveDashboardDir() {
|
|
|
35345
35792
|
const candidates = [];
|
|
35346
35793
|
try {
|
|
35347
35794
|
const scriptDir = dirname3(fileURLToPath2(import.meta.url));
|
|
35348
|
-
candidates.push(
|
|
35349
|
-
candidates.push(
|
|
35795
|
+
candidates.push(join7(scriptDir, "..", "dashboard", "dist"));
|
|
35796
|
+
candidates.push(join7(scriptDir, "..", "..", "dashboard", "dist"));
|
|
35350
35797
|
} catch {}
|
|
35351
35798
|
if (process.argv[1]) {
|
|
35352
35799
|
const mainDir = dirname3(process.argv[1]);
|
|
35353
|
-
candidates.push(
|
|
35354
|
-
candidates.push(
|
|
35800
|
+
candidates.push(join7(mainDir, "..", "dashboard", "dist"));
|
|
35801
|
+
candidates.push(join7(mainDir, "..", "..", "dashboard", "dist"));
|
|
35355
35802
|
}
|
|
35356
|
-
candidates.push(
|
|
35803
|
+
candidates.push(join7(process.cwd(), "dashboard", "dist"));
|
|
35357
35804
|
for (const candidate of candidates) {
|
|
35358
|
-
if (
|
|
35805
|
+
if (existsSync7(candidate))
|
|
35359
35806
|
return candidate;
|
|
35360
35807
|
}
|
|
35361
|
-
return
|
|
35808
|
+
return join7(process.cwd(), "dashboard", "dist");
|
|
35362
35809
|
}
|
|
35363
35810
|
function json2(data, status = 200) {
|
|
35364
35811
|
return new Response(JSON.stringify(data), {
|
|
@@ -35387,7 +35834,7 @@ function parseFields(searchParams) {
|
|
|
35387
35834
|
}
|
|
35388
35835
|
function getAllSkillsWithStatus() {
|
|
35389
35836
|
const installed = new Set(getInstalledSkills());
|
|
35390
|
-
return
|
|
35837
|
+
return loadRegistry().map((meta3) => {
|
|
35391
35838
|
const reqs = getSkillRequirements(meta3.name);
|
|
35392
35839
|
const envVars = reqs?.envVars || [];
|
|
35393
35840
|
return {
|
|
@@ -35400,12 +35847,13 @@ function getAllSkillsWithStatus() {
|
|
|
35400
35847
|
envVars,
|
|
35401
35848
|
envVarsSet: envVars.filter((v) => !!process.env[v]),
|
|
35402
35849
|
systemDeps: reqs?.systemDeps || [],
|
|
35403
|
-
cliCommand: reqs?.cliCommand || null
|
|
35850
|
+
cliCommand: reqs?.cliCommand || null,
|
|
35851
|
+
source: meta3.source ?? "official"
|
|
35404
35852
|
};
|
|
35405
35853
|
});
|
|
35406
35854
|
}
|
|
35407
35855
|
function serveStaticFile(filePath) {
|
|
35408
|
-
if (!
|
|
35856
|
+
if (!existsSync7(filePath))
|
|
35409
35857
|
return null;
|
|
35410
35858
|
const ext = extname(filePath);
|
|
35411
35859
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -35415,7 +35863,7 @@ function serveStaticFile(filePath) {
|
|
|
35415
35863
|
}
|
|
35416
35864
|
function createFetchHandler(options) {
|
|
35417
35865
|
const dashboardDir = options?.dashboardDir ?? resolveDashboardDir();
|
|
35418
|
-
const dashboardExists = options?.dashboardExists ??
|
|
35866
|
+
const dashboardExists = options?.dashboardExists ?? existsSync7(dashboardDir);
|
|
35419
35867
|
return async function fetchHandler(req) {
|
|
35420
35868
|
const url2 = new URL(req.url);
|
|
35421
35869
|
const path = url2.pathname.replace(/^\/api\/v1\//, "/api/");
|
|
@@ -35426,7 +35874,7 @@ function createFetchHandler(options) {
|
|
|
35426
35874
|
status: "ok",
|
|
35427
35875
|
version: pkg.version,
|
|
35428
35876
|
uptime: Math.floor(process.uptime()),
|
|
35429
|
-
skillCount:
|
|
35877
|
+
skillCount: loadRegistry().length
|
|
35430
35878
|
});
|
|
35431
35879
|
}
|
|
35432
35880
|
if (path === "/api/skills" && method === "GET") {
|
|
@@ -35467,7 +35915,7 @@ function createFetchHandler(options) {
|
|
|
35467
35915
|
}
|
|
35468
35916
|
if (path === "/api/tags" && method === "GET") {
|
|
35469
35917
|
const tagCounts = new Map;
|
|
35470
|
-
for (const skill of
|
|
35918
|
+
for (const skill of loadRegistry()) {
|
|
35471
35919
|
for (const tag of skill.tags) {
|
|
35472
35920
|
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
35473
35921
|
}
|
|
@@ -35699,12 +36147,12 @@ function createFetchHandler(options) {
|
|
|
35699
36147
|
}
|
|
35700
36148
|
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
35701
36149
|
if (path !== "/") {
|
|
35702
|
-
const filePath =
|
|
36150
|
+
const filePath = join7(dashboardDir, path);
|
|
35703
36151
|
const res2 = serveStaticFile(filePath);
|
|
35704
36152
|
if (res2)
|
|
35705
36153
|
return res2;
|
|
35706
36154
|
}
|
|
35707
|
-
const indexPath =
|
|
36155
|
+
const indexPath = join7(dashboardDir, "index.html");
|
|
35708
36156
|
const res = serveStaticFile(indexPath);
|
|
35709
36157
|
if (res)
|
|
35710
36158
|
return res;
|
|
@@ -35715,7 +36163,7 @@ function createFetchHandler(options) {
|
|
|
35715
36163
|
async function startServer(port = 0, options) {
|
|
35716
36164
|
const shouldOpen = options?.open ?? true;
|
|
35717
36165
|
const dashboardDir = resolveDashboardDir();
|
|
35718
|
-
const dashboardExists =
|
|
36166
|
+
const dashboardExists = existsSync7(dashboardDir);
|
|
35719
36167
|
if (!dashboardExists) {
|
|
35720
36168
|
console.error(`
|
|
35721
36169
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -35795,8 +36243,8 @@ var {
|
|
|
35795
36243
|
// src/cli/index.tsx
|
|
35796
36244
|
init_package();
|
|
35797
36245
|
import chalk2 from "chalk";
|
|
35798
|
-
import { existsSync as
|
|
35799
|
-
import { join as
|
|
36246
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync4, appendFileSync, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
36247
|
+
import { join as join8 } from "path";
|
|
35800
36248
|
|
|
35801
36249
|
// src/cli/components/App.tsx
|
|
35802
36250
|
import { useState as useState7 } from "react";
|
|
@@ -36922,25 +37370,38 @@ init_installer();
|
|
|
36922
37370
|
init_skillinfo();
|
|
36923
37371
|
|
|
36924
37372
|
// src/lib/config.ts
|
|
36925
|
-
import { existsSync as
|
|
36926
|
-
import { join as
|
|
36927
|
-
import { homedir as
|
|
37373
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, copyFileSync } from "fs";
|
|
37374
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
37375
|
+
import { homedir as homedir3 } from "os";
|
|
36928
37376
|
var VALID_KEYS = {
|
|
36929
37377
|
defaultAgent: ["claude", "codex", "gemini", "all"],
|
|
36930
37378
|
defaultScope: ["global", "project"],
|
|
36931
37379
|
format: ["compact", "json", "csv"]
|
|
36932
37380
|
};
|
|
37381
|
+
function getDataDir() {
|
|
37382
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir3();
|
|
37383
|
+
const newDir = join4(home, ".hasna", "skills");
|
|
37384
|
+
const oldConfigFile = join4(home, ".skillsrc");
|
|
37385
|
+
if (existsSync4(oldConfigFile) && !existsSync4(join4(newDir, "config.json"))) {
|
|
37386
|
+
mkdirSync2(newDir, { recursive: true });
|
|
37387
|
+
try {
|
|
37388
|
+
copyFileSync(oldConfigFile, join4(newDir, "config.json"));
|
|
37389
|
+
} catch {}
|
|
37390
|
+
}
|
|
37391
|
+
mkdirSync2(newDir, { recursive: true });
|
|
37392
|
+
return newDir;
|
|
37393
|
+
}
|
|
36933
37394
|
function getConfigPath(scope) {
|
|
36934
37395
|
if (scope === "global") {
|
|
36935
|
-
return
|
|
37396
|
+
return join4(getDataDir(), "config.json");
|
|
36936
37397
|
}
|
|
36937
|
-
return
|
|
37398
|
+
return join4(process.cwd(), "skills.config.json");
|
|
36938
37399
|
}
|
|
36939
37400
|
function readConfigFile(path) {
|
|
36940
|
-
if (!
|
|
37401
|
+
if (!existsSync4(path))
|
|
36941
37402
|
return {};
|
|
36942
37403
|
try {
|
|
36943
|
-
const raw =
|
|
37404
|
+
const raw = readFileSync4(path, "utf-8");
|
|
36944
37405
|
const parsed = JSON.parse(raw);
|
|
36945
37406
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
36946
37407
|
return {};
|
|
@@ -36971,9 +37432,9 @@ function saveConfig(key, value, scope = "project") {
|
|
|
36971
37432
|
}
|
|
36972
37433
|
const filePath = getConfigPath(scope);
|
|
36973
37434
|
let existing = {};
|
|
36974
|
-
if (
|
|
37435
|
+
if (existsSync4(filePath)) {
|
|
36975
37436
|
try {
|
|
36976
|
-
existing = JSON.parse(
|
|
37437
|
+
existing = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
36977
37438
|
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
|
|
36978
37439
|
existing = {};
|
|
36979
37440
|
}
|
|
@@ -36982,7 +37443,7 @@ function saveConfig(key, value, scope = "project") {
|
|
|
36982
37443
|
}
|
|
36983
37444
|
} else {
|
|
36984
37445
|
const dir = dirname2(filePath);
|
|
36985
|
-
if (!
|
|
37446
|
+
if (!existsSync4(dir)) {
|
|
36986
37447
|
mkdirSync2(dir, { recursive: true });
|
|
36987
37448
|
}
|
|
36988
37449
|
}
|
|
@@ -36992,6 +37453,7 @@ function saveConfig(key, value, scope = "project") {
|
|
|
36992
37453
|
}
|
|
36993
37454
|
|
|
36994
37455
|
// src/cli/index.tsx
|
|
37456
|
+
init_scheduler();
|
|
36995
37457
|
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
36996
37458
|
var isTTY = (process.stdout.isTTY ?? false) && (process.stdin.isTTY ?? false);
|
|
36997
37459
|
if (process.argv.includes("--no-color")) {
|
|
@@ -37024,7 +37486,7 @@ program2.command("interactive", { isDefault: true }).alias("i").description("Int
|
|
|
37024
37486
|
}
|
|
37025
37487
|
render(/* @__PURE__ */ jsxDEV7(App, {}, undefined, false, undefined, this));
|
|
37026
37488
|
});
|
|
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) => {
|
|
37489
|
+
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
37490
|
const config2 = loadConfig();
|
|
37029
37491
|
if (!options.for && config2.defaultAgent) {
|
|
37030
37492
|
options.for = config2.defaultAgent;
|
|
@@ -37165,7 +37627,14 @@ program2.command("list").alias("ls").option("-c, --category <category>", "Filter
|
|
|
37165
37627
|
if (options.installed) {
|
|
37166
37628
|
const installed = getInstalledSkills();
|
|
37167
37629
|
if (options.json) {
|
|
37168
|
-
|
|
37630
|
+
const meta4 = getInstallMeta();
|
|
37631
|
+
const registry3 = loadRegistry();
|
|
37632
|
+
const output = installed.map((name) => {
|
|
37633
|
+
const m = meta4.skills[name];
|
|
37634
|
+
const s = registry3.find((r) => r.name === name);
|
|
37635
|
+
return { name, version: m?.version ?? null, installedAt: m?.installedAt ?? null, source: s?.source ?? "official" };
|
|
37636
|
+
});
|
|
37637
|
+
console.log(JSON.stringify(output));
|
|
37169
37638
|
return;
|
|
37170
37639
|
}
|
|
37171
37640
|
if (installed.length === 0) {
|
|
@@ -37173,16 +37642,22 @@ program2.command("list").alias("ls").option("-c, --category <category>", "Filter
|
|
|
37173
37642
|
return;
|
|
37174
37643
|
}
|
|
37175
37644
|
if (brief) {
|
|
37176
|
-
for (const name of installed)
|
|
37645
|
+
for (const name of installed)
|
|
37177
37646
|
console.log(name);
|
|
37178
|
-
}
|
|
37179
37647
|
return;
|
|
37180
37648
|
}
|
|
37649
|
+
const meta3 = getInstallMeta();
|
|
37650
|
+
const registry2 = loadRegistry();
|
|
37181
37651
|
console.log(chalk2.bold(`
|
|
37182
37652
|
Installed skills (${installed.length}):
|
|
37183
37653
|
`));
|
|
37184
37654
|
for (const name of installed) {
|
|
37185
|
-
|
|
37655
|
+
const m = meta3.skills[name];
|
|
37656
|
+
const s = registry2.find((r) => r.name === name);
|
|
37657
|
+
const version2 = m?.version ? chalk2.dim(`v${m.version}`) : "";
|
|
37658
|
+
const installedAt = m?.installedAt ? chalk2.dim(new Date(m.installedAt).toLocaleDateString()) : "";
|
|
37659
|
+
const source = s?.source === "custom" ? chalk2.yellow(" [custom]") : "";
|
|
37660
|
+
console.log(` ${chalk2.cyan(name)}${source} ${version2} ${installedAt}`);
|
|
37186
37661
|
}
|
|
37187
37662
|
return;
|
|
37188
37663
|
}
|
|
@@ -37218,7 +37693,7 @@ ${category} (${skills.length}):
|
|
|
37218
37693
|
return;
|
|
37219
37694
|
}
|
|
37220
37695
|
if (tagFilter) {
|
|
37221
|
-
const skills =
|
|
37696
|
+
const skills = loadRegistry().filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
|
|
37222
37697
|
if (options.json) {
|
|
37223
37698
|
console.log(JSON.stringify(skills, null, 2));
|
|
37224
37699
|
return;
|
|
@@ -37233,29 +37708,31 @@ ${category} (${skills.length}):
|
|
|
37233
37708
|
Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
37234
37709
|
`));
|
|
37235
37710
|
for (const s of skills) {
|
|
37236
|
-
|
|
37711
|
+
const customBadge = s.source === "custom" ? chalk2.yellow(" [custom]") : "";
|
|
37712
|
+
console.log(` ${chalk2.cyan(s.name)}${customBadge} ${chalk2.dim(`[${s.category}]`)} - ${s.description}`);
|
|
37237
37713
|
}
|
|
37238
37714
|
return;
|
|
37239
37715
|
}
|
|
37716
|
+
const allSkills = loadRegistry();
|
|
37240
37717
|
if (options.json) {
|
|
37241
|
-
console.log(JSON.stringify(
|
|
37718
|
+
console.log(JSON.stringify(allSkills, null, 2));
|
|
37242
37719
|
return;
|
|
37243
37720
|
}
|
|
37244
37721
|
if (fmt === "compact") {
|
|
37245
|
-
for (const s of
|
|
37722
|
+
for (const s of allSkills)
|
|
37246
37723
|
console.log(s.name);
|
|
37247
37724
|
return;
|
|
37248
37725
|
}
|
|
37249
37726
|
if (fmt === "csv") {
|
|
37250
|
-
console.log("name,category,description");
|
|
37251
|
-
for (const s of
|
|
37727
|
+
console.log("name,category,description,source");
|
|
37728
|
+
for (const s of allSkills) {
|
|
37252
37729
|
const desc = s.description.replace(/"/g, '""');
|
|
37253
|
-
console.log(`${s.name},${s.category},"${desc}"`);
|
|
37730
|
+
console.log(`${s.name},${s.category},"${desc}",${s.source ?? "official"}`);
|
|
37254
37731
|
}
|
|
37255
37732
|
return;
|
|
37256
37733
|
}
|
|
37257
37734
|
if (brief) {
|
|
37258
|
-
const sorted = [...
|
|
37735
|
+
const sorted = [...allSkills].sort((a, b) => {
|
|
37259
37736
|
const catCmp = a.category.localeCompare(b.category);
|
|
37260
37737
|
return catCmp !== 0 ? catCmp : a.name.localeCompare(b.name);
|
|
37261
37738
|
});
|
|
@@ -37265,16 +37742,26 @@ Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
|
37265
37742
|
return;
|
|
37266
37743
|
}
|
|
37267
37744
|
console.log(chalk2.bold(`
|
|
37268
|
-
Available skills (${
|
|
37745
|
+
Available skills (${allSkills.length}):
|
|
37269
37746
|
`));
|
|
37270
37747
|
for (const category of CATEGORIES) {
|
|
37271
37748
|
const skills = getSkillsByCategory(category);
|
|
37749
|
+
if (skills.length === 0)
|
|
37750
|
+
continue;
|
|
37272
37751
|
console.log(chalk2.bold(`${category} (${skills.length}):`));
|
|
37273
37752
|
for (const s of skills) {
|
|
37274
|
-
|
|
37753
|
+
const customBadge = s.source === "custom" ? chalk2.yellow(" [custom]") : "";
|
|
37754
|
+
console.log(` ${chalk2.cyan(s.name)}${customBadge} - ${s.description}`);
|
|
37275
37755
|
}
|
|
37276
37756
|
console.log();
|
|
37277
37757
|
}
|
|
37758
|
+
const customUncategorized = allSkills.filter((s) => s.source === "custom" && !CATEGORIES.includes(s.category));
|
|
37759
|
+
if (customUncategorized.length > 0) {
|
|
37760
|
+
console.log(chalk2.bold(`Custom (${customUncategorized.length}):`));
|
|
37761
|
+
for (const s of customUncategorized) {
|
|
37762
|
+
console.log(` ${chalk2.yellow(s.name)} - ${s.description}`);
|
|
37763
|
+
}
|
|
37764
|
+
}
|
|
37278
37765
|
});
|
|
37279
37766
|
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
37767
|
let results = searchSkills(query);
|
|
@@ -37350,8 +37837,17 @@ program2.command("info").argument("<skill>", "Skill name").option("--json", "Out
|
|
|
37350
37837
|
console.log(`${skill.name} \u2014 ${skill.description} [${skill.category}] (tags: ${skill.tags.join(", ")})`);
|
|
37351
37838
|
return;
|
|
37352
37839
|
}
|
|
37840
|
+
const { execSync: execSyncInfo } = __require("child_process");
|
|
37841
|
+
function cmdAvailable(cmd) {
|
|
37842
|
+
try {
|
|
37843
|
+
execSyncInfo(`which ${cmd}`, { stdio: "ignore" });
|
|
37844
|
+
return true;
|
|
37845
|
+
} catch {
|
|
37846
|
+
return false;
|
|
37847
|
+
}
|
|
37848
|
+
}
|
|
37353
37849
|
console.log(`
|
|
37354
|
-
${chalk2.bold(skill.displayName)}`);
|
|
37850
|
+
${chalk2.bold(skill.displayName)}${skill.source === "custom" ? chalk2.yellow(" [custom]") : ""}`);
|
|
37355
37851
|
console.log(`${skill.description}`);
|
|
37356
37852
|
console.log(`${chalk2.dim("Category:")} ${skill.category}`);
|
|
37357
37853
|
console.log(`${chalk2.dim("Tags:")} ${skill.tags.join(", ")}`);
|
|
@@ -37359,10 +37855,18 @@ ${chalk2.bold(skill.displayName)}`);
|
|
|
37359
37855
|
console.log(`${chalk2.dim("CLI:")} ${reqs.cliCommand}`);
|
|
37360
37856
|
}
|
|
37361
37857
|
if (reqs?.envVars.length) {
|
|
37362
|
-
console.log(`${chalk2.dim("Env vars:")}
|
|
37858
|
+
console.log(`${chalk2.dim("Env vars:")}`);
|
|
37859
|
+
for (const v of reqs.envVars) {
|
|
37860
|
+
const set3 = !!process.env[v];
|
|
37861
|
+
console.log(` ${set3 ? chalk2.green("\u2713") : chalk2.red("\u2717")} ${v}${set3 ? "" : chalk2.dim(" (not set)")}`);
|
|
37862
|
+
}
|
|
37363
37863
|
}
|
|
37364
37864
|
if (reqs?.systemDeps.length) {
|
|
37365
|
-
console.log(`${chalk2.dim("System deps:")}
|
|
37865
|
+
console.log(`${chalk2.dim("System deps:")}`);
|
|
37866
|
+
for (const d of reqs.systemDeps) {
|
|
37867
|
+
const avail = cmdAvailable(d);
|
|
37868
|
+
console.log(` ${avail ? chalk2.green("\u2713") : chalk2.red("\u2717")} ${d}${avail ? "" : chalk2.dim(" (not found)")}`);
|
|
37869
|
+
}
|
|
37366
37870
|
}
|
|
37367
37871
|
console.log(`${chalk2.dim("Install:")} skills install ${skill.name}`);
|
|
37368
37872
|
});
|
|
@@ -37466,7 +37970,7 @@ program2.command("run").argument("<skill>", "Skill name").argument("[args...]",
|
|
|
37466
37970
|
}
|
|
37467
37971
|
process.exitCode = result.exitCode;
|
|
37468
37972
|
});
|
|
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) => {
|
|
37973
|
+
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
37974
|
const cwd = process.cwd();
|
|
37471
37975
|
if (options.for) {
|
|
37472
37976
|
let agents;
|
|
@@ -37580,8 +38084,8 @@ Installing recommended skills for ${options.for} (${options.scope})...
|
|
|
37580
38084
|
const envContent = lines.join(`
|
|
37581
38085
|
`) + `
|
|
37582
38086
|
`;
|
|
37583
|
-
const envPath =
|
|
37584
|
-
|
|
38087
|
+
const envPath = join8(cwd, ".env.example");
|
|
38088
|
+
writeFileSync4(envPath, envContent);
|
|
37585
38089
|
envVarCount = envMap.size;
|
|
37586
38090
|
if (!options.json) {
|
|
37587
38091
|
console.log(chalk2.green(`\u2713 Generated .env.example (${envVarCount} variables from ${installed.length} skills)`));
|
|
@@ -37591,11 +38095,11 @@ Installing recommended skills for ${options.for} (${options.scope})...
|
|
|
37591
38095
|
console.log(chalk2.dim(" No environment variables detected across installed skills"));
|
|
37592
38096
|
}
|
|
37593
38097
|
}
|
|
37594
|
-
const gitignorePath =
|
|
38098
|
+
const gitignorePath = join8(cwd, ".gitignore");
|
|
37595
38099
|
const gitignoreEntry = ".skills/";
|
|
37596
38100
|
let gitignoreContent = "";
|
|
37597
|
-
if (
|
|
37598
|
-
gitignoreContent =
|
|
38101
|
+
if (existsSync8(gitignorePath)) {
|
|
38102
|
+
gitignoreContent = readFileSync7(gitignorePath, "utf-8");
|
|
37599
38103
|
}
|
|
37600
38104
|
let gitignoreUpdated = false;
|
|
37601
38105
|
if (!gitignoreContent.includes(gitignoreEntry)) {
|
|
@@ -37641,7 +38145,7 @@ Initialized for ${installed.length} installed skill(s)`));
|
|
|
37641
38145
|
}
|
|
37642
38146
|
}
|
|
37643
38147
|
});
|
|
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) => {
|
|
38148
|
+
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
38149
|
debug(`remove: skill=${skill} for=${options.for ?? "none"} scope=${options.scope} dryRun=${options.dryRun}`);
|
|
37646
38150
|
if (!options.yes && !options.dryRun && isTTY) {
|
|
37647
38151
|
const skillName = normalizeSkillName(skill);
|
|
@@ -37724,10 +38228,10 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
37724
38228
|
}
|
|
37725
38229
|
function collectFiles(dir, base = "") {
|
|
37726
38230
|
const files = new Set;
|
|
37727
|
-
if (!
|
|
38231
|
+
if (!existsSync8(dir))
|
|
37728
38232
|
return files;
|
|
37729
|
-
for (const entry of
|
|
37730
|
-
const full =
|
|
38233
|
+
for (const entry of readdirSync5(dir)) {
|
|
38234
|
+
const full = join8(dir, entry);
|
|
37731
38235
|
const rel = base ? `${base}/${entry}` : entry;
|
|
37732
38236
|
if (statSync3(full).isDirectory()) {
|
|
37733
38237
|
for (const f of collectFiles(full, rel))
|
|
@@ -37741,7 +38245,7 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
37741
38245
|
const updateResults = [];
|
|
37742
38246
|
for (const name of toUpdate) {
|
|
37743
38247
|
const skillName = normalizeSkillName(name);
|
|
37744
|
-
const destPath =
|
|
38248
|
+
const destPath = join8(process.cwd(), ".skills", skillName);
|
|
37745
38249
|
const beforeFiles = collectFiles(destPath);
|
|
37746
38250
|
const result = installSkill(name, { overwrite: true });
|
|
37747
38251
|
const afterFiles = collectFiles(destPath);
|
|
@@ -37819,10 +38323,11 @@ Tags:
|
|
|
37819
38323
|
console.log(` ${chalk2.cyan(name)} (${count})`);
|
|
37820
38324
|
}
|
|
37821
38325
|
});
|
|
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) => {
|
|
38326
|
+
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
38327
|
if (options.register) {
|
|
37824
|
-
const agents = options.register === "all" ? [
|
|
37825
|
-
const binPath =
|
|
38328
|
+
const agents = options.register === "all" ? [...AGENT_TARGETS] : [options.register];
|
|
38329
|
+
const binPath = join8(import.meta.dir, "..", "mcp", "index.ts");
|
|
38330
|
+
const { homedir: hd } = await import("os");
|
|
37826
38331
|
for (const agent of agents) {
|
|
37827
38332
|
if (agent === "claude") {
|
|
37828
38333
|
try {
|
|
@@ -37836,8 +38341,7 @@ program2.command("mcp").option("--register <agent>", "Register MCP server with a
|
|
|
37836
38341
|
console.log(chalk2.yellow(`Manual registration: claude mcp add skills -- bun run ${binPath}`));
|
|
37837
38342
|
}
|
|
37838
38343
|
} else if (agent === "codex") {
|
|
37839
|
-
const
|
|
37840
|
-
const configPath = join6(homedir4(), ".codex", "config.toml");
|
|
38344
|
+
const configPath = join8(hd(), ".codex", "config.toml");
|
|
37841
38345
|
console.log(chalk2.bold(`
|
|
37842
38346
|
Add to ${configPath}:`));
|
|
37843
38347
|
console.log(chalk2.dim(`[mcp_servers.skills]
|
|
@@ -37845,16 +38349,25 @@ command = "bun"
|
|
|
37845
38349
|
args = ["run", "${binPath}"]`));
|
|
37846
38350
|
console.log(chalk2.green(`\u2713 Codex MCP config shown above`));
|
|
37847
38351
|
} else if (agent === "gemini") {
|
|
37848
|
-
const
|
|
37849
|
-
const configPath = join6(homedir4(), ".gemini", "settings.json");
|
|
38352
|
+
const configPath = join8(hd(), ".gemini", "settings.json");
|
|
37850
38353
|
console.log(chalk2.bold(`
|
|
37851
38354
|
Add to ${configPath} mcpServers:`));
|
|
37852
|
-
console.log(chalk2.dim(JSON.stringify({
|
|
37853
|
-
skills: { command: "bun", args: ["run", binPath] }
|
|
37854
|
-
}, null, 2)));
|
|
38355
|
+
console.log(chalk2.dim(JSON.stringify({ skills: { command: "bun", args: ["run", binPath] } }, null, 2)));
|
|
37855
38356
|
console.log(chalk2.green(`\u2713 Gemini MCP config shown above`));
|
|
38357
|
+
} else if (agent === "pi") {
|
|
38358
|
+
const configPath = join8(hd(), ".pi", "agent", "mcp.json");
|
|
38359
|
+
console.log(chalk2.bold(`
|
|
38360
|
+
Add to ${configPath}:`));
|
|
38361
|
+
console.log(chalk2.dim(JSON.stringify({ skills: { command: "bun", args: ["run", binPath] } }, null, 2)));
|
|
38362
|
+
console.log(chalk2.green(`\u2713 pi.dev MCP config shown above`));
|
|
38363
|
+
} else if (agent === "opencode") {
|
|
38364
|
+
const configPath = join8(hd(), ".opencode", "config.json");
|
|
38365
|
+
console.log(chalk2.bold(`
|
|
38366
|
+
Add to ${configPath} mcp section:`));
|
|
38367
|
+
console.log(chalk2.dim(JSON.stringify({ skills: { command: "bun", args: ["run", binPath] } }, null, 2)));
|
|
38368
|
+
console.log(chalk2.green(`\u2713 OpenCode MCP config shown above`));
|
|
37856
38369
|
} else {
|
|
37857
|
-
console.error(chalk2.red(`Unknown agent: ${agent}. Available:
|
|
38370
|
+
console.error(chalk2.red(`Unknown agent: ${agent}. Available: ${AGENT_TARGETS.join(", ")}, all`));
|
|
37858
38371
|
process.exitCode = 1;
|
|
37859
38372
|
}
|
|
37860
38373
|
}
|
|
@@ -38040,18 +38553,18 @@ program2.command("export").option("--json", "Output as JSON (default behavior)",
|
|
|
38040
38553
|
};
|
|
38041
38554
|
console.log(JSON.stringify(payload, null, 2));
|
|
38042
38555
|
});
|
|
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) => {
|
|
38556
|
+
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
38557
|
let raw;
|
|
38045
38558
|
try {
|
|
38046
38559
|
if (file2 === "-") {
|
|
38047
38560
|
raw = await new Response(process.stdin).text();
|
|
38048
38561
|
} else {
|
|
38049
|
-
if (!
|
|
38562
|
+
if (!existsSync8(file2)) {
|
|
38050
38563
|
console.error(chalk2.red(`File not found: ${file2}`));
|
|
38051
38564
|
process.exitCode = 1;
|
|
38052
38565
|
return;
|
|
38053
38566
|
}
|
|
38054
|
-
raw =
|
|
38567
|
+
raw = readFileSync7(file2, "utf-8");
|
|
38055
38568
|
}
|
|
38056
38569
|
} catch (err) {
|
|
38057
38570
|
console.error(chalk2.red(`Failed to read file: ${err.message}`));
|
|
@@ -38142,7 +38655,8 @@ Imported ${succeeded}/${total} skill(s)${failed > 0 ? ` (${failed} failed)` : ""
|
|
|
38142
38655
|
process.exitCode = 1;
|
|
38143
38656
|
}
|
|
38144
38657
|
});
|
|
38145
|
-
program2.command("doctor").option("--json", "Output as JSON", false).description("Check
|
|
38658
|
+
program2.command("doctor").option("--json", "Output as JSON", false).description("Check env vars, system deps, and install health for installed skills").action((options) => {
|
|
38659
|
+
const { execSync } = __require("child_process");
|
|
38146
38660
|
const installed = getInstalledSkills();
|
|
38147
38661
|
if (installed.length === 0) {
|
|
38148
38662
|
if (options.json) {
|
|
@@ -38152,37 +38666,53 @@ program2.command("doctor").option("--json", "Output as JSON", false).description
|
|
|
38152
38666
|
}
|
|
38153
38667
|
return;
|
|
38154
38668
|
}
|
|
38669
|
+
function isCommandAvailable(cmd) {
|
|
38670
|
+
try {
|
|
38671
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
38672
|
+
return true;
|
|
38673
|
+
} catch {
|
|
38674
|
+
return false;
|
|
38675
|
+
}
|
|
38676
|
+
}
|
|
38155
38677
|
const report = [];
|
|
38156
38678
|
for (const name of installed) {
|
|
38157
38679
|
const reqs = getSkillRequirements(name);
|
|
38158
|
-
const envVars = (reqs?.envVars ?? []).map((v) => ({
|
|
38159
|
-
|
|
38160
|
-
|
|
38161
|
-
})
|
|
38162
|
-
report.push({ skill: name, envVars });
|
|
38680
|
+
const envVars = (reqs?.envVars ?? []).map((v) => ({ name: v, set: !!process.env[v] }));
|
|
38681
|
+
const systemDeps = (reqs?.systemDeps ?? []).map((d) => ({ name: d, available: isCommandAvailable(d) }));
|
|
38682
|
+
const healthy = envVars.every((v) => v.set) && systemDeps.every((d) => d.available);
|
|
38683
|
+
report.push({ skill: name, envVars, systemDeps, healthy });
|
|
38163
38684
|
}
|
|
38164
38685
|
if (options.json) {
|
|
38165
38686
|
console.log(JSON.stringify(report, null, 2));
|
|
38166
38687
|
return;
|
|
38167
38688
|
}
|
|
38689
|
+
const issues = report.filter((r) => !r.healthy);
|
|
38168
38690
|
console.log(chalk2.bold(`
|
|
38169
|
-
Skills Doctor
|
|
38691
|
+
Skills Doctor \u2014 ${installed.length} installed, ${issues.length} with issues:
|
|
38170
38692
|
`));
|
|
38171
38693
|
for (const entry of report) {
|
|
38172
|
-
|
|
38173
|
-
|
|
38174
|
-
|
|
38175
|
-
|
|
38176
|
-
|
|
38177
|
-
const status = v.set ? chalk2.green("set") : chalk2.red("missing");
|
|
38178
|
-
console.log(` ${v.name} [${status}]`);
|
|
38179
|
-
}
|
|
38694
|
+
const icon = entry.healthy ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
38695
|
+
console.log(` ${icon} ${chalk2.bold(entry.skill)}`);
|
|
38696
|
+
for (const v of entry.envVars) {
|
|
38697
|
+
const status = v.set ? chalk2.green("set") : chalk2.red("missing");
|
|
38698
|
+
console.log(` ${v.name} [${status}]`);
|
|
38180
38699
|
}
|
|
38700
|
+
for (const d of entry.systemDeps) {
|
|
38701
|
+
const status = d.available ? chalk2.green("available") : chalk2.red("not found");
|
|
38702
|
+
console.log(` ${d.name} [${status}]`);
|
|
38703
|
+
}
|
|
38704
|
+
if (entry.envVars.length === 0 && entry.systemDeps.length === 0) {
|
|
38705
|
+
console.log(chalk2.dim(" No requirements"));
|
|
38706
|
+
}
|
|
38707
|
+
}
|
|
38708
|
+
if (issues.length === 0) {
|
|
38709
|
+
console.log(chalk2.green(`
|
|
38710
|
+
All skills healthy! \u2713`));
|
|
38181
38711
|
}
|
|
38182
38712
|
});
|
|
38183
38713
|
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
38714
|
const cwd = process.cwd();
|
|
38185
|
-
const envFilePath =
|
|
38715
|
+
const envFilePath = join8(cwd, ".env");
|
|
38186
38716
|
if (options.set) {
|
|
38187
38717
|
const eqIdx = options.set.indexOf("=");
|
|
38188
38718
|
if (eqIdx === -1) {
|
|
@@ -38198,8 +38728,8 @@ program2.command("auth").argument("[skill]", "Skill name (omit to check all inst
|
|
|
38198
38728
|
return;
|
|
38199
38729
|
}
|
|
38200
38730
|
let existing = "";
|
|
38201
|
-
if (
|
|
38202
|
-
existing =
|
|
38731
|
+
if (existsSync8(envFilePath)) {
|
|
38732
|
+
existing = readFileSync7(envFilePath, "utf-8");
|
|
38203
38733
|
}
|
|
38204
38734
|
const keyPattern = new RegExp(`^${key}=.*$`, "m");
|
|
38205
38735
|
let updated;
|
|
@@ -38212,7 +38742,7 @@ program2.command("auth").argument("[skill]", "Skill name (omit to check all inst
|
|
|
38212
38742
|
${key}=${value}
|
|
38213
38743
|
`;
|
|
38214
38744
|
}
|
|
38215
|
-
|
|
38745
|
+
writeFileSync4(envFilePath, updated, "utf-8");
|
|
38216
38746
|
console.log(chalk2.green(`Set ${key} in ${envFilePath}`));
|
|
38217
38747
|
return;
|
|
38218
38748
|
}
|
|
@@ -38282,21 +38812,20 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
|
|
|
38282
38812
|
const version2 = package_default.version;
|
|
38283
38813
|
const cwd = process.cwd();
|
|
38284
38814
|
const installed = getInstalledSkills();
|
|
38285
|
-
const agentNames = ["claude", "codex", "gemini"];
|
|
38286
38815
|
const agentConfigs = [];
|
|
38287
|
-
for (const agent of
|
|
38288
|
-
const agentSkillsPath =
|
|
38289
|
-
const exists =
|
|
38816
|
+
for (const agent of AGENT_TARGETS) {
|
|
38817
|
+
const agentSkillsPath = getAgentSkillsDir(agent, "global");
|
|
38818
|
+
const exists = existsSync8(agentSkillsPath);
|
|
38290
38819
|
let skillCount = 0;
|
|
38291
38820
|
if (exists) {
|
|
38292
38821
|
try {
|
|
38293
|
-
skillCount =
|
|
38294
|
-
const full =
|
|
38822
|
+
skillCount = readdirSync5(agentSkillsPath).filter((f) => {
|
|
38823
|
+
const full = join8(agentSkillsPath, f);
|
|
38295
38824
|
return f.startsWith("skill-") && statSync3(full).isDirectory();
|
|
38296
38825
|
}).length;
|
|
38297
38826
|
} catch {}
|
|
38298
38827
|
}
|
|
38299
|
-
agentConfigs.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
38828
|
+
agentConfigs.push({ agent, label: AGENT_LABELS[agent], path: agentSkillsPath, exists, skillCount });
|
|
38300
38829
|
}
|
|
38301
38830
|
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
38302
38831
|
if (options.json) {
|
|
@@ -38436,19 +38965,19 @@ program2.command("outdated").option("--json", "Output as JSON", false).descripti
|
|
|
38436
38965
|
const upToDate = [];
|
|
38437
38966
|
for (const name of installed) {
|
|
38438
38967
|
const skillName = normalizeSkillName(name);
|
|
38439
|
-
const installedPkgPath =
|
|
38968
|
+
const installedPkgPath = join8(cwd, ".skills", skillName, "package.json");
|
|
38440
38969
|
let installedVersion = "unknown";
|
|
38441
|
-
if (
|
|
38970
|
+
if (existsSync8(installedPkgPath)) {
|
|
38442
38971
|
try {
|
|
38443
|
-
installedVersion = JSON.parse(
|
|
38972
|
+
installedVersion = JSON.parse(readFileSync7(installedPkgPath, "utf-8")).version || "unknown";
|
|
38444
38973
|
} catch {}
|
|
38445
38974
|
}
|
|
38446
38975
|
const registryPath = getSkillPath(name);
|
|
38447
|
-
const registryPkgPath =
|
|
38976
|
+
const registryPkgPath = join8(registryPath, "package.json");
|
|
38448
38977
|
let registryVersion = "unknown";
|
|
38449
|
-
if (
|
|
38978
|
+
if (existsSync8(registryPkgPath)) {
|
|
38450
38979
|
try {
|
|
38451
|
-
registryVersion = JSON.parse(
|
|
38980
|
+
registryVersion = JSON.parse(readFileSync7(registryPkgPath, "utf-8")).version || "unknown";
|
|
38452
38981
|
} catch {}
|
|
38453
38982
|
}
|
|
38454
38983
|
if (installedVersion !== registryVersion) {
|
|
@@ -38513,7 +39042,470 @@ configCmd.command("get <key>").description("Get a specific configuration value")
|
|
|
38513
39042
|
configCmd.command("path").description("Show configuration file paths").action(() => {
|
|
38514
39043
|
const globalPath = getConfigPath("global");
|
|
38515
39044
|
const projectPath = getConfigPath("project");
|
|
38516
|
-
console.log(`${chalk2.cyan("global")}: ${globalPath}${
|
|
38517
|
-
console.log(`${chalk2.cyan("project")}: ${projectPath}${
|
|
39045
|
+
console.log(`${chalk2.cyan("global")}: ${globalPath}${existsSync8(globalPath) ? chalk2.green(" (exists)") : chalk2.dim(" (not found)")}`);
|
|
39046
|
+
console.log(`${chalk2.cyan("project")}: ${projectPath}${existsSync8(projectPath) ? chalk2.green(" (exists)") : chalk2.dim(" (not found)")}`);
|
|
39047
|
+
});
|
|
39048
|
+
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 ~/.hasna/skills/custom/ instead of .skills/custom-skills/", false).option("--json", "Output result as JSON", false).description("Scaffold a new custom skill directory").action((name, options) => {
|
|
39049
|
+
const { homedir: homedir4 } = __require("os");
|
|
39050
|
+
const bare = name.replace(/^skill-/, "");
|
|
39051
|
+
const dirName = `skill-${bare}`;
|
|
39052
|
+
const baseDir = options.global ? join8(homedir4(), ".hasna", "skills", "custom") : join8(process.cwd(), ".skills", "custom-skills");
|
|
39053
|
+
const skillDir = join8(baseDir, dirName);
|
|
39054
|
+
if (existsSync8(skillDir)) {
|
|
39055
|
+
if (options.json) {
|
|
39056
|
+
console.log(JSON.stringify({ error: `Skill '${bare}' already exists at ${skillDir}` }));
|
|
39057
|
+
} else {
|
|
39058
|
+
console.error(chalk2.red(`Skill '${bare}' already exists at ${skillDir}`));
|
|
39059
|
+
}
|
|
39060
|
+
process.exitCode = 1;
|
|
39061
|
+
return;
|
|
39062
|
+
}
|
|
39063
|
+
const description = options.description || `${bare} skill`;
|
|
39064
|
+
const tags = options.tags ? options.tags.split(",").map((t) => t.trim()).filter(Boolean) : [bare];
|
|
39065
|
+
const displayName = bare.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
39066
|
+
const category = options.category;
|
|
39067
|
+
mkdirSync4(join8(skillDir, "src"), { recursive: true });
|
|
39068
|
+
writeFileSync4(join8(skillDir, "SKILL.md"), [
|
|
39069
|
+
"---",
|
|
39070
|
+
`name: ${bare}`,
|
|
39071
|
+
`description: ${description}`,
|
|
39072
|
+
`displayName: ${displayName}`,
|
|
39073
|
+
`category: ${category}`,
|
|
39074
|
+
`tags: [${tags.join(", ")}]`,
|
|
39075
|
+
"---",
|
|
39076
|
+
"",
|
|
39077
|
+
`# ${displayName}`,
|
|
39078
|
+
"",
|
|
39079
|
+
description,
|
|
39080
|
+
"",
|
|
39081
|
+
"## Usage",
|
|
39082
|
+
"",
|
|
39083
|
+
"```bash",
|
|
39084
|
+
`${bare} --help`,
|
|
39085
|
+
"```",
|
|
39086
|
+
""
|
|
39087
|
+
].join(`
|
|
39088
|
+
`));
|
|
39089
|
+
writeFileSync4(join8(skillDir, "src", "index.ts"), [
|
|
39090
|
+
`#!/usr/bin/env bun`,
|
|
39091
|
+
`/**`,
|
|
39092
|
+
` * ${displayName} \u2014 ${description}`,
|
|
39093
|
+
` */`,
|
|
39094
|
+
"",
|
|
39095
|
+
`console.log("${displayName}");`,
|
|
39096
|
+
""
|
|
39097
|
+
].join(`
|
|
39098
|
+
`));
|
|
39099
|
+
writeFileSync4(join8(skillDir, "package.json"), JSON.stringify({
|
|
39100
|
+
name: `skill-${bare}`,
|
|
39101
|
+
version: "0.1.0",
|
|
39102
|
+
description,
|
|
39103
|
+
bin: { [bare]: "./src/index.ts" },
|
|
39104
|
+
scripts: { dev: `bun src/index.ts` },
|
|
39105
|
+
dependencies: {}
|
|
39106
|
+
}, null, 2) + `
|
|
39107
|
+
`);
|
|
39108
|
+
writeFileSync4(join8(skillDir, "tsconfig.json"), JSON.stringify({
|
|
39109
|
+
compilerOptions: {
|
|
39110
|
+
target: "ES2022",
|
|
39111
|
+
module: "ESNext",
|
|
39112
|
+
moduleResolution: "bundler",
|
|
39113
|
+
strict: true,
|
|
39114
|
+
outDir: "dist"
|
|
39115
|
+
},
|
|
39116
|
+
include: ["src/**/*.ts"]
|
|
39117
|
+
}, null, 2) + `
|
|
39118
|
+
`);
|
|
39119
|
+
clearRegistryCache();
|
|
39120
|
+
if (options.json) {
|
|
39121
|
+
console.log(JSON.stringify({ created: true, name: bare, path: skillDir, category, tags }));
|
|
39122
|
+
} else {
|
|
39123
|
+
console.log(chalk2.green(`\u2713 Created custom skill '${bare}' at ${skillDir}`));
|
|
39124
|
+
console.log(chalk2.dim(` Category: ${category}`));
|
|
39125
|
+
console.log(chalk2.dim(` Tags: ${tags.join(", ")}`));
|
|
39126
|
+
console.log("");
|
|
39127
|
+
console.log(` ${chalk2.cyan("Edit:")} ${join8(skillDir, "src", "index.ts")}`);
|
|
39128
|
+
console.log(` ${chalk2.cyan("Run:")} bun ${join8(skillDir, "src", "index.ts")}`);
|
|
39129
|
+
console.log(` ${chalk2.cyan("Docs:")} ${join8(skillDir, "SKILL.md")}`);
|
|
39130
|
+
}
|
|
39131
|
+
});
|
|
39132
|
+
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 ~/.hasna/skills/custom/ 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) => {
|
|
39133
|
+
const { homedir: homedir4 } = __require("os");
|
|
39134
|
+
if (!options.to && !options.from) {
|
|
39135
|
+
console.error(chalk2.red("Specify --to <agent> or --from <agent>"));
|
|
39136
|
+
process.exitCode = 1;
|
|
39137
|
+
return;
|
|
39138
|
+
}
|
|
39139
|
+
if (options.from) {
|
|
39140
|
+
const agentName = options.from;
|
|
39141
|
+
if (!AGENT_TARGETS.includes(agentName)) {
|
|
39142
|
+
console.error(chalk2.red(`Unknown agent: ${agentName}. Available: ${AGENT_TARGETS.join(", ")}`));
|
|
39143
|
+
process.exitCode = 1;
|
|
39144
|
+
return;
|
|
39145
|
+
}
|
|
39146
|
+
const agentDir = getAgentSkillsDir(agentName, options.scope);
|
|
39147
|
+
if (!existsSync8(agentDir)) {
|
|
39148
|
+
if (options.json) {
|
|
39149
|
+
console.log(JSON.stringify({ agentDir, skills: [], message: "Directory not found" }));
|
|
39150
|
+
} else {
|
|
39151
|
+
console.log(chalk2.dim(`No skills directory found at ${agentDir}`));
|
|
39152
|
+
}
|
|
39153
|
+
return;
|
|
39154
|
+
}
|
|
39155
|
+
const registry2 = loadRegistry();
|
|
39156
|
+
const registryNames = new Set(registry2.map((s) => s.name));
|
|
39157
|
+
const found = [];
|
|
39158
|
+
for (const entry of readdirSync5(agentDir, { withFileTypes: true })) {
|
|
39159
|
+
if (!entry.isDirectory())
|
|
39160
|
+
continue;
|
|
39161
|
+
const bare = entry.name.replace(/^skill-/, "");
|
|
39162
|
+
found.push({
|
|
39163
|
+
name: bare,
|
|
39164
|
+
path: join8(agentDir, entry.name),
|
|
39165
|
+
inRegistry: registryNames.has(bare)
|
|
39166
|
+
});
|
|
39167
|
+
}
|
|
39168
|
+
const unknown3 = found.filter((s) => !s.inRegistry);
|
|
39169
|
+
if (options.register && unknown3.length > 0) {
|
|
39170
|
+
const globalSkillsDir = join8(homedir4(), ".hasna", "skills", "custom");
|
|
39171
|
+
const registered = [];
|
|
39172
|
+
for (const s of unknown3) {
|
|
39173
|
+
const srcSkillMd = join8(s.path, "SKILL.md");
|
|
39174
|
+
if (!existsSync8(srcSkillMd))
|
|
39175
|
+
continue;
|
|
39176
|
+
const destDir = join8(globalSkillsDir, `skill-${s.name}`);
|
|
39177
|
+
if (!existsSync8(destDir)) {
|
|
39178
|
+
mkdirSync4(destDir, { recursive: true });
|
|
39179
|
+
}
|
|
39180
|
+
writeFileSync4(join8(destDir, "SKILL.md"), readFileSync7(srcSkillMd, "utf-8"));
|
|
39181
|
+
registered.push(s.name);
|
|
39182
|
+
}
|
|
39183
|
+
clearRegistryCache();
|
|
39184
|
+
if (options.json) {
|
|
39185
|
+
console.log(JSON.stringify({ agentDir, skills: found, registered }));
|
|
39186
|
+
} else {
|
|
39187
|
+
for (const name of registered) {
|
|
39188
|
+
console.log(chalk2.green(`\u2713 Registered '${name}' into ~/.hasna/skills/custom/ (global custom)`));
|
|
39189
|
+
}
|
|
39190
|
+
if (registered.length === 0)
|
|
39191
|
+
console.log(chalk2.dim("No new skills to register (all SKILL.md files missing)"));
|
|
39192
|
+
}
|
|
39193
|
+
return;
|
|
39194
|
+
}
|
|
39195
|
+
if (options.json) {
|
|
39196
|
+
console.log(JSON.stringify({ agentDir, skills: found }));
|
|
39197
|
+
} else {
|
|
39198
|
+
console.log(chalk2.bold(`
|
|
39199
|
+
Agent skills in ~/.${agentName}/skills/ (${found.length} found):
|
|
39200
|
+
`));
|
|
39201
|
+
for (const s of found) {
|
|
39202
|
+
const label = s.inRegistry ? chalk2.green("\u2713 in registry") : chalk2.yellow("\u2717 not in registry");
|
|
39203
|
+
console.log(` ${chalk2.cyan(s.name)} \u2014 ${label}`);
|
|
39204
|
+
}
|
|
39205
|
+
if (unknown3.length > 0) {
|
|
39206
|
+
console.log("");
|
|
39207
|
+
console.log(chalk2.dim(`Tip: ${unknown3.length} skill(s) not in registry. Run with --register to add them to ~/.hasna/skills/custom/.`));
|
|
39208
|
+
}
|
|
39209
|
+
}
|
|
39210
|
+
return;
|
|
39211
|
+
}
|
|
39212
|
+
if (options.to) {
|
|
39213
|
+
let agents;
|
|
39214
|
+
try {
|
|
39215
|
+
agents = resolveAgents(options.to);
|
|
39216
|
+
} catch (err) {
|
|
39217
|
+
console.error(chalk2.red(err.message));
|
|
39218
|
+
process.exitCode = 1;
|
|
39219
|
+
return;
|
|
39220
|
+
}
|
|
39221
|
+
const registry2 = loadRegistry();
|
|
39222
|
+
const customSkills = registry2.filter((s) => s.source === "custom");
|
|
39223
|
+
if (customSkills.length === 0) {
|
|
39224
|
+
if (options.json) {
|
|
39225
|
+
console.log(JSON.stringify({ pushed: 0, message: "No custom skills found" }));
|
|
39226
|
+
} else {
|
|
39227
|
+
console.log(chalk2.dim("No custom skills found. Use 'skills create <name>' to scaffold one."));
|
|
39228
|
+
}
|
|
39229
|
+
return;
|
|
39230
|
+
}
|
|
39231
|
+
const results = [];
|
|
39232
|
+
for (const skill of customSkills) {
|
|
39233
|
+
for (const agent of agents) {
|
|
39234
|
+
const result = installSkillForAgent(skill.name, {
|
|
39235
|
+
agent,
|
|
39236
|
+
scope: options.scope
|
|
39237
|
+
}, generateSkillMd);
|
|
39238
|
+
results.push({ skill: skill.name, agent, success: result.success, error: result.error });
|
|
39239
|
+
}
|
|
39240
|
+
}
|
|
39241
|
+
if (options.json) {
|
|
39242
|
+
console.log(JSON.stringify({ pushed: results.filter((r) => r.success).length, results }));
|
|
39243
|
+
} else {
|
|
39244
|
+
for (const r of results) {
|
|
39245
|
+
if (r.success) {
|
|
39246
|
+
console.log(chalk2.green(`\u2713 ${r.skill} \u2192 ${r.agent}`));
|
|
39247
|
+
} else {
|
|
39248
|
+
console.log(chalk2.red(`\u2717 ${r.skill} \u2192 ${r.agent}: ${r.error}`));
|
|
39249
|
+
}
|
|
39250
|
+
}
|
|
39251
|
+
}
|
|
39252
|
+
}
|
|
39253
|
+
});
|
|
39254
|
+
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) => {
|
|
39255
|
+
const skillPath = getSkillPath(name);
|
|
39256
|
+
const issues = [];
|
|
39257
|
+
if (!existsSync8(skillPath)) {
|
|
39258
|
+
if (options.json) {
|
|
39259
|
+
console.log(JSON.stringify({ name, valid: false, issues: [`Skill directory not found: ${skillPath}`] }));
|
|
39260
|
+
} else {
|
|
39261
|
+
console.error(chalk2.red(`Skill '${name}' not found at ${skillPath}`));
|
|
39262
|
+
}
|
|
39263
|
+
process.exitCode = 1;
|
|
39264
|
+
return;
|
|
39265
|
+
}
|
|
39266
|
+
if (!existsSync8(join8(skillPath, "SKILL.md")))
|
|
39267
|
+
issues.push("Missing SKILL.md");
|
|
39268
|
+
if (!existsSync8(join8(skillPath, "tsconfig.json")))
|
|
39269
|
+
issues.push("Missing tsconfig.json");
|
|
39270
|
+
const pkgPath = join8(skillPath, "package.json");
|
|
39271
|
+
if (!existsSync8(pkgPath)) {
|
|
39272
|
+
issues.push("Missing package.json");
|
|
39273
|
+
} else {
|
|
39274
|
+
try {
|
|
39275
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
39276
|
+
if (!pkg.bin || Object.keys(pkg.bin).length === 0)
|
|
39277
|
+
issues.push("package.json missing 'bin' entry");
|
|
39278
|
+
} catch {
|
|
39279
|
+
issues.push("package.json is invalid JSON");
|
|
39280
|
+
}
|
|
39281
|
+
}
|
|
39282
|
+
if (!existsSync8(join8(skillPath, "src"))) {
|
|
39283
|
+
issues.push("Missing src/ directory");
|
|
39284
|
+
} else if (!existsSync8(join8(skillPath, "src", "index.ts"))) {
|
|
39285
|
+
issues.push("Missing src/index.ts");
|
|
39286
|
+
}
|
|
39287
|
+
const valid = issues.length === 0;
|
|
39288
|
+
if (options.json) {
|
|
39289
|
+
console.log(JSON.stringify({ name, valid, path: skillPath, issues }));
|
|
39290
|
+
} else if (valid) {
|
|
39291
|
+
console.log(chalk2.green(`\u2713 ${name} \u2014 all checks passed`));
|
|
39292
|
+
} else {
|
|
39293
|
+
console.log(chalk2.red(`\u2717 ${name} \u2014 ${issues.length} issue(s):`));
|
|
39294
|
+
for (const issue2 of issues)
|
|
39295
|
+
console.log(chalk2.red(` \u2022 ${issue2}`));
|
|
39296
|
+
process.exitCode = 1;
|
|
39297
|
+
}
|
|
39298
|
+
});
|
|
39299
|
+
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) => {
|
|
39300
|
+
const bare = name.replace(/^skill-/, "");
|
|
39301
|
+
const skillName = `skill-${bare}`;
|
|
39302
|
+
const destPath = join8(process.cwd(), ".skills", skillName);
|
|
39303
|
+
const sourcePath = getSkillPath(bare);
|
|
39304
|
+
if (!existsSync8(sourcePath)) {
|
|
39305
|
+
if (options.json) {
|
|
39306
|
+
console.log(JSON.stringify({ error: `Skill '${bare}' not found in registry` }));
|
|
39307
|
+
} else {
|
|
39308
|
+
skillNotFound(bare);
|
|
39309
|
+
}
|
|
39310
|
+
process.exitCode = 1;
|
|
39311
|
+
return;
|
|
39312
|
+
}
|
|
39313
|
+
if (!existsSync8(destPath)) {
|
|
39314
|
+
if (options.json) {
|
|
39315
|
+
console.log(JSON.stringify({ installed: false, message: `'${bare}' is not installed locally` }));
|
|
39316
|
+
} else {
|
|
39317
|
+
console.log(chalk2.dim(`'${bare}' is not installed. Run: skills install ${bare}`));
|
|
39318
|
+
}
|
|
39319
|
+
return;
|
|
39320
|
+
}
|
|
39321
|
+
function collectFiles(dir, base = "") {
|
|
39322
|
+
const files = new Map;
|
|
39323
|
+
if (!existsSync8(dir))
|
|
39324
|
+
return files;
|
|
39325
|
+
for (const entry of readdirSync5(dir)) {
|
|
39326
|
+
const full = join8(dir, entry);
|
|
39327
|
+
const rel = base ? `${base}/${entry}` : entry;
|
|
39328
|
+
if (statSync3(full).isDirectory()) {
|
|
39329
|
+
for (const [k, v] of collectFiles(full, rel))
|
|
39330
|
+
files.set(k, v);
|
|
39331
|
+
} else {
|
|
39332
|
+
try {
|
|
39333
|
+
files.set(rel, readFileSync7(full, "utf-8"));
|
|
39334
|
+
} catch {
|
|
39335
|
+
files.set(rel, "");
|
|
39336
|
+
}
|
|
39337
|
+
}
|
|
39338
|
+
}
|
|
39339
|
+
return files;
|
|
39340
|
+
}
|
|
39341
|
+
const installed = collectFiles(destPath);
|
|
39342
|
+
const source = collectFiles(sourcePath);
|
|
39343
|
+
const changed = [];
|
|
39344
|
+
const added = [];
|
|
39345
|
+
const removed = [];
|
|
39346
|
+
for (const [file2, content] of source) {
|
|
39347
|
+
if (!installed.has(file2))
|
|
39348
|
+
added.push(file2);
|
|
39349
|
+
else if (installed.get(file2) !== content)
|
|
39350
|
+
changed.push(file2);
|
|
39351
|
+
}
|
|
39352
|
+
for (const file2 of installed.keys()) {
|
|
39353
|
+
if (!source.has(file2))
|
|
39354
|
+
removed.push(file2);
|
|
39355
|
+
}
|
|
39356
|
+
if (options.json) {
|
|
39357
|
+
console.log(JSON.stringify({ name: bare, changed, added, removed, upToDate: changed.length === 0 && added.length === 0 && removed.length === 0 }));
|
|
39358
|
+
return;
|
|
39359
|
+
}
|
|
39360
|
+
if (changed.length === 0 && added.length === 0 && removed.length === 0) {
|
|
39361
|
+
console.log(chalk2.green(`\u2713 ${bare} \u2014 up to date`));
|
|
39362
|
+
return;
|
|
39363
|
+
}
|
|
39364
|
+
console.log(chalk2.bold(`
|
|
39365
|
+
Diff for '${bare}':
|
|
39366
|
+
`));
|
|
39367
|
+
for (const f of changed)
|
|
39368
|
+
console.log(chalk2.yellow(` ~ ${f}`));
|
|
39369
|
+
for (const f of added)
|
|
39370
|
+
console.log(chalk2.green(` + ${f}`));
|
|
39371
|
+
for (const f of removed)
|
|
39372
|
+
console.log(chalk2.red(` - ${f}`));
|
|
39373
|
+
console.log(`
|
|
39374
|
+
${chalk2.dim(`Run 'skills update ${bare}' to apply changes`)}`);
|
|
39375
|
+
});
|
|
39376
|
+
var scheduleCmd = program2.command("schedule").description("Manage scheduled skill runs (cron-based)");
|
|
39377
|
+
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) => {
|
|
39378
|
+
const args = options.args ? options.args.split(" ").filter(Boolean) : undefined;
|
|
39379
|
+
const { schedule, error: error48 } = addSchedule(skill, cron, { name: options.name, args });
|
|
39380
|
+
if (options.json) {
|
|
39381
|
+
console.log(JSON.stringify(schedule ? { schedule } : { error: error48 }));
|
|
39382
|
+
return;
|
|
39383
|
+
}
|
|
39384
|
+
if (error48 || !schedule) {
|
|
39385
|
+
console.error(chalk2.red(`\u2717 ${error48 || "Failed to add schedule"}`));
|
|
39386
|
+
process.exitCode = 1;
|
|
39387
|
+
return;
|
|
39388
|
+
}
|
|
39389
|
+
console.log(chalk2.green(`\u2713 Scheduled '${schedule.name}'`));
|
|
39390
|
+
console.log(chalk2.dim(` Cron: ${schedule.cron}`));
|
|
39391
|
+
if (schedule.nextRun) {
|
|
39392
|
+
console.log(chalk2.dim(` Next run: ${new Date(schedule.nextRun).toLocaleString()}`));
|
|
39393
|
+
}
|
|
39394
|
+
console.log(chalk2.dim(` ID: ${schedule.id}`));
|
|
39395
|
+
});
|
|
39396
|
+
scheduleCmd.command("list").option("--json", "Output as JSON", false).description("List all scheduled skills").action((options) => {
|
|
39397
|
+
const schedules = listSchedules();
|
|
39398
|
+
if (options.json) {
|
|
39399
|
+
console.log(JSON.stringify(schedules));
|
|
39400
|
+
return;
|
|
39401
|
+
}
|
|
39402
|
+
if (schedules.length === 0) {
|
|
39403
|
+
console.log(chalk2.dim("No schedules. Run: skills schedule add <skill> <cron>"));
|
|
39404
|
+
return;
|
|
39405
|
+
}
|
|
39406
|
+
console.log(chalk2.bold(`
|
|
39407
|
+
Scheduled skills (${schedules.length}):
|
|
39408
|
+
`));
|
|
39409
|
+
for (const s of schedules) {
|
|
39410
|
+
const status = s.enabled ? chalk2.green("enabled") : chalk2.dim("disabled");
|
|
39411
|
+
const last = s.lastRun ? `last: ${new Date(s.lastRun).toLocaleString()} [${s.lastRunStatus ?? "?"}]` : "never run";
|
|
39412
|
+
const next = s.nextRun ? `next: ${new Date(s.nextRun).toLocaleString()}` : "";
|
|
39413
|
+
console.log(` ${chalk2.cyan(s.name)} [${status}]`);
|
|
39414
|
+
console.log(chalk2.dim(` skill: ${s.skill} cron: ${s.cron} ${last} ${next}`));
|
|
39415
|
+
}
|
|
39416
|
+
});
|
|
39417
|
+
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) => {
|
|
39418
|
+
const removed = removeSchedule(idOrName);
|
|
39419
|
+
if (options.json) {
|
|
39420
|
+
console.log(JSON.stringify({ removed, idOrName }));
|
|
39421
|
+
return;
|
|
39422
|
+
}
|
|
39423
|
+
if (removed) {
|
|
39424
|
+
console.log(chalk2.green(`\u2713 Removed schedule '${idOrName}'`));
|
|
39425
|
+
} else {
|
|
39426
|
+
console.error(chalk2.red(`Schedule '${idOrName}' not found`));
|
|
39427
|
+
process.exitCode = 1;
|
|
39428
|
+
}
|
|
39429
|
+
});
|
|
39430
|
+
scheduleCmd.command("enable").argument("<id-or-name>", "Schedule ID or name").description("Enable a disabled schedule").action((idOrName) => {
|
|
39431
|
+
const ok = setScheduleEnabled(idOrName, true);
|
|
39432
|
+
if (ok)
|
|
39433
|
+
console.log(chalk2.green(`\u2713 Enabled '${idOrName}'`));
|
|
39434
|
+
else {
|
|
39435
|
+
console.error(chalk2.red(`Schedule '${idOrName}' not found`));
|
|
39436
|
+
process.exitCode = 1;
|
|
39437
|
+
}
|
|
39438
|
+
});
|
|
39439
|
+
scheduleCmd.command("disable").argument("<id-or-name>", "Schedule ID or name").description("Disable a schedule without removing it").action((idOrName) => {
|
|
39440
|
+
const ok = setScheduleEnabled(idOrName, false);
|
|
39441
|
+
if (ok)
|
|
39442
|
+
console.log(chalk2.green(`\u2713 Disabled '${idOrName}'`));
|
|
39443
|
+
else {
|
|
39444
|
+
console.error(chalk2.red(`Schedule '${idOrName}' not found`));
|
|
39445
|
+
process.exitCode = 1;
|
|
39446
|
+
}
|
|
39447
|
+
});
|
|
39448
|
+
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) => {
|
|
39449
|
+
const due = getDueSchedules();
|
|
39450
|
+
if (due.length === 0) {
|
|
39451
|
+
if (options.json)
|
|
39452
|
+
console.log(JSON.stringify({ ran: 0, schedules: [] }));
|
|
39453
|
+
else
|
|
39454
|
+
console.log(chalk2.dim("No schedules are due."));
|
|
39455
|
+
return;
|
|
39456
|
+
}
|
|
39457
|
+
if (options.dryRun) {
|
|
39458
|
+
if (options.json)
|
|
39459
|
+
console.log(JSON.stringify({ due: due.map((s) => s.name) }));
|
|
39460
|
+
else {
|
|
39461
|
+
console.log(chalk2.bold(`${due.length} schedule(s) due:
|
|
39462
|
+
`));
|
|
39463
|
+
for (const s of due)
|
|
39464
|
+
console.log(` ${chalk2.cyan(s.name)} \u2014 ${s.skill} (${s.cron})`);
|
|
39465
|
+
}
|
|
39466
|
+
return;
|
|
39467
|
+
}
|
|
39468
|
+
const results = [];
|
|
39469
|
+
for (const s of due) {
|
|
39470
|
+
try {
|
|
39471
|
+
const { runSkill: runSkill2 } = await Promise.resolve().then(() => (init_skillinfo(), exports_skillinfo));
|
|
39472
|
+
await runSkill2(s.skill, s.args ?? []);
|
|
39473
|
+
recordScheduleRun(s.id, "success");
|
|
39474
|
+
results.push({ name: s.name, skill: s.skill, status: "success" });
|
|
39475
|
+
} catch (err) {
|
|
39476
|
+
recordScheduleRun(s.id, "error");
|
|
39477
|
+
results.push({ name: s.name, skill: s.skill, status: "error", error: err.message });
|
|
39478
|
+
}
|
|
39479
|
+
}
|
|
39480
|
+
if (options.json) {
|
|
39481
|
+
console.log(JSON.stringify({ ran: results.length, results }));
|
|
39482
|
+
} else {
|
|
39483
|
+
for (const r of results) {
|
|
39484
|
+
const icon = r.status === "success" ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
39485
|
+
console.log(`${icon} ${r.name} (${r.skill})`);
|
|
39486
|
+
if (r.error)
|
|
39487
|
+
console.log(chalk2.dim(` ${r.error}`));
|
|
39488
|
+
}
|
|
39489
|
+
}
|
|
39490
|
+
});
|
|
39491
|
+
scheduleCmd.command("validate").argument("<cron>", "Cron expression to validate").description("Validate a cron expression and show the next 5 run times").action((cron) => {
|
|
39492
|
+
const { getNextRun: getNextRun2 } = (init_scheduler(), __toCommonJS(exports_scheduler));
|
|
39493
|
+
const { valid, error: error48 } = validateCron(cron);
|
|
39494
|
+
if (!valid) {
|
|
39495
|
+
console.error(chalk2.red(`Invalid cron: ${error48}`));
|
|
39496
|
+
process.exitCode = 1;
|
|
39497
|
+
return;
|
|
39498
|
+
}
|
|
39499
|
+
console.log(chalk2.green(`\u2713 Valid cron: "${cron}"`));
|
|
39500
|
+
console.log(chalk2.dim(`
|
|
39501
|
+
Next 5 run times:`));
|
|
39502
|
+
let d = new Date;
|
|
39503
|
+
for (let i = 0;i < 5; i++) {
|
|
39504
|
+
const next = getNextRun2(cron, d);
|
|
39505
|
+
if (!next)
|
|
39506
|
+
break;
|
|
39507
|
+
console.log(` ${next.toLocaleString()}`);
|
|
39508
|
+
d = next;
|
|
39509
|
+
}
|
|
38518
39510
|
});
|
|
38519
39511
|
program2.parse();
|