@hasna/skills 0.1.7 → 0.1.8
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 +673 -41
- package/bin/mcp.js +140 -1
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -1878,7 +1878,7 @@ var package_default;
|
|
|
1878
1878
|
var init_package = __esm(() => {
|
|
1879
1879
|
package_default = {
|
|
1880
1880
|
name: "@hasna/skills",
|
|
1881
|
-
version: "0.1.
|
|
1881
|
+
version: "0.1.8",
|
|
1882
1882
|
description: "Skills library for AI coding agents",
|
|
1883
1883
|
type: "module",
|
|
1884
1884
|
bin: {
|
|
@@ -34782,6 +34782,9 @@ var init_stdio2 = __esm(() => {
|
|
|
34782
34782
|
|
|
34783
34783
|
// src/mcp/index.ts
|
|
34784
34784
|
var exports_mcp = {};
|
|
34785
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
34786
|
+
import { join as join3 } from "path";
|
|
34787
|
+
import { homedir as homedir2 } from "os";
|
|
34785
34788
|
async function main() {
|
|
34786
34789
|
const transport = new StdioServerTransport;
|
|
34787
34790
|
await server.connect(transport);
|
|
@@ -34883,6 +34886,52 @@ var init_mcp2 = __esm(() => {
|
|
|
34883
34886
|
isError: !result.success
|
|
34884
34887
|
};
|
|
34885
34888
|
});
|
|
34889
|
+
server.registerTool("install_category", {
|
|
34890
|
+
title: "Install Category",
|
|
34891
|
+
description: "Install all skills in a category. Optionally install for a specific agent (claude, codex, gemini, or all) with a given scope.",
|
|
34892
|
+
inputSchema: {
|
|
34893
|
+
category: exports_external.string().describe("Category name (case-insensitive, e.g. 'Event Management')"),
|
|
34894
|
+
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
34895
|
+
scope: exports_external.string().optional().describe("Install scope: global or project (default: global)")
|
|
34896
|
+
}
|
|
34897
|
+
}, async ({ category, for: agentArg, scope }) => {
|
|
34898
|
+
const matchedCategory = CATEGORIES.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
34899
|
+
if (!matchedCategory) {
|
|
34900
|
+
return {
|
|
34901
|
+
content: [{ type: "text", text: `Unknown category: ${category}. Available: ${CATEGORIES.join(", ")}` }],
|
|
34902
|
+
isError: true
|
|
34903
|
+
};
|
|
34904
|
+
}
|
|
34905
|
+
const categorySkills = getSkillsByCategory(matchedCategory);
|
|
34906
|
+
const names = categorySkills.map((s) => s.name);
|
|
34907
|
+
if (agentArg) {
|
|
34908
|
+
let agents;
|
|
34909
|
+
try {
|
|
34910
|
+
agents = resolveAgents(agentArg);
|
|
34911
|
+
} catch (err) {
|
|
34912
|
+
return {
|
|
34913
|
+
content: [{ type: "text", text: err.message }],
|
|
34914
|
+
isError: true
|
|
34915
|
+
};
|
|
34916
|
+
}
|
|
34917
|
+
const results2 = [];
|
|
34918
|
+
for (const name of names) {
|
|
34919
|
+
for (const a of agents) {
|
|
34920
|
+
const r = installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd);
|
|
34921
|
+
results2.push({ ...r, agent: a, scope: scope || "global" });
|
|
34922
|
+
}
|
|
34923
|
+
}
|
|
34924
|
+
return {
|
|
34925
|
+
content: [{ type: "text", text: JSON.stringify({ category: matchedCategory, count: names.length, results: results2 }, null, 2) }],
|
|
34926
|
+
isError: results2.some((r) => !r.success)
|
|
34927
|
+
};
|
|
34928
|
+
}
|
|
34929
|
+
const results = names.map((name) => installSkill(name));
|
|
34930
|
+
return {
|
|
34931
|
+
content: [{ type: "text", text: JSON.stringify({ category: matchedCategory, count: names.length, results }, null, 2) }],
|
|
34932
|
+
isError: results.some((r) => !r.success)
|
|
34933
|
+
};
|
|
34934
|
+
});
|
|
34886
34935
|
server.registerTool("remove_skill", {
|
|
34887
34936
|
title: "Remove Skill",
|
|
34888
34937
|
description: "Remove an installed skill. Without --for, removes from .skills/. With --for, removes from agent skill directory.",
|
|
@@ -34975,6 +35024,94 @@ var init_mcp2 = __esm(() => {
|
|
|
34975
35024
|
content: [{ type: "text", text: JSON.stringify({ exitCode: result.exitCode, skill: name }, null, 2) }]
|
|
34976
35025
|
};
|
|
34977
35026
|
});
|
|
35027
|
+
server.registerTool("export_skills", {
|
|
35028
|
+
title: "Export Skills",
|
|
35029
|
+
description: "Export the list of currently installed skills as a JSON payload that can be imported elsewhere"
|
|
35030
|
+
}, async () => {
|
|
35031
|
+
const skills = getInstalledSkills();
|
|
35032
|
+
const payload = {
|
|
35033
|
+
version: 1,
|
|
35034
|
+
skills,
|
|
35035
|
+
timestamp: new Date().toISOString()
|
|
35036
|
+
};
|
|
35037
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
35038
|
+
});
|
|
35039
|
+
server.registerTool("import_skills", {
|
|
35040
|
+
title: "Import Skills",
|
|
35041
|
+
description: "Install a list of skills from an export payload. Supports agent-specific installs via the 'for' parameter.",
|
|
35042
|
+
inputSchema: {
|
|
35043
|
+
skills: exports_external.array(exports_external.string()).describe("List of skill names to install"),
|
|
35044
|
+
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
35045
|
+
scope: exports_external.string().optional().describe("Install scope: global or project (default: global)")
|
|
35046
|
+
}
|
|
35047
|
+
}, async ({ skills: skillList, for: agentArg, scope }) => {
|
|
35048
|
+
if (!skillList || skillList.length === 0) {
|
|
35049
|
+
return { content: [{ type: "text", text: JSON.stringify({ imported: 0, results: [] }, null, 2) }] };
|
|
35050
|
+
}
|
|
35051
|
+
const results = [];
|
|
35052
|
+
if (agentArg) {
|
|
35053
|
+
let agents;
|
|
35054
|
+
try {
|
|
35055
|
+
agents = resolveAgents(agentArg);
|
|
35056
|
+
} catch (err) {
|
|
35057
|
+
return {
|
|
35058
|
+
content: [{ type: "text", text: err.message }],
|
|
35059
|
+
isError: true
|
|
35060
|
+
};
|
|
35061
|
+
}
|
|
35062
|
+
for (const name of skillList) {
|
|
35063
|
+
const agentResults = agents.map((a) => installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd));
|
|
35064
|
+
const success2 = agentResults.every((r) => r.success);
|
|
35065
|
+
const errors4 = agentResults.filter((r) => !r.success).map((r) => r.error).filter(Boolean);
|
|
35066
|
+
results.push({ skill: name, success: success2, ...errors4.length > 0 ? { error: errors4.join("; ") } : {} });
|
|
35067
|
+
}
|
|
35068
|
+
} else {
|
|
35069
|
+
for (const name of skillList) {
|
|
35070
|
+
const result = installSkill(name);
|
|
35071
|
+
results.push({ skill: result.skill, success: result.success, ...result.error ? { error: result.error } : {} });
|
|
35072
|
+
}
|
|
35073
|
+
}
|
|
35074
|
+
const imported = results.filter((r) => r.success).length;
|
|
35075
|
+
const hasErrors = results.some((r) => !r.success);
|
|
35076
|
+
return {
|
|
35077
|
+
content: [{ type: "text", text: JSON.stringify({ imported, total: skillList.length, results }, null, 2) }],
|
|
35078
|
+
isError: hasErrors
|
|
35079
|
+
};
|
|
35080
|
+
});
|
|
35081
|
+
server.registerTool("whoami", {
|
|
35082
|
+
title: "Skills Whoami",
|
|
35083
|
+
description: "Show setup summary: package version, installed skills, agent configurations, skills directory location, and working directory"
|
|
35084
|
+
}, async () => {
|
|
35085
|
+
const version2 = package_default.version;
|
|
35086
|
+
const cwd = process.cwd();
|
|
35087
|
+
const installed = getInstalledSkills();
|
|
35088
|
+
const agentNames = ["claude", "codex", "gemini"];
|
|
35089
|
+
const agents = [];
|
|
35090
|
+
for (const agent of agentNames) {
|
|
35091
|
+
const agentSkillsPath = join3(homedir2(), `.${agent}`, "skills");
|
|
35092
|
+
const exists = existsSync3(agentSkillsPath);
|
|
35093
|
+
let skillCount = 0;
|
|
35094
|
+
if (exists) {
|
|
35095
|
+
try {
|
|
35096
|
+
skillCount = readdirSync3(agentSkillsPath).filter((f) => {
|
|
35097
|
+
const full = join3(agentSkillsPath, f);
|
|
35098
|
+
return f.startsWith("skill-") && statSync2(full).isDirectory();
|
|
35099
|
+
}).length;
|
|
35100
|
+
} catch {}
|
|
35101
|
+
}
|
|
35102
|
+
agents.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
35103
|
+
}
|
|
35104
|
+
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
35105
|
+
const result = {
|
|
35106
|
+
version: version2,
|
|
35107
|
+
installedCount: installed.length,
|
|
35108
|
+
installed,
|
|
35109
|
+
agents,
|
|
35110
|
+
skillsDir,
|
|
35111
|
+
cwd
|
|
35112
|
+
};
|
|
35113
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
35114
|
+
});
|
|
34978
35115
|
server.registerResource("Skills Registry", "skills://registry", {
|
|
34979
35116
|
description: "Full list of all available skills as JSON"
|
|
34980
35117
|
}, async () => ({
|
|
@@ -35010,15 +35147,15 @@ __export(exports_serve, {
|
|
|
35010
35147
|
startServer: () => startServer,
|
|
35011
35148
|
createFetchHandler: () => createFetchHandler
|
|
35012
35149
|
});
|
|
35013
|
-
import { existsSync as
|
|
35014
|
-
import { join as
|
|
35150
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
35151
|
+
import { join as join4, dirname as dirname2, extname } from "path";
|
|
35015
35152
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
35016
35153
|
function getPackageJson() {
|
|
35017
35154
|
try {
|
|
35018
35155
|
const scriptDir = dirname2(fileURLToPath2(import.meta.url));
|
|
35019
35156
|
for (const rel of ["../..", ".."]) {
|
|
35020
|
-
const pkgPath =
|
|
35021
|
-
if (
|
|
35157
|
+
const pkgPath = join4(scriptDir, rel, "package.json");
|
|
35158
|
+
if (existsSync4(pkgPath)) {
|
|
35022
35159
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
35023
35160
|
return { version: pkg.version || "unknown", name: pkg.name || "skills" };
|
|
35024
35161
|
}
|
|
@@ -35030,20 +35167,20 @@ function resolveDashboardDir() {
|
|
|
35030
35167
|
const candidates = [];
|
|
35031
35168
|
try {
|
|
35032
35169
|
const scriptDir = dirname2(fileURLToPath2(import.meta.url));
|
|
35033
|
-
candidates.push(
|
|
35034
|
-
candidates.push(
|
|
35170
|
+
candidates.push(join4(scriptDir, "..", "dashboard", "dist"));
|
|
35171
|
+
candidates.push(join4(scriptDir, "..", "..", "dashboard", "dist"));
|
|
35035
35172
|
} catch {}
|
|
35036
35173
|
if (process.argv[1]) {
|
|
35037
35174
|
const mainDir = dirname2(process.argv[1]);
|
|
35038
|
-
candidates.push(
|
|
35039
|
-
candidates.push(
|
|
35175
|
+
candidates.push(join4(mainDir, "..", "dashboard", "dist"));
|
|
35176
|
+
candidates.push(join4(mainDir, "..", "..", "dashboard", "dist"));
|
|
35040
35177
|
}
|
|
35041
|
-
candidates.push(
|
|
35178
|
+
candidates.push(join4(process.cwd(), "dashboard", "dist"));
|
|
35042
35179
|
for (const candidate of candidates) {
|
|
35043
|
-
if (
|
|
35180
|
+
if (existsSync4(candidate))
|
|
35044
35181
|
return candidate;
|
|
35045
35182
|
}
|
|
35046
|
-
return
|
|
35183
|
+
return join4(process.cwd(), "dashboard", "dist");
|
|
35047
35184
|
}
|
|
35048
35185
|
function json2(data, status = 200) {
|
|
35049
35186
|
return new Response(JSON.stringify(data), {
|
|
@@ -35077,7 +35214,7 @@ function getAllSkillsWithStatus() {
|
|
|
35077
35214
|
});
|
|
35078
35215
|
}
|
|
35079
35216
|
function serveStaticFile(filePath) {
|
|
35080
|
-
if (!
|
|
35217
|
+
if (!existsSync4(filePath))
|
|
35081
35218
|
return null;
|
|
35082
35219
|
const ext = extname(filePath);
|
|
35083
35220
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -35087,7 +35224,7 @@ function serveStaticFile(filePath) {
|
|
|
35087
35224
|
}
|
|
35088
35225
|
function createFetchHandler(options) {
|
|
35089
35226
|
const dashboardDir = options?.dashboardDir ?? resolveDashboardDir();
|
|
35090
|
-
const dashboardExists = options?.dashboardExists ??
|
|
35227
|
+
const dashboardExists = options?.dashboardExists ?? existsSync4(dashboardDir);
|
|
35091
35228
|
return async function fetchHandler(req) {
|
|
35092
35229
|
const url2 = new URL(req.url);
|
|
35093
35230
|
const path = url2.pathname;
|
|
@@ -35201,6 +35338,43 @@ function createFetchHandler(options) {
|
|
|
35201
35338
|
return json2(result, result.success ? 200 : 400);
|
|
35202
35339
|
}
|
|
35203
35340
|
}
|
|
35341
|
+
if (path === "/api/skills/install-category" && method === "POST") {
|
|
35342
|
+
let body = {};
|
|
35343
|
+
try {
|
|
35344
|
+
const text = await req.text();
|
|
35345
|
+
if (text)
|
|
35346
|
+
body = JSON.parse(text);
|
|
35347
|
+
} catch {}
|
|
35348
|
+
if (!body.category) {
|
|
35349
|
+
return json2({ error: "Missing required field: category" }, 400);
|
|
35350
|
+
}
|
|
35351
|
+
const matchedCategory = CATEGORIES.find((c) => c.toLowerCase() === body.category.toLowerCase());
|
|
35352
|
+
if (!matchedCategory) {
|
|
35353
|
+
return json2({ error: `Unknown category: ${body.category}. Available: ${CATEGORIES.join(", ")}` }, 400);
|
|
35354
|
+
}
|
|
35355
|
+
const categorySkills = getSkillsByCategory(matchedCategory);
|
|
35356
|
+
const names = categorySkills.map((s) => s.name);
|
|
35357
|
+
if (body.for) {
|
|
35358
|
+
try {
|
|
35359
|
+
const agents = resolveAgents(body.for);
|
|
35360
|
+
const scope = body.scope === "project" ? "project" : "global";
|
|
35361
|
+
const results = [];
|
|
35362
|
+
for (const name of names) {
|
|
35363
|
+
for (const agent of agents) {
|
|
35364
|
+
results.push(installSkillForAgent(name, { agent, scope }, generateSkillMd));
|
|
35365
|
+
}
|
|
35366
|
+
}
|
|
35367
|
+
const allSuccess = results.every((r) => r.success);
|
|
35368
|
+
return json2({ category: matchedCategory, count: names.length, success: allSuccess, results }, allSuccess ? 200 : 207);
|
|
35369
|
+
} catch (e) {
|
|
35370
|
+
return json2({ success: false, error: e instanceof Error ? e.message : "Unknown error" }, 400);
|
|
35371
|
+
}
|
|
35372
|
+
} else {
|
|
35373
|
+
const results = names.map((name) => installSkill(name));
|
|
35374
|
+
const allSuccess = results.every((r) => r.success);
|
|
35375
|
+
return json2({ category: matchedCategory, count: names.length, success: allSuccess, results }, allSuccess ? 200 : 207);
|
|
35376
|
+
}
|
|
35377
|
+
}
|
|
35204
35378
|
const removeMatch = path.match(/^\/api\/skills\/([^/]+)\/remove$/);
|
|
35205
35379
|
if (removeMatch && method === "POST") {
|
|
35206
35380
|
const name = removeMatch[1];
|
|
@@ -35213,6 +35387,50 @@ function createFetchHandler(options) {
|
|
|
35213
35387
|
const pkg = getPackageJson();
|
|
35214
35388
|
return json2({ version: pkg.version, name: pkg.name });
|
|
35215
35389
|
}
|
|
35390
|
+
if (path === "/api/export" && method === "GET") {
|
|
35391
|
+
const skills = getInstalledSkills();
|
|
35392
|
+
return json2({
|
|
35393
|
+
version: 1,
|
|
35394
|
+
skills,
|
|
35395
|
+
timestamp: new Date().toISOString()
|
|
35396
|
+
});
|
|
35397
|
+
}
|
|
35398
|
+
if (path === "/api/import" && method === "POST") {
|
|
35399
|
+
let body = {};
|
|
35400
|
+
try {
|
|
35401
|
+
const text = await req.text();
|
|
35402
|
+
if (text)
|
|
35403
|
+
body = JSON.parse(text);
|
|
35404
|
+
} catch {
|
|
35405
|
+
return json2({ error: "Invalid JSON body" }, 400);
|
|
35406
|
+
}
|
|
35407
|
+
if (!Array.isArray(body.skills)) {
|
|
35408
|
+
return json2({ error: 'Body must include "skills" array' }, 400);
|
|
35409
|
+
}
|
|
35410
|
+
const skillList = body.skills;
|
|
35411
|
+
const results = [];
|
|
35412
|
+
if (body.for) {
|
|
35413
|
+
try {
|
|
35414
|
+
const agents = resolveAgents(body.for);
|
|
35415
|
+
const scope = body.scope === "project" ? "project" : "global";
|
|
35416
|
+
for (const name of skillList) {
|
|
35417
|
+
const agentResults = agents.map((agent) => installSkillForAgent(name, { agent, scope }, generateSkillMd));
|
|
35418
|
+
const success2 = agentResults.every((r) => r.success);
|
|
35419
|
+
const errors4 = agentResults.filter((r) => !r.success).map((r) => r.error).filter(Boolean);
|
|
35420
|
+
results.push({ skill: name, success: success2, ...errors4.length > 0 ? { error: errors4.join("; ") } : {} });
|
|
35421
|
+
}
|
|
35422
|
+
} catch (e) {
|
|
35423
|
+
return json2({ error: e instanceof Error ? e.message : "Unknown error" }, 400);
|
|
35424
|
+
}
|
|
35425
|
+
} else {
|
|
35426
|
+
for (const name of skillList) {
|
|
35427
|
+
const result = installSkill(name);
|
|
35428
|
+
results.push({ skill: result.skill, success: result.success, ...result.error ? { error: result.error } : {} });
|
|
35429
|
+
}
|
|
35430
|
+
}
|
|
35431
|
+
const imported = results.filter((r) => r.success).length;
|
|
35432
|
+
return json2({ imported, total: skillList.length, results }, imported === skillList.length ? 200 : 207);
|
|
35433
|
+
}
|
|
35216
35434
|
if (path === "/api/self-update" && method === "POST") {
|
|
35217
35435
|
try {
|
|
35218
35436
|
const pkg = getPackageJson();
|
|
@@ -35242,12 +35460,12 @@ function createFetchHandler(options) {
|
|
|
35242
35460
|
}
|
|
35243
35461
|
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
35244
35462
|
if (path !== "/") {
|
|
35245
|
-
const filePath =
|
|
35463
|
+
const filePath = join4(dashboardDir, path);
|
|
35246
35464
|
const res2 = serveStaticFile(filePath);
|
|
35247
35465
|
if (res2)
|
|
35248
35466
|
return res2;
|
|
35249
35467
|
}
|
|
35250
|
-
const indexPath =
|
|
35468
|
+
const indexPath = join4(dashboardDir, "index.html");
|
|
35251
35469
|
const res = serveStaticFile(indexPath);
|
|
35252
35470
|
if (res)
|
|
35253
35471
|
return res;
|
|
@@ -35258,7 +35476,7 @@ function createFetchHandler(options) {
|
|
|
35258
35476
|
async function startServer(port = 0, options) {
|
|
35259
35477
|
const shouldOpen = options?.open ?? true;
|
|
35260
35478
|
const dashboardDir = resolveDashboardDir();
|
|
35261
|
-
const dashboardExists =
|
|
35479
|
+
const dashboardExists = existsSync4(dashboardDir);
|
|
35262
35480
|
if (!dashboardExists) {
|
|
35263
35481
|
console.error(`
|
|
35264
35482
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -35338,8 +35556,8 @@ var {
|
|
|
35338
35556
|
// src/cli/index.tsx
|
|
35339
35557
|
init_package();
|
|
35340
35558
|
import chalk2 from "chalk";
|
|
35341
|
-
import { existsSync as
|
|
35342
|
-
import { join as
|
|
35559
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync2, appendFileSync, readFileSync as readFileSync4, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
|
|
35560
|
+
import { join as join5 } from "path";
|
|
35343
35561
|
|
|
35344
35562
|
// src/cli/components/App.tsx
|
|
35345
35563
|
import { useState as useState7 } from "react";
|
|
@@ -36474,7 +36692,28 @@ program2.command("interactive", { isDefault: true }).alias("i").description("Int
|
|
|
36474
36692
|
}
|
|
36475
36693
|
render(/* @__PURE__ */ jsxDEV7(App, {}, undefined, false, undefined, this));
|
|
36476
36694
|
});
|
|
36477
|
-
program2.command("install").alias("add").argument("
|
|
36695
|
+
program2.command("install").alias("add").argument("[skills...]", "Skills to install").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) => {
|
|
36696
|
+
if (skills.length === 0 && !options.category) {
|
|
36697
|
+
console.error("error: missing required argument 'skills' or --category option");
|
|
36698
|
+
process.exitCode = 1;
|
|
36699
|
+
return;
|
|
36700
|
+
}
|
|
36701
|
+
if (options.category) {
|
|
36702
|
+
const matchedCategory = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
36703
|
+
if (!matchedCategory) {
|
|
36704
|
+
console.error(`Unknown category: ${options.category}`);
|
|
36705
|
+
console.error(`Available: ${CATEGORIES.join(", ")}`);
|
|
36706
|
+
process.exitCode = 1;
|
|
36707
|
+
return;
|
|
36708
|
+
}
|
|
36709
|
+
const categorySkills = getSkillsByCategory(matchedCategory);
|
|
36710
|
+
skills = categorySkills.map((s) => s.name);
|
|
36711
|
+
if (!options.json) {
|
|
36712
|
+
console.log(chalk2.bold(`
|
|
36713
|
+
Installing ${skills.length} skills from "${matchedCategory}"...
|
|
36714
|
+
`));
|
|
36715
|
+
}
|
|
36716
|
+
}
|
|
36478
36717
|
const results = [];
|
|
36479
36718
|
if (options.for) {
|
|
36480
36719
|
let agents;
|
|
@@ -36561,7 +36800,8 @@ Skills installed to .skills/`));
|
|
|
36561
36800
|
process.exitCode = 1;
|
|
36562
36801
|
}
|
|
36563
36802
|
});
|
|
36564
|
-
program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-i, --installed", "Show only installed skills", false).option("-t, --tags <tags>", "Filter by comma-separated tags (OR logic, case-insensitive)").option("--json", "Output as JSON", false).description("List available or installed skills").action((options) => {
|
|
36803
|
+
program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-i, --installed", "Show only installed skills", false).option("-t, --tags <tags>", "Filter by comma-separated tags (OR logic, case-insensitive)").option("--json", "Output as JSON", false).option("--brief", "One line per skill: name \u2014 description [category]", false).description("List available or installed skills").action((options) => {
|
|
36804
|
+
const brief = options.brief && !options.json;
|
|
36565
36805
|
if (options.installed) {
|
|
36566
36806
|
const installed = getInstalledSkills();
|
|
36567
36807
|
if (options.json) {
|
|
@@ -36572,6 +36812,12 @@ program2.command("list").alias("ls").option("-c, --category <category>", "Filter
|
|
|
36572
36812
|
console.log(chalk2.dim("No skills installed"));
|
|
36573
36813
|
return;
|
|
36574
36814
|
}
|
|
36815
|
+
if (brief) {
|
|
36816
|
+
for (const name of installed) {
|
|
36817
|
+
console.log(name);
|
|
36818
|
+
}
|
|
36819
|
+
return;
|
|
36820
|
+
}
|
|
36575
36821
|
console.log(chalk2.bold(`
|
|
36576
36822
|
Installed skills (${installed.length}):
|
|
36577
36823
|
`));
|
|
@@ -36597,6 +36843,12 @@ Installed skills (${installed.length}):
|
|
|
36597
36843
|
console.log(JSON.stringify(skills, null, 2));
|
|
36598
36844
|
return;
|
|
36599
36845
|
}
|
|
36846
|
+
if (brief) {
|
|
36847
|
+
for (const s of skills) {
|
|
36848
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36849
|
+
}
|
|
36850
|
+
return;
|
|
36851
|
+
}
|
|
36600
36852
|
console.log(chalk2.bold(`
|
|
36601
36853
|
${category} (${skills.length}):
|
|
36602
36854
|
`));
|
|
@@ -36611,6 +36863,12 @@ ${category} (${skills.length}):
|
|
|
36611
36863
|
console.log(JSON.stringify(skills, null, 2));
|
|
36612
36864
|
return;
|
|
36613
36865
|
}
|
|
36866
|
+
if (brief) {
|
|
36867
|
+
for (const s of skills) {
|
|
36868
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36869
|
+
}
|
|
36870
|
+
return;
|
|
36871
|
+
}
|
|
36614
36872
|
console.log(chalk2.bold(`
|
|
36615
36873
|
Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
36616
36874
|
`));
|
|
@@ -36623,6 +36881,16 @@ Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
|
36623
36881
|
console.log(JSON.stringify(SKILLS, null, 2));
|
|
36624
36882
|
return;
|
|
36625
36883
|
}
|
|
36884
|
+
if (brief) {
|
|
36885
|
+
const sorted = [...SKILLS].sort((a, b) => {
|
|
36886
|
+
const catCmp = a.category.localeCompare(b.category);
|
|
36887
|
+
return catCmp !== 0 ? catCmp : a.name.localeCompare(b.name);
|
|
36888
|
+
});
|
|
36889
|
+
for (const s of sorted) {
|
|
36890
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36891
|
+
}
|
|
36892
|
+
return;
|
|
36893
|
+
}
|
|
36626
36894
|
console.log(chalk2.bold(`
|
|
36627
36895
|
Available skills (${SKILLS.length}):
|
|
36628
36896
|
`));
|
|
@@ -36635,7 +36903,7 @@ Available skills (${SKILLS.length}):
|
|
|
36635
36903
|
console.log();
|
|
36636
36904
|
}
|
|
36637
36905
|
});
|
|
36638
|
-
program2.command("search").argument("<query>", "Search term").option("--json", "Output as JSON", false).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) => {
|
|
36906
|
+
program2.command("search").argument("<query>", "Search term").option("--json", "Output as JSON", false).option("--brief", "One line per skill: name \u2014 description [category]", false).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) => {
|
|
36639
36907
|
let results = searchSkills(query);
|
|
36640
36908
|
if (options.category) {
|
|
36641
36909
|
const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
@@ -36651,6 +36919,7 @@ program2.command("search").argument("<query>", "Search term").option("--json", "
|
|
|
36651
36919
|
const tagFilter = options.tags.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
36652
36920
|
results = results.filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
|
|
36653
36921
|
}
|
|
36922
|
+
const brief = options.brief && !options.json;
|
|
36654
36923
|
if (options.json) {
|
|
36655
36924
|
console.log(JSON.stringify(results, null, 2));
|
|
36656
36925
|
return;
|
|
@@ -36659,6 +36928,12 @@ program2.command("search").argument("<query>", "Search term").option("--json", "
|
|
|
36659
36928
|
console.log(chalk2.dim(`No skills found for "${query}"`));
|
|
36660
36929
|
return;
|
|
36661
36930
|
}
|
|
36931
|
+
if (brief) {
|
|
36932
|
+
for (const s of results) {
|
|
36933
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36934
|
+
}
|
|
36935
|
+
return;
|
|
36936
|
+
}
|
|
36662
36937
|
console.log(chalk2.bold(`
|
|
36663
36938
|
Found ${results.length} skill(s):
|
|
36664
36939
|
`));
|
|
@@ -36667,7 +36942,7 @@ Found ${results.length} skill(s):
|
|
|
36667
36942
|
console.log(` ${s.description}`);
|
|
36668
36943
|
}
|
|
36669
36944
|
});
|
|
36670
|
-
program2.command("info").argument("<skill>", "Skill name").option("--json", "Output as JSON", false).description("Show details about a specific skill").action((name, options) => {
|
|
36945
|
+
program2.command("info").argument("<skill>", "Skill name").option("--json", "Output as JSON", false).option("--brief", "Single line: name \u2014 description [category] (tags: ...)", false).description("Show details about a specific skill").action((name, options) => {
|
|
36671
36946
|
const skill = getSkill(name);
|
|
36672
36947
|
if (!skill) {
|
|
36673
36948
|
console.error(`Skill '${name}' not found`);
|
|
@@ -36679,6 +36954,10 @@ program2.command("info").argument("<skill>", "Skill name").option("--json", "Out
|
|
|
36679
36954
|
console.log(JSON.stringify({ ...skill, ...reqs }, null, 2));
|
|
36680
36955
|
return;
|
|
36681
36956
|
}
|
|
36957
|
+
if (options.brief) {
|
|
36958
|
+
console.log(`${skill.name} \u2014 ${skill.description} [${skill.category}] (tags: ${skill.tags.join(", ")})`);
|
|
36959
|
+
return;
|
|
36960
|
+
}
|
|
36682
36961
|
console.log(`
|
|
36683
36962
|
${chalk2.bold(skill.displayName)}`);
|
|
36684
36963
|
console.log(`${skill.description}`);
|
|
@@ -36909,7 +37188,7 @@ Installing recommended skills for ${options.for} (${options.scope})...
|
|
|
36909
37188
|
const envContent = lines.join(`
|
|
36910
37189
|
`) + `
|
|
36911
37190
|
`;
|
|
36912
|
-
const envPath =
|
|
37191
|
+
const envPath = join5(cwd, ".env.example");
|
|
36913
37192
|
writeFileSync2(envPath, envContent);
|
|
36914
37193
|
envVarCount = envMap.size;
|
|
36915
37194
|
if (!options.json) {
|
|
@@ -36920,10 +37199,10 @@ Installing recommended skills for ${options.for} (${options.scope})...
|
|
|
36920
37199
|
console.log(chalk2.dim(" No environment variables detected across installed skills"));
|
|
36921
37200
|
}
|
|
36922
37201
|
}
|
|
36923
|
-
const gitignorePath =
|
|
37202
|
+
const gitignorePath = join5(cwd, ".gitignore");
|
|
36924
37203
|
const gitignoreEntry = ".skills/";
|
|
36925
37204
|
let gitignoreContent = "";
|
|
36926
|
-
if (
|
|
37205
|
+
if (existsSync5(gitignorePath)) {
|
|
36927
37206
|
gitignoreContent = readFileSync4(gitignorePath, "utf-8");
|
|
36928
37207
|
}
|
|
36929
37208
|
let gitignoreUpdated = false;
|
|
@@ -37033,12 +37312,12 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
37033
37312
|
}
|
|
37034
37313
|
function collectFiles(dir, base = "") {
|
|
37035
37314
|
const files = new Set;
|
|
37036
|
-
if (!
|
|
37315
|
+
if (!existsSync5(dir))
|
|
37037
37316
|
return files;
|
|
37038
|
-
for (const entry of
|
|
37039
|
-
const full =
|
|
37317
|
+
for (const entry of readdirSync4(dir)) {
|
|
37318
|
+
const full = join5(dir, entry);
|
|
37040
37319
|
const rel = base ? `${base}/${entry}` : entry;
|
|
37041
|
-
if (
|
|
37320
|
+
if (statSync3(full).isDirectory()) {
|
|
37042
37321
|
for (const f of collectFiles(full, rel))
|
|
37043
37322
|
files.add(f);
|
|
37044
37323
|
} else {
|
|
@@ -37050,7 +37329,7 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
37050
37329
|
const updateResults = [];
|
|
37051
37330
|
for (const name of toUpdate) {
|
|
37052
37331
|
const skillName = normalizeSkillName(name);
|
|
37053
|
-
const destPath =
|
|
37332
|
+
const destPath = join5(process.cwd(), ".skills", skillName);
|
|
37054
37333
|
const beforeFiles = collectFiles(destPath);
|
|
37055
37334
|
const result = installSkill(name, { overwrite: true });
|
|
37056
37335
|
const afterFiles = collectFiles(destPath);
|
|
@@ -37131,7 +37410,7 @@ Tags:
|
|
|
37131
37410
|
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) => {
|
|
37132
37411
|
if (options.register) {
|
|
37133
37412
|
const agents = options.register === "all" ? ["claude", "codex", "gemini"] : [options.register];
|
|
37134
|
-
const binPath =
|
|
37413
|
+
const binPath = join5(import.meta.dir, "..", "mcp", "index.ts");
|
|
37135
37414
|
for (const agent of agents) {
|
|
37136
37415
|
if (agent === "claude") {
|
|
37137
37416
|
try {
|
|
@@ -37145,8 +37424,8 @@ program2.command("mcp").option("--register <agent>", "Register MCP server with a
|
|
|
37145
37424
|
console.log(chalk2.yellow(`Manual registration: claude mcp add skills -- bun run ${binPath}`));
|
|
37146
37425
|
}
|
|
37147
37426
|
} else if (agent === "codex") {
|
|
37148
|
-
const { homedir:
|
|
37149
|
-
const configPath =
|
|
37427
|
+
const { homedir: homedir3 } = await import("os");
|
|
37428
|
+
const configPath = join5(homedir3(), ".codex", "config.toml");
|
|
37150
37429
|
console.log(chalk2.bold(`
|
|
37151
37430
|
Add to ${configPath}:`));
|
|
37152
37431
|
console.log(chalk2.dim(`[mcp_servers.skills]
|
|
@@ -37154,8 +37433,8 @@ command = "bun"
|
|
|
37154
37433
|
args = ["run", "${binPath}"]`));
|
|
37155
37434
|
console.log(chalk2.green(`\u2713 Codex MCP config shown above`));
|
|
37156
37435
|
} else if (agent === "gemini") {
|
|
37157
|
-
const { homedir:
|
|
37158
|
-
const configPath =
|
|
37436
|
+
const { homedir: homedir3 } = await import("os");
|
|
37437
|
+
const configPath = join5(homedir3(), ".gemini", "settings.json");
|
|
37159
37438
|
console.log(chalk2.bold(`
|
|
37160
37439
|
Add to ${configPath} mcpServers:`));
|
|
37161
37440
|
console.log(chalk2.dim(JSON.stringify({
|
|
@@ -37216,7 +37495,8 @@ program2.command("completion").argument("<shell>", "Shell type: bash, zsh, or fi
|
|
|
37216
37495
|
"self-update",
|
|
37217
37496
|
"completion",
|
|
37218
37497
|
"outdated",
|
|
37219
|
-
"doctor"
|
|
37498
|
+
"doctor",
|
|
37499
|
+
"auth"
|
|
37220
37500
|
];
|
|
37221
37501
|
const skillNames = SKILLS.map((s) => s.name);
|
|
37222
37502
|
const categoryNames = CATEGORIES.map((c) => c);
|
|
@@ -37339,6 +37619,117 @@ _skills "$@"
|
|
|
37339
37619
|
process.exitCode = 1;
|
|
37340
37620
|
}
|
|
37341
37621
|
});
|
|
37622
|
+
program2.command("export").option("--json", "Output as JSON (default behavior)", false).description("Export installed skills to JSON for sharing or backup").action((_options) => {
|
|
37623
|
+
const skills = getInstalledSkills();
|
|
37624
|
+
const payload = {
|
|
37625
|
+
version: 1,
|
|
37626
|
+
skills,
|
|
37627
|
+
timestamp: new Date().toISOString()
|
|
37628
|
+
};
|
|
37629
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
37630
|
+
});
|
|
37631
|
+
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) => {
|
|
37632
|
+
let raw;
|
|
37633
|
+
try {
|
|
37634
|
+
if (file2 === "-") {
|
|
37635
|
+
raw = await new Response(process.stdin).text();
|
|
37636
|
+
} else {
|
|
37637
|
+
if (!existsSync5(file2)) {
|
|
37638
|
+
console.error(chalk2.red(`File not found: ${file2}`));
|
|
37639
|
+
process.exitCode = 1;
|
|
37640
|
+
return;
|
|
37641
|
+
}
|
|
37642
|
+
raw = readFileSync4(file2, "utf-8");
|
|
37643
|
+
}
|
|
37644
|
+
} catch (err) {
|
|
37645
|
+
console.error(chalk2.red(`Failed to read file: ${err.message}`));
|
|
37646
|
+
process.exitCode = 1;
|
|
37647
|
+
return;
|
|
37648
|
+
}
|
|
37649
|
+
let payload;
|
|
37650
|
+
try {
|
|
37651
|
+
payload = JSON.parse(raw);
|
|
37652
|
+
} catch {
|
|
37653
|
+
console.error(chalk2.red("Invalid JSON in import file"));
|
|
37654
|
+
process.exitCode = 1;
|
|
37655
|
+
return;
|
|
37656
|
+
}
|
|
37657
|
+
if (!payload || typeof payload !== "object" || !Array.isArray(payload.skills)) {
|
|
37658
|
+
console.error(chalk2.red('Invalid format: expected { "version": 1, "skills": [...] }'));
|
|
37659
|
+
process.exitCode = 1;
|
|
37660
|
+
return;
|
|
37661
|
+
}
|
|
37662
|
+
const skillList = payload.skills;
|
|
37663
|
+
const total = skillList.length;
|
|
37664
|
+
if (total === 0) {
|
|
37665
|
+
if (options.json) {
|
|
37666
|
+
console.log(JSON.stringify({ imported: 0, results: [] }));
|
|
37667
|
+
} else {
|
|
37668
|
+
console.log(chalk2.dim("No skills to import"));
|
|
37669
|
+
}
|
|
37670
|
+
return;
|
|
37671
|
+
}
|
|
37672
|
+
if (options.dryRun) {
|
|
37673
|
+
if (options.json) {
|
|
37674
|
+
console.log(JSON.stringify({ dryRun: true, skills: skillList }));
|
|
37675
|
+
} else {
|
|
37676
|
+
console.log(chalk2.bold(`
|
|
37677
|
+
[dry-run] Would install ${total} skill(s):
|
|
37678
|
+
`));
|
|
37679
|
+
for (let i = 0;i < total; i++) {
|
|
37680
|
+
const target = options.for ? ` for ${options.for} (${options.scope})` : " to .skills/";
|
|
37681
|
+
console.log(chalk2.dim(` [${i + 1}/${total}] ${skillList[i]}${target}`));
|
|
37682
|
+
}
|
|
37683
|
+
}
|
|
37684
|
+
return;
|
|
37685
|
+
}
|
|
37686
|
+
const results = [];
|
|
37687
|
+
if (options.for) {
|
|
37688
|
+
let agents;
|
|
37689
|
+
try {
|
|
37690
|
+
agents = resolveAgents(options.for);
|
|
37691
|
+
} catch (err) {
|
|
37692
|
+
console.error(chalk2.red(err.message));
|
|
37693
|
+
process.exitCode = 1;
|
|
37694
|
+
return;
|
|
37695
|
+
}
|
|
37696
|
+
for (let i = 0;i < total; i++) {
|
|
37697
|
+
const name = skillList[i];
|
|
37698
|
+
if (!options.json) {
|
|
37699
|
+
process.stdout.write(`[${i + 1}/${total}] Installing ${name}...`);
|
|
37700
|
+
}
|
|
37701
|
+
const agentResults = agents.map((agent) => installSkillForAgent(name, { agent, scope: options.scope }, generateSkillMd));
|
|
37702
|
+
const success2 = agentResults.every((r) => r.success);
|
|
37703
|
+
results.push({ skill: name, success: success2, agentResults });
|
|
37704
|
+
if (!options.json) {
|
|
37705
|
+
console.log(success2 ? " done" : " failed");
|
|
37706
|
+
}
|
|
37707
|
+
}
|
|
37708
|
+
} else {
|
|
37709
|
+
for (let i = 0;i < total; i++) {
|
|
37710
|
+
const name = skillList[i];
|
|
37711
|
+
if (!options.json) {
|
|
37712
|
+
process.stdout.write(`[${i + 1}/${total}] Installing ${name}...`);
|
|
37713
|
+
}
|
|
37714
|
+
const result = installSkill(name);
|
|
37715
|
+
results.push(result);
|
|
37716
|
+
if (!options.json) {
|
|
37717
|
+
console.log(result.success ? " done" : " failed");
|
|
37718
|
+
}
|
|
37719
|
+
}
|
|
37720
|
+
}
|
|
37721
|
+
if (options.json) {
|
|
37722
|
+
console.log(JSON.stringify({ imported: results.filter((r) => r.success).length, total, results }));
|
|
37723
|
+
} else {
|
|
37724
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
37725
|
+
const failed = total - succeeded;
|
|
37726
|
+
console.log(chalk2.bold(`
|
|
37727
|
+
Imported ${succeeded}/${total} skill(s)${failed > 0 ? ` (${failed} failed)` : ""}`));
|
|
37728
|
+
}
|
|
37729
|
+
if (results.some((r) => !r.success)) {
|
|
37730
|
+
process.exitCode = 1;
|
|
37731
|
+
}
|
|
37732
|
+
});
|
|
37342
37733
|
program2.command("doctor").option("--json", "Output as JSON", false).description("Check environment variables for installed skills").action((options) => {
|
|
37343
37734
|
const installed = getInstalledSkills();
|
|
37344
37735
|
if (installed.length === 0) {
|
|
@@ -37377,6 +37768,247 @@ Skills Doctor (${installed.length} installed):
|
|
|
37377
37768
|
}
|
|
37378
37769
|
}
|
|
37379
37770
|
});
|
|
37771
|
+
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) => {
|
|
37772
|
+
const cwd = process.cwd();
|
|
37773
|
+
const envFilePath = join5(cwd, ".env");
|
|
37774
|
+
if (options.set) {
|
|
37775
|
+
const eqIdx = options.set.indexOf("=");
|
|
37776
|
+
if (eqIdx === -1) {
|
|
37777
|
+
console.error(chalk2.red(`Invalid format for --set. Expected KEY=VALUE, got: ${options.set}`));
|
|
37778
|
+
process.exitCode = 1;
|
|
37779
|
+
return;
|
|
37780
|
+
}
|
|
37781
|
+
const key = options.set.slice(0, eqIdx).trim();
|
|
37782
|
+
const value = options.set.slice(eqIdx + 1);
|
|
37783
|
+
if (!key) {
|
|
37784
|
+
console.error(chalk2.red("Key cannot be empty"));
|
|
37785
|
+
process.exitCode = 1;
|
|
37786
|
+
return;
|
|
37787
|
+
}
|
|
37788
|
+
let existing = "";
|
|
37789
|
+
if (existsSync5(envFilePath)) {
|
|
37790
|
+
existing = readFileSync4(envFilePath, "utf-8");
|
|
37791
|
+
}
|
|
37792
|
+
const keyPattern = new RegExp(`^${key}=.*$`, "m");
|
|
37793
|
+
let updated;
|
|
37794
|
+
if (keyPattern.test(existing)) {
|
|
37795
|
+
updated = existing.replace(keyPattern, `${key}=${value}`);
|
|
37796
|
+
} else {
|
|
37797
|
+
updated = existing.endsWith(`
|
|
37798
|
+
`) || existing === "" ? existing + `${key}=${value}
|
|
37799
|
+
` : existing + `
|
|
37800
|
+
${key}=${value}
|
|
37801
|
+
`;
|
|
37802
|
+
}
|
|
37803
|
+
writeFileSync2(envFilePath, updated, "utf-8");
|
|
37804
|
+
console.log(chalk2.green(`Set ${key} in ${envFilePath}`));
|
|
37805
|
+
return;
|
|
37806
|
+
}
|
|
37807
|
+
if (name) {
|
|
37808
|
+
const reqs = getSkillRequirements(name);
|
|
37809
|
+
if (!reqs) {
|
|
37810
|
+
console.error(`Skill '${name}' not found`);
|
|
37811
|
+
process.exitCode = 1;
|
|
37812
|
+
return;
|
|
37813
|
+
}
|
|
37814
|
+
const envVars = reqs.envVars.map((v) => ({ name: v, set: !!process.env[v] }));
|
|
37815
|
+
if (options.json) {
|
|
37816
|
+
console.log(JSON.stringify({ skill: name, envVars }, null, 2));
|
|
37817
|
+
return;
|
|
37818
|
+
}
|
|
37819
|
+
console.log(chalk2.bold(`
|
|
37820
|
+
Auth status for ${name}:
|
|
37821
|
+
`));
|
|
37822
|
+
if (envVars.length === 0) {
|
|
37823
|
+
console.log(chalk2.dim(" No environment variables required"));
|
|
37824
|
+
} else {
|
|
37825
|
+
for (const v of envVars) {
|
|
37826
|
+
const icon = v.set ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
37827
|
+
const status = v.set ? chalk2.green("set") : chalk2.red("missing");
|
|
37828
|
+
console.log(` ${icon} ${v.name} (${status})`);
|
|
37829
|
+
}
|
|
37830
|
+
}
|
|
37831
|
+
return;
|
|
37832
|
+
}
|
|
37833
|
+
const installed = getInstalledSkills();
|
|
37834
|
+
if (installed.length === 0) {
|
|
37835
|
+
if (options.json) {
|
|
37836
|
+
console.log(JSON.stringify([]));
|
|
37837
|
+
} else {
|
|
37838
|
+
console.log("No skills installed");
|
|
37839
|
+
}
|
|
37840
|
+
return;
|
|
37841
|
+
}
|
|
37842
|
+
const report = [];
|
|
37843
|
+
for (const skillName of installed) {
|
|
37844
|
+
const reqs = getSkillRequirements(skillName);
|
|
37845
|
+
const envVars = (reqs?.envVars ?? []).map((v) => ({ name: v, set: !!process.env[v] }));
|
|
37846
|
+
report.push({ skill: skillName, envVars });
|
|
37847
|
+
}
|
|
37848
|
+
if (options.json) {
|
|
37849
|
+
console.log(JSON.stringify(report, null, 2));
|
|
37850
|
+
return;
|
|
37851
|
+
}
|
|
37852
|
+
console.log(chalk2.bold(`
|
|
37853
|
+
Auth status (${installed.length} installed skills):
|
|
37854
|
+
`));
|
|
37855
|
+
for (const entry of report) {
|
|
37856
|
+
console.log(chalk2.bold(` ${entry.skill}`));
|
|
37857
|
+
if (entry.envVars.length === 0) {
|
|
37858
|
+
console.log(chalk2.dim(" No environment variables required"));
|
|
37859
|
+
} else {
|
|
37860
|
+
for (const v of entry.envVars) {
|
|
37861
|
+
const icon = v.set ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
37862
|
+
const status = v.set ? chalk2.green("set") : chalk2.red("missing");
|
|
37863
|
+
console.log(` ${icon} ${v.name} (${status})`);
|
|
37864
|
+
}
|
|
37865
|
+
}
|
|
37866
|
+
}
|
|
37867
|
+
});
|
|
37868
|
+
program2.command("whoami").option("--json", "Output as JSON", false).description("Show setup summary: version, installed skills, agent configs, and paths").action((options) => {
|
|
37869
|
+
const { homedir: homedir3 } = __require("os");
|
|
37870
|
+
const version2 = package_default.version;
|
|
37871
|
+
const cwd = process.cwd();
|
|
37872
|
+
const installed = getInstalledSkills();
|
|
37873
|
+
const agentNames = ["claude", "codex", "gemini"];
|
|
37874
|
+
const agentConfigs = [];
|
|
37875
|
+
for (const agent of agentNames) {
|
|
37876
|
+
const agentSkillsPath = join5(homedir3(), `.${agent}`, "skills");
|
|
37877
|
+
const exists = existsSync5(agentSkillsPath);
|
|
37878
|
+
let skillCount = 0;
|
|
37879
|
+
if (exists) {
|
|
37880
|
+
try {
|
|
37881
|
+
skillCount = readdirSync4(agentSkillsPath).filter((f) => {
|
|
37882
|
+
const full = join5(agentSkillsPath, f);
|
|
37883
|
+
return f.startsWith("skill-") && statSync3(full).isDirectory();
|
|
37884
|
+
}).length;
|
|
37885
|
+
} catch {}
|
|
37886
|
+
}
|
|
37887
|
+
agentConfigs.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
37888
|
+
}
|
|
37889
|
+
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
37890
|
+
if (options.json) {
|
|
37891
|
+
console.log(JSON.stringify({
|
|
37892
|
+
version: version2,
|
|
37893
|
+
installedCount: installed.length,
|
|
37894
|
+
installed,
|
|
37895
|
+
agents: agentConfigs,
|
|
37896
|
+
skillsDir,
|
|
37897
|
+
cwd
|
|
37898
|
+
}, null, 2));
|
|
37899
|
+
return;
|
|
37900
|
+
}
|
|
37901
|
+
console.log(chalk2.bold(`
|
|
37902
|
+
skills v${version2}
|
|
37903
|
+
`));
|
|
37904
|
+
console.log(`${chalk2.dim("Working directory:")} ${cwd}`);
|
|
37905
|
+
console.log(`${chalk2.dim("Skills directory:")} ${skillsDir}`);
|
|
37906
|
+
console.log();
|
|
37907
|
+
if (installed.length === 0) {
|
|
37908
|
+
console.log(chalk2.dim("No skills installed in current project"));
|
|
37909
|
+
} else {
|
|
37910
|
+
console.log(chalk2.bold(`Installed skills (${installed.length}):`));
|
|
37911
|
+
for (const name of installed) {
|
|
37912
|
+
console.log(` ${chalk2.cyan(name)}`);
|
|
37913
|
+
}
|
|
37914
|
+
}
|
|
37915
|
+
console.log();
|
|
37916
|
+
console.log(chalk2.bold("Agent configurations:"));
|
|
37917
|
+
for (const cfg of agentConfigs) {
|
|
37918
|
+
if (cfg.exists) {
|
|
37919
|
+
console.log(` ${chalk2.green("\u2713")} ${cfg.agent} \u2014 ${cfg.skillCount} skill(s) at ${cfg.path}`);
|
|
37920
|
+
} else {
|
|
37921
|
+
console.log(` ${chalk2.dim("\u2717")} ${cfg.agent} \u2014 not configured`);
|
|
37922
|
+
}
|
|
37923
|
+
}
|
|
37924
|
+
});
|
|
37925
|
+
program2.command("test").argument("[skill]", "Skill name to test (omit to test all installed skills)").option("--json", "Output results as JSON", false).description("Test skill readiness: env vars, system deps, and npm deps").action(async (skillArg, options) => {
|
|
37926
|
+
let skillNames;
|
|
37927
|
+
if (skillArg) {
|
|
37928
|
+
const registryName = skillArg.startsWith("skill-") ? skillArg.replace("skill-", "") : skillArg;
|
|
37929
|
+
const skill = getSkill(registryName);
|
|
37930
|
+
if (!skill) {
|
|
37931
|
+
if (options.json) {
|
|
37932
|
+
console.log(JSON.stringify({ error: `Skill '${skillArg}' not found` }));
|
|
37933
|
+
} else {
|
|
37934
|
+
console.error(chalk2.red(`Skill '${skillArg}' not found`));
|
|
37935
|
+
}
|
|
37936
|
+
process.exitCode = 1;
|
|
37937
|
+
return;
|
|
37938
|
+
}
|
|
37939
|
+
skillNames = [registryName];
|
|
37940
|
+
} else {
|
|
37941
|
+
skillNames = getInstalledSkills();
|
|
37942
|
+
if (skillNames.length === 0) {
|
|
37943
|
+
if (options.json) {
|
|
37944
|
+
console.log(JSON.stringify([]));
|
|
37945
|
+
} else {
|
|
37946
|
+
console.log(chalk2.dim("No skills installed. Run: skills install <name>"));
|
|
37947
|
+
}
|
|
37948
|
+
return;
|
|
37949
|
+
}
|
|
37950
|
+
}
|
|
37951
|
+
const results = [];
|
|
37952
|
+
for (const name of skillNames) {
|
|
37953
|
+
const reqs = getSkillRequirements(name);
|
|
37954
|
+
const envVars = (reqs?.envVars ?? []).map((v) => ({
|
|
37955
|
+
name: v,
|
|
37956
|
+
set: !!process.env[v]
|
|
37957
|
+
}));
|
|
37958
|
+
const systemDeps = (reqs?.systemDeps ?? []).map((dep) => {
|
|
37959
|
+
const proc = Bun.spawnSync(["which", dep]);
|
|
37960
|
+
return { name: dep, available: proc.exitCode === 0 };
|
|
37961
|
+
});
|
|
37962
|
+
const npmDeps = Object.entries(reqs?.dependencies ?? {}).map(([pkgName, version2]) => ({
|
|
37963
|
+
name: pkgName,
|
|
37964
|
+
version: version2
|
|
37965
|
+
}));
|
|
37966
|
+
const ready = envVars.every((v) => v.set) && systemDeps.every((d) => d.available);
|
|
37967
|
+
results.push({ skill: name, envVars, systemDeps, npmDeps, ready });
|
|
37968
|
+
}
|
|
37969
|
+
if (options.json) {
|
|
37970
|
+
console.log(JSON.stringify(results, null, 2));
|
|
37971
|
+
return;
|
|
37972
|
+
}
|
|
37973
|
+
const allReady = results.every((r) => r.ready);
|
|
37974
|
+
console.log(chalk2.bold(`
|
|
37975
|
+
Skills Test (${results.length} skill${results.length === 1 ? "" : "s"}):
|
|
37976
|
+
`));
|
|
37977
|
+
for (const result of results) {
|
|
37978
|
+
const readyLabel = result.ready ? chalk2.green("ready") : chalk2.red("not ready");
|
|
37979
|
+
console.log(chalk2.bold(` ${result.skill}`) + chalk2.dim(` [${readyLabel}]`));
|
|
37980
|
+
if (result.envVars.length === 0 && result.systemDeps.length === 0) {
|
|
37981
|
+
console.log(chalk2.dim(" No requirements"));
|
|
37982
|
+
}
|
|
37983
|
+
for (const v of result.envVars) {
|
|
37984
|
+
if (v.set) {
|
|
37985
|
+
console.log(` ${chalk2.green("\u2713")} ${v.name}`);
|
|
37986
|
+
} else {
|
|
37987
|
+
console.log(` ${chalk2.red("\u2717")} ${v.name} ${chalk2.dim("(missing)")}`);
|
|
37988
|
+
}
|
|
37989
|
+
}
|
|
37990
|
+
for (const dep of result.systemDeps) {
|
|
37991
|
+
if (dep.available) {
|
|
37992
|
+
console.log(` ${chalk2.green("\u2713")} ${dep.name} ${chalk2.dim("(system)")}`);
|
|
37993
|
+
} else {
|
|
37994
|
+
console.log(` ${chalk2.red("\u2717")} ${dep.name} ${chalk2.dim("(not installed)")}`);
|
|
37995
|
+
}
|
|
37996
|
+
}
|
|
37997
|
+
if (result.npmDeps.length > 0) {
|
|
37998
|
+
console.log(chalk2.dim(` npm: ${result.npmDeps.map((d) => d.name).join(", ")}`));
|
|
37999
|
+
}
|
|
38000
|
+
}
|
|
38001
|
+
console.log();
|
|
38002
|
+
if (allReady) {
|
|
38003
|
+
console.log(chalk2.green(`All ${results.length} skill(s) ready`));
|
|
38004
|
+
} else {
|
|
38005
|
+
const notReady = results.filter((r) => !r.ready).length;
|
|
38006
|
+
console.log(chalk2.yellow(`${notReady} skill(s) not ready`));
|
|
38007
|
+
}
|
|
38008
|
+
if (!allReady) {
|
|
38009
|
+
process.exitCode = 1;
|
|
38010
|
+
}
|
|
38011
|
+
});
|
|
37380
38012
|
program2.command("outdated").option("--json", "Output as JSON", false).description("Check for outdated installed skills").action((options) => {
|
|
37381
38013
|
const installed = getInstalledSkills();
|
|
37382
38014
|
if (installed.length === 0) {
|
|
@@ -37392,17 +38024,17 @@ program2.command("outdated").option("--json", "Output as JSON", false).descripti
|
|
|
37392
38024
|
const upToDate = [];
|
|
37393
38025
|
for (const name of installed) {
|
|
37394
38026
|
const skillName = normalizeSkillName(name);
|
|
37395
|
-
const installedPkgPath =
|
|
38027
|
+
const installedPkgPath = join5(cwd, ".skills", skillName, "package.json");
|
|
37396
38028
|
let installedVersion = "unknown";
|
|
37397
|
-
if (
|
|
38029
|
+
if (existsSync5(installedPkgPath)) {
|
|
37398
38030
|
try {
|
|
37399
38031
|
installedVersion = JSON.parse(readFileSync4(installedPkgPath, "utf-8")).version || "unknown";
|
|
37400
38032
|
} catch {}
|
|
37401
38033
|
}
|
|
37402
38034
|
const registryPath = getSkillPath(name);
|
|
37403
|
-
const registryPkgPath =
|
|
38035
|
+
const registryPkgPath = join5(registryPath, "package.json");
|
|
37404
38036
|
let registryVersion = "unknown";
|
|
37405
|
-
if (
|
|
38037
|
+
if (existsSync5(registryPkgPath)) {
|
|
37406
38038
|
try {
|
|
37407
38039
|
registryVersion = JSON.parse(readFileSync4(registryPkgPath, "utf-8")).version || "unknown";
|
|
37408
38040
|
} catch {}
|
package/bin/mcp.js
CHANGED
|
@@ -28596,10 +28596,15 @@ class StdioServerTransport {
|
|
|
28596
28596
|
});
|
|
28597
28597
|
}
|
|
28598
28598
|
}
|
|
28599
|
+
|
|
28600
|
+
// src/mcp/index.ts
|
|
28601
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
28602
|
+
import { join as join3 } from "path";
|
|
28603
|
+
import { homedir as homedir2 } from "os";
|
|
28599
28604
|
// package.json
|
|
28600
28605
|
var package_default = {
|
|
28601
28606
|
name: "@hasna/skills",
|
|
28602
|
-
version: "0.1.
|
|
28607
|
+
version: "0.1.8",
|
|
28603
28608
|
description: "Skills library for AI coding agents",
|
|
28604
28609
|
type: "module",
|
|
28605
28610
|
bin: {
|
|
@@ -30709,6 +30714,52 @@ server.registerTool("install_skill", {
|
|
|
30709
30714
|
isError: !result.success
|
|
30710
30715
|
};
|
|
30711
30716
|
});
|
|
30717
|
+
server.registerTool("install_category", {
|
|
30718
|
+
title: "Install Category",
|
|
30719
|
+
description: "Install all skills in a category. Optionally install for a specific agent (claude, codex, gemini, or all) with a given scope.",
|
|
30720
|
+
inputSchema: {
|
|
30721
|
+
category: exports_external.string().describe("Category name (case-insensitive, e.g. 'Event Management')"),
|
|
30722
|
+
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
30723
|
+
scope: exports_external.string().optional().describe("Install scope: global or project (default: global)")
|
|
30724
|
+
}
|
|
30725
|
+
}, async ({ category, for: agentArg, scope }) => {
|
|
30726
|
+
const matchedCategory = CATEGORIES.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
30727
|
+
if (!matchedCategory) {
|
|
30728
|
+
return {
|
|
30729
|
+
content: [{ type: "text", text: `Unknown category: ${category}. Available: ${CATEGORIES.join(", ")}` }],
|
|
30730
|
+
isError: true
|
|
30731
|
+
};
|
|
30732
|
+
}
|
|
30733
|
+
const categorySkills = getSkillsByCategory(matchedCategory);
|
|
30734
|
+
const names = categorySkills.map((s) => s.name);
|
|
30735
|
+
if (agentArg) {
|
|
30736
|
+
let agents;
|
|
30737
|
+
try {
|
|
30738
|
+
agents = resolveAgents(agentArg);
|
|
30739
|
+
} catch (err) {
|
|
30740
|
+
return {
|
|
30741
|
+
content: [{ type: "text", text: err.message }],
|
|
30742
|
+
isError: true
|
|
30743
|
+
};
|
|
30744
|
+
}
|
|
30745
|
+
const results2 = [];
|
|
30746
|
+
for (const name of names) {
|
|
30747
|
+
for (const a of agents) {
|
|
30748
|
+
const r = installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd);
|
|
30749
|
+
results2.push({ ...r, agent: a, scope: scope || "global" });
|
|
30750
|
+
}
|
|
30751
|
+
}
|
|
30752
|
+
return {
|
|
30753
|
+
content: [{ type: "text", text: JSON.stringify({ category: matchedCategory, count: names.length, results: results2 }, null, 2) }],
|
|
30754
|
+
isError: results2.some((r) => !r.success)
|
|
30755
|
+
};
|
|
30756
|
+
}
|
|
30757
|
+
const results = names.map((name) => installSkill(name));
|
|
30758
|
+
return {
|
|
30759
|
+
content: [{ type: "text", text: JSON.stringify({ category: matchedCategory, count: names.length, results }, null, 2) }],
|
|
30760
|
+
isError: results.some((r) => !r.success)
|
|
30761
|
+
};
|
|
30762
|
+
});
|
|
30712
30763
|
server.registerTool("remove_skill", {
|
|
30713
30764
|
title: "Remove Skill",
|
|
30714
30765
|
description: "Remove an installed skill. Without --for, removes from .skills/. With --for, removes from agent skill directory.",
|
|
@@ -30801,6 +30852,94 @@ server.registerTool("run_skill", {
|
|
|
30801
30852
|
content: [{ type: "text", text: JSON.stringify({ exitCode: result.exitCode, skill: name }, null, 2) }]
|
|
30802
30853
|
};
|
|
30803
30854
|
});
|
|
30855
|
+
server.registerTool("export_skills", {
|
|
30856
|
+
title: "Export Skills",
|
|
30857
|
+
description: "Export the list of currently installed skills as a JSON payload that can be imported elsewhere"
|
|
30858
|
+
}, async () => {
|
|
30859
|
+
const skills = getInstalledSkills();
|
|
30860
|
+
const payload = {
|
|
30861
|
+
version: 1,
|
|
30862
|
+
skills,
|
|
30863
|
+
timestamp: new Date().toISOString()
|
|
30864
|
+
};
|
|
30865
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
30866
|
+
});
|
|
30867
|
+
server.registerTool("import_skills", {
|
|
30868
|
+
title: "Import Skills",
|
|
30869
|
+
description: "Install a list of skills from an export payload. Supports agent-specific installs via the 'for' parameter.",
|
|
30870
|
+
inputSchema: {
|
|
30871
|
+
skills: exports_external.array(exports_external.string()).describe("List of skill names to install"),
|
|
30872
|
+
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
30873
|
+
scope: exports_external.string().optional().describe("Install scope: global or project (default: global)")
|
|
30874
|
+
}
|
|
30875
|
+
}, async ({ skills: skillList, for: agentArg, scope }) => {
|
|
30876
|
+
if (!skillList || skillList.length === 0) {
|
|
30877
|
+
return { content: [{ type: "text", text: JSON.stringify({ imported: 0, results: [] }, null, 2) }] };
|
|
30878
|
+
}
|
|
30879
|
+
const results = [];
|
|
30880
|
+
if (agentArg) {
|
|
30881
|
+
let agents;
|
|
30882
|
+
try {
|
|
30883
|
+
agents = resolveAgents(agentArg);
|
|
30884
|
+
} catch (err) {
|
|
30885
|
+
return {
|
|
30886
|
+
content: [{ type: "text", text: err.message }],
|
|
30887
|
+
isError: true
|
|
30888
|
+
};
|
|
30889
|
+
}
|
|
30890
|
+
for (const name of skillList) {
|
|
30891
|
+
const agentResults = agents.map((a) => installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd));
|
|
30892
|
+
const success2 = agentResults.every((r) => r.success);
|
|
30893
|
+
const errors3 = agentResults.filter((r) => !r.success).map((r) => r.error).filter(Boolean);
|
|
30894
|
+
results.push({ skill: name, success: success2, ...errors3.length > 0 ? { error: errors3.join("; ") } : {} });
|
|
30895
|
+
}
|
|
30896
|
+
} else {
|
|
30897
|
+
for (const name of skillList) {
|
|
30898
|
+
const result = installSkill(name);
|
|
30899
|
+
results.push({ skill: result.skill, success: result.success, ...result.error ? { error: result.error } : {} });
|
|
30900
|
+
}
|
|
30901
|
+
}
|
|
30902
|
+
const imported = results.filter((r) => r.success).length;
|
|
30903
|
+
const hasErrors = results.some((r) => !r.success);
|
|
30904
|
+
return {
|
|
30905
|
+
content: [{ type: "text", text: JSON.stringify({ imported, total: skillList.length, results }, null, 2) }],
|
|
30906
|
+
isError: hasErrors
|
|
30907
|
+
};
|
|
30908
|
+
});
|
|
30909
|
+
server.registerTool("whoami", {
|
|
30910
|
+
title: "Skills Whoami",
|
|
30911
|
+
description: "Show setup summary: package version, installed skills, agent configurations, skills directory location, and working directory"
|
|
30912
|
+
}, async () => {
|
|
30913
|
+
const version2 = package_default.version;
|
|
30914
|
+
const cwd = process.cwd();
|
|
30915
|
+
const installed = getInstalledSkills();
|
|
30916
|
+
const agentNames = ["claude", "codex", "gemini"];
|
|
30917
|
+
const agents = [];
|
|
30918
|
+
for (const agent of agentNames) {
|
|
30919
|
+
const agentSkillsPath = join3(homedir2(), `.${agent}`, "skills");
|
|
30920
|
+
const exists = existsSync3(agentSkillsPath);
|
|
30921
|
+
let skillCount = 0;
|
|
30922
|
+
if (exists) {
|
|
30923
|
+
try {
|
|
30924
|
+
skillCount = readdirSync3(agentSkillsPath).filter((f) => {
|
|
30925
|
+
const full = join3(agentSkillsPath, f);
|
|
30926
|
+
return f.startsWith("skill-") && statSync2(full).isDirectory();
|
|
30927
|
+
}).length;
|
|
30928
|
+
} catch {}
|
|
30929
|
+
}
|
|
30930
|
+
agents.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
30931
|
+
}
|
|
30932
|
+
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
30933
|
+
const result = {
|
|
30934
|
+
version: version2,
|
|
30935
|
+
installedCount: installed.length,
|
|
30936
|
+
installed,
|
|
30937
|
+
agents,
|
|
30938
|
+
skillsDir,
|
|
30939
|
+
cwd
|
|
30940
|
+
};
|
|
30941
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
30942
|
+
});
|
|
30804
30943
|
server.registerResource("Skills Registry", "skills://registry", {
|
|
30805
30944
|
description: "Full list of all available skills as JSON"
|
|
30806
30945
|
}, async () => ({
|