@hasna/skills 0.1.24 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/index.js CHANGED
@@ -1891,12 +1891,12 @@ var package_default;
1891
1891
  var init_package = __esm(() => {
1892
1892
  package_default = {
1893
1893
  name: "@hasna/skills",
1894
- version: "0.1.24",
1894
+ version: "0.1.25",
1895
1895
  description: "Skills library for AI coding agents",
1896
1896
  type: "module",
1897
1897
  bin: {
1898
- skills: "./bin/index.js",
1899
- "skills-mcp": "./bin/mcp.js"
1898
+ skills: "bin/index.js",
1899
+ "skills-mcp": "bin/mcp.js"
1900
1900
  },
1901
1901
  exports: {
1902
1902
  ".": {
@@ -2122,7 +2122,7 @@ function discoverSkillsInDir(dir) {
2122
2122
  const fm = parseSkillMdFrontmatter(content);
2123
2123
  if (!fm?.name)
2124
2124
  continue;
2125
- const name = fm.name.replace(/^skill-/, "");
2125
+ const name = fm.name;
2126
2126
  result.push({
2127
2127
  name,
2128
2128
  displayName: fm.displayName || name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
@@ -7216,7 +7216,7 @@ Skills Doctor \u2014 ${installed.length} installed, ${issues.length} with issues
7216
7216
  async function handleTest(skillArg, options) {
7217
7217
  let skillNames;
7218
7218
  if (skillArg) {
7219
- const registryName = skillArg.startsWith("skill-") ? skillArg.replace("skill-", "") : skillArg;
7219
+ const registryName = skillArg;
7220
7220
  if (!getSkill(registryName)) {
7221
7221
  if (options.json) {
7222
7222
  console.log(JSON.stringify({ error: `Skill '${skillArg}' not found` }));
@@ -7354,7 +7354,7 @@ function handleWhoami(options) {
7354
7354
  let skillCount = 0;
7355
7355
  if (exists)
7356
7356
  try {
7357
- skillCount = readdirSync6(agentSkillsPath).filter((f) => f.startsWith("skill-") && statSync5(join8(agentSkillsPath, f)).isDirectory()).length;
7357
+ skillCount = readdirSync6(agentSkillsPath).filter((f) => !f.startsWith(".") && statSync5(join8(agentSkillsPath, f)).isDirectory()).length;
7358
7358
  } catch {}
7359
7359
  agentConfigs.push({ agent, label: AGENT_LABELS[agent], path: agentSkillsPath, exists, skillCount });
7360
7360
  }
@@ -47223,11 +47223,58 @@ function recordScheduleRun(id, status, targetDir) {
47223
47223
  }
47224
47224
  var init_scheduler = () => {};
47225
47225
 
47226
+ // src/lib/feedback.ts
47227
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5 } from "fs";
47228
+ import { homedir as homedir6 } from "os";
47229
+ import { dirname as dirname5, join as join12 } from "path";
47230
+ function getFeedbackDbPath() {
47231
+ return process.env.SKILLS_FEEDBACK_DB_PATH || join12(homedir6(), ".hasna", "skills", "skills.db");
47232
+ }
47233
+ function getFeedbackDb() {
47234
+ const dbPath = getFeedbackDbPath();
47235
+ const dir = dirname5(dbPath);
47236
+ if (!existsSync12(dir))
47237
+ mkdirSync5(dir, { recursive: true });
47238
+ const db = new SqliteAdapter(dbPath);
47239
+ db.exec("PRAGMA journal_mode = WAL");
47240
+ db.exec([
47241
+ "CREATE TABLE IF NOT EXISTS feedback (",
47242
+ "id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),",
47243
+ "message TEXT NOT NULL,",
47244
+ "email TEXT,",
47245
+ "category TEXT DEFAULT 'general',",
47246
+ "agent TEXT,",
47247
+ "version TEXT,",
47248
+ "machine_id TEXT,",
47249
+ "created_at TEXT NOT NULL DEFAULT (datetime('now'))",
47250
+ ")"
47251
+ ].join(" "));
47252
+ try {
47253
+ db.exec("ALTER TABLE feedback ADD COLUMN agent TEXT");
47254
+ } catch {}
47255
+ return db;
47256
+ }
47257
+ function saveFeedback2(input) {
47258
+ const message = input.message.trim();
47259
+ if (!message)
47260
+ throw new Error("Feedback message is required");
47261
+ const category = input.category ?? "general";
47262
+ const db = getFeedbackDb();
47263
+ try {
47264
+ db.run("INSERT INTO feedback (message, email, category, agent, version) VALUES (?, ?, ?, ?, ?)", [message, input.email || null, category, input.agent || null, input.version || null]);
47265
+ } finally {
47266
+ db.close();
47267
+ }
47268
+ return { saved: true, category, path: getFeedbackDbPath() };
47269
+ }
47270
+ var init_feedback = __esm(() => {
47271
+ init_dist();
47272
+ });
47273
+
47226
47274
  // src/mcp/index.ts
47227
47275
  var exports_mcp = {};
47228
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readdirSync as readdirSync8, statSync as statSync6 } from "fs";
47229
- import { join as join12, dirname as dirname5 } from "path";
47230
- import { homedir as homedir6 } from "os";
47276
+ import { existsSync as existsSync13, readdirSync as readdirSync8, statSync as statSync6 } from "fs";
47277
+ import { join as join13 } from "path";
47231
47278
  function stripNulls(obj) {
47232
47279
  return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== undefined && !(Array.isArray(v) && v.length === 0)));
47233
47280
  }
@@ -47254,17 +47301,6 @@ function mcpError(code, message, suggestions) {
47254
47301
  isError: true
47255
47302
  };
47256
47303
  }
47257
- function getFeedbackDb() {
47258
- const home = homedir6();
47259
- const dbPath = join12(home, ".hasna", "skills", "skills.db");
47260
- const dir = dirname5(dbPath);
47261
- if (!existsSync12(dir))
47262
- mkdirSync5(dir, { recursive: true });
47263
- const db = new SqliteAdapter(dbPath);
47264
- db.exec("PRAGMA journal_mode = WAL");
47265
- db.exec("CREATE TABLE IF NOT EXISTS feedback (id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))), message TEXT NOT NULL, email TEXT, category TEXT DEFAULT 'general', version TEXT, machine_id TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')))");
47266
- return db;
47267
- }
47268
47304
  async function main() {
47269
47305
  const transport = new StdioServerTransport;
47270
47306
  registerCloudTools(server, "skills");
@@ -47281,6 +47317,7 @@ var init_mcp2 = __esm(() => {
47281
47317
  init_installer();
47282
47318
  init_skillinfo();
47283
47319
  init_scheduler();
47320
+ init_feedback();
47284
47321
  server = new McpServer({
47285
47322
  name: "skills",
47286
47323
  version: package_default.version
@@ -47607,13 +47644,13 @@ var init_mcp2 = __esm(() => {
47607
47644
  const agents = [];
47608
47645
  for (const agent of AGENT_TARGETS) {
47609
47646
  const agentSkillsPath = getAgentSkillsDir(agent, "global");
47610
- const exists = existsSync12(agentSkillsPath);
47647
+ const exists = existsSync13(agentSkillsPath);
47611
47648
  let skillCount = 0;
47612
47649
  if (exists) {
47613
47650
  try {
47614
47651
  skillCount = readdirSync8(agentSkillsPath).filter((f) => {
47615
- const full = join12(agentSkillsPath, f);
47616
- return f.startsWith("skill-") && statSync6(full).isDirectory();
47652
+ const full = join13(agentSkillsPath, f);
47653
+ return !f.startsWith(".") && statSync6(full).isDirectory();
47617
47654
  }).length;
47618
47655
  } catch {}
47619
47656
  }
@@ -47693,17 +47730,17 @@ var init_mcp2 = __esm(() => {
47693
47730
  }, async ({ name }) => {
47694
47731
  const skillPath = getSkillPath(name);
47695
47732
  const issues = [];
47696
- if (!existsSync12(skillPath)) {
47733
+ if (!existsSync13(skillPath)) {
47697
47734
  return {
47698
47735
  content: [{ type: "text", text: JSON.stringify({ name, valid: false, issues: [`Skill directory not found: ${skillPath}`] }) }]
47699
47736
  };
47700
47737
  }
47701
- if (!existsSync12(join12(skillPath, "SKILL.md")))
47738
+ if (!existsSync13(join13(skillPath, "SKILL.md")))
47702
47739
  issues.push("Missing SKILL.md");
47703
- if (!existsSync12(join12(skillPath, "tsconfig.json")))
47740
+ if (!existsSync13(join13(skillPath, "tsconfig.json")))
47704
47741
  issues.push("Missing tsconfig.json");
47705
- const pkgPath = join12(skillPath, "package.json");
47706
- if (!existsSync12(pkgPath)) {
47742
+ const pkgPath = join13(skillPath, "package.json");
47743
+ if (!existsSync13(pkgPath)) {
47707
47744
  issues.push("Missing package.json");
47708
47745
  } else {
47709
47746
  try {
@@ -47714,10 +47751,10 @@ var init_mcp2 = __esm(() => {
47714
47751
  issues.push("package.json is invalid JSON");
47715
47752
  }
47716
47753
  }
47717
- const srcDir = join12(skillPath, "src");
47718
- if (!existsSync12(srcDir)) {
47754
+ const srcDir = join13(skillPath, "src");
47755
+ if (!existsSync13(srcDir)) {
47719
47756
  issues.push("Missing src/ directory");
47720
- } else if (!existsSync12(join12(srcDir, "index.ts"))) {
47757
+ } else if (!existsSync13(join13(srcDir, "index.ts"))) {
47721
47758
  issues.push("Missing src/index.ts");
47722
47759
  }
47723
47760
  const valid = issues.length === 0;
@@ -47841,12 +47878,10 @@ var init_mcp2 = __esm(() => {
47841
47878
  return { content: [{ type: "text", text: "No agents registered." }] };
47842
47879
  return { content: [{ type: "text", text: JSON.stringify(agents, null, 2) }] };
47843
47880
  });
47844
- server.tool("send_feedback", "Send feedback about this service", { message: exports_external.string(), email: exports_external.string().optional(), category: exports_external.enum(["bug", "feature", "general"]).optional() }, async (params) => {
47881
+ server.tool("send_feedback", "Send feedback about this service", { message: exports_external.string(), email: exports_external.string().optional(), agent: exports_external.string().optional(), category: exports_external.enum(["bug", "feature", "general"]).optional() }, async (params) => {
47845
47882
  try {
47846
- const db = getFeedbackDb();
47847
- db.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", [params.message, params.email || null, params.category || "general", package_default.version]);
47848
- db.close();
47849
- return { content: [{ type: "text", text: "Feedback saved. Thank you!" }] };
47883
+ const result = saveFeedback2({ ...params, version: package_default.version });
47884
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
47850
47885
  } catch (e) {
47851
47886
  return { content: [{ type: "text", text: String(e) }], isError: true };
47852
47887
  }
@@ -47863,7 +47898,7 @@ __export(exports_runtime, {
47863
47898
  registerRuntime: () => registerRuntime
47864
47899
  });
47865
47900
  import chalk7 from "chalk";
47866
- import { join as join13 } from "path";
47901
+ import { join as join14 } from "path";
47867
47902
  import { homedir as homedir8 } from "os";
47868
47903
  function registerRuntime(parent) {
47869
47904
  parent.command("run").argument("<skill>", "Skill name").argument("[args...]", "Arguments to pass to the skill").allowUnknownOption(true).passThroughOptions(true).description("Run a skill directly").action(async (name, args) => handleRun(name, args));
@@ -47914,7 +47949,7 @@ async function handleRun(name, args) {
47914
47949
  async function handleMcp(options) {
47915
47950
  if (options.register) {
47916
47951
  const agents = options.register === "all" ? [...AGENT_TARGETS] : [options.register];
47917
- const binPath = join13(import.meta.dir, "..", "mcp", "index.ts");
47952
+ const binPath = join14(import.meta.dir, "..", "mcp", "index.ts");
47918
47953
  for (const agent of agents) {
47919
47954
  if (agent === "claude") {
47920
47955
  try {
@@ -47926,10 +47961,10 @@ async function handleMcp(options) {
47926
47961
  }
47927
47962
  } else {
47928
47963
  const dirs = {
47929
- codex: join13(homedir8(), ".codex", "config.toml"),
47930
- gemini: join13(homedir8(), ".gemini", "settings.json"),
47931
- pi: join13(homedir8(), ".pi", "agent", "mcp.json"),
47932
- opencode: join13(homedir8(), ".opencode", "config.json")
47964
+ codex: join14(homedir8(), ".codex", "config.toml"),
47965
+ gemini: join14(homedir8(), ".gemini", "settings.json"),
47966
+ pi: join14(homedir8(), ".pi", "agent", "mcp.json"),
47967
+ opencode: join14(homedir8(), ".opencode", "config.json")
47933
47968
  };
47934
47969
  const cfg = {
47935
47970
  codex: `[mcp_servers.skills]
@@ -48119,7 +48154,8 @@ var init_completion = __esm(() => {
48119
48154
  "sync",
48120
48155
  "validate",
48121
48156
  "diff",
48122
- "schedule"
48157
+ "schedule",
48158
+ "feedback"
48123
48159
  ];
48124
48160
  skillCmds = ["install", "info", "docs", "requires", "run", "remove"];
48125
48161
  skillNames = SKILLS.map((s) => s.name);
@@ -48132,8 +48168,8 @@ __export(exports_create_sync_config, {
48132
48168
  registerCreateSync: () => registerCreateSync
48133
48169
  });
48134
48170
  import chalk8 from "chalk";
48135
- import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, readdirSync as readdirSync9 } from "fs";
48136
- import { join as join14 } from "path";
48171
+ import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, readdirSync as readdirSync9 } from "fs";
48172
+ import { join as join15 } from "path";
48137
48173
  import { homedir as homedir9 } from "os";
48138
48174
  function registerCreateSync(parent) {
48139
48175
  const configCmd = parent.command("config").description("Manage skills configuration");
@@ -48164,18 +48200,18 @@ function registerCreateSync(parent) {
48164
48200
  configCmd.command("path").description("Show configuration file paths").action(() => {
48165
48201
  const gp = getConfigPath("global");
48166
48202
  const pp = getConfigPath("project");
48167
- console.log(`${chalk8.cyan("global")}: ${gp}${existsSync13(gp) ? chalk8.green(" (exists)") : chalk8.dim(" (not found)")}`);
48168
- console.log(`${chalk8.cyan("project")}: ${pp}${existsSync13(pp) ? chalk8.green(" (exists)") : chalk8.dim(" (not found)")}`);
48203
+ console.log(`${chalk8.cyan("global")}: ${gp}${existsSync14(gp) ? chalk8.green(" (exists)") : chalk8.dim(" (not found)")}`);
48204
+ console.log(`${chalk8.cyan("project")}: ${pp}${existsSync14(pp) ? chalk8.green(" (exists)") : chalk8.dim(" (not found)")}`);
48169
48205
  });
48170
48206
  parent.command("create").argument("<name>", "Skill name (e.g. my-tool)").option("--category <category>", "Skill category", "Development Tools").option("--description <description>", "Short description of what the skill does").option("--tags <tags>", "Comma-separated tags (e.g. api,testing,automation)").option("--global", "Create in ~/.hasna/skills/custom/ instead of .skills/custom-skills/", false).option("--json", "Output result as JSON", false).description("Scaffold a new custom skill directory").action((name, options) => handleCreate(name, options));
48171
48207
  parent.command("sync").option("--to <agent>", "Push custom skills to agent").option("--from <agent>", "List agent skills and show which are unknown").option("--register", "Copy unknown agent skills into ~/.hasna/skills/custom/", false).option("--scope <scope>", "Agent install scope: global or project", "global").option("--json", "Output as JSON", false).description("Sync custom skills with agent directories (--to or --from)").action((options) => handleSync(options));
48172
48208
  }
48173
48209
  function handleCreate(name, options) {
48174
- const bare = name.replace(/^skill-/, "");
48175
- const dirName = `skill-${bare}`;
48176
- const baseDir = options.global ? join14(homedir9(), ".hasna", "skills", "custom") : join14(process.cwd(), ".skills", "custom-skills");
48177
- const skillDir = join14(baseDir, dirName);
48178
- if (existsSync13(skillDir)) {
48210
+ const bare = name.trim();
48211
+ const dirName = bare;
48212
+ const baseDir = options.global ? join15(homedir9(), ".hasna", "skills", "custom") : join15(process.cwd(), ".skills", "custom-skills");
48213
+ const skillDir = join15(baseDir, dirName);
48214
+ if (existsSync14(skillDir)) {
48179
48215
  console.log(options.json ? JSON.stringify({ error: `Skill '${bare}' already exists at ${skillDir}` }) : chalk8.red(`Skill '${bare}' already exists at ${skillDir}`));
48180
48216
  process.exitCode = 1;
48181
48217
  return;
@@ -48183,8 +48219,8 @@ function handleCreate(name, options) {
48183
48219
  const description = options.description || `${bare} skill`;
48184
48220
  const tags = options.tags ? options.tags.split(",").map((t) => t.trim()).filter(Boolean) : [bare];
48185
48221
  const displayName = bare.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
48186
- mkdirSync6(join14(skillDir, "src"), { recursive: true });
48187
- writeFileSync7(join14(skillDir, "SKILL.md"), [
48222
+ mkdirSync6(join15(skillDir, "src"), { recursive: true });
48223
+ writeFileSync7(join15(skillDir, "SKILL.md"), [
48188
48224
  "---",
48189
48225
  `name: ${bare}`,
48190
48226
  `description: ${description}`,
@@ -48204,11 +48240,11 @@ function handleCreate(name, options) {
48204
48240
  ""
48205
48241
  ].join(`
48206
48242
  `));
48207
- writeFileSync7(join14(skillDir, "src", "index.ts"), [`#!/usr/bin/env bun`, `/**`, ` * ${displayName} \u2014 ${description}`, ` */`, "", `console.log("${displayName}");`, ""].join(`
48243
+ writeFileSync7(join15(skillDir, "src", "index.ts"), [`#!/usr/bin/env bun`, `/**`, ` * ${displayName} \u2014 ${description}`, ` */`, "", `console.log("${displayName}");`, ""].join(`
48208
48244
  `));
48209
- writeFileSync7(join14(skillDir, "package.json"), JSON.stringify({ name: `skill-${bare}`, version: "0.1.0", description, bin: { [bare]: "./src/index.ts" }, scripts: { dev: `bun src/index.ts` }, dependencies: {} }, null, 2) + `
48245
+ writeFileSync7(join15(skillDir, "package.json"), JSON.stringify({ name: bare, version: "0.1.0", description, bin: { [bare]: "./src/index.ts" }, scripts: { dev: `bun src/index.ts` }, dependencies: {} }, null, 2) + `
48210
48246
  `);
48211
- writeFileSync7(join14(skillDir, "tsconfig.json"), JSON.stringify({ compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, outDir: "dist" }, include: ["src/**/*.ts"] }, null, 2) + `
48247
+ writeFileSync7(join15(skillDir, "tsconfig.json"), JSON.stringify({ compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, outDir: "dist" }, include: ["src/**/*.ts"] }, null, 2) + `
48212
48248
  `);
48213
48249
  clearRegistryCache();
48214
48250
  if (options.json)
@@ -48217,8 +48253,8 @@ function handleCreate(name, options) {
48217
48253
  console.log(chalk8.green(`\u2713 Created custom skill '${bare}' at ${skillDir}`));
48218
48254
  console.log(chalk8.dim(` Category: ${options.category}`));
48219
48255
  console.log(chalk8.dim(` Tags: ${tags.join(", ")}`));
48220
- console.log(` ${chalk8.cyan("Edit:")} ${join14(skillDir, "src", "index.ts")}`);
48221
- console.log(` ${chalk8.cyan("Run:")} bun ${join14(skillDir, "src", "index.ts")}`);
48256
+ console.log(` ${chalk8.cyan("Edit:")} ${join15(skillDir, "src", "index.ts")}`);
48257
+ console.log(` ${chalk8.cyan("Run:")} bun ${join15(skillDir, "src", "index.ts")}`);
48222
48258
  }
48223
48259
  }
48224
48260
  function handleSync(options) {
@@ -48235,7 +48271,7 @@ function handleSync(options) {
48235
48271
  return;
48236
48272
  }
48237
48273
  const agentDir = getAgentSkillsDir(agentName, options.scope);
48238
- if (!existsSync13(agentDir)) {
48274
+ if (!existsSync14(agentDir)) {
48239
48275
  console.log(options.json ? JSON.stringify({ agentDir, skills: [], message: "Directory not found" }) : chalk8.dim(`No skills directory found at ${agentDir}`));
48240
48276
  return;
48241
48277
  }
@@ -48245,21 +48281,21 @@ function handleSync(options) {
48245
48281
  for (const entry of readdirSync9(agentDir, { withFileTypes: true })) {
48246
48282
  if (!entry.isDirectory())
48247
48283
  continue;
48248
- const bare = entry.name.replace(/^skill-/, "");
48249
- found.push({ name: bare, path: join14(agentDir, entry.name), inRegistry: registryNames.has(bare) });
48284
+ const bare = entry.name;
48285
+ found.push({ name: bare, path: join15(agentDir, entry.name), inRegistry: registryNames.has(bare) });
48250
48286
  }
48251
48287
  const unknown3 = found.filter((s) => !s.inRegistry);
48252
48288
  if (options.register && unknown3.length > 0) {
48253
- const globalSkillsDir = join14(homedir9(), ".hasna", "skills", "custom");
48289
+ const globalSkillsDir = join15(homedir9(), ".hasna", "skills", "custom");
48254
48290
  const registered = [];
48255
48291
  for (const s of unknown3) {
48256
- const srcSkillMd = join14(s.path, "SKILL.md");
48257
- if (!existsSync13(srcSkillMd))
48292
+ const srcSkillMd = join15(s.path, "SKILL.md");
48293
+ if (!existsSync14(srcSkillMd))
48258
48294
  continue;
48259
- const destDir = join14(globalSkillsDir, `skill-${s.name}`);
48260
- if (!existsSync13(destDir))
48295
+ const destDir = join15(globalSkillsDir, s.name);
48296
+ if (!existsSync14(destDir))
48261
48297
  mkdirSync6(destDir, { recursive: true });
48262
- writeFileSync7(join14(destDir, "SKILL.md"), readFileSync12(srcSkillMd, "utf-8"));
48298
+ writeFileSync7(join15(destDir, "SKILL.md"), readFileSync12(srcSkillMd, "utf-8"));
48263
48299
  registered.push(s.name);
48264
48300
  }
48265
48301
  clearRegistryCache();
@@ -48450,6 +48486,51 @@ var init_schedule = __esm(() => {
48450
48486
  init_scheduler();
48451
48487
  });
48452
48488
 
48489
+ // src/cli/commands/feedback.ts
48490
+ var exports_feedback = {};
48491
+ __export(exports_feedback, {
48492
+ registerFeedback: () => registerFeedback
48493
+ });
48494
+ import chalk10 from "chalk";
48495
+ function registerFeedback(parent) {
48496
+ parent.command("feedback").argument("<message...>", "Feedback message").option("--category <category>", "Feedback category: bug, feature, or general", "general").option("--email <email>", "Contact email for follow-up").option("--agent <name>", "Agent name sending feedback").option("--json", "Output as JSON", false).description("Send feedback from an agent or local CLI session").action((messageParts, options) => {
48497
+ const category = options.category;
48498
+ if (!["bug", "feature", "general"].includes(category)) {
48499
+ const error48 = `Invalid category: ${options.category}. Use bug, feature, or general.`;
48500
+ if (options.json)
48501
+ console.log(JSON.stringify({ saved: false, error: error48 }));
48502
+ else
48503
+ console.error(chalk10.red(error48));
48504
+ process.exitCode = 1;
48505
+ return;
48506
+ }
48507
+ try {
48508
+ const result = saveFeedback2({
48509
+ message: messageParts.join(" "),
48510
+ category,
48511
+ email: options.email,
48512
+ agent: options.agent,
48513
+ version: package_default.version
48514
+ });
48515
+ if (options.json)
48516
+ console.log(JSON.stringify(result, null, 2));
48517
+ else
48518
+ console.log(chalk10.green(`Feedback saved (${result.category})`));
48519
+ } catch (error48) {
48520
+ const message = error48 instanceof Error ? error48.message : String(error48);
48521
+ if (options.json)
48522
+ console.log(JSON.stringify({ saved: false, error: message }));
48523
+ else
48524
+ console.error(chalk10.red(message));
48525
+ process.exitCode = 1;
48526
+ }
48527
+ });
48528
+ }
48529
+ var init_feedback2 = __esm(() => {
48530
+ init_package();
48531
+ init_feedback();
48532
+ });
48533
+
48453
48534
  // src/cli/index.tsx
48454
48535
  import { render } from "ink";
48455
48536
 
@@ -48471,7 +48552,7 @@ var {
48471
48552
 
48472
48553
  // src/cli/index.tsx
48473
48554
  init_package();
48474
- import chalk10 from "chalk";
48555
+ import chalk11 from "chalk";
48475
48556
 
48476
48557
  // src/cli/components/App.tsx
48477
48558
  import { useState as useState7 } from "react";
@@ -49596,7 +49677,7 @@ init_registry();
49596
49677
  import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
49597
49678
  var isTTY = (process.stdout.isTTY ?? false) && (process.stdin.isTTY ?? false);
49598
49679
  if (process.argv.includes("--no-color")) {
49599
- chalk10.level = 0;
49680
+ chalk11.level = 0;
49600
49681
  const idx = process.argv.indexOf("--no-color");
49601
49682
  process.argv.splice(idx, 1);
49602
49683
  }
@@ -49627,4 +49708,6 @@ var { registerCreateSync: registerCreateSync2 } = await Promise.resolve().then((
49627
49708
  registerCreateSync2(program2);
49628
49709
  var { registerSchedule: registerSchedule2 } = await Promise.resolve().then(() => (init_schedule(), exports_schedule));
49629
49710
  registerSchedule2(program2);
49711
+ var { registerFeedback: registerFeedback2 } = await Promise.resolve().then(() => (init_feedback2(), exports_feedback));
49712
+ registerFeedback2(program2);
49630
49713
  program2.parse();
package/bin/mcp.js CHANGED
@@ -28561,9 +28561,8 @@ class StdioServerTransport {
28561
28561
  }
28562
28562
 
28563
28563
  // src/mcp/index.ts
28564
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
28565
- import { join as join10, dirname as dirname3 } from "path";
28566
- import { homedir as homedir9 } from "os";
28564
+ import { existsSync as existsSync10, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
28565
+ import { join as join11 } from "path";
28567
28566
 
28568
28567
  // node_modules/@hasna/cloud/dist/index.js
28569
28568
  import { createRequire } from "module";
@@ -38746,12 +38745,12 @@ init_adapter();
38746
38745
  // package.json
38747
38746
  var package_default = {
38748
38747
  name: "@hasna/skills",
38749
- version: "0.1.24",
38748
+ version: "0.1.25",
38750
38749
  description: "Skills library for AI coding agents",
38751
38750
  type: "module",
38752
38751
  bin: {
38753
- skills: "./bin/index.js",
38754
- "skills-mcp": "./bin/mcp.js"
38752
+ skills: "bin/index.js",
38753
+ "skills-mcp": "bin/mcp.js"
38755
38754
  },
38756
38755
  exports: {
38757
38756
  ".": {
@@ -40529,7 +40528,7 @@ function discoverSkillsInDir(dir) {
40529
40528
  const fm = parseSkillMdFrontmatter(content);
40530
40529
  if (!fm?.name)
40531
40530
  continue;
40532
- const name = fm.name.replace(/^skill-/, "");
40531
+ const name = fm.name;
40533
40532
  result.push({
40534
40533
  name,
40535
40534
  displayName: fm.displayName || name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
@@ -41342,6 +41341,51 @@ function removeSchedule(idOrName, targetDir) {
41342
41341
  return true;
41343
41342
  }
41344
41343
 
41344
+ // src/lib/feedback.ts
41345
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
41346
+ import { homedir as homedir9 } from "os";
41347
+ import { dirname as dirname3, join as join10 } from "path";
41348
+ function getFeedbackDbPath() {
41349
+ return process.env.SKILLS_FEEDBACK_DB_PATH || join10(homedir9(), ".hasna", "skills", "skills.db");
41350
+ }
41351
+ function getFeedbackDb() {
41352
+ const dbPath = getFeedbackDbPath();
41353
+ const dir = dirname3(dbPath);
41354
+ if (!existsSync9(dir))
41355
+ mkdirSync6(dir, { recursive: true });
41356
+ const db = new SqliteAdapter(dbPath);
41357
+ db.exec("PRAGMA journal_mode = WAL");
41358
+ db.exec([
41359
+ "CREATE TABLE IF NOT EXISTS feedback (",
41360
+ "id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),",
41361
+ "message TEXT NOT NULL,",
41362
+ "email TEXT,",
41363
+ "category TEXT DEFAULT 'general',",
41364
+ "agent TEXT,",
41365
+ "version TEXT,",
41366
+ "machine_id TEXT,",
41367
+ "created_at TEXT NOT NULL DEFAULT (datetime('now'))",
41368
+ ")"
41369
+ ].join(" "));
41370
+ try {
41371
+ db.exec("ALTER TABLE feedback ADD COLUMN agent TEXT");
41372
+ } catch {}
41373
+ return db;
41374
+ }
41375
+ function saveFeedback2(input) {
41376
+ const message = input.message.trim();
41377
+ if (!message)
41378
+ throw new Error("Feedback message is required");
41379
+ const category = input.category ?? "general";
41380
+ const db = getFeedbackDb();
41381
+ try {
41382
+ db.run("INSERT INTO feedback (message, email, category, agent, version) VALUES (?, ?, ?, ?, ?)", [message, input.email || null, category, input.agent || null, input.version || null]);
41383
+ } finally {
41384
+ db.close();
41385
+ }
41386
+ return { saved: true, category, path: getFeedbackDbPath() };
41387
+ }
41388
+
41345
41389
  // src/mcp/index.ts
41346
41390
  var server = new McpServer({
41347
41391
  name: "skills",
@@ -41696,13 +41740,13 @@ server.registerTool("whoami", {
41696
41740
  const agents = [];
41697
41741
  for (const agent of AGENT_TARGETS) {
41698
41742
  const agentSkillsPath = getAgentSkillsDir(agent, "global");
41699
- const exists = existsSync9(agentSkillsPath);
41743
+ const exists = existsSync10(agentSkillsPath);
41700
41744
  let skillCount = 0;
41701
41745
  if (exists) {
41702
41746
  try {
41703
41747
  skillCount = readdirSync7(agentSkillsPath).filter((f) => {
41704
- const full = join10(agentSkillsPath, f);
41705
- return f.startsWith("skill-") && statSync3(full).isDirectory();
41748
+ const full = join11(agentSkillsPath, f);
41749
+ return !f.startsWith(".") && statSync3(full).isDirectory();
41706
41750
  }).length;
41707
41751
  } catch {}
41708
41752
  }
@@ -41782,17 +41826,17 @@ server.registerTool("validate_skill", {
41782
41826
  }, async ({ name }) => {
41783
41827
  const skillPath = getSkillPath(name);
41784
41828
  const issues = [];
41785
- if (!existsSync9(skillPath)) {
41829
+ if (!existsSync10(skillPath)) {
41786
41830
  return {
41787
41831
  content: [{ type: "text", text: JSON.stringify({ name, valid: false, issues: [`Skill directory not found: ${skillPath}`] }) }]
41788
41832
  };
41789
41833
  }
41790
- if (!existsSync9(join10(skillPath, "SKILL.md")))
41834
+ if (!existsSync10(join11(skillPath, "SKILL.md")))
41791
41835
  issues.push("Missing SKILL.md");
41792
- if (!existsSync9(join10(skillPath, "tsconfig.json")))
41836
+ if (!existsSync10(join11(skillPath, "tsconfig.json")))
41793
41837
  issues.push("Missing tsconfig.json");
41794
- const pkgPath = join10(skillPath, "package.json");
41795
- if (!existsSync9(pkgPath)) {
41838
+ const pkgPath = join11(skillPath, "package.json");
41839
+ if (!existsSync10(pkgPath)) {
41796
41840
  issues.push("Missing package.json");
41797
41841
  } else {
41798
41842
  try {
@@ -41803,10 +41847,10 @@ server.registerTool("validate_skill", {
41803
41847
  issues.push("package.json is invalid JSON");
41804
41848
  }
41805
41849
  }
41806
- const srcDir = join10(skillPath, "src");
41807
- if (!existsSync9(srcDir)) {
41850
+ const srcDir = join11(skillPath, "src");
41851
+ if (!existsSync10(srcDir)) {
41808
41852
  issues.push("Missing src/ directory");
41809
- } else if (!existsSync9(join10(srcDir, "index.ts"))) {
41853
+ } else if (!existsSync10(join11(srcDir, "index.ts"))) {
41810
41854
  issues.push("Missing src/index.ts");
41811
41855
  }
41812
41856
  const valid = issues.length === 0;
@@ -41930,23 +41974,10 @@ server.tool("list_agents", "List all registered agents.", {}, async () => {
41930
41974
  return { content: [{ type: "text", text: "No agents registered." }] };
41931
41975
  return { content: [{ type: "text", text: JSON.stringify(agents, null, 2) }] };
41932
41976
  });
41933
- function getFeedbackDb() {
41934
- const home = homedir9();
41935
- const dbPath = join10(home, ".hasna", "skills", "skills.db");
41936
- const dir = dirname3(dbPath);
41937
- if (!existsSync9(dir))
41938
- mkdirSync6(dir, { recursive: true });
41939
- const db = new SqliteAdapter(dbPath);
41940
- db.exec("PRAGMA journal_mode = WAL");
41941
- db.exec("CREATE TABLE IF NOT EXISTS feedback (id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))), message TEXT NOT NULL, email TEXT, category TEXT DEFAULT 'general', version TEXT, machine_id TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')))");
41942
- return db;
41943
- }
41944
- server.tool("send_feedback", "Send feedback about this service", { message: exports_external.string(), email: exports_external.string().optional(), category: exports_external.enum(["bug", "feature", "general"]).optional() }, async (params) => {
41977
+ server.tool("send_feedback", "Send feedback about this service", { message: exports_external.string(), email: exports_external.string().optional(), agent: exports_external.string().optional(), category: exports_external.enum(["bug", "feature", "general"]).optional() }, async (params) => {
41945
41978
  try {
41946
- const db = getFeedbackDb();
41947
- db.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", [params.message, params.email || null, params.category || "general", package_default.version]);
41948
- db.close();
41949
- return { content: [{ type: "text", text: "Feedback saved. Thank you!" }] };
41979
+ const result = saveFeedback2({ ...params, version: package_default.version });
41980
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
41950
41981
  } catch (e) {
41951
41982
  return { content: [{ type: "text", text: String(e) }], isError: true };
41952
41983
  }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerFeedback(parent: Command): void;
package/dist/index.js CHANGED
@@ -1697,7 +1697,7 @@ function discoverSkillsInDir(dir) {
1697
1697
  const fm = parseSkillMdFrontmatter(content);
1698
1698
  if (!fm?.name)
1699
1699
  continue;
1700
- const name = fm.name.replace(/^skill-/, "");
1700
+ const name = fm.name;
1701
1701
  result.push({
1702
1702
  name,
1703
1703
  displayName: fm.displayName || name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
@@ -0,0 +1,15 @@
1
+ export type FeedbackCategory = "bug" | "feature" | "general";
2
+ export interface FeedbackInput {
3
+ message: string;
4
+ category?: FeedbackCategory;
5
+ email?: string;
6
+ agent?: string;
7
+ version?: string;
8
+ }
9
+ export interface FeedbackResult {
10
+ saved: true;
11
+ category: FeedbackCategory;
12
+ path: string;
13
+ }
14
+ export declare function getFeedbackDbPath(): string;
15
+ export declare function saveFeedback(input: FeedbackInput): FeedbackResult;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
7
- "skills": "./bin/index.js",
8
- "skills-mcp": "./bin/mcp.js"
7
+ "skills": "bin/index.js",
8
+ "skills-mcp": "bin/mcp.js"
9
9
  },
10
10
  "exports": {
11
11
  ".": {
@@ -47,7 +47,7 @@ export async function executeSkill(request: SkillRequest): Promise<SkillResponse
47
47
  method: "POST",
48
48
  headers: {
49
49
  "Content-Type": "application/json",
50
- "User-Agent": `skill-${skill}-cli/1.0`,
50
+ "User-Agent": `${skill}-cli/1.0`,
51
51
  "X-API-Key": apiKey
52
52
  },
53
53
  body: JSON.stringify(params)
@@ -4,7 +4,7 @@
4
4
  * Skill Installer Module
5
5
  *
6
6
  * Universal installer for skills to integrate with AI code assistants
7
- * Usage: skill-[name] install [claude|codex|windsurf|cursor]
7
+ * Usage: skills run [name] -- install [claude|codex|windsurf|cursor]
8
8
  */
9
9
 
10
10
  import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'fs';
@@ -47,7 +47,7 @@ export async function executeSkill(request: SkillRequest): Promise<SkillResponse
47
47
  method: "POST",
48
48
  headers: {
49
49
  "Content-Type": "application/json",
50
- "User-Agent": `skill-${skill}-cli/1.0`,
50
+ "User-Agent": `${skill}-cli/1.0`,
51
51
  "X-API-Key": apiKey
52
52
  },
53
53
  body: JSON.stringify(params)
@@ -5,7 +5,7 @@ import { join } from 'node:path';
5
5
  * Standard skill data directory structure
6
6
  *
7
7
  * ~/.skills/
8
- * └── skill-[name]/
8
+ * └── [name]/
9
9
  * ├── exports/ # Output data, results, artifacts
10
10
  * ├── logs/ # Execution logs, debug info
11
11
  * ├── cache/ # Temporary cached data