@hasna/skills 0.1.7 → 0.1.9

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