@hasna/skills 0.1.3 → 0.1.5
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/README.md +354 -330
- package/bin/index.js +687 -196
- package/bin/mcp.js +93 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +25 -0
- package/dist/lib/registry.d.ts +1 -0
- package/dist/server/serve.d.ts +5 -1
- package/package.json +1 -1
- package/skills/skill-scancommitpr/SKILL.md +178 -0
- package/skills/skill-scancommitpr/package.json +36 -0
- package/skills/skill-scancommitpr/src/index.ts +44 -0
- package/skills/skill-scancommitpr/tsconfig.json +13 -0
- package/skills/skill-scancommitpush/SKILL.md +174 -0
- package/skills/skill-scancommitpush/package.json +34 -0
- package/skills/skill-scancommitpush/src/index.ts +44 -0
- package/skills/skill-scancommitpush/tsconfig.json +13 -0
- package/skills/skill-hook/bunfig.toml +0 -5
- package/skills/skill-implementation/bunfig.toml +0 -5
package/bin/index.js
CHANGED
|
@@ -1868,12 +1868,17 @@ var require_commander = __commonJS((exports) => {
|
|
|
1868
1868
|
exports.InvalidOptionArgumentError = InvalidArgumentError;
|
|
1869
1869
|
});
|
|
1870
1870
|
|
|
1871
|
+
// src/lib/utils.ts
|
|
1872
|
+
function normalizeSkillName(name) {
|
|
1873
|
+
return name.startsWith("skill-") ? name : `skill-${name}`;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1871
1876
|
// package.json
|
|
1872
1877
|
var package_default;
|
|
1873
1878
|
var init_package = __esm(() => {
|
|
1874
1879
|
package_default = {
|
|
1875
1880
|
name: "@hasna/skills",
|
|
1876
|
-
version: "0.1.
|
|
1881
|
+
version: "0.1.5",
|
|
1877
1882
|
description: "Skills library for AI coding agents",
|
|
1878
1883
|
type: "module",
|
|
1879
1884
|
bin: {
|
|
@@ -2222,6 +2227,21 @@ var init_registry = __esm(() => {
|
|
|
2222
2227
|
category: "Development Tools",
|
|
2223
2228
|
tags: ["scaffold", "project", "boilerplate", "template"]
|
|
2224
2229
|
},
|
|
2230
|
+
{
|
|
2231
|
+
name: "scancommitpr",
|
|
2232
|
+
displayName: "Scan Commit PR",
|
|
2233
|
+
description: "Scan repo changes, group into logical commits, push, and optionally create a PR",
|
|
2234
|
+
category: "Development Tools",
|
|
2235
|
+
tags: ["git", "commit", "push", "pull-request", "automation"],
|
|
2236
|
+
dependencies: ["scancommitpush"]
|
|
2237
|
+
},
|
|
2238
|
+
{
|
|
2239
|
+
name: "scancommitpush",
|
|
2240
|
+
displayName: "Scan Commit Push",
|
|
2241
|
+
description: "Scan repo changes, group into logical commits with conventional messages, and push to GitHub",
|
|
2242
|
+
category: "Development Tools",
|
|
2243
|
+
tags: ["git", "commit", "push", "automation"]
|
|
2244
|
+
},
|
|
2225
2245
|
{
|
|
2226
2246
|
name: "security-audit",
|
|
2227
2247
|
displayName: "Security Audit",
|
|
@@ -5062,11 +5082,6 @@ var require_cli_spinners = __commonJS((exports, module) => {
|
|
|
5062
5082
|
module.exports = spinners;
|
|
5063
5083
|
});
|
|
5064
5084
|
|
|
5065
|
-
// src/lib/utils.ts
|
|
5066
|
-
function normalizeSkillName(name) {
|
|
5067
|
-
return name.startsWith("skill-") ? name : `skill-${name}`;
|
|
5068
|
-
}
|
|
5069
|
-
|
|
5070
5085
|
// src/lib/installer.ts
|
|
5071
5086
|
import { existsSync, cpSync, mkdirSync, writeFileSync, rmSync, readdirSync, statSync, readFileSync } from "fs";
|
|
5072
5087
|
import { join, dirname } from "path";
|
|
@@ -5123,6 +5138,16 @@ function installSkill(name, options = {}) {
|
|
|
5123
5138
|
}
|
|
5124
5139
|
});
|
|
5125
5140
|
updateSkillsIndex(destDir);
|
|
5141
|
+
const meta = getSkill(name);
|
|
5142
|
+
if (meta?.dependencies && meta.dependencies.length > 0) {
|
|
5143
|
+
const installed = getInstalledSkills(targetDir);
|
|
5144
|
+
const installedSet = new Set(installed);
|
|
5145
|
+
for (const dep of meta.dependencies) {
|
|
5146
|
+
if (!installedSet.has(dep)) {
|
|
5147
|
+
console.warn(`Warning: skill-${meta.name} depends on skill-${dep} which is not installed`);
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5126
5151
|
return {
|
|
5127
5152
|
skill: name,
|
|
5128
5153
|
success: true,
|
|
@@ -5235,6 +5260,7 @@ function removeSkillForAgent(name, options) {
|
|
|
5235
5260
|
}
|
|
5236
5261
|
var __dirname2, SKILLS_DIR, AGENT_TARGETS;
|
|
5237
5262
|
var init_installer = __esm(() => {
|
|
5263
|
+
init_registry();
|
|
5238
5264
|
__dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
5239
5265
|
SKILLS_DIR = findSkillsDir();
|
|
5240
5266
|
AGENT_TARGETS = ["claude", "codex", "gemini"];
|
|
@@ -5369,57 +5395,6 @@ async function runSkill(name, args, options = {}) {
|
|
|
5369
5395
|
const exitCode = await proc.exited;
|
|
5370
5396
|
return { exitCode };
|
|
5371
5397
|
}
|
|
5372
|
-
function generateEnvExample(targetDir = process.cwd()) {
|
|
5373
|
-
const skillsDir = join2(targetDir, ".skills");
|
|
5374
|
-
if (!existsSync2(skillsDir))
|
|
5375
|
-
return "";
|
|
5376
|
-
const dirs = readdirSync2(skillsDir).filter((f) => f.startsWith("skill-") && existsSync2(join2(skillsDir, f, "package.json")));
|
|
5377
|
-
const envMap = new Map;
|
|
5378
|
-
for (const dir of dirs) {
|
|
5379
|
-
const skillName = dir.replace("skill-", "");
|
|
5380
|
-
const skillPath = join2(skillsDir, dir);
|
|
5381
|
-
const texts = [];
|
|
5382
|
-
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example"]) {
|
|
5383
|
-
const content = readIfExists(join2(skillPath, file));
|
|
5384
|
-
if (content)
|
|
5385
|
-
texts.push(content);
|
|
5386
|
-
}
|
|
5387
|
-
const allText = texts.join(`
|
|
5388
|
-
`);
|
|
5389
|
-
const foundVars = extractEnvVars(allText);
|
|
5390
|
-
for (const envVar of foundVars) {
|
|
5391
|
-
if (!envMap.has(envVar)) {
|
|
5392
|
-
envMap.set(envVar, []);
|
|
5393
|
-
}
|
|
5394
|
-
if (!envMap.get(envVar).includes(skillName)) {
|
|
5395
|
-
envMap.get(envVar).push(skillName);
|
|
5396
|
-
}
|
|
5397
|
-
}
|
|
5398
|
-
}
|
|
5399
|
-
if (envMap.size === 0)
|
|
5400
|
-
return "";
|
|
5401
|
-
const lines = [
|
|
5402
|
-
"# Environment variables for installed skills",
|
|
5403
|
-
"# Auto-generated by: skills init",
|
|
5404
|
-
""
|
|
5405
|
-
];
|
|
5406
|
-
const sorted = Array.from(envMap.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
5407
|
-
let lastPrefix = "";
|
|
5408
|
-
for (const [envVar, skills] of sorted) {
|
|
5409
|
-
const prefix = envVar.split("_")[0];
|
|
5410
|
-
if (prefix !== lastPrefix) {
|
|
5411
|
-
if (lastPrefix)
|
|
5412
|
-
lines.push("");
|
|
5413
|
-
lines.push(`# ${prefix}`);
|
|
5414
|
-
lastPrefix = prefix;
|
|
5415
|
-
}
|
|
5416
|
-
lines.push(`# Used by: ${skills.join(", ")}`);
|
|
5417
|
-
lines.push(`${envVar}=`);
|
|
5418
|
-
}
|
|
5419
|
-
return lines.join(`
|
|
5420
|
-
`) + `
|
|
5421
|
-
`;
|
|
5422
|
-
}
|
|
5423
5398
|
function generateSkillMd(name) {
|
|
5424
5399
|
const meta = getSkill(name);
|
|
5425
5400
|
if (!meta)
|
|
@@ -32808,7 +32783,7 @@ var require_formats = __commonJS((exports) => {
|
|
|
32808
32783
|
}
|
|
32809
32784
|
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
32810
32785
|
function getTime(strictTimeZone) {
|
|
32811
|
-
return function
|
|
32786
|
+
return function time(str) {
|
|
32812
32787
|
const matches = TIME.exec(str);
|
|
32813
32788
|
if (!matches)
|
|
32814
32789
|
return false;
|
|
@@ -33072,6 +33047,62 @@ class ExperimentalServerTasks {
|
|
|
33072
33047
|
requestStream(request, resultSchema, options) {
|
|
33073
33048
|
return this._server.requestStream(request, resultSchema, options);
|
|
33074
33049
|
}
|
|
33050
|
+
createMessageStream(params, options) {
|
|
33051
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
33052
|
+
if ((params.tools || params.toolChoice) && !clientCapabilities?.sampling?.tools) {
|
|
33053
|
+
throw new Error("Client does not support sampling tools capability.");
|
|
33054
|
+
}
|
|
33055
|
+
if (params.messages.length > 0) {
|
|
33056
|
+
const lastMessage = params.messages[params.messages.length - 1];
|
|
33057
|
+
const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
|
|
33058
|
+
const hasToolResults = lastContent.some((c) => c.type === "tool_result");
|
|
33059
|
+
const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
|
|
33060
|
+
const previousContent = previousMessage ? Array.isArray(previousMessage.content) ? previousMessage.content : [previousMessage.content] : [];
|
|
33061
|
+
const hasPreviousToolUse = previousContent.some((c) => c.type === "tool_use");
|
|
33062
|
+
if (hasToolResults) {
|
|
33063
|
+
if (lastContent.some((c) => c.type !== "tool_result")) {
|
|
33064
|
+
throw new Error("The last message must contain only tool_result content if any is present");
|
|
33065
|
+
}
|
|
33066
|
+
if (!hasPreviousToolUse) {
|
|
33067
|
+
throw new Error("tool_result blocks are not matching any tool_use from the previous message");
|
|
33068
|
+
}
|
|
33069
|
+
}
|
|
33070
|
+
if (hasPreviousToolUse) {
|
|
33071
|
+
const toolUseIds = new Set(previousContent.filter((c) => c.type === "tool_use").map((c) => c.id));
|
|
33072
|
+
const toolResultIds = new Set(lastContent.filter((c) => c.type === "tool_result").map((c) => c.toolUseId));
|
|
33073
|
+
if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every((id) => toolResultIds.has(id))) {
|
|
33074
|
+
throw new Error("ids of tool_result blocks and tool_use blocks from previous message do not match");
|
|
33075
|
+
}
|
|
33076
|
+
}
|
|
33077
|
+
}
|
|
33078
|
+
return this.requestStream({
|
|
33079
|
+
method: "sampling/createMessage",
|
|
33080
|
+
params
|
|
33081
|
+
}, CreateMessageResultSchema, options);
|
|
33082
|
+
}
|
|
33083
|
+
elicitInputStream(params, options) {
|
|
33084
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
33085
|
+
const mode = params.mode ?? "form";
|
|
33086
|
+
switch (mode) {
|
|
33087
|
+
case "url": {
|
|
33088
|
+
if (!clientCapabilities?.elicitation?.url) {
|
|
33089
|
+
throw new Error("Client does not support url elicitation.");
|
|
33090
|
+
}
|
|
33091
|
+
break;
|
|
33092
|
+
}
|
|
33093
|
+
case "form": {
|
|
33094
|
+
if (!clientCapabilities?.elicitation?.form) {
|
|
33095
|
+
throw new Error("Client does not support form elicitation.");
|
|
33096
|
+
}
|
|
33097
|
+
break;
|
|
33098
|
+
}
|
|
33099
|
+
}
|
|
33100
|
+
const normalizedParams = mode === "form" && params.mode === undefined ? { ...params, mode: "form" } : params;
|
|
33101
|
+
return this.requestStream({
|
|
33102
|
+
method: "elicitation/create",
|
|
33103
|
+
params: normalizedParams
|
|
33104
|
+
}, ElicitResultSchema, options);
|
|
33105
|
+
}
|
|
33075
33106
|
async getTask(taskId, options) {
|
|
33076
33107
|
return this._server.getTask({ taskId }, options);
|
|
33077
33108
|
}
|
|
@@ -33085,6 +33116,9 @@ class ExperimentalServerTasks {
|
|
|
33085
33116
|
return this._server.cancelTask({ taskId }, options);
|
|
33086
33117
|
}
|
|
33087
33118
|
}
|
|
33119
|
+
var init_server = __esm(() => {
|
|
33120
|
+
init_types2();
|
|
33121
|
+
});
|
|
33088
33122
|
|
|
33089
33123
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
|
|
33090
33124
|
function assertToolsCallTaskCapability(requests, method, entityName) {
|
|
@@ -33123,11 +33157,12 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
|
|
|
33123
33157
|
|
|
33124
33158
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
|
|
33125
33159
|
var Server;
|
|
33126
|
-
var
|
|
33160
|
+
var init_server2 = __esm(() => {
|
|
33127
33161
|
init_protocol();
|
|
33128
33162
|
init_types2();
|
|
33129
33163
|
init_ajv_provider();
|
|
33130
33164
|
init_zod_compat();
|
|
33165
|
+
init_server();
|
|
33131
33166
|
Server = class Server extends Protocol {
|
|
33132
33167
|
constructor(_serverInfo, options) {
|
|
33133
33168
|
super(options);
|
|
@@ -34500,7 +34535,7 @@ function createCompletionResult(suggestions) {
|
|
|
34500
34535
|
}
|
|
34501
34536
|
var EMPTY_OBJECT_JSON_SCHEMA, EMPTY_COMPLETION_RESULT;
|
|
34502
34537
|
var init_mcp = __esm(() => {
|
|
34503
|
-
|
|
34538
|
+
init_server2();
|
|
34504
34539
|
init_zod_compat();
|
|
34505
34540
|
init_zod_json_schema_compat();
|
|
34506
34541
|
init_types2();
|
|
@@ -34829,11 +34864,25 @@ var init_mcp2 = __esm(() => {
|
|
|
34829
34864
|
// src/server/serve.ts
|
|
34830
34865
|
var exports_serve = {};
|
|
34831
34866
|
__export(exports_serve, {
|
|
34832
|
-
startServer: () => startServer
|
|
34867
|
+
startServer: () => startServer,
|
|
34868
|
+
createFetchHandler: () => createFetchHandler
|
|
34833
34869
|
});
|
|
34834
|
-
import { existsSync as existsSync3 } from "fs";
|
|
34870
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
34835
34871
|
import { join as join3, dirname as dirname2, extname } from "path";
|
|
34836
34872
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
34873
|
+
function getPackageJson() {
|
|
34874
|
+
try {
|
|
34875
|
+
const scriptDir = dirname2(fileURLToPath2(import.meta.url));
|
|
34876
|
+
for (const rel of ["../..", ".."]) {
|
|
34877
|
+
const pkgPath = join3(scriptDir, rel, "package.json");
|
|
34878
|
+
if (existsSync3(pkgPath)) {
|
|
34879
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
34880
|
+
return { version: pkg.version || "unknown", name: pkg.name || "skills" };
|
|
34881
|
+
}
|
|
34882
|
+
}
|
|
34883
|
+
} catch {}
|
|
34884
|
+
return { version: "unknown", name: "skills" };
|
|
34885
|
+
}
|
|
34837
34886
|
function resolveDashboardDir() {
|
|
34838
34887
|
const candidates = [];
|
|
34839
34888
|
try {
|
|
@@ -34869,6 +34918,7 @@ function getAllSkillsWithStatus() {
|
|
|
34869
34918
|
const installed = new Set(getInstalledSkills());
|
|
34870
34919
|
return SKILLS.map((meta3) => {
|
|
34871
34920
|
const reqs = getSkillRequirements(meta3.name);
|
|
34921
|
+
const envVars = reqs?.envVars || [];
|
|
34872
34922
|
return {
|
|
34873
34923
|
name: meta3.name,
|
|
34874
34924
|
displayName: meta3.displayName,
|
|
@@ -34876,7 +34926,8 @@ function getAllSkillsWithStatus() {
|
|
|
34876
34926
|
category: meta3.category,
|
|
34877
34927
|
tags: meta3.tags,
|
|
34878
34928
|
installed: installed.has(meta3.name),
|
|
34879
|
-
envVars
|
|
34929
|
+
envVars,
|
|
34930
|
+
envVarsSet: envVars.filter((v) => !!process.env[v]),
|
|
34880
34931
|
systemDeps: reqs?.systemDeps || [],
|
|
34881
34932
|
cliCommand: reqs?.cliCommand || null
|
|
34882
34933
|
};
|
|
@@ -34891,126 +34942,181 @@ function serveStaticFile(filePath) {
|
|
|
34891
34942
|
headers: { "Content-Type": contentType }
|
|
34892
34943
|
});
|
|
34893
34944
|
}
|
|
34894
|
-
|
|
34895
|
-
const
|
|
34896
|
-
const
|
|
34897
|
-
|
|
34898
|
-
|
|
34899
|
-
|
|
34900
|
-
|
|
34901
|
-
|
|
34902
|
-
|
|
34903
|
-
|
|
34904
|
-
|
|
34905
|
-
|
|
34906
|
-
|
|
34907
|
-
|
|
34908
|
-
|
|
34909
|
-
|
|
34910
|
-
|
|
34911
|
-
|
|
34912
|
-
|
|
34913
|
-
|
|
34914
|
-
|
|
34915
|
-
|
|
34916
|
-
|
|
34917
|
-
|
|
34918
|
-
|
|
34919
|
-
|
|
34920
|
-
return
|
|
34921
|
-
}
|
|
34922
|
-
if (path === "/api/skills/search" && method === "GET") {
|
|
34923
|
-
const query = url2.searchParams.get("q") || "";
|
|
34924
|
-
if (!query.trim())
|
|
34925
|
-
return json2([]);
|
|
34926
|
-
const results = searchSkills(query);
|
|
34927
|
-
const installed = new Set(getInstalledSkills());
|
|
34928
|
-
return json2(results.map((meta3) => {
|
|
34929
|
-
const reqs = getSkillRequirements(meta3.name);
|
|
34930
|
-
return {
|
|
34931
|
-
name: meta3.name,
|
|
34932
|
-
displayName: meta3.displayName,
|
|
34933
|
-
description: meta3.description,
|
|
34934
|
-
category: meta3.category,
|
|
34935
|
-
tags: meta3.tags,
|
|
34936
|
-
installed: installed.has(meta3.name),
|
|
34937
|
-
envVars: reqs?.envVars || [],
|
|
34938
|
-
systemDeps: reqs?.systemDeps || [],
|
|
34939
|
-
cliCommand: reqs?.cliCommand || null
|
|
34940
|
-
};
|
|
34941
|
-
}));
|
|
34942
|
-
}
|
|
34943
|
-
const singleMatch = path.match(/^\/api\/skills\/([^/]+)$/);
|
|
34944
|
-
if (singleMatch && method === "GET") {
|
|
34945
|
-
const name = singleMatch[1];
|
|
34946
|
-
if (!isValidSkillName(name))
|
|
34947
|
-
return json2({ error: "Invalid skill name" }, 400);
|
|
34948
|
-
const meta3 = getSkill(name);
|
|
34949
|
-
if (!meta3)
|
|
34950
|
-
return json2({ error: `Skill '${name}' not found` }, 404);
|
|
34951
|
-
const reqs = getSkillRequirements(name);
|
|
34952
|
-
const docs = getSkillBestDoc(name);
|
|
34953
|
-
const installed = new Set(getInstalledSkills());
|
|
34954
|
-
return json2({
|
|
34945
|
+
function createFetchHandler(options) {
|
|
34946
|
+
const dashboardDir = options?.dashboardDir ?? resolveDashboardDir();
|
|
34947
|
+
const dashboardExists = options?.dashboardExists ?? existsSync3(dashboardDir);
|
|
34948
|
+
return async function fetchHandler(req) {
|
|
34949
|
+
const url2 = new URL(req.url);
|
|
34950
|
+
const path = url2.pathname;
|
|
34951
|
+
const method = req.method;
|
|
34952
|
+
if (path === "/api/skills" && method === "GET") {
|
|
34953
|
+
return json2(getAllSkillsWithStatus());
|
|
34954
|
+
}
|
|
34955
|
+
if (path === "/api/categories" && method === "GET") {
|
|
34956
|
+
const counts = CATEGORIES.map((cat) => ({
|
|
34957
|
+
name: cat,
|
|
34958
|
+
count: getSkillsByCategory(cat).length
|
|
34959
|
+
}));
|
|
34960
|
+
return json2(counts);
|
|
34961
|
+
}
|
|
34962
|
+
if (path === "/api/skills/search" && method === "GET") {
|
|
34963
|
+
const query = url2.searchParams.get("q") || "";
|
|
34964
|
+
if (!query.trim())
|
|
34965
|
+
return json2([]);
|
|
34966
|
+
const results = searchSkills(query);
|
|
34967
|
+
const installed = new Set(getInstalledSkills());
|
|
34968
|
+
return json2(results.map((meta3) => {
|
|
34969
|
+
const reqs = getSkillRequirements(meta3.name);
|
|
34970
|
+
const envVars = reqs?.envVars || [];
|
|
34971
|
+
return {
|
|
34955
34972
|
name: meta3.name,
|
|
34956
34973
|
displayName: meta3.displayName,
|
|
34957
34974
|
description: meta3.description,
|
|
34958
34975
|
category: meta3.category,
|
|
34959
34976
|
tags: meta3.tags,
|
|
34960
34977
|
installed: installed.has(meta3.name),
|
|
34961
|
-
envVars
|
|
34978
|
+
envVars,
|
|
34979
|
+
envVarsSet: envVars.filter((v) => !!process.env[v]),
|
|
34962
34980
|
systemDeps: reqs?.systemDeps || [],
|
|
34963
|
-
cliCommand: reqs?.cliCommand || null
|
|
34964
|
-
|
|
34965
|
-
|
|
34966
|
-
|
|
34967
|
-
|
|
34968
|
-
|
|
34969
|
-
|
|
34970
|
-
|
|
34971
|
-
|
|
34972
|
-
|
|
34973
|
-
|
|
34974
|
-
|
|
34975
|
-
const
|
|
34976
|
-
|
|
34977
|
-
|
|
34978
|
-
|
|
34979
|
-
|
|
34981
|
+
cliCommand: reqs?.cliCommand || null
|
|
34982
|
+
};
|
|
34983
|
+
}));
|
|
34984
|
+
}
|
|
34985
|
+
const singleMatch = path.match(/^\/api\/skills\/([^/]+)$/);
|
|
34986
|
+
if (singleMatch && method === "GET") {
|
|
34987
|
+
const name = singleMatch[1];
|
|
34988
|
+
if (!isValidSkillName(name))
|
|
34989
|
+
return json2({ error: "Invalid skill name" }, 400);
|
|
34990
|
+
const meta3 = getSkill(name);
|
|
34991
|
+
if (!meta3)
|
|
34992
|
+
return json2({ error: `Skill '${name}' not found` }, 404);
|
|
34993
|
+
const reqs = getSkillRequirements(name);
|
|
34994
|
+
const docs = getSkillBestDoc(name);
|
|
34995
|
+
const installed = new Set(getInstalledSkills());
|
|
34996
|
+
const envVars = reqs?.envVars || [];
|
|
34997
|
+
return json2({
|
|
34998
|
+
name: meta3.name,
|
|
34999
|
+
displayName: meta3.displayName,
|
|
35000
|
+
description: meta3.description,
|
|
35001
|
+
category: meta3.category,
|
|
35002
|
+
tags: meta3.tags,
|
|
35003
|
+
installed: installed.has(meta3.name),
|
|
35004
|
+
envVars,
|
|
35005
|
+
envVarsSet: envVars.filter((v) => !!process.env[v]),
|
|
35006
|
+
systemDeps: reqs?.systemDeps || [],
|
|
35007
|
+
cliCommand: reqs?.cliCommand || null,
|
|
35008
|
+
docs: docs || null
|
|
35009
|
+
});
|
|
35010
|
+
}
|
|
35011
|
+
const docsMatch = path.match(/^\/api\/skills\/([^/]+)\/docs$/);
|
|
35012
|
+
if (docsMatch && method === "GET") {
|
|
35013
|
+
const name = docsMatch[1];
|
|
35014
|
+
if (!isValidSkillName(name))
|
|
35015
|
+
return json2({ error: "Invalid skill name" }, 400);
|
|
35016
|
+
const content = getSkillBestDoc(name);
|
|
35017
|
+
return json2({ content: content || null });
|
|
35018
|
+
}
|
|
35019
|
+
const installMatch = path.match(/^\/api\/skills\/([^/]+)\/install$/);
|
|
35020
|
+
if (installMatch && method === "POST") {
|
|
35021
|
+
const name = installMatch[1];
|
|
35022
|
+
if (!isValidSkillName(name))
|
|
35023
|
+
return json2({ error: "Invalid skill name" }, 400);
|
|
35024
|
+
let body = {};
|
|
35025
|
+
try {
|
|
35026
|
+
const text = await req.text();
|
|
35027
|
+
if (text)
|
|
35028
|
+
body = JSON.parse(text);
|
|
35029
|
+
} catch {}
|
|
35030
|
+
if (body.for) {
|
|
35031
|
+
try {
|
|
35032
|
+
const agents = resolveAgents(body.for);
|
|
35033
|
+
const scope = body.scope === "project" ? "project" : "global";
|
|
35034
|
+
const results = agents.map((agent) => installSkillForAgent(name, { agent, scope }, generateSkillMd));
|
|
35035
|
+
const allSuccess = results.every((r) => r.success);
|
|
35036
|
+
const errors4 = results.filter((r) => !r.success).map((r) => r.error);
|
|
35037
|
+
return json2({
|
|
35038
|
+
skill: name,
|
|
35039
|
+
success: allSuccess,
|
|
35040
|
+
results,
|
|
35041
|
+
...errors4.length > 0 ? { error: errors4.join("; ") } : {}
|
|
35042
|
+
}, allSuccess ? 200 : 400);
|
|
35043
|
+
} catch (e) {
|
|
35044
|
+
return json2({ skill: name, success: false, error: e instanceof Error ? e.message : "Unknown error" }, 400);
|
|
35045
|
+
}
|
|
35046
|
+
} else {
|
|
34980
35047
|
const result = installSkill(name);
|
|
34981
35048
|
return json2(result, result.success ? 200 : 400);
|
|
34982
35049
|
}
|
|
34983
|
-
|
|
34984
|
-
|
|
34985
|
-
|
|
34986
|
-
|
|
34987
|
-
|
|
34988
|
-
|
|
34989
|
-
|
|
34990
|
-
}
|
|
34991
|
-
|
|
34992
|
-
|
|
34993
|
-
|
|
34994
|
-
|
|
34995
|
-
|
|
34996
|
-
|
|
34997
|
-
|
|
35050
|
+
}
|
|
35051
|
+
const removeMatch = path.match(/^\/api\/skills\/([^/]+)\/remove$/);
|
|
35052
|
+
if (removeMatch && method === "POST") {
|
|
35053
|
+
const name = removeMatch[1];
|
|
35054
|
+
if (!isValidSkillName(name))
|
|
35055
|
+
return json2({ error: "Invalid skill name" }, 400);
|
|
35056
|
+
const success2 = removeSkill(name);
|
|
35057
|
+
return json2({ success: success2, skill: name }, success2 ? 200 : 404);
|
|
35058
|
+
}
|
|
35059
|
+
if (path === "/api/version" && method === "GET") {
|
|
35060
|
+
const pkg = getPackageJson();
|
|
35061
|
+
return json2({ version: pkg.version, name: pkg.name });
|
|
35062
|
+
}
|
|
35063
|
+
if (path === "/api/self-update" && method === "POST") {
|
|
35064
|
+
try {
|
|
35065
|
+
const pkg = getPackageJson();
|
|
35066
|
+
const proc = Bun.spawn(["bun", "add", "-g", `${pkg.name}@latest`], {
|
|
35067
|
+
stdout: "pipe",
|
|
35068
|
+
stderr: "pipe"
|
|
34998
35069
|
});
|
|
35070
|
+
const stdout = await new Response(proc.stdout).text();
|
|
35071
|
+
const stderr = await new Response(proc.stderr).text();
|
|
35072
|
+
const exitCode = await proc.exited;
|
|
35073
|
+
if (exitCode === 0) {
|
|
35074
|
+
return json2({ success: true, output: stdout.trim() || stderr.trim() });
|
|
35075
|
+
}
|
|
35076
|
+
return json2({ success: false, error: stderr.trim() || stdout.trim() }, 500);
|
|
35077
|
+
} catch (e) {
|
|
35078
|
+
return json2({ success: false, error: e instanceof Error ? e.message : "Update failed" }, 500);
|
|
34999
35079
|
}
|
|
35000
|
-
|
|
35001
|
-
|
|
35002
|
-
|
|
35003
|
-
|
|
35004
|
-
|
|
35005
|
-
|
|
35080
|
+
}
|
|
35081
|
+
if (method === "OPTIONS") {
|
|
35082
|
+
return new Response(null, {
|
|
35083
|
+
headers: {
|
|
35084
|
+
"Access-Control-Allow-Origin": "*",
|
|
35085
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
35086
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
35006
35087
|
}
|
|
35007
|
-
|
|
35008
|
-
|
|
35009
|
-
|
|
35010
|
-
|
|
35088
|
+
});
|
|
35089
|
+
}
|
|
35090
|
+
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
35091
|
+
if (path !== "/") {
|
|
35092
|
+
const filePath = join3(dashboardDir, path);
|
|
35093
|
+
const res2 = serveStaticFile(filePath);
|
|
35094
|
+
if (res2)
|
|
35095
|
+
return res2;
|
|
35011
35096
|
}
|
|
35012
|
-
|
|
35097
|
+
const indexPath = join3(dashboardDir, "index.html");
|
|
35098
|
+
const res = serveStaticFile(indexPath);
|
|
35099
|
+
if (res)
|
|
35100
|
+
return res;
|
|
35013
35101
|
}
|
|
35102
|
+
return json2({ error: "Not found" }, 404);
|
|
35103
|
+
};
|
|
35104
|
+
}
|
|
35105
|
+
async function startServer(port = 0, options) {
|
|
35106
|
+
const shouldOpen = options?.open ?? true;
|
|
35107
|
+
const dashboardDir = resolveDashboardDir();
|
|
35108
|
+
const dashboardExists = existsSync3(dashboardDir);
|
|
35109
|
+
if (!dashboardExists) {
|
|
35110
|
+
console.error(`
|
|
35111
|
+
Dashboard not found at: ${dashboardDir}`);
|
|
35112
|
+
console.error(`Run this to build it:
|
|
35113
|
+
`);
|
|
35114
|
+
console.error(` cd dashboard && bun install && bun run build
|
|
35115
|
+
`);
|
|
35116
|
+
}
|
|
35117
|
+
const server2 = Bun.serve({
|
|
35118
|
+
port,
|
|
35119
|
+
fetch: createFetchHandler({ dashboardDir, dashboardExists })
|
|
35014
35120
|
});
|
|
35015
35121
|
const shutdown = () => {
|
|
35016
35122
|
server2.stop();
|
|
@@ -35018,7 +35124,8 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
35018
35124
|
};
|
|
35019
35125
|
process.on("SIGINT", shutdown);
|
|
35020
35126
|
process.on("SIGTERM", shutdown);
|
|
35021
|
-
const
|
|
35127
|
+
const actualPort = server2.port;
|
|
35128
|
+
const serverUrl = `http://localhost:${actualPort}`;
|
|
35022
35129
|
console.log(`Skills Dashboard running at ${serverUrl}`);
|
|
35023
35130
|
if (shouldOpen) {
|
|
35024
35131
|
try {
|
|
@@ -35051,7 +35158,7 @@ var init_serve = __esm(() => {
|
|
|
35051
35158
|
};
|
|
35052
35159
|
isMain = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("serve.ts") || process.argv[1]?.endsWith("serve.js");
|
|
35053
35160
|
if (isMain) {
|
|
35054
|
-
const port = parseInt(process.env.PORT || "
|
|
35161
|
+
const port = parseInt(process.env.PORT || "0", 10);
|
|
35055
35162
|
startServer(port, { open: !process.env.NO_OPEN });
|
|
35056
35163
|
}
|
|
35057
35164
|
});
|
|
@@ -35078,7 +35185,7 @@ var {
|
|
|
35078
35185
|
// src/cli/index.tsx
|
|
35079
35186
|
init_package();
|
|
35080
35187
|
import chalk2 from "chalk";
|
|
35081
|
-
import { existsSync as existsSync4, writeFileSync as writeFileSync2, appendFileSync, readFileSync as
|
|
35188
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync2, appendFileSync, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
35082
35189
|
import { join as join4 } from "path";
|
|
35083
35190
|
|
|
35084
35191
|
// src/cli/components/App.tsx
|
|
@@ -36204,7 +36311,7 @@ init_registry();
|
|
|
36204
36311
|
init_installer();
|
|
36205
36312
|
init_skillinfo();
|
|
36206
36313
|
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
36207
|
-
var isTTY = process.stdout.isTTY ?? false;
|
|
36314
|
+
var isTTY = (process.stdout.isTTY ?? false) && (process.stdin.isTTY ?? false);
|
|
36208
36315
|
var program2 = new Command;
|
|
36209
36316
|
program2.name("skills").description("Install AI agent skills for your project").version(package_default.version).option("--verbose", "Enable verbose logging", false).enablePositionalOptions();
|
|
36210
36317
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive skill browser (TUI)").action(() => {
|
|
@@ -36274,21 +36381,31 @@ SKILL.md copied to agent skill directories`));
|
|
|
36274
36381
|
}
|
|
36275
36382
|
return;
|
|
36276
36383
|
}
|
|
36277
|
-
|
|
36384
|
+
const total = skills.length;
|
|
36385
|
+
for (let i = 0;i < total; i++) {
|
|
36386
|
+
const name = skills[i];
|
|
36387
|
+
if (total > 1 && !options.json) {
|
|
36388
|
+
process.stdout.write(`[${i + 1}/${total}] Installing ${name}...`);
|
|
36389
|
+
}
|
|
36278
36390
|
const result = installSkill(name, { overwrite: options.overwrite });
|
|
36279
36391
|
results.push(result);
|
|
36392
|
+
if (total > 1 && !options.json) {
|
|
36393
|
+
console.log(result.success ? " done" : " failed");
|
|
36394
|
+
}
|
|
36280
36395
|
}
|
|
36281
36396
|
if (options.json) {
|
|
36282
36397
|
console.log(JSON.stringify(results, null, 2));
|
|
36283
36398
|
} else {
|
|
36284
|
-
|
|
36399
|
+
if (total <= 1) {
|
|
36400
|
+
console.log(chalk2.bold(`
|
|
36285
36401
|
Installing skills...
|
|
36286
36402
|
`));
|
|
36287
|
-
|
|
36288
|
-
|
|
36289
|
-
|
|
36290
|
-
|
|
36291
|
-
|
|
36403
|
+
for (const result of results) {
|
|
36404
|
+
if (result.success) {
|
|
36405
|
+
console.log(chalk2.green(`\u2713 ${result.skill}`));
|
|
36406
|
+
} else {
|
|
36407
|
+
console.log(chalk2.red(`\u2717 ${result.skill}: ${result.error}`));
|
|
36408
|
+
}
|
|
36292
36409
|
}
|
|
36293
36410
|
}
|
|
36294
36411
|
console.log(chalk2.dim(`
|
|
@@ -36355,8 +36472,18 @@ Available skills (${SKILLS.length}):
|
|
|
36355
36472
|
console.log();
|
|
36356
36473
|
}
|
|
36357
36474
|
});
|
|
36358
|
-
program2.command("search").argument("<query>", "Search term").option("--json", "Output as JSON", false).description("Search for skills").action((query, options) => {
|
|
36359
|
-
|
|
36475
|
+
program2.command("search").argument("<query>", "Search term").option("--json", "Output as JSON", false).option("-c, --category <category>", "Filter results by category").description("Search for skills").action((query, options) => {
|
|
36476
|
+
let results = searchSkills(query);
|
|
36477
|
+
if (options.category) {
|
|
36478
|
+
const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
36479
|
+
if (!category) {
|
|
36480
|
+
console.error(`Unknown category: ${options.category}`);
|
|
36481
|
+
console.error(`Available: ${CATEGORIES.join(", ")}`);
|
|
36482
|
+
process.exitCode = 1;
|
|
36483
|
+
return;
|
|
36484
|
+
}
|
|
36485
|
+
results = results.filter((s) => s.category === category);
|
|
36486
|
+
}
|
|
36360
36487
|
if (options.json) {
|
|
36361
36488
|
console.log(JSON.stringify(results, null, 2));
|
|
36362
36489
|
return;
|
|
@@ -36501,27 +36628,70 @@ program2.command("run").argument("<skill>", "Skill name").argument("[args...]",
|
|
|
36501
36628
|
}
|
|
36502
36629
|
process.exitCode = result.exitCode;
|
|
36503
36630
|
});
|
|
36504
|
-
program2.command("init").description("Initialize project for installed skills (.env.example, .gitignore)").action(() => {
|
|
36631
|
+
program2.command("init").option("--json", "Output as JSON", false).description("Initialize project for installed skills (.env.example, .gitignore)").action((options) => {
|
|
36505
36632
|
const cwd = process.cwd();
|
|
36506
36633
|
const installed = getInstalledSkills();
|
|
36507
36634
|
if (installed.length === 0) {
|
|
36508
|
-
|
|
36635
|
+
if (options.json) {
|
|
36636
|
+
console.log(JSON.stringify({ skills: [], envVars: 0, gitignoreUpdated: false }));
|
|
36637
|
+
} else {
|
|
36638
|
+
console.log(chalk2.dim("No skills installed. Run: skills install <name>"));
|
|
36639
|
+
}
|
|
36509
36640
|
return;
|
|
36510
36641
|
}
|
|
36511
|
-
const
|
|
36512
|
-
|
|
36642
|
+
const envMap = new Map;
|
|
36643
|
+
for (const name of installed) {
|
|
36644
|
+
const reqs = getSkillRequirements(name);
|
|
36645
|
+
if (reqs?.envVars.length) {
|
|
36646
|
+
for (const v of reqs.envVars) {
|
|
36647
|
+
if (!envMap.has(v))
|
|
36648
|
+
envMap.set(v, []);
|
|
36649
|
+
if (!envMap.get(v).includes(name))
|
|
36650
|
+
envMap.get(v).push(name);
|
|
36651
|
+
}
|
|
36652
|
+
}
|
|
36653
|
+
}
|
|
36654
|
+
let envVarCount = 0;
|
|
36655
|
+
if (envMap.size > 0) {
|
|
36656
|
+
const lines = [
|
|
36657
|
+
"# Environment variables for installed skills",
|
|
36658
|
+
"# Auto-generated by: skills init",
|
|
36659
|
+
""
|
|
36660
|
+
];
|
|
36661
|
+
const sorted = Array.from(envMap.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
36662
|
+
let lastPrefix = "";
|
|
36663
|
+
for (const [envVar, skills] of sorted) {
|
|
36664
|
+
const prefix = envVar.split("_")[0];
|
|
36665
|
+
if (prefix !== lastPrefix) {
|
|
36666
|
+
if (lastPrefix)
|
|
36667
|
+
lines.push("");
|
|
36668
|
+
lines.push(`# ${prefix}`);
|
|
36669
|
+
lastPrefix = prefix;
|
|
36670
|
+
}
|
|
36671
|
+
lines.push(`# Used by: ${skills.join(", ")}`);
|
|
36672
|
+
lines.push(`${envVar}=`);
|
|
36673
|
+
}
|
|
36674
|
+
const envContent = lines.join(`
|
|
36675
|
+
`) + `
|
|
36676
|
+
`;
|
|
36513
36677
|
const envPath = join4(cwd, ".env.example");
|
|
36514
36678
|
writeFileSync2(envPath, envContent);
|
|
36515
|
-
|
|
36679
|
+
envVarCount = envMap.size;
|
|
36680
|
+
if (!options.json) {
|
|
36681
|
+
console.log(chalk2.green(`\u2713 Generated .env.example (${envVarCount} variables from ${installed.length} skills)`));
|
|
36682
|
+
}
|
|
36516
36683
|
} else {
|
|
36517
|
-
|
|
36684
|
+
if (!options.json) {
|
|
36685
|
+
console.log(chalk2.dim(" No environment variables detected across installed skills"));
|
|
36686
|
+
}
|
|
36518
36687
|
}
|
|
36519
36688
|
const gitignorePath = join4(cwd, ".gitignore");
|
|
36520
36689
|
const gitignoreEntry = ".skills/";
|
|
36521
36690
|
let gitignoreContent = "";
|
|
36522
36691
|
if (existsSync4(gitignorePath)) {
|
|
36523
|
-
gitignoreContent =
|
|
36692
|
+
gitignoreContent = readFileSync4(gitignorePath, "utf-8");
|
|
36524
36693
|
}
|
|
36694
|
+
let gitignoreUpdated = false;
|
|
36525
36695
|
if (!gitignoreContent.includes(gitignoreEntry)) {
|
|
36526
36696
|
const addition = gitignoreContent.endsWith(`
|
|
36527
36697
|
`) || gitignoreContent === "" ? `
|
|
@@ -36533,12 +36703,35 @@ ${gitignoreEntry}
|
|
|
36533
36703
|
${gitignoreEntry}
|
|
36534
36704
|
`;
|
|
36535
36705
|
appendFileSync(gitignorePath, addition);
|
|
36536
|
-
|
|
36706
|
+
gitignoreUpdated = true;
|
|
36707
|
+
if (!options.json) {
|
|
36708
|
+
console.log(chalk2.green(`\u2713 Added .skills/ to .gitignore`));
|
|
36709
|
+
}
|
|
36537
36710
|
} else {
|
|
36538
|
-
|
|
36711
|
+
if (!options.json) {
|
|
36712
|
+
console.log(chalk2.dim(" .skills/ already in .gitignore"));
|
|
36713
|
+
}
|
|
36539
36714
|
}
|
|
36540
|
-
|
|
36715
|
+
if (options.json) {
|
|
36716
|
+
console.log(JSON.stringify({
|
|
36717
|
+
skills: installed,
|
|
36718
|
+
envVars: envVarCount,
|
|
36719
|
+
gitignoreUpdated
|
|
36720
|
+
}, null, 2));
|
|
36721
|
+
} else {
|
|
36722
|
+
if (envMap.size > 0) {
|
|
36723
|
+
console.log(chalk2.bold(`
|
|
36724
|
+
Skill environment requirements:`));
|
|
36725
|
+
for (const name of installed) {
|
|
36726
|
+
const reqs = getSkillRequirements(name);
|
|
36727
|
+
if (reqs?.envVars.length) {
|
|
36728
|
+
console.log(` ${chalk2.cyan(name)}: ${reqs.envVars.join(", ")}`);
|
|
36729
|
+
}
|
|
36730
|
+
}
|
|
36731
|
+
}
|
|
36732
|
+
console.log(chalk2.bold(`
|
|
36541
36733
|
Initialized for ${installed.length} installed skill(s)`));
|
|
36734
|
+
}
|
|
36542
36735
|
});
|
|
36543
36736
|
program2.command("remove").alias("rm").argument("<skill>", "Skill to remove").option("--json", "Output as JSON", false).option("--for <agent>", "Remove from agent: claude, codex, gemini, or all").option("--scope <scope>", "Remove scope: global or project", "global").option("--dry-run", "Print what would happen without actually removing", false).description("Remove an installed skill").action((skill, options) => {
|
|
36544
36737
|
if (options.for) {
|
|
@@ -36601,16 +36794,57 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
36601
36794
|
console.log(chalk2.dim("No skills installed. Run: skills install <name>"));
|
|
36602
36795
|
return;
|
|
36603
36796
|
}
|
|
36604
|
-
|
|
36797
|
+
function collectFiles(dir, base = "") {
|
|
36798
|
+
const files = new Set;
|
|
36799
|
+
if (!existsSync4(dir))
|
|
36800
|
+
return files;
|
|
36801
|
+
for (const entry of readdirSync3(dir)) {
|
|
36802
|
+
const full = join4(dir, entry);
|
|
36803
|
+
const rel = base ? `${base}/${entry}` : entry;
|
|
36804
|
+
if (statSync2(full).isDirectory()) {
|
|
36805
|
+
for (const f of collectFiles(full, rel))
|
|
36806
|
+
files.add(f);
|
|
36807
|
+
} else {
|
|
36808
|
+
files.add(rel);
|
|
36809
|
+
}
|
|
36810
|
+
}
|
|
36811
|
+
return files;
|
|
36812
|
+
}
|
|
36813
|
+
const updateResults = [];
|
|
36814
|
+
for (const name of toUpdate) {
|
|
36815
|
+
const skillName = normalizeSkillName(name);
|
|
36816
|
+
const destPath = join4(process.cwd(), ".skills", skillName);
|
|
36817
|
+
const beforeFiles = collectFiles(destPath);
|
|
36818
|
+
const result = installSkill(name, { overwrite: true });
|
|
36819
|
+
const afterFiles = collectFiles(destPath);
|
|
36820
|
+
const newFiles = [...afterFiles].filter((f) => !beforeFiles.has(f));
|
|
36821
|
+
const removedFiles = [...beforeFiles].filter((f) => !afterFiles.has(f));
|
|
36822
|
+
const unchangedCount = [...afterFiles].filter((f) => beforeFiles.has(f)).length;
|
|
36823
|
+
updateResults.push({
|
|
36824
|
+
skill: result.skill,
|
|
36825
|
+
success: result.success,
|
|
36826
|
+
error: result.error,
|
|
36827
|
+
newFiles,
|
|
36828
|
+
removedFiles,
|
|
36829
|
+
unchangedCount
|
|
36830
|
+
});
|
|
36831
|
+
}
|
|
36605
36832
|
if (options.json) {
|
|
36606
|
-
console.log(JSON.stringify(
|
|
36833
|
+
console.log(JSON.stringify(updateResults, null, 2));
|
|
36607
36834
|
} else {
|
|
36608
36835
|
console.log(chalk2.bold(`
|
|
36609
36836
|
Updating skills...
|
|
36610
36837
|
`));
|
|
36611
|
-
for (const result of
|
|
36838
|
+
for (const result of updateResults) {
|
|
36612
36839
|
if (result.success) {
|
|
36613
36840
|
console.log(chalk2.green(`\u2713 ${result.skill}`));
|
|
36841
|
+
if (result.newFiles.length > 0) {
|
|
36842
|
+
console.log(chalk2.green(` + ${result.newFiles.length} new file(s): ${result.newFiles.join(", ")}`));
|
|
36843
|
+
}
|
|
36844
|
+
if (result.removedFiles.length > 0) {
|
|
36845
|
+
console.log(chalk2.red(` - ${result.removedFiles.length} removed file(s): ${result.removedFiles.join(", ")}`));
|
|
36846
|
+
}
|
|
36847
|
+
console.log(chalk2.dim(` ${result.unchangedCount} file(s) updated`));
|
|
36614
36848
|
} else {
|
|
36615
36849
|
console.log(chalk2.red(`\u2717 ${result.skill}: ${result.error}`));
|
|
36616
36850
|
}
|
|
@@ -36618,7 +36852,7 @@ Updating skills...
|
|
|
36618
36852
|
console.log(chalk2.dim(`
|
|
36619
36853
|
Skills updated in .skills/`));
|
|
36620
36854
|
}
|
|
36621
|
-
if (
|
|
36855
|
+
if (updateResults.some((r) => !r.success)) {
|
|
36622
36856
|
process.exitCode = 1;
|
|
36623
36857
|
}
|
|
36624
36858
|
});
|
|
@@ -36681,9 +36915,266 @@ Add to ${configPath} mcpServers:`));
|
|
|
36681
36915
|
}
|
|
36682
36916
|
await Promise.resolve().then(() => (init_mcp2(), exports_mcp));
|
|
36683
36917
|
});
|
|
36684
|
-
program2.command("serve").description("Start the Skills Dashboard web server").option("-p, --port <port>", "Port number", "
|
|
36918
|
+
program2.command("serve").description("Start the Skills Dashboard web server").option("-p, --port <port>", "Port number (0 = auto-assign free port)", "0").option("--no-open", "Don't open browser automatically").action(async (options) => {
|
|
36685
36919
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
36686
36920
|
const port = parseInt(options.port, 10);
|
|
36687
36921
|
await startServer2(port, { open: options.open });
|
|
36688
36922
|
});
|
|
36923
|
+
program2.command("self-update").description(`Update ${package_default.name} to the latest version`).action(async () => {
|
|
36924
|
+
console.log(chalk2.bold(`
|
|
36925
|
+
Updating ${package_default.name}...
|
|
36926
|
+
`));
|
|
36927
|
+
const proc = Bun.spawn(["bun", "add", "-g", `${package_default.name}@latest`], {
|
|
36928
|
+
stdout: "inherit",
|
|
36929
|
+
stderr: "inherit"
|
|
36930
|
+
});
|
|
36931
|
+
const exitCode = await proc.exited;
|
|
36932
|
+
if (exitCode === 0) {
|
|
36933
|
+
console.log(chalk2.green(`
|
|
36934
|
+
\u2713 Updated to latest version`));
|
|
36935
|
+
const vProc = Bun.spawn(["skills", "--version"], { stdout: "pipe" });
|
|
36936
|
+
const ver = (await new Response(vProc.stdout).text()).trim();
|
|
36937
|
+
console.log(chalk2.dim(` Version: ${ver}`));
|
|
36938
|
+
} else {
|
|
36939
|
+
console.error(chalk2.red(`
|
|
36940
|
+
\u2717 Update failed`));
|
|
36941
|
+
process.exitCode = 1;
|
|
36942
|
+
}
|
|
36943
|
+
});
|
|
36944
|
+
program2.command("completion").argument("<shell>", "Shell type: bash, zsh, or fish").description("Generate shell completions").action((shell) => {
|
|
36945
|
+
const subcommands = [
|
|
36946
|
+
"install",
|
|
36947
|
+
"list",
|
|
36948
|
+
"search",
|
|
36949
|
+
"info",
|
|
36950
|
+
"docs",
|
|
36951
|
+
"requires",
|
|
36952
|
+
"run",
|
|
36953
|
+
"remove",
|
|
36954
|
+
"update",
|
|
36955
|
+
"categories",
|
|
36956
|
+
"mcp",
|
|
36957
|
+
"serve",
|
|
36958
|
+
"init",
|
|
36959
|
+
"self-update",
|
|
36960
|
+
"completion",
|
|
36961
|
+
"outdated"
|
|
36962
|
+
];
|
|
36963
|
+
const skillNames = SKILLS.map((s) => s.name);
|
|
36964
|
+
const categoryNames = CATEGORIES.map((c) => c);
|
|
36965
|
+
const skillCmds = ["install", "info", "docs", "requires", "run", "remove"];
|
|
36966
|
+
switch (shell) {
|
|
36967
|
+
case "bash": {
|
|
36968
|
+
const script = `# Bash completion for skills CLI
|
|
36969
|
+
_skills_completions() {
|
|
36970
|
+
local cur prev subcmds skill_cmds skills categories
|
|
36971
|
+
COMPREPLY=()
|
|
36972
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
36973
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
36974
|
+
subcmds="${subcommands.join(" ")}"
|
|
36975
|
+
skill_cmds="${skillCmds.join(" ")}"
|
|
36976
|
+
skills="${skillNames.join(" ")}"
|
|
36977
|
+
categories="${categoryNames.join(" ")}"
|
|
36978
|
+
|
|
36979
|
+
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
36980
|
+
COMPREPLY=( $(compgen -W "\${subcmds}" -- "\${cur}") )
|
|
36981
|
+
return 0
|
|
36982
|
+
fi
|
|
36983
|
+
|
|
36984
|
+
case "\${prev}" in
|
|
36985
|
+
--category|-c)
|
|
36986
|
+
COMPREPLY=( $(compgen -W "\${categories}" -- "\${cur}") )
|
|
36987
|
+
return 0
|
|
36988
|
+
;;
|
|
36989
|
+
esac
|
|
36990
|
+
|
|
36991
|
+
for cmd in \${skill_cmds}; do
|
|
36992
|
+
if [[ "\${COMP_WORDS[1]}" == "\${cmd}" && \${COMP_CWORD} -eq 2 ]]; then
|
|
36993
|
+
COMPREPLY=( $(compgen -W "\${skills}" -- "\${cur}") )
|
|
36994
|
+
return 0
|
|
36995
|
+
fi
|
|
36996
|
+
done
|
|
36997
|
+
|
|
36998
|
+
return 0
|
|
36999
|
+
}
|
|
37000
|
+
complete -F _skills_completions skills
|
|
37001
|
+
`;
|
|
37002
|
+
console.log(script);
|
|
37003
|
+
break;
|
|
37004
|
+
}
|
|
37005
|
+
case "zsh": {
|
|
37006
|
+
const script = `#compdef skills
|
|
37007
|
+
# Zsh completion for skills CLI
|
|
37008
|
+
|
|
37009
|
+
_skills() {
|
|
37010
|
+
local -a subcmds skill_cmds skills categories
|
|
37011
|
+
|
|
37012
|
+
subcmds=(
|
|
37013
|
+
${subcommands.map((c) => ` '${c}:${c} command'`).join(`
|
|
37014
|
+
`)}
|
|
37015
|
+
)
|
|
37016
|
+
|
|
37017
|
+
skills=(${skillNames.join(" ")})
|
|
37018
|
+
categories=(${categoryNames.map((c) => `'${c.replace(/'/g, "'\\''")}'`).join(" ")})
|
|
37019
|
+
|
|
37020
|
+
skill_cmds=(${skillCmds.join(" ")})
|
|
37021
|
+
|
|
37022
|
+
_arguments -C \\
|
|
37023
|
+
'1:command:->command' \\
|
|
37024
|
+
'*::arg:->args'
|
|
37025
|
+
|
|
37026
|
+
case $state in
|
|
37027
|
+
command)
|
|
37028
|
+
_describe 'skills command' subcmds
|
|
37029
|
+
;;
|
|
37030
|
+
args)
|
|
37031
|
+
case \${words[1]} in
|
|
37032
|
+
${skillCmds.join("|")})
|
|
37033
|
+
_describe 'skill' skills
|
|
37034
|
+
;;
|
|
37035
|
+
list|search)
|
|
37036
|
+
_arguments '--category[Filter by category]:category:($categories)'
|
|
37037
|
+
;;
|
|
37038
|
+
completion)
|
|
37039
|
+
_describe 'shell' '(bash zsh fish)'
|
|
37040
|
+
;;
|
|
37041
|
+
esac
|
|
37042
|
+
;;
|
|
37043
|
+
esac
|
|
37044
|
+
}
|
|
37045
|
+
|
|
37046
|
+
_skills "$@"
|
|
37047
|
+
`;
|
|
37048
|
+
console.log(script);
|
|
37049
|
+
break;
|
|
37050
|
+
}
|
|
37051
|
+
case "fish": {
|
|
37052
|
+
const lines = [
|
|
37053
|
+
"# Fish completion for skills CLI",
|
|
37054
|
+
"",
|
|
37055
|
+
"# Disable file completions by default",
|
|
37056
|
+
"complete -c skills -f",
|
|
37057
|
+
"",
|
|
37058
|
+
"# Subcommands"
|
|
37059
|
+
];
|
|
37060
|
+
for (const cmd of subcommands) {
|
|
37061
|
+
lines.push(`complete -c skills -n '__fish_use_subcommand' -a '${cmd}' -d '${cmd} command'`);
|
|
37062
|
+
}
|
|
37063
|
+
lines.push("");
|
|
37064
|
+
lines.push("# Skill names for relevant subcommands");
|
|
37065
|
+
for (const cmd of skillCmds) {
|
|
37066
|
+
for (const name of skillNames) {
|
|
37067
|
+
lines.push(`complete -c skills -n '__fish_seen_subcommand_from ${cmd}' -a '${name}'`);
|
|
37068
|
+
}
|
|
37069
|
+
}
|
|
37070
|
+
lines.push("");
|
|
37071
|
+
lines.push("# Category completions for --category flag");
|
|
37072
|
+
for (const cat of categoryNames) {
|
|
37073
|
+
lines.push(`complete -c skills -l category -s c -a '${cat}' -d 'Category'`);
|
|
37074
|
+
}
|
|
37075
|
+
console.log(lines.join(`
|
|
37076
|
+
`));
|
|
37077
|
+
break;
|
|
37078
|
+
}
|
|
37079
|
+
default:
|
|
37080
|
+
console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
|
|
37081
|
+
process.exitCode = 1;
|
|
37082
|
+
}
|
|
37083
|
+
});
|
|
37084
|
+
program2.command("doctor").option("--json", "Output as JSON", false).description("Check environment variables for installed skills").action((options) => {
|
|
37085
|
+
const installed = getInstalledSkills();
|
|
37086
|
+
if (installed.length === 0) {
|
|
37087
|
+
if (options.json) {
|
|
37088
|
+
console.log(JSON.stringify({ skills: [], message: "No skills installed" }));
|
|
37089
|
+
} else {
|
|
37090
|
+
console.log("No skills installed");
|
|
37091
|
+
}
|
|
37092
|
+
return;
|
|
37093
|
+
}
|
|
37094
|
+
const report = [];
|
|
37095
|
+
for (const name of installed) {
|
|
37096
|
+
const reqs = getSkillRequirements(name);
|
|
37097
|
+
const envVars = (reqs?.envVars ?? []).map((v) => ({
|
|
37098
|
+
name: v,
|
|
37099
|
+
set: !!process.env[v]
|
|
37100
|
+
}));
|
|
37101
|
+
report.push({ skill: name, envVars });
|
|
37102
|
+
}
|
|
37103
|
+
if (options.json) {
|
|
37104
|
+
console.log(JSON.stringify(report, null, 2));
|
|
37105
|
+
return;
|
|
37106
|
+
}
|
|
37107
|
+
console.log(chalk2.bold(`
|
|
37108
|
+
Skills Doctor (${installed.length} installed):
|
|
37109
|
+
`));
|
|
37110
|
+
for (const entry of report) {
|
|
37111
|
+
console.log(chalk2.bold(` ${entry.skill}`));
|
|
37112
|
+
if (entry.envVars.length === 0) {
|
|
37113
|
+
console.log(chalk2.dim(" No environment variables required"));
|
|
37114
|
+
} else {
|
|
37115
|
+
for (const v of entry.envVars) {
|
|
37116
|
+
const status = v.set ? chalk2.green("set") : chalk2.red("missing");
|
|
37117
|
+
console.log(` ${v.name} [${status}]`);
|
|
37118
|
+
}
|
|
37119
|
+
}
|
|
37120
|
+
}
|
|
37121
|
+
});
|
|
37122
|
+
program2.command("outdated").option("--json", "Output as JSON", false).description("Check for outdated installed skills").action((options) => {
|
|
37123
|
+
const installed = getInstalledSkills();
|
|
37124
|
+
if (installed.length === 0) {
|
|
37125
|
+
if (options.json) {
|
|
37126
|
+
console.log(JSON.stringify([]));
|
|
37127
|
+
} else {
|
|
37128
|
+
console.log(chalk2.dim("No skills installed. Run: skills install <name>"));
|
|
37129
|
+
}
|
|
37130
|
+
return;
|
|
37131
|
+
}
|
|
37132
|
+
const cwd = process.cwd();
|
|
37133
|
+
const outdated = [];
|
|
37134
|
+
const upToDate = [];
|
|
37135
|
+
for (const name of installed) {
|
|
37136
|
+
const skillName = normalizeSkillName(name);
|
|
37137
|
+
const installedPkgPath = join4(cwd, ".skills", skillName, "package.json");
|
|
37138
|
+
let installedVersion = "unknown";
|
|
37139
|
+
if (existsSync4(installedPkgPath)) {
|
|
37140
|
+
try {
|
|
37141
|
+
installedVersion = JSON.parse(readFileSync4(installedPkgPath, "utf-8")).version || "unknown";
|
|
37142
|
+
} catch {}
|
|
37143
|
+
}
|
|
37144
|
+
const registryPath = getSkillPath(name);
|
|
37145
|
+
const registryPkgPath = join4(registryPath, "package.json");
|
|
37146
|
+
let registryVersion = "unknown";
|
|
37147
|
+
if (existsSync4(registryPkgPath)) {
|
|
37148
|
+
try {
|
|
37149
|
+
registryVersion = JSON.parse(readFileSync4(registryPkgPath, "utf-8")).version || "unknown";
|
|
37150
|
+
} catch {}
|
|
37151
|
+
}
|
|
37152
|
+
if (installedVersion !== registryVersion) {
|
|
37153
|
+
outdated.push({ skill: name, installedVersion, registryVersion });
|
|
37154
|
+
} else {
|
|
37155
|
+
upToDate.push(name);
|
|
37156
|
+
}
|
|
37157
|
+
}
|
|
37158
|
+
if (options.json) {
|
|
37159
|
+
console.log(JSON.stringify(outdated, null, 2));
|
|
37160
|
+
return;
|
|
37161
|
+
}
|
|
37162
|
+
if (outdated.length === 0) {
|
|
37163
|
+
console.log(chalk2.green(`
|
|
37164
|
+
All ${installed.length} installed skill(s) are up to date`));
|
|
37165
|
+
return;
|
|
37166
|
+
}
|
|
37167
|
+
console.log(chalk2.bold(`
|
|
37168
|
+
Outdated skills (${outdated.length}):
|
|
37169
|
+
`));
|
|
37170
|
+
for (const entry of outdated) {
|
|
37171
|
+
console.log(` ${chalk2.cyan(entry.skill)} ${chalk2.red(entry.installedVersion)} \u2192 ${chalk2.green(entry.registryVersion)}`);
|
|
37172
|
+
}
|
|
37173
|
+
if (upToDate.length > 0) {
|
|
37174
|
+
console.log(chalk2.dim(`
|
|
37175
|
+
${upToDate.length} skill(s) up to date`));
|
|
37176
|
+
}
|
|
37177
|
+
console.log(chalk2.dim(`
|
|
37178
|
+
Run ${chalk2.bold("skills update")} to update all outdated skills`));
|
|
37179
|
+
});
|
|
36689
37180
|
program2.parse();
|