@hasna/skills 0.1.4 → 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 +656 -218
- 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,22 +34864,24 @@ 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
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";
|
|
34837
|
-
function
|
|
34873
|
+
function getPackageJson() {
|
|
34838
34874
|
try {
|
|
34839
34875
|
const scriptDir = dirname2(fileURLToPath2(import.meta.url));
|
|
34840
34876
|
for (const rel of ["../..", ".."]) {
|
|
34841
34877
|
const pkgPath = join3(scriptDir, rel, "package.json");
|
|
34842
34878
|
if (existsSync3(pkgPath)) {
|
|
34843
|
-
|
|
34879
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
34880
|
+
return { version: pkg.version || "unknown", name: pkg.name || "skills" };
|
|
34844
34881
|
}
|
|
34845
34882
|
}
|
|
34846
34883
|
} catch {}
|
|
34847
|
-
return "unknown";
|
|
34884
|
+
return { version: "unknown", name: "skills" };
|
|
34848
34885
|
}
|
|
34849
34886
|
function resolveDashboardDir() {
|
|
34850
34887
|
const candidates = [];
|
|
@@ -34881,6 +34918,7 @@ function getAllSkillsWithStatus() {
|
|
|
34881
34918
|
const installed = new Set(getInstalledSkills());
|
|
34882
34919
|
return SKILLS.map((meta3) => {
|
|
34883
34920
|
const reqs = getSkillRequirements(meta3.name);
|
|
34921
|
+
const envVars = reqs?.envVars || [];
|
|
34884
34922
|
return {
|
|
34885
34923
|
name: meta3.name,
|
|
34886
34924
|
displayName: meta3.displayName,
|
|
@@ -34888,7 +34926,8 @@ function getAllSkillsWithStatus() {
|
|
|
34888
34926
|
category: meta3.category,
|
|
34889
34927
|
tags: meta3.tags,
|
|
34890
34928
|
installed: installed.has(meta3.name),
|
|
34891
|
-
envVars
|
|
34929
|
+
envVars,
|
|
34930
|
+
envVarsSet: envVars.filter((v) => !!process.env[v]),
|
|
34892
34931
|
systemDeps: reqs?.systemDeps || [],
|
|
34893
34932
|
cliCommand: reqs?.cliCommand || null
|
|
34894
34933
|
};
|
|
@@ -34903,146 +34942,181 @@ function serveStaticFile(filePath) {
|
|
|
34903
34942
|
headers: { "Content-Type": contentType }
|
|
34904
34943
|
});
|
|
34905
34944
|
}
|
|
34906
|
-
|
|
34907
|
-
const
|
|
34908
|
-
const
|
|
34909
|
-
|
|
34910
|
-
|
|
34911
|
-
|
|
34912
|
-
|
|
34913
|
-
|
|
34914
|
-
|
|
34915
|
-
|
|
34916
|
-
|
|
34917
|
-
|
|
34918
|
-
|
|
34919
|
-
|
|
34920
|
-
|
|
34921
|
-
|
|
34922
|
-
|
|
34923
|
-
|
|
34924
|
-
|
|
34925
|
-
|
|
34926
|
-
|
|
34927
|
-
|
|
34928
|
-
|
|
34929
|
-
|
|
34930
|
-
|
|
34931
|
-
|
|
34932
|
-
return
|
|
34933
|
-
}
|
|
34934
|
-
if (path === "/api/skills/search" && method === "GET") {
|
|
34935
|
-
const query = url2.searchParams.get("q") || "";
|
|
34936
|
-
if (!query.trim())
|
|
34937
|
-
return json2([]);
|
|
34938
|
-
const results = searchSkills(query);
|
|
34939
|
-
const installed = new Set(getInstalledSkills());
|
|
34940
|
-
return json2(results.map((meta3) => {
|
|
34941
|
-
const reqs = getSkillRequirements(meta3.name);
|
|
34942
|
-
return {
|
|
34943
|
-
name: meta3.name,
|
|
34944
|
-
displayName: meta3.displayName,
|
|
34945
|
-
description: meta3.description,
|
|
34946
|
-
category: meta3.category,
|
|
34947
|
-
tags: meta3.tags,
|
|
34948
|
-
installed: installed.has(meta3.name),
|
|
34949
|
-
envVars: reqs?.envVars || [],
|
|
34950
|
-
systemDeps: reqs?.systemDeps || [],
|
|
34951
|
-
cliCommand: reqs?.cliCommand || null
|
|
34952
|
-
};
|
|
34953
|
-
}));
|
|
34954
|
-
}
|
|
34955
|
-
const singleMatch = path.match(/^\/api\/skills\/([^/]+)$/);
|
|
34956
|
-
if (singleMatch && method === "GET") {
|
|
34957
|
-
const name = singleMatch[1];
|
|
34958
|
-
if (!isValidSkillName(name))
|
|
34959
|
-
return json2({ error: "Invalid skill name" }, 400);
|
|
34960
|
-
const meta3 = getSkill(name);
|
|
34961
|
-
if (!meta3)
|
|
34962
|
-
return json2({ error: `Skill '${name}' not found` }, 404);
|
|
34963
|
-
const reqs = getSkillRequirements(name);
|
|
34964
|
-
const docs = getSkillBestDoc(name);
|
|
34965
|
-
const installed = new Set(getInstalledSkills());
|
|
34966
|
-
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 {
|
|
34967
34972
|
name: meta3.name,
|
|
34968
34973
|
displayName: meta3.displayName,
|
|
34969
34974
|
description: meta3.description,
|
|
34970
34975
|
category: meta3.category,
|
|
34971
34976
|
tags: meta3.tags,
|
|
34972
34977
|
installed: installed.has(meta3.name),
|
|
34973
|
-
envVars
|
|
34978
|
+
envVars,
|
|
34979
|
+
envVarsSet: envVars.filter((v) => !!process.env[v]),
|
|
34974
34980
|
systemDeps: reqs?.systemDeps || [],
|
|
34975
|
-
cliCommand: reqs?.cliCommand || null
|
|
34976
|
-
|
|
34977
|
-
|
|
34978
|
-
|
|
34979
|
-
|
|
34980
|
-
|
|
34981
|
-
|
|
34982
|
-
|
|
34983
|
-
|
|
34984
|
-
|
|
34985
|
-
|
|
34986
|
-
|
|
34987
|
-
const
|
|
34988
|
-
|
|
34989
|
-
|
|
34990
|
-
|
|
34991
|
-
|
|
34992
|
-
|
|
34993
|
-
|
|
34994
|
-
|
|
34995
|
-
|
|
34996
|
-
|
|
34997
|
-
|
|
34998
|
-
|
|
34999
|
-
|
|
35000
|
-
|
|
35001
|
-
|
|
35002
|
-
|
|
35003
|
-
|
|
35004
|
-
|
|
35005
|
-
|
|
35006
|
-
|
|
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) {
|
|
35007
35031
|
try {
|
|
35008
|
-
const
|
|
35009
|
-
|
|
35010
|
-
|
|
35011
|
-
|
|
35012
|
-
const
|
|
35013
|
-
|
|
35014
|
-
|
|
35015
|
-
|
|
35016
|
-
|
|
35017
|
-
|
|
35018
|
-
|
|
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);
|
|
35019
35043
|
} catch (e) {
|
|
35020
|
-
return json2({ success: false, error: e instanceof Error ? e.message : "
|
|
35044
|
+
return json2({ skill: name, success: false, error: e instanceof Error ? e.message : "Unknown error" }, 400);
|
|
35021
35045
|
}
|
|
35046
|
+
} else {
|
|
35047
|
+
const result = installSkill(name);
|
|
35048
|
+
return json2(result, result.success ? 200 : 400);
|
|
35022
35049
|
}
|
|
35023
|
-
|
|
35024
|
-
|
|
35025
|
-
|
|
35026
|
-
|
|
35027
|
-
|
|
35028
|
-
|
|
35029
|
-
|
|
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"
|
|
35030
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);
|
|
35031
35079
|
}
|
|
35032
|
-
|
|
35033
|
-
|
|
35034
|
-
|
|
35035
|
-
|
|
35036
|
-
|
|
35037
|
-
|
|
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"
|
|
35038
35087
|
}
|
|
35039
|
-
|
|
35040
|
-
|
|
35041
|
-
|
|
35042
|
-
|
|
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;
|
|
35043
35096
|
}
|
|
35044
|
-
|
|
35097
|
+
const indexPath = join3(dashboardDir, "index.html");
|
|
35098
|
+
const res = serveStaticFile(indexPath);
|
|
35099
|
+
if (res)
|
|
35100
|
+
return res;
|
|
35045
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 })
|
|
35046
35120
|
});
|
|
35047
35121
|
const shutdown = () => {
|
|
35048
35122
|
server2.stop();
|
|
@@ -35050,7 +35124,8 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
35050
35124
|
};
|
|
35051
35125
|
process.on("SIGINT", shutdown);
|
|
35052
35126
|
process.on("SIGTERM", shutdown);
|
|
35053
|
-
const
|
|
35127
|
+
const actualPort = server2.port;
|
|
35128
|
+
const serverUrl = `http://localhost:${actualPort}`;
|
|
35054
35129
|
console.log(`Skills Dashboard running at ${serverUrl}`);
|
|
35055
35130
|
if (shouldOpen) {
|
|
35056
35131
|
try {
|
|
@@ -35083,7 +35158,7 @@ var init_serve = __esm(() => {
|
|
|
35083
35158
|
};
|
|
35084
35159
|
isMain = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("serve.ts") || process.argv[1]?.endsWith("serve.js");
|
|
35085
35160
|
if (isMain) {
|
|
35086
|
-
const port = parseInt(process.env.PORT || "
|
|
35161
|
+
const port = parseInt(process.env.PORT || "0", 10);
|
|
35087
35162
|
startServer(port, { open: !process.env.NO_OPEN });
|
|
35088
35163
|
}
|
|
35089
35164
|
});
|
|
@@ -35110,7 +35185,7 @@ var {
|
|
|
35110
35185
|
// src/cli/index.tsx
|
|
35111
35186
|
init_package();
|
|
35112
35187
|
import chalk2 from "chalk";
|
|
35113
|
-
import { existsSync as existsSync4, writeFileSync as writeFileSync2, appendFileSync, readFileSync as readFileSync4 } from "fs";
|
|
35188
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync2, appendFileSync, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
35114
35189
|
import { join as join4 } from "path";
|
|
35115
35190
|
|
|
35116
35191
|
// src/cli/components/App.tsx
|
|
@@ -36306,21 +36381,31 @@ SKILL.md copied to agent skill directories`));
|
|
|
36306
36381
|
}
|
|
36307
36382
|
return;
|
|
36308
36383
|
}
|
|
36309
|
-
|
|
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
|
+
}
|
|
36310
36390
|
const result = installSkill(name, { overwrite: options.overwrite });
|
|
36311
36391
|
results.push(result);
|
|
36392
|
+
if (total > 1 && !options.json) {
|
|
36393
|
+
console.log(result.success ? " done" : " failed");
|
|
36394
|
+
}
|
|
36312
36395
|
}
|
|
36313
36396
|
if (options.json) {
|
|
36314
36397
|
console.log(JSON.stringify(results, null, 2));
|
|
36315
36398
|
} else {
|
|
36316
|
-
|
|
36399
|
+
if (total <= 1) {
|
|
36400
|
+
console.log(chalk2.bold(`
|
|
36317
36401
|
Installing skills...
|
|
36318
36402
|
`));
|
|
36319
|
-
|
|
36320
|
-
|
|
36321
|
-
|
|
36322
|
-
|
|
36323
|
-
|
|
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
|
+
}
|
|
36324
36409
|
}
|
|
36325
36410
|
}
|
|
36326
36411
|
console.log(chalk2.dim(`
|
|
@@ -36387,8 +36472,18 @@ Available skills (${SKILLS.length}):
|
|
|
36387
36472
|
console.log();
|
|
36388
36473
|
}
|
|
36389
36474
|
});
|
|
36390
|
-
program2.command("search").argument("<query>", "Search term").option("--json", "Output as JSON", false).description("Search for skills").action((query, options) => {
|
|
36391
|
-
|
|
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
|
+
}
|
|
36392
36487
|
if (options.json) {
|
|
36393
36488
|
console.log(JSON.stringify(results, null, 2));
|
|
36394
36489
|
return;
|
|
@@ -36533,20 +36628,62 @@ program2.command("run").argument("<skill>", "Skill name").argument("[args...]",
|
|
|
36533
36628
|
}
|
|
36534
36629
|
process.exitCode = result.exitCode;
|
|
36535
36630
|
});
|
|
36536
|
-
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) => {
|
|
36537
36632
|
const cwd = process.cwd();
|
|
36538
36633
|
const installed = getInstalledSkills();
|
|
36539
36634
|
if (installed.length === 0) {
|
|
36540
|
-
|
|
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
|
+
}
|
|
36541
36640
|
return;
|
|
36542
36641
|
}
|
|
36543
|
-
const
|
|
36544
|
-
|
|
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
|
+
`;
|
|
36545
36677
|
const envPath = join4(cwd, ".env.example");
|
|
36546
36678
|
writeFileSync2(envPath, envContent);
|
|
36547
|
-
|
|
36679
|
+
envVarCount = envMap.size;
|
|
36680
|
+
if (!options.json) {
|
|
36681
|
+
console.log(chalk2.green(`\u2713 Generated .env.example (${envVarCount} variables from ${installed.length} skills)`));
|
|
36682
|
+
}
|
|
36548
36683
|
} else {
|
|
36549
|
-
|
|
36684
|
+
if (!options.json) {
|
|
36685
|
+
console.log(chalk2.dim(" No environment variables detected across installed skills"));
|
|
36686
|
+
}
|
|
36550
36687
|
}
|
|
36551
36688
|
const gitignorePath = join4(cwd, ".gitignore");
|
|
36552
36689
|
const gitignoreEntry = ".skills/";
|
|
@@ -36554,6 +36691,7 @@ program2.command("init").description("Initialize project for installed skills (.
|
|
|
36554
36691
|
if (existsSync4(gitignorePath)) {
|
|
36555
36692
|
gitignoreContent = readFileSync4(gitignorePath, "utf-8");
|
|
36556
36693
|
}
|
|
36694
|
+
let gitignoreUpdated = false;
|
|
36557
36695
|
if (!gitignoreContent.includes(gitignoreEntry)) {
|
|
36558
36696
|
const addition = gitignoreContent.endsWith(`
|
|
36559
36697
|
`) || gitignoreContent === "" ? `
|
|
@@ -36565,12 +36703,35 @@ ${gitignoreEntry}
|
|
|
36565
36703
|
${gitignoreEntry}
|
|
36566
36704
|
`;
|
|
36567
36705
|
appendFileSync(gitignorePath, addition);
|
|
36568
|
-
|
|
36706
|
+
gitignoreUpdated = true;
|
|
36707
|
+
if (!options.json) {
|
|
36708
|
+
console.log(chalk2.green(`\u2713 Added .skills/ to .gitignore`));
|
|
36709
|
+
}
|
|
36569
36710
|
} else {
|
|
36570
|
-
|
|
36711
|
+
if (!options.json) {
|
|
36712
|
+
console.log(chalk2.dim(" .skills/ already in .gitignore"));
|
|
36713
|
+
}
|
|
36571
36714
|
}
|
|
36572
|
-
|
|
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(`
|
|
36573
36733
|
Initialized for ${installed.length} installed skill(s)`));
|
|
36734
|
+
}
|
|
36574
36735
|
});
|
|
36575
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) => {
|
|
36576
36737
|
if (options.for) {
|
|
@@ -36633,16 +36794,57 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
36633
36794
|
console.log(chalk2.dim("No skills installed. Run: skills install <name>"));
|
|
36634
36795
|
return;
|
|
36635
36796
|
}
|
|
36636
|
-
|
|
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
|
+
}
|
|
36637
36832
|
if (options.json) {
|
|
36638
|
-
console.log(JSON.stringify(
|
|
36833
|
+
console.log(JSON.stringify(updateResults, null, 2));
|
|
36639
36834
|
} else {
|
|
36640
36835
|
console.log(chalk2.bold(`
|
|
36641
36836
|
Updating skills...
|
|
36642
36837
|
`));
|
|
36643
|
-
for (const result of
|
|
36838
|
+
for (const result of updateResults) {
|
|
36644
36839
|
if (result.success) {
|
|
36645
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`));
|
|
36646
36848
|
} else {
|
|
36647
36849
|
console.log(chalk2.red(`\u2717 ${result.skill}: ${result.error}`));
|
|
36648
36850
|
}
|
|
@@ -36650,7 +36852,7 @@ Updating skills...
|
|
|
36650
36852
|
console.log(chalk2.dim(`
|
|
36651
36853
|
Skills updated in .skills/`));
|
|
36652
36854
|
}
|
|
36653
|
-
if (
|
|
36855
|
+
if (updateResults.some((r) => !r.success)) {
|
|
36654
36856
|
process.exitCode = 1;
|
|
36655
36857
|
}
|
|
36656
36858
|
});
|
|
@@ -36713,16 +36915,16 @@ Add to ${configPath} mcpServers:`));
|
|
|
36713
36915
|
}
|
|
36714
36916
|
await Promise.resolve().then(() => (init_mcp2(), exports_mcp));
|
|
36715
36917
|
});
|
|
36716
|
-
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) => {
|
|
36717
36919
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
36718
36920
|
const port = parseInt(options.port, 10);
|
|
36719
36921
|
await startServer2(port, { open: options.open });
|
|
36720
36922
|
});
|
|
36721
|
-
program2.command("self-update").description(
|
|
36923
|
+
program2.command("self-update").description(`Update ${package_default.name} to the latest version`).action(async () => {
|
|
36722
36924
|
console.log(chalk2.bold(`
|
|
36723
|
-
Updating
|
|
36925
|
+
Updating ${package_default.name}...
|
|
36724
36926
|
`));
|
|
36725
|
-
const proc = Bun.spawn(["bun", "add", "-g",
|
|
36927
|
+
const proc = Bun.spawn(["bun", "add", "-g", `${package_default.name}@latest`], {
|
|
36726
36928
|
stdout: "inherit",
|
|
36727
36929
|
stderr: "inherit"
|
|
36728
36930
|
});
|
|
@@ -36739,4 +36941,240 @@ Updating @hasna/skills...
|
|
|
36739
36941
|
process.exitCode = 1;
|
|
36740
36942
|
}
|
|
36741
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
|
+
});
|
|
36742
37180
|
program2.parse();
|