@hasna/skills 0.1.7 → 0.1.9
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 +702 -62
- package/bin/mcp.js +168 -21
- 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.9",
|
|
1882
1882
|
description: "Skills library for AI coding agents",
|
|
1883
1883
|
type: "module",
|
|
1884
1884
|
bin: {
|
|
@@ -34782,6 +34782,12 @@ 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";
|
|
34788
|
+
function stripNulls(obj) {
|
|
34789
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== undefined && !(Array.isArray(v) && v.length === 0)));
|
|
34790
|
+
}
|
|
34785
34791
|
async function main() {
|
|
34786
34792
|
const transport = new StdioServerTransport;
|
|
34787
34793
|
await server.connect(transport);
|
|
@@ -34801,33 +34807,37 @@ var init_mcp2 = __esm(() => {
|
|
|
34801
34807
|
});
|
|
34802
34808
|
server.registerTool("list_skills", {
|
|
34803
34809
|
title: "List Skills",
|
|
34804
|
-
description: "List
|
|
34810
|
+
description: "List skills. Returns {name,category} by default; detail:true for full objects.",
|
|
34805
34811
|
inputSchema: {
|
|
34806
|
-
category: exports_external.string().optional().describe("Filter by category
|
|
34812
|
+
category: exports_external.string().optional().describe("Filter by category"),
|
|
34813
|
+
detail: exports_external.boolean().optional().describe("Return full objects (default: false)")
|
|
34807
34814
|
}
|
|
34808
|
-
}, async ({ category }) => {
|
|
34815
|
+
}, async ({ category, detail }) => {
|
|
34809
34816
|
const skills = category ? getSkillsByCategory(category) : SKILLS;
|
|
34817
|
+
const result = detail ? skills : skills.map((s) => ({ name: s.name, category: s.category }));
|
|
34810
34818
|
return {
|
|
34811
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
34819
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
34812
34820
|
};
|
|
34813
34821
|
});
|
|
34814
34822
|
server.registerTool("search_skills", {
|
|
34815
34823
|
title: "Search Skills",
|
|
34816
|
-
description: "Search skills by
|
|
34824
|
+
description: "Search skills by name, description, or tags. Returns compact list by default.",
|
|
34817
34825
|
inputSchema: {
|
|
34818
|
-
query: exports_external.string().describe("Search query")
|
|
34826
|
+
query: exports_external.string().describe("Search query (fuzzy-matched)"),
|
|
34827
|
+
detail: exports_external.boolean().optional().describe("Return full objects (default: false)")
|
|
34819
34828
|
}
|
|
34820
|
-
}, async ({ query }) => {
|
|
34829
|
+
}, async ({ query, detail }) => {
|
|
34821
34830
|
const results = searchSkills(query);
|
|
34831
|
+
const out = detail ? results : results.map((s) => ({ name: s.name, category: s.category }));
|
|
34822
34832
|
return {
|
|
34823
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
34833
|
+
content: [{ type: "text", text: JSON.stringify(out) }]
|
|
34824
34834
|
};
|
|
34825
34835
|
});
|
|
34826
34836
|
server.registerTool("get_skill_info", {
|
|
34827
34837
|
title: "Get Skill Info",
|
|
34828
|
-
description: "Get
|
|
34838
|
+
description: "Get skill metadata, env vars, and dependencies.",
|
|
34829
34839
|
inputSchema: {
|
|
34830
|
-
name: exports_external.string().describe("Skill name (e.g. 'image'
|
|
34840
|
+
name: exports_external.string().describe("Skill name (e.g. 'image')")
|
|
34831
34841
|
}
|
|
34832
34842
|
}, async ({ name }) => {
|
|
34833
34843
|
const skill = getSkill(name);
|
|
@@ -34835,13 +34845,14 @@ var init_mcp2 = __esm(() => {
|
|
|
34835
34845
|
return { content: [{ type: "text", text: `Skill '${name}' not found` }], isError: true };
|
|
34836
34846
|
}
|
|
34837
34847
|
const reqs = getSkillRequirements(name);
|
|
34848
|
+
const result = stripNulls({ ...skill, ...reqs });
|
|
34838
34849
|
return {
|
|
34839
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
34850
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
34840
34851
|
};
|
|
34841
34852
|
});
|
|
34842
34853
|
server.registerTool("get_skill_docs", {
|
|
34843
34854
|
title: "Get Skill Docs",
|
|
34844
|
-
description: "Get documentation
|
|
34855
|
+
description: "Get skill documentation (SKILL.md > README.md > CLAUDE.md).",
|
|
34845
34856
|
inputSchema: {
|
|
34846
34857
|
name: exports_external.string().describe("Skill name")
|
|
34847
34858
|
}
|
|
@@ -34854,7 +34865,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34854
34865
|
});
|
|
34855
34866
|
server.registerTool("install_skill", {
|
|
34856
34867
|
title: "Install Skill",
|
|
34857
|
-
description: "Install a skill
|
|
34868
|
+
description: "Install a skill to .skills/ or to an agent dir (for: claude|codex|gemini|all).",
|
|
34858
34869
|
inputSchema: {
|
|
34859
34870
|
name: exports_external.string().describe("Skill name to install"),
|
|
34860
34871
|
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
@@ -34883,9 +34894,55 @@ var init_mcp2 = __esm(() => {
|
|
|
34883
34894
|
isError: !result.success
|
|
34884
34895
|
};
|
|
34885
34896
|
});
|
|
34897
|
+
server.registerTool("install_category", {
|
|
34898
|
+
title: "Install Category",
|
|
34899
|
+
description: "Install all skills in a category, optionally for a specific agent.",
|
|
34900
|
+
inputSchema: {
|
|
34901
|
+
category: exports_external.string().describe("Category name (case-insensitive, e.g. 'Event Management')"),
|
|
34902
|
+
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
34903
|
+
scope: exports_external.string().optional().describe("Install scope: global or project (default: global)")
|
|
34904
|
+
}
|
|
34905
|
+
}, async ({ category, for: agentArg, scope }) => {
|
|
34906
|
+
const matchedCategory = CATEGORIES.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
34907
|
+
if (!matchedCategory) {
|
|
34908
|
+
return {
|
|
34909
|
+
content: [{ type: "text", text: `Unknown category: ${category}. Available: ${CATEGORIES.join(", ")}` }],
|
|
34910
|
+
isError: true
|
|
34911
|
+
};
|
|
34912
|
+
}
|
|
34913
|
+
const categorySkills = getSkillsByCategory(matchedCategory);
|
|
34914
|
+
const names = categorySkills.map((s) => s.name);
|
|
34915
|
+
if (agentArg) {
|
|
34916
|
+
let agents;
|
|
34917
|
+
try {
|
|
34918
|
+
agents = resolveAgents(agentArg);
|
|
34919
|
+
} catch (err) {
|
|
34920
|
+
return {
|
|
34921
|
+
content: [{ type: "text", text: err.message }],
|
|
34922
|
+
isError: true
|
|
34923
|
+
};
|
|
34924
|
+
}
|
|
34925
|
+
const results2 = [];
|
|
34926
|
+
for (const name of names) {
|
|
34927
|
+
for (const a of agents) {
|
|
34928
|
+
const r = installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd);
|
|
34929
|
+
results2.push({ ...r, agent: a, scope: scope || "global" });
|
|
34930
|
+
}
|
|
34931
|
+
}
|
|
34932
|
+
return {
|
|
34933
|
+
content: [{ type: "text", text: JSON.stringify({ category: matchedCategory, count: names.length, results: results2 }, null, 2) }],
|
|
34934
|
+
isError: results2.some((r) => !r.success)
|
|
34935
|
+
};
|
|
34936
|
+
}
|
|
34937
|
+
const results = names.map((name) => installSkill(name));
|
|
34938
|
+
return {
|
|
34939
|
+
content: [{ type: "text", text: JSON.stringify({ category: matchedCategory, count: names.length, results }, null, 2) }],
|
|
34940
|
+
isError: results.some((r) => !r.success)
|
|
34941
|
+
};
|
|
34942
|
+
});
|
|
34886
34943
|
server.registerTool("remove_skill", {
|
|
34887
34944
|
title: "Remove Skill",
|
|
34888
|
-
description: "Remove
|
|
34945
|
+
description: "Remove a skill from .skills/ or from an agent dir.",
|
|
34889
34946
|
inputSchema: {
|
|
34890
34947
|
name: exports_external.string().describe("Skill name to remove"),
|
|
34891
34948
|
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
@@ -34918,7 +34975,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34918
34975
|
});
|
|
34919
34976
|
server.registerTool("list_categories", {
|
|
34920
34977
|
title: "List Categories",
|
|
34921
|
-
description: "List all skill categories with counts"
|
|
34978
|
+
description: "List all 17 skill categories with skill counts."
|
|
34922
34979
|
}, async () => {
|
|
34923
34980
|
const cats = CATEGORIES.map((category) => ({
|
|
34924
34981
|
name: category,
|
|
@@ -34928,7 +34985,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34928
34985
|
});
|
|
34929
34986
|
server.registerTool("list_tags", {
|
|
34930
34987
|
title: "List Tags",
|
|
34931
|
-
description: "List all unique tags
|
|
34988
|
+
description: "List all unique skill tags with occurrence counts."
|
|
34932
34989
|
}, async () => {
|
|
34933
34990
|
const tagCounts = new Map;
|
|
34934
34991
|
for (const skill of SKILLS) {
|
|
@@ -34941,7 +34998,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34941
34998
|
});
|
|
34942
34999
|
server.registerTool("get_requirements", {
|
|
34943
35000
|
title: "Get Requirements",
|
|
34944
|
-
description: "Get
|
|
35001
|
+
description: "Get env vars, system deps, and npm dependencies for a skill.",
|
|
34945
35002
|
inputSchema: {
|
|
34946
35003
|
name: exports_external.string().describe("Skill name")
|
|
34947
35004
|
}
|
|
@@ -34954,7 +35011,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34954
35011
|
});
|
|
34955
35012
|
server.registerTool("run_skill", {
|
|
34956
35013
|
title: "Run Skill",
|
|
34957
|
-
description: "Run a skill by name with optional arguments.
|
|
35014
|
+
description: "Run a skill by name with optional arguments.",
|
|
34958
35015
|
inputSchema: {
|
|
34959
35016
|
name: exports_external.string().describe("Skill name to run"),
|
|
34960
35017
|
args: exports_external.array(exports_external.string()).optional().describe("Arguments to pass to the skill")
|
|
@@ -34975,12 +35032,100 @@ var init_mcp2 = __esm(() => {
|
|
|
34975
35032
|
content: [{ type: "text", text: JSON.stringify({ exitCode: result.exitCode, skill: name }, null, 2) }]
|
|
34976
35033
|
};
|
|
34977
35034
|
});
|
|
35035
|
+
server.registerTool("export_skills", {
|
|
35036
|
+
title: "Export Skills",
|
|
35037
|
+
description: "Export installed skills as a JSON payload for import elsewhere."
|
|
35038
|
+
}, async () => {
|
|
35039
|
+
const skills = getInstalledSkills();
|
|
35040
|
+
const payload = {
|
|
35041
|
+
version: 1,
|
|
35042
|
+
skills,
|
|
35043
|
+
timestamp: new Date().toISOString()
|
|
35044
|
+
};
|
|
35045
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
35046
|
+
});
|
|
35047
|
+
server.registerTool("import_skills", {
|
|
35048
|
+
title: "Import Skills",
|
|
35049
|
+
description: "Install skills from an export payload. Supports agent installs via 'for'.",
|
|
35050
|
+
inputSchema: {
|
|
35051
|
+
skills: exports_external.array(exports_external.string()).describe("List of skill names to install"),
|
|
35052
|
+
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
35053
|
+
scope: exports_external.string().optional().describe("Install scope: global or project (default: global)")
|
|
35054
|
+
}
|
|
35055
|
+
}, async ({ skills: skillList, for: agentArg, scope }) => {
|
|
35056
|
+
if (!skillList || skillList.length === 0) {
|
|
35057
|
+
return { content: [{ type: "text", text: JSON.stringify({ imported: 0, results: [] }, null, 2) }] };
|
|
35058
|
+
}
|
|
35059
|
+
const results = [];
|
|
35060
|
+
if (agentArg) {
|
|
35061
|
+
let agents;
|
|
35062
|
+
try {
|
|
35063
|
+
agents = resolveAgents(agentArg);
|
|
35064
|
+
} catch (err) {
|
|
35065
|
+
return {
|
|
35066
|
+
content: [{ type: "text", text: err.message }],
|
|
35067
|
+
isError: true
|
|
35068
|
+
};
|
|
35069
|
+
}
|
|
35070
|
+
for (const name of skillList) {
|
|
35071
|
+
const agentResults = agents.map((a) => installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd));
|
|
35072
|
+
const success2 = agentResults.every((r) => r.success);
|
|
35073
|
+
const errors4 = agentResults.filter((r) => !r.success).map((r) => r.error).filter(Boolean);
|
|
35074
|
+
results.push({ skill: name, success: success2, ...errors4.length > 0 ? { error: errors4.join("; ") } : {} });
|
|
35075
|
+
}
|
|
35076
|
+
} else {
|
|
35077
|
+
for (const name of skillList) {
|
|
35078
|
+
const result = installSkill(name);
|
|
35079
|
+
results.push({ skill: result.skill, success: result.success, ...result.error ? { error: result.error } : {} });
|
|
35080
|
+
}
|
|
35081
|
+
}
|
|
35082
|
+
const imported = results.filter((r) => r.success).length;
|
|
35083
|
+
const hasErrors = results.some((r) => !r.success);
|
|
35084
|
+
return {
|
|
35085
|
+
content: [{ type: "text", text: JSON.stringify({ imported, total: skillList.length, results }, null, 2) }],
|
|
35086
|
+
isError: hasErrors
|
|
35087
|
+
};
|
|
35088
|
+
});
|
|
35089
|
+
server.registerTool("whoami", {
|
|
35090
|
+
title: "Skills Whoami",
|
|
35091
|
+
description: "Show setup summary: version, installed skills, agent configs, cwd."
|
|
35092
|
+
}, async () => {
|
|
35093
|
+
const version2 = package_default.version;
|
|
35094
|
+
const cwd = process.cwd();
|
|
35095
|
+
const installed = getInstalledSkills();
|
|
35096
|
+
const agentNames = ["claude", "codex", "gemini"];
|
|
35097
|
+
const agents = [];
|
|
35098
|
+
for (const agent of agentNames) {
|
|
35099
|
+
const agentSkillsPath = join3(homedir2(), `.${agent}`, "skills");
|
|
35100
|
+
const exists = existsSync3(agentSkillsPath);
|
|
35101
|
+
let skillCount = 0;
|
|
35102
|
+
if (exists) {
|
|
35103
|
+
try {
|
|
35104
|
+
skillCount = readdirSync3(agentSkillsPath).filter((f) => {
|
|
35105
|
+
const full = join3(agentSkillsPath, f);
|
|
35106
|
+
return f.startsWith("skill-") && statSync2(full).isDirectory();
|
|
35107
|
+
}).length;
|
|
35108
|
+
} catch {}
|
|
35109
|
+
}
|
|
35110
|
+
agents.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
35111
|
+
}
|
|
35112
|
+
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
35113
|
+
const result = {
|
|
35114
|
+
version: version2,
|
|
35115
|
+
installedCount: installed.length,
|
|
35116
|
+
installed,
|
|
35117
|
+
agents,
|
|
35118
|
+
skillsDir,
|
|
35119
|
+
cwd
|
|
35120
|
+
};
|
|
35121
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
35122
|
+
});
|
|
34978
35123
|
server.registerResource("Skills Registry", "skills://registry", {
|
|
34979
|
-
description: "
|
|
35124
|
+
description: "Compact skill list [{name,category}]. Use skills://{name} for detail."
|
|
34980
35125
|
}, async () => ({
|
|
34981
35126
|
contents: [{
|
|
34982
35127
|
uri: "skills://registry",
|
|
34983
|
-
text: JSON.stringify(SKILLS
|
|
35128
|
+
text: JSON.stringify(SKILLS.map((s) => ({ name: s.name, category: s.category }))),
|
|
34984
35129
|
mimeType: "application/json"
|
|
34985
35130
|
}]
|
|
34986
35131
|
}));
|
|
@@ -35010,15 +35155,15 @@ __export(exports_serve, {
|
|
|
35010
35155
|
startServer: () => startServer,
|
|
35011
35156
|
createFetchHandler: () => createFetchHandler
|
|
35012
35157
|
});
|
|
35013
|
-
import { existsSync as
|
|
35014
|
-
import { join as
|
|
35158
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
35159
|
+
import { join as join4, dirname as dirname2, extname } from "path";
|
|
35015
35160
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
35016
35161
|
function getPackageJson() {
|
|
35017
35162
|
try {
|
|
35018
35163
|
const scriptDir = dirname2(fileURLToPath2(import.meta.url));
|
|
35019
35164
|
for (const rel of ["../..", ".."]) {
|
|
35020
|
-
const pkgPath =
|
|
35021
|
-
if (
|
|
35165
|
+
const pkgPath = join4(scriptDir, rel, "package.json");
|
|
35166
|
+
if (existsSync4(pkgPath)) {
|
|
35022
35167
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
35023
35168
|
return { version: pkg.version || "unknown", name: pkg.name || "skills" };
|
|
35024
35169
|
}
|
|
@@ -35030,20 +35175,20 @@ function resolveDashboardDir() {
|
|
|
35030
35175
|
const candidates = [];
|
|
35031
35176
|
try {
|
|
35032
35177
|
const scriptDir = dirname2(fileURLToPath2(import.meta.url));
|
|
35033
|
-
candidates.push(
|
|
35034
|
-
candidates.push(
|
|
35178
|
+
candidates.push(join4(scriptDir, "..", "dashboard", "dist"));
|
|
35179
|
+
candidates.push(join4(scriptDir, "..", "..", "dashboard", "dist"));
|
|
35035
35180
|
} catch {}
|
|
35036
35181
|
if (process.argv[1]) {
|
|
35037
35182
|
const mainDir = dirname2(process.argv[1]);
|
|
35038
|
-
candidates.push(
|
|
35039
|
-
candidates.push(
|
|
35183
|
+
candidates.push(join4(mainDir, "..", "dashboard", "dist"));
|
|
35184
|
+
candidates.push(join4(mainDir, "..", "..", "dashboard", "dist"));
|
|
35040
35185
|
}
|
|
35041
|
-
candidates.push(
|
|
35186
|
+
candidates.push(join4(process.cwd(), "dashboard", "dist"));
|
|
35042
35187
|
for (const candidate of candidates) {
|
|
35043
|
-
if (
|
|
35188
|
+
if (existsSync4(candidate))
|
|
35044
35189
|
return candidate;
|
|
35045
35190
|
}
|
|
35046
|
-
return
|
|
35191
|
+
return join4(process.cwd(), "dashboard", "dist");
|
|
35047
35192
|
}
|
|
35048
35193
|
function json2(data, status = 200) {
|
|
35049
35194
|
return new Response(JSON.stringify(data), {
|
|
@@ -35077,7 +35222,7 @@ function getAllSkillsWithStatus() {
|
|
|
35077
35222
|
});
|
|
35078
35223
|
}
|
|
35079
35224
|
function serveStaticFile(filePath) {
|
|
35080
|
-
if (!
|
|
35225
|
+
if (!existsSync4(filePath))
|
|
35081
35226
|
return null;
|
|
35082
35227
|
const ext = extname(filePath);
|
|
35083
35228
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -35087,7 +35232,7 @@ function serveStaticFile(filePath) {
|
|
|
35087
35232
|
}
|
|
35088
35233
|
function createFetchHandler(options) {
|
|
35089
35234
|
const dashboardDir = options?.dashboardDir ?? resolveDashboardDir();
|
|
35090
|
-
const dashboardExists = options?.dashboardExists ??
|
|
35235
|
+
const dashboardExists = options?.dashboardExists ?? existsSync4(dashboardDir);
|
|
35091
35236
|
return async function fetchHandler(req) {
|
|
35092
35237
|
const url2 = new URL(req.url);
|
|
35093
35238
|
const path = url2.pathname;
|
|
@@ -35201,6 +35346,43 @@ function createFetchHandler(options) {
|
|
|
35201
35346
|
return json2(result, result.success ? 200 : 400);
|
|
35202
35347
|
}
|
|
35203
35348
|
}
|
|
35349
|
+
if (path === "/api/skills/install-category" && method === "POST") {
|
|
35350
|
+
let body = {};
|
|
35351
|
+
try {
|
|
35352
|
+
const text = await req.text();
|
|
35353
|
+
if (text)
|
|
35354
|
+
body = JSON.parse(text);
|
|
35355
|
+
} catch {}
|
|
35356
|
+
if (!body.category) {
|
|
35357
|
+
return json2({ error: "Missing required field: category" }, 400);
|
|
35358
|
+
}
|
|
35359
|
+
const matchedCategory = CATEGORIES.find((c) => c.toLowerCase() === body.category.toLowerCase());
|
|
35360
|
+
if (!matchedCategory) {
|
|
35361
|
+
return json2({ error: `Unknown category: ${body.category}. Available: ${CATEGORIES.join(", ")}` }, 400);
|
|
35362
|
+
}
|
|
35363
|
+
const categorySkills = getSkillsByCategory(matchedCategory);
|
|
35364
|
+
const names = categorySkills.map((s) => s.name);
|
|
35365
|
+
if (body.for) {
|
|
35366
|
+
try {
|
|
35367
|
+
const agents = resolveAgents(body.for);
|
|
35368
|
+
const scope = body.scope === "project" ? "project" : "global";
|
|
35369
|
+
const results = [];
|
|
35370
|
+
for (const name of names) {
|
|
35371
|
+
for (const agent of agents) {
|
|
35372
|
+
results.push(installSkillForAgent(name, { agent, scope }, generateSkillMd));
|
|
35373
|
+
}
|
|
35374
|
+
}
|
|
35375
|
+
const allSuccess = results.every((r) => r.success);
|
|
35376
|
+
return json2({ category: matchedCategory, count: names.length, success: allSuccess, results }, allSuccess ? 200 : 207);
|
|
35377
|
+
} catch (e) {
|
|
35378
|
+
return json2({ success: false, error: e instanceof Error ? e.message : "Unknown error" }, 400);
|
|
35379
|
+
}
|
|
35380
|
+
} else {
|
|
35381
|
+
const results = names.map((name) => installSkill(name));
|
|
35382
|
+
const allSuccess = results.every((r) => r.success);
|
|
35383
|
+
return json2({ category: matchedCategory, count: names.length, success: allSuccess, results }, allSuccess ? 200 : 207);
|
|
35384
|
+
}
|
|
35385
|
+
}
|
|
35204
35386
|
const removeMatch = path.match(/^\/api\/skills\/([^/]+)\/remove$/);
|
|
35205
35387
|
if (removeMatch && method === "POST") {
|
|
35206
35388
|
const name = removeMatch[1];
|
|
@@ -35213,6 +35395,50 @@ function createFetchHandler(options) {
|
|
|
35213
35395
|
const pkg = getPackageJson();
|
|
35214
35396
|
return json2({ version: pkg.version, name: pkg.name });
|
|
35215
35397
|
}
|
|
35398
|
+
if (path === "/api/export" && method === "GET") {
|
|
35399
|
+
const skills = getInstalledSkills();
|
|
35400
|
+
return json2({
|
|
35401
|
+
version: 1,
|
|
35402
|
+
skills,
|
|
35403
|
+
timestamp: new Date().toISOString()
|
|
35404
|
+
});
|
|
35405
|
+
}
|
|
35406
|
+
if (path === "/api/import" && method === "POST") {
|
|
35407
|
+
let body = {};
|
|
35408
|
+
try {
|
|
35409
|
+
const text = await req.text();
|
|
35410
|
+
if (text)
|
|
35411
|
+
body = JSON.parse(text);
|
|
35412
|
+
} catch {
|
|
35413
|
+
return json2({ error: "Invalid JSON body" }, 400);
|
|
35414
|
+
}
|
|
35415
|
+
if (!Array.isArray(body.skills)) {
|
|
35416
|
+
return json2({ error: 'Body must include "skills" array' }, 400);
|
|
35417
|
+
}
|
|
35418
|
+
const skillList = body.skills;
|
|
35419
|
+
const results = [];
|
|
35420
|
+
if (body.for) {
|
|
35421
|
+
try {
|
|
35422
|
+
const agents = resolveAgents(body.for);
|
|
35423
|
+
const scope = body.scope === "project" ? "project" : "global";
|
|
35424
|
+
for (const name of skillList) {
|
|
35425
|
+
const agentResults = agents.map((agent) => installSkillForAgent(name, { agent, scope }, generateSkillMd));
|
|
35426
|
+
const success2 = agentResults.every((r) => r.success);
|
|
35427
|
+
const errors4 = agentResults.filter((r) => !r.success).map((r) => r.error).filter(Boolean);
|
|
35428
|
+
results.push({ skill: name, success: success2, ...errors4.length > 0 ? { error: errors4.join("; ") } : {} });
|
|
35429
|
+
}
|
|
35430
|
+
} catch (e) {
|
|
35431
|
+
return json2({ error: e instanceof Error ? e.message : "Unknown error" }, 400);
|
|
35432
|
+
}
|
|
35433
|
+
} else {
|
|
35434
|
+
for (const name of skillList) {
|
|
35435
|
+
const result = installSkill(name);
|
|
35436
|
+
results.push({ skill: result.skill, success: result.success, ...result.error ? { error: result.error } : {} });
|
|
35437
|
+
}
|
|
35438
|
+
}
|
|
35439
|
+
const imported = results.filter((r) => r.success).length;
|
|
35440
|
+
return json2({ imported, total: skillList.length, results }, imported === skillList.length ? 200 : 207);
|
|
35441
|
+
}
|
|
35216
35442
|
if (path === "/api/self-update" && method === "POST") {
|
|
35217
35443
|
try {
|
|
35218
35444
|
const pkg = getPackageJson();
|
|
@@ -35242,12 +35468,12 @@ function createFetchHandler(options) {
|
|
|
35242
35468
|
}
|
|
35243
35469
|
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
35244
35470
|
if (path !== "/") {
|
|
35245
|
-
const filePath =
|
|
35471
|
+
const filePath = join4(dashboardDir, path);
|
|
35246
35472
|
const res2 = serveStaticFile(filePath);
|
|
35247
35473
|
if (res2)
|
|
35248
35474
|
return res2;
|
|
35249
35475
|
}
|
|
35250
|
-
const indexPath =
|
|
35476
|
+
const indexPath = join4(dashboardDir, "index.html");
|
|
35251
35477
|
const res = serveStaticFile(indexPath);
|
|
35252
35478
|
if (res)
|
|
35253
35479
|
return res;
|
|
@@ -35258,7 +35484,7 @@ function createFetchHandler(options) {
|
|
|
35258
35484
|
async function startServer(port = 0, options) {
|
|
35259
35485
|
const shouldOpen = options?.open ?? true;
|
|
35260
35486
|
const dashboardDir = resolveDashboardDir();
|
|
35261
|
-
const dashboardExists =
|
|
35487
|
+
const dashboardExists = existsSync4(dashboardDir);
|
|
35262
35488
|
if (!dashboardExists) {
|
|
35263
35489
|
console.error(`
|
|
35264
35490
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -35338,8 +35564,8 @@ var {
|
|
|
35338
35564
|
// src/cli/index.tsx
|
|
35339
35565
|
init_package();
|
|
35340
35566
|
import chalk2 from "chalk";
|
|
35341
|
-
import { existsSync as
|
|
35342
|
-
import { join as
|
|
35567
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync2, appendFileSync, readFileSync as readFileSync4, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
|
|
35568
|
+
import { join as join5 } from "path";
|
|
35343
35569
|
|
|
35344
35570
|
// src/cli/components/App.tsx
|
|
35345
35571
|
import { useState as useState7 } from "react";
|
|
@@ -36469,12 +36695,33 @@ var program2 = new Command;
|
|
|
36469
36695
|
program2.name("skills").description("Install AI agent skills for your project").version(package_default.version).option("--verbose", "Enable verbose logging", false).enablePositionalOptions();
|
|
36470
36696
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive skill browser (TUI)").action(() => {
|
|
36471
36697
|
if (!isTTY) {
|
|
36472
|
-
console.log(JSON.stringify(SKILLS
|
|
36698
|
+
console.log(JSON.stringify(SKILLS.map((s) => ({ name: s.name, category: s.category }))));
|
|
36473
36699
|
process.exit(0);
|
|
36474
36700
|
}
|
|
36475
36701
|
render(/* @__PURE__ */ jsxDEV7(App, {}, undefined, false, undefined, this));
|
|
36476
36702
|
});
|
|
36477
|
-
program2.command("install").alias("add").argument("
|
|
36703
|
+
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) => {
|
|
36704
|
+
if (skills.length === 0 && !options.category) {
|
|
36705
|
+
console.error("error: missing required argument 'skills' or --category option");
|
|
36706
|
+
process.exitCode = 1;
|
|
36707
|
+
return;
|
|
36708
|
+
}
|
|
36709
|
+
if (options.category) {
|
|
36710
|
+
const matchedCategory = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
36711
|
+
if (!matchedCategory) {
|
|
36712
|
+
console.error(`Unknown category: ${options.category}`);
|
|
36713
|
+
console.error(`Available: ${CATEGORIES.join(", ")}`);
|
|
36714
|
+
process.exitCode = 1;
|
|
36715
|
+
return;
|
|
36716
|
+
}
|
|
36717
|
+
const categorySkills = getSkillsByCategory(matchedCategory);
|
|
36718
|
+
skills = categorySkills.map((s) => s.name);
|
|
36719
|
+
if (!options.json) {
|
|
36720
|
+
console.log(chalk2.bold(`
|
|
36721
|
+
Installing ${skills.length} skills from "${matchedCategory}"...
|
|
36722
|
+
`));
|
|
36723
|
+
}
|
|
36724
|
+
}
|
|
36478
36725
|
const results = [];
|
|
36479
36726
|
if (options.for) {
|
|
36480
36727
|
let agents;
|
|
@@ -36561,7 +36808,8 @@ Skills installed to .skills/`));
|
|
|
36561
36808
|
process.exitCode = 1;
|
|
36562
36809
|
}
|
|
36563
36810
|
});
|
|
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) => {
|
|
36811
|
+
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) => {
|
|
36812
|
+
const brief = options.brief && !options.json;
|
|
36565
36813
|
if (options.installed) {
|
|
36566
36814
|
const installed = getInstalledSkills();
|
|
36567
36815
|
if (options.json) {
|
|
@@ -36572,6 +36820,12 @@ program2.command("list").alias("ls").option("-c, --category <category>", "Filter
|
|
|
36572
36820
|
console.log(chalk2.dim("No skills installed"));
|
|
36573
36821
|
return;
|
|
36574
36822
|
}
|
|
36823
|
+
if (brief) {
|
|
36824
|
+
for (const name of installed) {
|
|
36825
|
+
console.log(name);
|
|
36826
|
+
}
|
|
36827
|
+
return;
|
|
36828
|
+
}
|
|
36575
36829
|
console.log(chalk2.bold(`
|
|
36576
36830
|
Installed skills (${installed.length}):
|
|
36577
36831
|
`));
|
|
@@ -36597,6 +36851,12 @@ Installed skills (${installed.length}):
|
|
|
36597
36851
|
console.log(JSON.stringify(skills, null, 2));
|
|
36598
36852
|
return;
|
|
36599
36853
|
}
|
|
36854
|
+
if (brief) {
|
|
36855
|
+
for (const s of skills) {
|
|
36856
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36857
|
+
}
|
|
36858
|
+
return;
|
|
36859
|
+
}
|
|
36600
36860
|
console.log(chalk2.bold(`
|
|
36601
36861
|
${category} (${skills.length}):
|
|
36602
36862
|
`));
|
|
@@ -36611,6 +36871,12 @@ ${category} (${skills.length}):
|
|
|
36611
36871
|
console.log(JSON.stringify(skills, null, 2));
|
|
36612
36872
|
return;
|
|
36613
36873
|
}
|
|
36874
|
+
if (brief) {
|
|
36875
|
+
for (const s of skills) {
|
|
36876
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36877
|
+
}
|
|
36878
|
+
return;
|
|
36879
|
+
}
|
|
36614
36880
|
console.log(chalk2.bold(`
|
|
36615
36881
|
Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
36616
36882
|
`));
|
|
@@ -36623,6 +36889,16 @@ Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
|
36623
36889
|
console.log(JSON.stringify(SKILLS, null, 2));
|
|
36624
36890
|
return;
|
|
36625
36891
|
}
|
|
36892
|
+
if (brief) {
|
|
36893
|
+
const sorted = [...SKILLS].sort((a, b) => {
|
|
36894
|
+
const catCmp = a.category.localeCompare(b.category);
|
|
36895
|
+
return catCmp !== 0 ? catCmp : a.name.localeCompare(b.name);
|
|
36896
|
+
});
|
|
36897
|
+
for (const s of sorted) {
|
|
36898
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36899
|
+
}
|
|
36900
|
+
return;
|
|
36901
|
+
}
|
|
36626
36902
|
console.log(chalk2.bold(`
|
|
36627
36903
|
Available skills (${SKILLS.length}):
|
|
36628
36904
|
`));
|
|
@@ -36635,7 +36911,7 @@ Available skills (${SKILLS.length}):
|
|
|
36635
36911
|
console.log();
|
|
36636
36912
|
}
|
|
36637
36913
|
});
|
|
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) => {
|
|
36914
|
+
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
36915
|
let results = searchSkills(query);
|
|
36640
36916
|
if (options.category) {
|
|
36641
36917
|
const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
@@ -36651,6 +36927,7 @@ program2.command("search").argument("<query>", "Search term").option("--json", "
|
|
|
36651
36927
|
const tagFilter = options.tags.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
36652
36928
|
results = results.filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
|
|
36653
36929
|
}
|
|
36930
|
+
const brief = options.brief && !options.json;
|
|
36654
36931
|
if (options.json) {
|
|
36655
36932
|
console.log(JSON.stringify(results, null, 2));
|
|
36656
36933
|
return;
|
|
@@ -36659,6 +36936,12 @@ program2.command("search").argument("<query>", "Search term").option("--json", "
|
|
|
36659
36936
|
console.log(chalk2.dim(`No skills found for "${query}"`));
|
|
36660
36937
|
return;
|
|
36661
36938
|
}
|
|
36939
|
+
if (brief) {
|
|
36940
|
+
for (const s of results) {
|
|
36941
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36942
|
+
}
|
|
36943
|
+
return;
|
|
36944
|
+
}
|
|
36662
36945
|
console.log(chalk2.bold(`
|
|
36663
36946
|
Found ${results.length} skill(s):
|
|
36664
36947
|
`));
|
|
@@ -36667,7 +36950,7 @@ Found ${results.length} skill(s):
|
|
|
36667
36950
|
console.log(` ${s.description}`);
|
|
36668
36951
|
}
|
|
36669
36952
|
});
|
|
36670
|
-
program2.command("info").argument("<skill>", "Skill name").option("--json", "Output as JSON", false).description("Show details about a specific skill").action((name, options) => {
|
|
36953
|
+
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
36954
|
const skill = getSkill(name);
|
|
36672
36955
|
if (!skill) {
|
|
36673
36956
|
console.error(`Skill '${name}' not found`);
|
|
@@ -36679,6 +36962,10 @@ program2.command("info").argument("<skill>", "Skill name").option("--json", "Out
|
|
|
36679
36962
|
console.log(JSON.stringify({ ...skill, ...reqs }, null, 2));
|
|
36680
36963
|
return;
|
|
36681
36964
|
}
|
|
36965
|
+
if (options.brief) {
|
|
36966
|
+
console.log(`${skill.name} \u2014 ${skill.description} [${skill.category}] (tags: ${skill.tags.join(", ")})`);
|
|
36967
|
+
return;
|
|
36968
|
+
}
|
|
36682
36969
|
console.log(`
|
|
36683
36970
|
${chalk2.bold(skill.displayName)}`);
|
|
36684
36971
|
console.log(`${skill.description}`);
|
|
@@ -36909,7 +37196,7 @@ Installing recommended skills for ${options.for} (${options.scope})...
|
|
|
36909
37196
|
const envContent = lines.join(`
|
|
36910
37197
|
`) + `
|
|
36911
37198
|
`;
|
|
36912
|
-
const envPath =
|
|
37199
|
+
const envPath = join5(cwd, ".env.example");
|
|
36913
37200
|
writeFileSync2(envPath, envContent);
|
|
36914
37201
|
envVarCount = envMap.size;
|
|
36915
37202
|
if (!options.json) {
|
|
@@ -36920,10 +37207,10 @@ Installing recommended skills for ${options.for} (${options.scope})...
|
|
|
36920
37207
|
console.log(chalk2.dim(" No environment variables detected across installed skills"));
|
|
36921
37208
|
}
|
|
36922
37209
|
}
|
|
36923
|
-
const gitignorePath =
|
|
37210
|
+
const gitignorePath = join5(cwd, ".gitignore");
|
|
36924
37211
|
const gitignoreEntry = ".skills/";
|
|
36925
37212
|
let gitignoreContent = "";
|
|
36926
|
-
if (
|
|
37213
|
+
if (existsSync5(gitignorePath)) {
|
|
36927
37214
|
gitignoreContent = readFileSync4(gitignorePath, "utf-8");
|
|
36928
37215
|
}
|
|
36929
37216
|
let gitignoreUpdated = false;
|
|
@@ -37033,12 +37320,12 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
37033
37320
|
}
|
|
37034
37321
|
function collectFiles(dir, base = "") {
|
|
37035
37322
|
const files = new Set;
|
|
37036
|
-
if (!
|
|
37323
|
+
if (!existsSync5(dir))
|
|
37037
37324
|
return files;
|
|
37038
|
-
for (const entry of
|
|
37039
|
-
const full =
|
|
37325
|
+
for (const entry of readdirSync4(dir)) {
|
|
37326
|
+
const full = join5(dir, entry);
|
|
37040
37327
|
const rel = base ? `${base}/${entry}` : entry;
|
|
37041
|
-
if (
|
|
37328
|
+
if (statSync3(full).isDirectory()) {
|
|
37042
37329
|
for (const f of collectFiles(full, rel))
|
|
37043
37330
|
files.add(f);
|
|
37044
37331
|
} else {
|
|
@@ -37050,7 +37337,7 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
37050
37337
|
const updateResults = [];
|
|
37051
37338
|
for (const name of toUpdate) {
|
|
37052
37339
|
const skillName = normalizeSkillName(name);
|
|
37053
|
-
const destPath =
|
|
37340
|
+
const destPath = join5(process.cwd(), ".skills", skillName);
|
|
37054
37341
|
const beforeFiles = collectFiles(destPath);
|
|
37055
37342
|
const result = installSkill(name, { overwrite: true });
|
|
37056
37343
|
const afterFiles = collectFiles(destPath);
|
|
@@ -37131,7 +37418,7 @@ Tags:
|
|
|
37131
37418
|
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
37419
|
if (options.register) {
|
|
37133
37420
|
const agents = options.register === "all" ? ["claude", "codex", "gemini"] : [options.register];
|
|
37134
|
-
const binPath =
|
|
37421
|
+
const binPath = join5(import.meta.dir, "..", "mcp", "index.ts");
|
|
37135
37422
|
for (const agent of agents) {
|
|
37136
37423
|
if (agent === "claude") {
|
|
37137
37424
|
try {
|
|
@@ -37145,8 +37432,8 @@ program2.command("mcp").option("--register <agent>", "Register MCP server with a
|
|
|
37145
37432
|
console.log(chalk2.yellow(`Manual registration: claude mcp add skills -- bun run ${binPath}`));
|
|
37146
37433
|
}
|
|
37147
37434
|
} else if (agent === "codex") {
|
|
37148
|
-
const { homedir:
|
|
37149
|
-
const configPath =
|
|
37435
|
+
const { homedir: homedir3 } = await import("os");
|
|
37436
|
+
const configPath = join5(homedir3(), ".codex", "config.toml");
|
|
37150
37437
|
console.log(chalk2.bold(`
|
|
37151
37438
|
Add to ${configPath}:`));
|
|
37152
37439
|
console.log(chalk2.dim(`[mcp_servers.skills]
|
|
@@ -37154,8 +37441,8 @@ command = "bun"
|
|
|
37154
37441
|
args = ["run", "${binPath}"]`));
|
|
37155
37442
|
console.log(chalk2.green(`\u2713 Codex MCP config shown above`));
|
|
37156
37443
|
} else if (agent === "gemini") {
|
|
37157
|
-
const { homedir:
|
|
37158
|
-
const configPath =
|
|
37444
|
+
const { homedir: homedir3 } = await import("os");
|
|
37445
|
+
const configPath = join5(homedir3(), ".gemini", "settings.json");
|
|
37159
37446
|
console.log(chalk2.bold(`
|
|
37160
37447
|
Add to ${configPath} mcpServers:`));
|
|
37161
37448
|
console.log(chalk2.dim(JSON.stringify({
|
|
@@ -37216,7 +37503,8 @@ program2.command("completion").argument("<shell>", "Shell type: bash, zsh, or fi
|
|
|
37216
37503
|
"self-update",
|
|
37217
37504
|
"completion",
|
|
37218
37505
|
"outdated",
|
|
37219
|
-
"doctor"
|
|
37506
|
+
"doctor",
|
|
37507
|
+
"auth"
|
|
37220
37508
|
];
|
|
37221
37509
|
const skillNames = SKILLS.map((s) => s.name);
|
|
37222
37510
|
const categoryNames = CATEGORIES.map((c) => c);
|
|
@@ -37339,6 +37627,117 @@ _skills "$@"
|
|
|
37339
37627
|
process.exitCode = 1;
|
|
37340
37628
|
}
|
|
37341
37629
|
});
|
|
37630
|
+
program2.command("export").option("--json", "Output as JSON (default behavior)", false).description("Export installed skills to JSON for sharing or backup").action((_options) => {
|
|
37631
|
+
const skills = getInstalledSkills();
|
|
37632
|
+
const payload = {
|
|
37633
|
+
version: 1,
|
|
37634
|
+
skills,
|
|
37635
|
+
timestamp: new Date().toISOString()
|
|
37636
|
+
};
|
|
37637
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
37638
|
+
});
|
|
37639
|
+
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) => {
|
|
37640
|
+
let raw;
|
|
37641
|
+
try {
|
|
37642
|
+
if (file2 === "-") {
|
|
37643
|
+
raw = await new Response(process.stdin).text();
|
|
37644
|
+
} else {
|
|
37645
|
+
if (!existsSync5(file2)) {
|
|
37646
|
+
console.error(chalk2.red(`File not found: ${file2}`));
|
|
37647
|
+
process.exitCode = 1;
|
|
37648
|
+
return;
|
|
37649
|
+
}
|
|
37650
|
+
raw = readFileSync4(file2, "utf-8");
|
|
37651
|
+
}
|
|
37652
|
+
} catch (err) {
|
|
37653
|
+
console.error(chalk2.red(`Failed to read file: ${err.message}`));
|
|
37654
|
+
process.exitCode = 1;
|
|
37655
|
+
return;
|
|
37656
|
+
}
|
|
37657
|
+
let payload;
|
|
37658
|
+
try {
|
|
37659
|
+
payload = JSON.parse(raw);
|
|
37660
|
+
} catch {
|
|
37661
|
+
console.error(chalk2.red("Invalid JSON in import file"));
|
|
37662
|
+
process.exitCode = 1;
|
|
37663
|
+
return;
|
|
37664
|
+
}
|
|
37665
|
+
if (!payload || typeof payload !== "object" || !Array.isArray(payload.skills)) {
|
|
37666
|
+
console.error(chalk2.red('Invalid format: expected { "version": 1, "skills": [...] }'));
|
|
37667
|
+
process.exitCode = 1;
|
|
37668
|
+
return;
|
|
37669
|
+
}
|
|
37670
|
+
const skillList = payload.skills;
|
|
37671
|
+
const total = skillList.length;
|
|
37672
|
+
if (total === 0) {
|
|
37673
|
+
if (options.json) {
|
|
37674
|
+
console.log(JSON.stringify({ imported: 0, results: [] }));
|
|
37675
|
+
} else {
|
|
37676
|
+
console.log(chalk2.dim("No skills to import"));
|
|
37677
|
+
}
|
|
37678
|
+
return;
|
|
37679
|
+
}
|
|
37680
|
+
if (options.dryRun) {
|
|
37681
|
+
if (options.json) {
|
|
37682
|
+
console.log(JSON.stringify({ dryRun: true, skills: skillList }));
|
|
37683
|
+
} else {
|
|
37684
|
+
console.log(chalk2.bold(`
|
|
37685
|
+
[dry-run] Would install ${total} skill(s):
|
|
37686
|
+
`));
|
|
37687
|
+
for (let i = 0;i < total; i++) {
|
|
37688
|
+
const target = options.for ? ` for ${options.for} (${options.scope})` : " to .skills/";
|
|
37689
|
+
console.log(chalk2.dim(` [${i + 1}/${total}] ${skillList[i]}${target}`));
|
|
37690
|
+
}
|
|
37691
|
+
}
|
|
37692
|
+
return;
|
|
37693
|
+
}
|
|
37694
|
+
const results = [];
|
|
37695
|
+
if (options.for) {
|
|
37696
|
+
let agents;
|
|
37697
|
+
try {
|
|
37698
|
+
agents = resolveAgents(options.for);
|
|
37699
|
+
} catch (err) {
|
|
37700
|
+
console.error(chalk2.red(err.message));
|
|
37701
|
+
process.exitCode = 1;
|
|
37702
|
+
return;
|
|
37703
|
+
}
|
|
37704
|
+
for (let i = 0;i < total; i++) {
|
|
37705
|
+
const name = skillList[i];
|
|
37706
|
+
if (!options.json) {
|
|
37707
|
+
process.stdout.write(`[${i + 1}/${total}] Installing ${name}...`);
|
|
37708
|
+
}
|
|
37709
|
+
const agentResults = agents.map((agent) => installSkillForAgent(name, { agent, scope: options.scope }, generateSkillMd));
|
|
37710
|
+
const success2 = agentResults.every((r) => r.success);
|
|
37711
|
+
results.push({ skill: name, success: success2, agentResults });
|
|
37712
|
+
if (!options.json) {
|
|
37713
|
+
console.log(success2 ? " done" : " failed");
|
|
37714
|
+
}
|
|
37715
|
+
}
|
|
37716
|
+
} else {
|
|
37717
|
+
for (let i = 0;i < total; i++) {
|
|
37718
|
+
const name = skillList[i];
|
|
37719
|
+
if (!options.json) {
|
|
37720
|
+
process.stdout.write(`[${i + 1}/${total}] Installing ${name}...`);
|
|
37721
|
+
}
|
|
37722
|
+
const result = installSkill(name);
|
|
37723
|
+
results.push(result);
|
|
37724
|
+
if (!options.json) {
|
|
37725
|
+
console.log(result.success ? " done" : " failed");
|
|
37726
|
+
}
|
|
37727
|
+
}
|
|
37728
|
+
}
|
|
37729
|
+
if (options.json) {
|
|
37730
|
+
console.log(JSON.stringify({ imported: results.filter((r) => r.success).length, total, results }));
|
|
37731
|
+
} else {
|
|
37732
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
37733
|
+
const failed = total - succeeded;
|
|
37734
|
+
console.log(chalk2.bold(`
|
|
37735
|
+
Imported ${succeeded}/${total} skill(s)${failed > 0 ? ` (${failed} failed)` : ""}`));
|
|
37736
|
+
}
|
|
37737
|
+
if (results.some((r) => !r.success)) {
|
|
37738
|
+
process.exitCode = 1;
|
|
37739
|
+
}
|
|
37740
|
+
});
|
|
37342
37741
|
program2.command("doctor").option("--json", "Output as JSON", false).description("Check environment variables for installed skills").action((options) => {
|
|
37343
37742
|
const installed = getInstalledSkills();
|
|
37344
37743
|
if (installed.length === 0) {
|
|
@@ -37377,6 +37776,247 @@ Skills Doctor (${installed.length} installed):
|
|
|
37377
37776
|
}
|
|
37378
37777
|
}
|
|
37379
37778
|
});
|
|
37779
|
+
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) => {
|
|
37780
|
+
const cwd = process.cwd();
|
|
37781
|
+
const envFilePath = join5(cwd, ".env");
|
|
37782
|
+
if (options.set) {
|
|
37783
|
+
const eqIdx = options.set.indexOf("=");
|
|
37784
|
+
if (eqIdx === -1) {
|
|
37785
|
+
console.error(chalk2.red(`Invalid format for --set. Expected KEY=VALUE, got: ${options.set}`));
|
|
37786
|
+
process.exitCode = 1;
|
|
37787
|
+
return;
|
|
37788
|
+
}
|
|
37789
|
+
const key = options.set.slice(0, eqIdx).trim();
|
|
37790
|
+
const value = options.set.slice(eqIdx + 1);
|
|
37791
|
+
if (!key) {
|
|
37792
|
+
console.error(chalk2.red("Key cannot be empty"));
|
|
37793
|
+
process.exitCode = 1;
|
|
37794
|
+
return;
|
|
37795
|
+
}
|
|
37796
|
+
let existing = "";
|
|
37797
|
+
if (existsSync5(envFilePath)) {
|
|
37798
|
+
existing = readFileSync4(envFilePath, "utf-8");
|
|
37799
|
+
}
|
|
37800
|
+
const keyPattern = new RegExp(`^${key}=.*$`, "m");
|
|
37801
|
+
let updated;
|
|
37802
|
+
if (keyPattern.test(existing)) {
|
|
37803
|
+
updated = existing.replace(keyPattern, `${key}=${value}`);
|
|
37804
|
+
} else {
|
|
37805
|
+
updated = existing.endsWith(`
|
|
37806
|
+
`) || existing === "" ? existing + `${key}=${value}
|
|
37807
|
+
` : existing + `
|
|
37808
|
+
${key}=${value}
|
|
37809
|
+
`;
|
|
37810
|
+
}
|
|
37811
|
+
writeFileSync2(envFilePath, updated, "utf-8");
|
|
37812
|
+
console.log(chalk2.green(`Set ${key} in ${envFilePath}`));
|
|
37813
|
+
return;
|
|
37814
|
+
}
|
|
37815
|
+
if (name) {
|
|
37816
|
+
const reqs = getSkillRequirements(name);
|
|
37817
|
+
if (!reqs) {
|
|
37818
|
+
console.error(`Skill '${name}' not found`);
|
|
37819
|
+
process.exitCode = 1;
|
|
37820
|
+
return;
|
|
37821
|
+
}
|
|
37822
|
+
const envVars = reqs.envVars.map((v) => ({ name: v, set: !!process.env[v] }));
|
|
37823
|
+
if (options.json) {
|
|
37824
|
+
console.log(JSON.stringify({ skill: name, envVars }, null, 2));
|
|
37825
|
+
return;
|
|
37826
|
+
}
|
|
37827
|
+
console.log(chalk2.bold(`
|
|
37828
|
+
Auth status for ${name}:
|
|
37829
|
+
`));
|
|
37830
|
+
if (envVars.length === 0) {
|
|
37831
|
+
console.log(chalk2.dim(" No environment variables required"));
|
|
37832
|
+
} else {
|
|
37833
|
+
for (const v of envVars) {
|
|
37834
|
+
const icon = v.set ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
37835
|
+
const status = v.set ? chalk2.green("set") : chalk2.red("missing");
|
|
37836
|
+
console.log(` ${icon} ${v.name} (${status})`);
|
|
37837
|
+
}
|
|
37838
|
+
}
|
|
37839
|
+
return;
|
|
37840
|
+
}
|
|
37841
|
+
const installed = getInstalledSkills();
|
|
37842
|
+
if (installed.length === 0) {
|
|
37843
|
+
if (options.json) {
|
|
37844
|
+
console.log(JSON.stringify([]));
|
|
37845
|
+
} else {
|
|
37846
|
+
console.log("No skills installed");
|
|
37847
|
+
}
|
|
37848
|
+
return;
|
|
37849
|
+
}
|
|
37850
|
+
const report = [];
|
|
37851
|
+
for (const skillName of installed) {
|
|
37852
|
+
const reqs = getSkillRequirements(skillName);
|
|
37853
|
+
const envVars = (reqs?.envVars ?? []).map((v) => ({ name: v, set: !!process.env[v] }));
|
|
37854
|
+
report.push({ skill: skillName, envVars });
|
|
37855
|
+
}
|
|
37856
|
+
if (options.json) {
|
|
37857
|
+
console.log(JSON.stringify(report, null, 2));
|
|
37858
|
+
return;
|
|
37859
|
+
}
|
|
37860
|
+
console.log(chalk2.bold(`
|
|
37861
|
+
Auth status (${installed.length} installed skills):
|
|
37862
|
+
`));
|
|
37863
|
+
for (const entry of report) {
|
|
37864
|
+
console.log(chalk2.bold(` ${entry.skill}`));
|
|
37865
|
+
if (entry.envVars.length === 0) {
|
|
37866
|
+
console.log(chalk2.dim(" No environment variables required"));
|
|
37867
|
+
} else {
|
|
37868
|
+
for (const v of entry.envVars) {
|
|
37869
|
+
const icon = v.set ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
37870
|
+
const status = v.set ? chalk2.green("set") : chalk2.red("missing");
|
|
37871
|
+
console.log(` ${icon} ${v.name} (${status})`);
|
|
37872
|
+
}
|
|
37873
|
+
}
|
|
37874
|
+
}
|
|
37875
|
+
});
|
|
37876
|
+
program2.command("whoami").option("--json", "Output as JSON", false).description("Show setup summary: version, installed skills, agent configs, and paths").action((options) => {
|
|
37877
|
+
const { homedir: homedir3 } = __require("os");
|
|
37878
|
+
const version2 = package_default.version;
|
|
37879
|
+
const cwd = process.cwd();
|
|
37880
|
+
const installed = getInstalledSkills();
|
|
37881
|
+
const agentNames = ["claude", "codex", "gemini"];
|
|
37882
|
+
const agentConfigs = [];
|
|
37883
|
+
for (const agent of agentNames) {
|
|
37884
|
+
const agentSkillsPath = join5(homedir3(), `.${agent}`, "skills");
|
|
37885
|
+
const exists = existsSync5(agentSkillsPath);
|
|
37886
|
+
let skillCount = 0;
|
|
37887
|
+
if (exists) {
|
|
37888
|
+
try {
|
|
37889
|
+
skillCount = readdirSync4(agentSkillsPath).filter((f) => {
|
|
37890
|
+
const full = join5(agentSkillsPath, f);
|
|
37891
|
+
return f.startsWith("skill-") && statSync3(full).isDirectory();
|
|
37892
|
+
}).length;
|
|
37893
|
+
} catch {}
|
|
37894
|
+
}
|
|
37895
|
+
agentConfigs.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
37896
|
+
}
|
|
37897
|
+
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
37898
|
+
if (options.json) {
|
|
37899
|
+
console.log(JSON.stringify({
|
|
37900
|
+
version: version2,
|
|
37901
|
+
installedCount: installed.length,
|
|
37902
|
+
installed,
|
|
37903
|
+
agents: agentConfigs,
|
|
37904
|
+
skillsDir,
|
|
37905
|
+
cwd
|
|
37906
|
+
}, null, 2));
|
|
37907
|
+
return;
|
|
37908
|
+
}
|
|
37909
|
+
console.log(chalk2.bold(`
|
|
37910
|
+
skills v${version2}
|
|
37911
|
+
`));
|
|
37912
|
+
console.log(`${chalk2.dim("Working directory:")} ${cwd}`);
|
|
37913
|
+
console.log(`${chalk2.dim("Skills directory:")} ${skillsDir}`);
|
|
37914
|
+
console.log();
|
|
37915
|
+
if (installed.length === 0) {
|
|
37916
|
+
console.log(chalk2.dim("No skills installed in current project"));
|
|
37917
|
+
} else {
|
|
37918
|
+
console.log(chalk2.bold(`Installed skills (${installed.length}):`));
|
|
37919
|
+
for (const name of installed) {
|
|
37920
|
+
console.log(` ${chalk2.cyan(name)}`);
|
|
37921
|
+
}
|
|
37922
|
+
}
|
|
37923
|
+
console.log();
|
|
37924
|
+
console.log(chalk2.bold("Agent configurations:"));
|
|
37925
|
+
for (const cfg of agentConfigs) {
|
|
37926
|
+
if (cfg.exists) {
|
|
37927
|
+
console.log(` ${chalk2.green("\u2713")} ${cfg.agent} \u2014 ${cfg.skillCount} skill(s) at ${cfg.path}`);
|
|
37928
|
+
} else {
|
|
37929
|
+
console.log(` ${chalk2.dim("\u2717")} ${cfg.agent} \u2014 not configured`);
|
|
37930
|
+
}
|
|
37931
|
+
}
|
|
37932
|
+
});
|
|
37933
|
+
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) => {
|
|
37934
|
+
let skillNames;
|
|
37935
|
+
if (skillArg) {
|
|
37936
|
+
const registryName = skillArg.startsWith("skill-") ? skillArg.replace("skill-", "") : skillArg;
|
|
37937
|
+
const skill = getSkill(registryName);
|
|
37938
|
+
if (!skill) {
|
|
37939
|
+
if (options.json) {
|
|
37940
|
+
console.log(JSON.stringify({ error: `Skill '${skillArg}' not found` }));
|
|
37941
|
+
} else {
|
|
37942
|
+
console.error(chalk2.red(`Skill '${skillArg}' not found`));
|
|
37943
|
+
}
|
|
37944
|
+
process.exitCode = 1;
|
|
37945
|
+
return;
|
|
37946
|
+
}
|
|
37947
|
+
skillNames = [registryName];
|
|
37948
|
+
} else {
|
|
37949
|
+
skillNames = getInstalledSkills();
|
|
37950
|
+
if (skillNames.length === 0) {
|
|
37951
|
+
if (options.json) {
|
|
37952
|
+
console.log(JSON.stringify([]));
|
|
37953
|
+
} else {
|
|
37954
|
+
console.log(chalk2.dim("No skills installed. Run: skills install <name>"));
|
|
37955
|
+
}
|
|
37956
|
+
return;
|
|
37957
|
+
}
|
|
37958
|
+
}
|
|
37959
|
+
const results = [];
|
|
37960
|
+
for (const name of skillNames) {
|
|
37961
|
+
const reqs = getSkillRequirements(name);
|
|
37962
|
+
const envVars = (reqs?.envVars ?? []).map((v) => ({
|
|
37963
|
+
name: v,
|
|
37964
|
+
set: !!process.env[v]
|
|
37965
|
+
}));
|
|
37966
|
+
const systemDeps = (reqs?.systemDeps ?? []).map((dep) => {
|
|
37967
|
+
const proc = Bun.spawnSync(["which", dep]);
|
|
37968
|
+
return { name: dep, available: proc.exitCode === 0 };
|
|
37969
|
+
});
|
|
37970
|
+
const npmDeps = Object.entries(reqs?.dependencies ?? {}).map(([pkgName, version2]) => ({
|
|
37971
|
+
name: pkgName,
|
|
37972
|
+
version: version2
|
|
37973
|
+
}));
|
|
37974
|
+
const ready = envVars.every((v) => v.set) && systemDeps.every((d) => d.available);
|
|
37975
|
+
results.push({ skill: name, envVars, systemDeps, npmDeps, ready });
|
|
37976
|
+
}
|
|
37977
|
+
if (options.json) {
|
|
37978
|
+
console.log(JSON.stringify(results, null, 2));
|
|
37979
|
+
return;
|
|
37980
|
+
}
|
|
37981
|
+
const allReady = results.every((r) => r.ready);
|
|
37982
|
+
console.log(chalk2.bold(`
|
|
37983
|
+
Skills Test (${results.length} skill${results.length === 1 ? "" : "s"}):
|
|
37984
|
+
`));
|
|
37985
|
+
for (const result of results) {
|
|
37986
|
+
const readyLabel = result.ready ? chalk2.green("ready") : chalk2.red("not ready");
|
|
37987
|
+
console.log(chalk2.bold(` ${result.skill}`) + chalk2.dim(` [${readyLabel}]`));
|
|
37988
|
+
if (result.envVars.length === 0 && result.systemDeps.length === 0) {
|
|
37989
|
+
console.log(chalk2.dim(" No requirements"));
|
|
37990
|
+
}
|
|
37991
|
+
for (const v of result.envVars) {
|
|
37992
|
+
if (v.set) {
|
|
37993
|
+
console.log(` ${chalk2.green("\u2713")} ${v.name}`);
|
|
37994
|
+
} else {
|
|
37995
|
+
console.log(` ${chalk2.red("\u2717")} ${v.name} ${chalk2.dim("(missing)")}`);
|
|
37996
|
+
}
|
|
37997
|
+
}
|
|
37998
|
+
for (const dep of result.systemDeps) {
|
|
37999
|
+
if (dep.available) {
|
|
38000
|
+
console.log(` ${chalk2.green("\u2713")} ${dep.name} ${chalk2.dim("(system)")}`);
|
|
38001
|
+
} else {
|
|
38002
|
+
console.log(` ${chalk2.red("\u2717")} ${dep.name} ${chalk2.dim("(not installed)")}`);
|
|
38003
|
+
}
|
|
38004
|
+
}
|
|
38005
|
+
if (result.npmDeps.length > 0) {
|
|
38006
|
+
console.log(chalk2.dim(` npm: ${result.npmDeps.map((d) => d.name).join(", ")}`));
|
|
38007
|
+
}
|
|
38008
|
+
}
|
|
38009
|
+
console.log();
|
|
38010
|
+
if (allReady) {
|
|
38011
|
+
console.log(chalk2.green(`All ${results.length} skill(s) ready`));
|
|
38012
|
+
} else {
|
|
38013
|
+
const notReady = results.filter((r) => !r.ready).length;
|
|
38014
|
+
console.log(chalk2.yellow(`${notReady} skill(s) not ready`));
|
|
38015
|
+
}
|
|
38016
|
+
if (!allReady) {
|
|
38017
|
+
process.exitCode = 1;
|
|
38018
|
+
}
|
|
38019
|
+
});
|
|
37380
38020
|
program2.command("outdated").option("--json", "Output as JSON", false).description("Check for outdated installed skills").action((options) => {
|
|
37381
38021
|
const installed = getInstalledSkills();
|
|
37382
38022
|
if (installed.length === 0) {
|
|
@@ -37392,17 +38032,17 @@ program2.command("outdated").option("--json", "Output as JSON", false).descripti
|
|
|
37392
38032
|
const upToDate = [];
|
|
37393
38033
|
for (const name of installed) {
|
|
37394
38034
|
const skillName = normalizeSkillName(name);
|
|
37395
|
-
const installedPkgPath =
|
|
38035
|
+
const installedPkgPath = join5(cwd, ".skills", skillName, "package.json");
|
|
37396
38036
|
let installedVersion = "unknown";
|
|
37397
|
-
if (
|
|
38037
|
+
if (existsSync5(installedPkgPath)) {
|
|
37398
38038
|
try {
|
|
37399
38039
|
installedVersion = JSON.parse(readFileSync4(installedPkgPath, "utf-8")).version || "unknown";
|
|
37400
38040
|
} catch {}
|
|
37401
38041
|
}
|
|
37402
38042
|
const registryPath = getSkillPath(name);
|
|
37403
|
-
const registryPkgPath =
|
|
38043
|
+
const registryPkgPath = join5(registryPath, "package.json");
|
|
37404
38044
|
let registryVersion = "unknown";
|
|
37405
|
-
if (
|
|
38045
|
+
if (existsSync5(registryPkgPath)) {
|
|
37406
38046
|
try {
|
|
37407
38047
|
registryVersion = JSON.parse(readFileSync4(registryPkgPath, "utf-8")).version || "unknown";
|
|
37408
38048
|
} catch {}
|