@hasna/skills 0.1.7 → 0.1.8

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