@hasna/skills 0.1.40 → 0.1.42

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
@@ -861,7 +861,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
861
861
  this._exitCallback = (err) => {
862
862
  if (err.code !== "commander.executeSubCommandAsync") {
863
863
  throw err;
864
- } else {}
864
+ }
865
865
  };
866
866
  }
867
867
  return this;
@@ -1910,7 +1910,7 @@ var package_default;
1910
1910
  var init_package = __esm(() => {
1911
1911
  package_default = {
1912
1912
  name: "@hasna/skills",
1913
- version: "0.1.40",
1913
+ version: "0.1.42",
1914
1914
  description: "Skills library for AI coding agents",
1915
1915
  type: "module",
1916
1916
  bin: {
@@ -1973,6 +1973,7 @@ var init_package = __esm(() => {
1973
1973
  typescript: "^5"
1974
1974
  },
1975
1975
  dependencies: {
1976
+ "@hasna/events": "^0.1.3",
1976
1977
  "@modelcontextprotocol/sdk": "^1.26.0",
1977
1978
  chalk: "^5.3.0",
1978
1979
  commander: "^12.1.0",
@@ -4061,9 +4062,9 @@ __export(exports_registry, {
4061
4062
  CATEGORIES: () => CATEGORIES,
4062
4063
  BASIC_SKILL_NAMES: () => BASIC_SKILL_NAMES
4063
4064
  });
4064
- import { existsSync, readFileSync, readdirSync } from "fs";
4065
- import { homedir } from "os";
4066
- import { join } from "path";
4065
+ import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
4066
+ import { homedir as homedir2 } from "os";
4067
+ import { join as join2 } from "path";
4067
4068
  function isBasicSkillName(name) {
4068
4069
  return BASIC_SKILL_NAMES.includes(name);
4069
4070
  }
@@ -4096,7 +4097,7 @@ function parseSkillMdFrontmatter(content) {
4096
4097
  return Object.keys(result).length > 0 ? result : null;
4097
4098
  }
4098
4099
  function discoverSkillsInDir(dir) {
4099
- if (!existsSync(dir))
4100
+ if (!existsSync2(dir))
4100
4101
  return [];
4101
4102
  const result = [];
4102
4103
  try {
@@ -4104,8 +4105,8 @@ function discoverSkillsInDir(dir) {
4104
4105
  for (const entry of entries) {
4105
4106
  if (!entry.isDirectory())
4106
4107
  continue;
4107
- const skillMdPath = join(dir, entry.name, "SKILL.md");
4108
- if (!existsSync(skillMdPath))
4108
+ const skillMdPath = join2(dir, entry.name, "SKILL.md");
4109
+ if (!existsSync2(skillMdPath))
4109
4110
  continue;
4110
4111
  let content;
4111
4112
  try {
@@ -4130,16 +4131,16 @@ function discoverSkillsInDir(dir) {
4130
4131
  return result;
4131
4132
  }
4132
4133
  function loadRegistry(cwd) {
4133
- const now = Date.now();
4134
- if (registryCache && now - registryCacheTime < REGISTRY_CACHE_TTL) {
4134
+ const now2 = Date.now();
4135
+ if (registryCache && now2 - registryCacheTime < REGISTRY_CACHE_TTL) {
4135
4136
  return registryCache;
4136
4137
  }
4137
4138
  const official = SKILLS.map((s) => ({ ...s, source: "official" }));
4138
- const globalCustom = discoverSkillsInDir(join(homedir(), ".hasna", "skills", "custom"));
4139
+ const globalCustom = discoverSkillsInDir(join2(homedir2(), ".hasna", "skills", "custom"));
4139
4140
  const customNames = new Set(globalCustom.map((s) => s.name));
4140
4141
  const filtered = official.filter((s) => !customNames.has(s.name));
4141
4142
  registryCache = [...filtered, ...globalCustom];
4142
- registryCacheTime = now;
4143
+ registryCacheTime = now2;
4143
4144
  return registryCache;
4144
4145
  }
4145
4146
  function loadBasicRegistry(cwd) {
@@ -5828,17 +5829,17 @@ function normalizeSkillName(name) {
5828
5829
  }
5829
5830
 
5830
5831
  // src/lib/project-state.ts
5831
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
5832
- import { join as join2 } from "path";
5832
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
5833
+ import { join as join3 } from "path";
5833
5834
  function getProjectStateDir(targetDir = process.cwd()) {
5834
- return join2(targetDir, SKILLS_PROJECT_DIR);
5835
+ return join3(targetDir, SKILLS_PROJECT_DIR);
5835
5836
  }
5836
5837
  function getProjectConfigPath(targetDir = process.cwd()) {
5837
- return join2(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
5838
+ return join3(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
5838
5839
  }
5839
5840
  function loadProjectConfig(targetDir = process.cwd()) {
5840
5841
  const path = getProjectConfigPath(targetDir);
5841
- if (!existsSync2(path))
5842
+ if (!existsSync3(path))
5842
5843
  return null;
5843
5844
  try {
5844
5845
  return normalizeProjectConfig(JSON.parse(readFileSync2(path, "utf-8")));
@@ -5850,14 +5851,14 @@ function ensureProjectConfig(targetDir = process.cwd()) {
5850
5851
  const existing = loadProjectConfig(targetDir);
5851
5852
  if (existing)
5852
5853
  return existing;
5853
- const now = new Date().toISOString();
5854
+ const now2 = new Date().toISOString();
5854
5855
  return {
5855
5856
  version: 1,
5856
5857
  defaultExportDir: DEFAULT_EXPORT_DIR,
5857
5858
  pinnedSkills: [],
5858
5859
  pins: {},
5859
- createdAt: now,
5860
- updatedAt: now
5860
+ createdAt: now2,
5861
+ updatedAt: now2
5861
5862
  };
5862
5863
  }
5863
5864
  function saveProjectConfig(config, targetDir = process.cwd()) {
@@ -5927,7 +5928,7 @@ function getDisabledProjectSkills(targetDir = process.cwd()) {
5927
5928
  return loadProjectConfig(targetDir)?.disabledSkills ?? [];
5928
5929
  }
5929
5930
  function normalizeProjectConfig(raw) {
5930
- const now = new Date().toISOString();
5931
+ const now2 = new Date().toISOString();
5931
5932
  const pinnedSkills = Array.isArray(raw.pinnedSkills) ? [...new Set(raw.pinnedSkills.map((name) => normalizeSkillName(String(name))))].sort() : [];
5932
5933
  const pins = {};
5933
5934
  const rawPins = raw.pins && typeof raw.pins === "object" ? raw.pins : {};
@@ -5935,7 +5936,7 @@ function normalizeProjectConfig(raw) {
5935
5936
  const pin = rawPins[name];
5936
5937
  pins[name] = {
5937
5938
  name,
5938
- pinnedAt: typeof pin?.pinnedAt === "string" ? pin.pinnedAt : now,
5939
+ pinnedAt: typeof pin?.pinnedAt === "string" ? pin.pinnedAt : now2,
5939
5940
  version: typeof pin?.version === "string" ? pin.version : "unknown",
5940
5941
  source: isPinSource(pin?.source) ? pin.source : "official"
5941
5942
  };
@@ -5946,8 +5947,8 @@ function normalizeProjectConfig(raw) {
5946
5947
  pinnedSkills,
5947
5948
  pins,
5948
5949
  disabledSkills: Array.isArray(raw.disabledSkills) ? [...new Set(raw.disabledSkills.map((name) => normalizeSkillName(String(name))))].sort() : [],
5949
- createdAt: typeof raw.createdAt === "string" ? raw.createdAt : now,
5950
- updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : now
5950
+ createdAt: typeof raw.createdAt === "string" ? raw.createdAt : now2,
5951
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : now2
5951
5952
  };
5952
5953
  }
5953
5954
  function isPinSource(value) {
@@ -5984,35 +5985,35 @@ __export(exports_installer, {
5984
5985
  AGENT_TARGETS: () => AGENT_TARGETS,
5985
5986
  AGENT_LABELS: () => AGENT_LABELS
5986
5987
  });
5987
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
5988
- import { dirname, join as join3 } from "path";
5989
- import { homedir as homedir2 } from "os";
5988
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
5989
+ import { dirname, join as join4 } from "path";
5990
+ import { homedir as homedir3 } from "os";
5990
5991
  import { fileURLToPath } from "url";
5991
5992
  function findSkillsDir() {
5992
5993
  let dir = __dirname2;
5993
5994
  for (let i = 0;i < 5; i++) {
5994
- const candidate = join3(dir, "skills");
5995
- if (existsSync3(candidate) && !dir.includes(".skills"))
5995
+ const candidate = join4(dir, "skills");
5996
+ if (existsSync4(candidate) && !dir.includes(".skills"))
5996
5997
  return candidate;
5997
5998
  dir = dirname(dir);
5998
5999
  }
5999
- return join3(__dirname2, "..", "skills");
6000
+ return join4(__dirname2, "..", "skills");
6000
6001
  }
6001
6002
  function getSkillPath(name) {
6002
6003
  const skillName = normalizeSkillName(getCanonicalSkillName(name));
6003
- return join3(SKILLS_DIR, skillName);
6004
+ return join4(SKILLS_DIR, skillName);
6004
6005
  }
6005
6006
  function getCanonicalSkillName(name) {
6006
6007
  return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
6007
6008
  }
6008
6009
  function skillExists(name) {
6009
- return existsSync3(getSkillPath(name));
6010
+ return existsSync4(getSkillPath(name));
6010
6011
  }
6011
6012
  function installSkill(name, options = {}) {
6012
6013
  const { targetDir = process.cwd(), overwrite = false } = options;
6013
6014
  const canonicalName = getCanonicalSkillName(name);
6014
6015
  const skillName = normalizeSkillName(canonicalName);
6015
- if (!existsSync3(getSkillPath(name))) {
6016
+ if (!existsSync4(getSkillPath(name))) {
6016
6017
  return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "pin" };
6017
6018
  }
6018
6019
  const existing = new Set(listPinnedSkills(targetDir));
@@ -6056,7 +6057,7 @@ function installRemoteSkill(skill, options = {}) {
6056
6057
  }
6057
6058
  function installSkillSource(name, _options = {}) {
6058
6059
  const canonicalName = getCanonicalSkillName(name);
6059
- if (!existsSync3(getSkillPath(name))) {
6060
+ if (!existsSync4(getSkillPath(name))) {
6060
6061
  return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "source" };
6061
6062
  }
6062
6063
  return {
@@ -6077,11 +6078,11 @@ function installSkillManifest(manifest, _options = {}) {
6077
6078
  }
6078
6079
  function createLocalSkillManifest(name, generateSkillMd) {
6079
6080
  const sourcePath = getSkillPath(name);
6080
- if (!existsSync3(sourcePath))
6081
+ if (!existsSync4(sourcePath))
6081
6082
  return null;
6082
6083
  let skillMd = "";
6083
- const skillMdPath = join3(sourcePath, "SKILL.md");
6084
- if (existsSync3(skillMdPath)) {
6084
+ const skillMdPath = join4(sourcePath, "SKILL.md");
6085
+ if (existsSync4(skillMdPath)) {
6085
6086
  skillMd = readFileSync3(skillMdPath, "utf-8");
6086
6087
  } else if (generateSkillMd) {
6087
6088
  skillMd = generateSkillMd(name) ?? "";
@@ -6162,20 +6163,20 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
6162
6163
  const base = projectDir || process.cwd();
6163
6164
  switch (agent) {
6164
6165
  case "pi":
6165
- return scope === "project" ? join3(base, ".pi", "skills") : join3(homedir2(), ".pi", "agent", "skills");
6166
+ return scope === "project" ? join4(base, ".pi", "skills") : join4(homedir3(), ".pi", "agent", "skills");
6166
6167
  case "opencode":
6167
- return scope === "project" ? join3(base, ".opencode", "skills") : join3(homedir2(), ".config", "opencode", "skills");
6168
+ return scope === "project" ? join4(base, ".opencode", "skills") : join4(homedir3(), ".config", "opencode", "skills");
6168
6169
  default:
6169
- return scope === "project" ? join3(base, `.${agent}`, "skills") : join3(homedir2(), `.${agent}`, "skills");
6170
+ return scope === "project" ? join4(base, `.${agent}`, "skills") : join4(homedir3(), `.${agent}`, "skills");
6170
6171
  }
6171
6172
  }
6172
6173
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
6173
6174
  const skillName = normalizeSkillName(getCanonicalSkillName(name));
6174
- return join3(getAgentSkillsDir(agent, scope, projectDir), skillName);
6175
+ return join4(getAgentSkillsDir(agent, scope, projectDir), skillName);
6175
6176
  }
6176
6177
  function installSkillForAgent(name, options, _generateSkillMd) {
6177
6178
  const canonicalName = getCanonicalSkillName(name);
6178
- if (!existsSync3(getSkillPath(name))) {
6179
+ if (!existsSync4(getSkillPath(name))) {
6179
6180
  return { skill: canonicalName, success: false, error: `Skill '${name}' not found` };
6180
6181
  }
6181
6182
  return {
@@ -6200,7 +6201,7 @@ function warnMissingDependencies(name, targetDir) {
6200
6201
  }
6201
6202
  function generateMinimalSkillMd(name) {
6202
6203
  const sourcePath = getSkillPath(name);
6203
- if (!existsSync3(sourcePath))
6204
+ if (!existsSync4(sourcePath))
6204
6205
  return null;
6205
6206
  const canonicalName = getCanonicalSkillName(name);
6206
6207
  const meta = getSkill(canonicalName);
@@ -6215,7 +6216,7 @@ function generateMinimalSkillMd(name) {
6215
6216
  "---",
6216
6217
  ""
6217
6218
  ].filter(Boolean);
6218
- const fallbackDoc = readFileIfExists(join3(sourcePath, "README.md")) || readFileIfExists(join3(sourcePath, "CLAUDE.md"));
6219
+ const fallbackDoc = readFileIfExists(join4(sourcePath, "README.md")) || readFileIfExists(join4(sourcePath, "CLAUDE.md"));
6219
6220
  if (fallbackDoc)
6220
6221
  return `${frontmatter.join(`
6221
6222
  `)}${fallbackDoc.trim()}
@@ -6234,8 +6235,8 @@ skills run ${canonicalName}
6234
6235
  `;
6235
6236
  }
6236
6237
  function readBundledSkillVersion(name) {
6237
- const pkgPath = join3(getSkillPath(name), "package.json");
6238
- if (!existsSync3(pkgPath))
6238
+ const pkgPath = join4(getSkillPath(name), "package.json");
6239
+ if (!existsSync4(pkgPath))
6239
6240
  return "unknown";
6240
6241
  try {
6241
6242
  const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
@@ -6245,7 +6246,7 @@ function readBundledSkillVersion(name) {
6245
6246
  }
6246
6247
  }
6247
6248
  function readFileIfExists(path) {
6248
- return existsSync3(path) ? readFileSync3(path, "utf-8") : null;
6249
+ return existsSync4(path) ? readFileSync3(path, "utf-8") : null;
6249
6250
  }
6250
6251
  function loadProjectConfigCompat(targetDir) {
6251
6252
  return loadProjectConfig(targetDir);
@@ -6879,9 +6880,9 @@ var init_discovery = __esm(() => {
6879
6880
  });
6880
6881
 
6881
6882
  // src/lib/config.ts
6882
- import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, copyFileSync } from "fs";
6883
- import { join as join4, dirname as dirname2 } from "path";
6884
- import { homedir as homedir3 } from "os";
6883
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, copyFileSync } from "fs";
6884
+ import { join as join5, dirname as dirname2 } from "path";
6885
+ import { homedir as homedir4 } from "os";
6885
6886
  function validKeys() {
6886
6887
  return ["mode", ...Object.keys(ENUM_KEYS), ...STRING_KEYS];
6887
6888
  }
@@ -6911,13 +6912,13 @@ function normalizeConfigValue(key, value) {
6911
6912
  return;
6912
6913
  }
6913
6914
  function getDataDir() {
6914
- const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir3();
6915
- const newDir = join4(home, ".hasna", "skills");
6916
- const oldConfigFile = join4(home, ".skillsrc");
6917
- if (existsSync4(oldConfigFile) && !existsSync4(join4(newDir, "config.json"))) {
6915
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir4();
6916
+ const newDir = join5(home, ".hasna", "skills");
6917
+ const oldConfigFile = join5(home, ".skillsrc");
6918
+ if (existsSync5(oldConfigFile) && !existsSync5(join5(newDir, "config.json"))) {
6918
6919
  mkdirSync2(newDir, { recursive: true });
6919
6920
  try {
6920
- copyFileSync(oldConfigFile, join4(newDir, "config.json"));
6921
+ copyFileSync(oldConfigFile, join5(newDir, "config.json"));
6921
6922
  } catch {}
6922
6923
  }
6923
6924
  mkdirSync2(newDir, { recursive: true });
@@ -6925,12 +6926,12 @@ function getDataDir() {
6925
6926
  }
6926
6927
  function getConfigPath(scope) {
6927
6928
  if (scope === "global") {
6928
- return join4(getDataDir(), "config.json");
6929
+ return join5(getDataDir(), "config.json");
6929
6930
  }
6930
- return join4(process.cwd(), "skills.config.json");
6931
+ return join5(process.cwd(), "skills.config.json");
6931
6932
  }
6932
6933
  function readConfigFile(path) {
6933
- if (!existsSync4(path))
6934
+ if (!existsSync5(path))
6934
6935
  return {};
6935
6936
  try {
6936
6937
  const raw = readFileSync4(path, "utf-8");
@@ -6964,7 +6965,7 @@ function saveConfig(key, value, scope = "project") {
6964
6965
  }
6965
6966
  const filePath = getConfigPath(scope);
6966
6967
  let existing = {};
6967
- if (existsSync4(filePath)) {
6968
+ if (existsSync5(filePath)) {
6968
6969
  try {
6969
6970
  existing = JSON.parse(readFileSync4(filePath, "utf-8"));
6970
6971
  if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
@@ -6975,7 +6976,7 @@ function saveConfig(key, value, scope = "project") {
6975
6976
  }
6976
6977
  } else {
6977
6978
  const dir = dirname2(filePath);
6978
- if (!existsSync4(dir)) {
6979
+ if (!existsSync5(dir)) {
6979
6980
  mkdirSync2(dir, { recursive: true });
6980
6981
  }
6981
6982
  }
@@ -17698,7 +17699,7 @@ function finalize(ctx, schema) {
17698
17699
  result.$schema = "http://json-schema.org/draft-07/schema#";
17699
17700
  } else if (ctx.target === "draft-04") {
17700
17701
  result.$schema = "http://json-schema.org/draft-04/schema#";
17701
- } else if (ctx.target === "openapi-3.0") {} else {}
17702
+ } else if (ctx.target === "openapi-3.0") {}
17702
17703
  if (ctx.external?.uri) {
17703
17704
  const id = ctx.external.registry.get(schema)?.id;
17704
17705
  if (!id)
@@ -17963,7 +17964,7 @@ var formatMap, stringProcessor = (schema, ctx, _json, _params) => {
17963
17964
  if (val === undefined) {
17964
17965
  if (ctx.unrepresentable === "throw") {
17965
17966
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
17966
- } else {}
17967
+ }
17967
17968
  } else if (typeof val === "bigint") {
17968
17969
  if (ctx.unrepresentable === "throw") {
17969
17970
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -20878,14 +20879,14 @@ __export(exports_auth_store, {
20878
20879
  getApiKey: () => getApiKey,
20879
20880
  clearAuthConfig: () => clearAuthConfig
20880
20881
  });
20881
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync } from "fs";
20882
- import { join as join5 } from "path";
20883
- import { homedir as homedir4 } from "os";
20882
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync } from "fs";
20883
+ import { join as join6 } from "path";
20884
+ import { homedir as homedir5 } from "os";
20884
20885
  function getAuthConfig() {
20885
20886
  if (cachedConfig !== undefined)
20886
20887
  return cachedConfig;
20887
20888
  try {
20888
- const raw = readFileSync5(existsSync5(AUTH_FILE) ? AUTH_FILE : LEGACY_AUTH_FILE, "utf-8");
20889
+ const raw = readFileSync5(existsSync6(AUTH_FILE) ? AUTH_FILE : LEGACY_AUTH_FILE, "utf-8");
20889
20890
  const config2 = JSON.parse(raw);
20890
20891
  if (!config2.apiKey || !config2.email) {
20891
20892
  cachedConfig = null;
@@ -20938,9 +20939,9 @@ function getApiUrl() {
20938
20939
  var AUTH_DIR, AUTH_FILE, LEGACY_AUTH_FILE, cachedConfig;
20939
20940
  var init_auth_store = __esm(() => {
20940
20941
  init_config();
20941
- AUTH_DIR = join5(homedir4(), ".hasna", "skills");
20942
- AUTH_FILE = join5(AUTH_DIR, "auth.json");
20943
- LEGACY_AUTH_FILE = join5(homedir4(), ".skills", "auth.json");
20942
+ AUTH_DIR = join6(homedir5(), ".hasna", "skills");
20943
+ AUTH_FILE = join6(AUTH_DIR, "auth.json");
20944
+ LEGACY_AUTH_FILE = join6(homedir5(), ".skills", "auth.json");
20944
20945
  });
20945
20946
 
20946
20947
  // src/lib/remote-registry.ts
@@ -21591,16 +21592,16 @@ __export(exports_skillinfo, {
21591
21592
  generateEnvExample: () => generateEnvExample,
21592
21593
  detectProjectSkills: () => detectProjectSkills
21593
21594
  });
21594
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
21595
- import { join as join6 } from "path";
21595
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
21596
+ import { join as join7 } from "path";
21596
21597
  function getSkillDocs(name) {
21597
21598
  const skillPath = getSkillPath(name);
21598
- if (!existsSync6(skillPath))
21599
+ if (!existsSync7(skillPath))
21599
21600
  return null;
21600
21601
  return {
21601
- skillMd: readIfExists(join6(skillPath, "SKILL.md")),
21602
- readme: readIfExists(join6(skillPath, "README.md")),
21603
- claudeMd: readIfExists(join6(skillPath, "CLAUDE.md"))
21602
+ skillMd: readIfExists(join7(skillPath, "SKILL.md")),
21603
+ readme: readIfExists(join7(skillPath, "README.md")),
21604
+ claudeMd: readIfExists(join7(skillPath, "CLAUDE.md"))
21604
21605
  };
21605
21606
  }
21606
21607
  function getSkillBestDoc(name) {
@@ -21611,11 +21612,11 @@ function getSkillBestDoc(name) {
21611
21612
  }
21612
21613
  function getSkillRequirements(name) {
21613
21614
  const skillPath = getSkillPath(name);
21614
- if (!existsSync6(skillPath))
21615
+ if (!existsSync7(skillPath))
21615
21616
  return null;
21616
21617
  const texts = [];
21617
21618
  for (const file2 of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
21618
- const content = readIfExists(join6(skillPath, file2));
21619
+ const content = readIfExists(join7(skillPath, file2));
21619
21620
  if (content)
21620
21621
  texts.push(content);
21621
21622
  }
@@ -21654,8 +21655,8 @@ function getSkillRequirements(name) {
21654
21655
  const skillName = normalizeSkillName(name);
21655
21656
  let cliCommand = `skills run ${skillName}`;
21656
21657
  let dependencies = {};
21657
- const pkgPath = join6(skillPath, "package.json");
21658
- if (existsSync6(pkgPath)) {
21658
+ const pkgPath = join7(skillPath, "package.json");
21659
+ if (existsSync7(pkgPath)) {
21659
21660
  try {
21660
21661
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
21661
21662
  dependencies = pkg.dependencies || {};
@@ -21674,11 +21675,11 @@ function isHostedPremiumSkill(skillName, meta3) {
21674
21675
  async function runSkill(name, args, options = {}) {
21675
21676
  const canonicalName = getSkill(name)?.name ?? name;
21676
21677
  const skillPath = getSkillPath(canonicalName);
21677
- if (!existsSync6(skillPath)) {
21678
+ if (!existsSync7(skillPath)) {
21678
21679
  return { exitCode: 1, error: `Skill '${name}' not found` };
21679
21680
  }
21680
- const pkgPath = join6(skillPath, "package.json");
21681
- if (!existsSync6(pkgPath)) {
21681
+ const pkgPath = join7(skillPath, "package.json");
21682
+ if (!existsSync7(pkgPath)) {
21682
21683
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
21683
21684
  }
21684
21685
  let entryPoint;
@@ -21697,12 +21698,12 @@ async function runSkill(name, args, options = {}) {
21697
21698
  } catch {
21698
21699
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
21699
21700
  }
21700
- const entryPath = join6(skillPath, entryPoint);
21701
- if (!existsSync6(entryPath)) {
21701
+ const entryPath = join7(skillPath, entryPoint);
21702
+ if (!existsSync7(entryPath)) {
21702
21703
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
21703
21704
  }
21704
- const nodeModules = join6(skillPath, "node_modules");
21705
- if (!existsSync6(nodeModules)) {
21705
+ const nodeModules = join7(skillPath, "node_modules");
21706
+ if (!existsSync7(nodeModules)) {
21706
21707
  const install = Bun.spawn(["bun", "install", "--no-save"], {
21707
21708
  cwd: skillPath,
21708
21709
  stdout: "pipe",
@@ -21729,8 +21730,8 @@ async function runSkill(name, args, options = {}) {
21729
21730
  return { exitCode };
21730
21731
  }
21731
21732
  function detectProjectSkills(cwd = process.cwd()) {
21732
- const pkgPath = join6(cwd, "package.json");
21733
- if (!existsSync6(pkgPath)) {
21733
+ const pkgPath = join7(cwd, "package.json");
21734
+ if (!existsSync7(pkgPath)) {
21734
21735
  const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
21735
21736
  const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
21736
21737
  return { detected: [], recommended: recommended2 };
@@ -21861,7 +21862,7 @@ function generateSkillMd(name) {
21861
21862
  if (!meta3)
21862
21863
  return null;
21863
21864
  const skillPath = getSkillPath(name);
21864
- if (!existsSync6(skillPath))
21865
+ if (!existsSync7(skillPath))
21865
21866
  return null;
21866
21867
  const frontmatter = [
21867
21868
  "---",
@@ -21870,11 +21871,11 @@ function generateSkillMd(name) {
21870
21871
  "---"
21871
21872
  ].join(`
21872
21873
  `);
21873
- const readme = readIfExists(join6(skillPath, "README.md"));
21874
- const claudeMd = readIfExists(join6(skillPath, "CLAUDE.md"));
21874
+ const readme = readIfExists(join7(skillPath, "README.md"));
21875
+ const claudeMd = readIfExists(join7(skillPath, "CLAUDE.md"));
21875
21876
  let cliCommand = null;
21876
- const pkgPath = join6(skillPath, "package.json");
21877
- if (existsSync6(pkgPath)) {
21877
+ const pkgPath = join7(skillPath, "package.json");
21878
+ if (existsSync7(pkgPath)) {
21878
21879
  try {
21879
21880
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
21880
21881
  if (pkg.bin) {
@@ -21949,7 +21950,7 @@ function extractEnvVars(text) {
21949
21950
  }
21950
21951
  function readIfExists(path) {
21951
21952
  try {
21952
- if (existsSync6(path)) {
21953
+ if (existsSync7(path)) {
21953
21954
  return readFileSync6(path, "utf-8");
21954
21955
  }
21955
21956
  } catch {}
@@ -21981,8 +21982,8 @@ var init_skillinfo = __esm(() => {
21981
21982
  });
21982
21983
 
21983
21984
  // src/lib/skill-validation.ts
21984
- import { existsSync as existsSync7, lstatSync, readFileSync as readFileSync7, readdirSync as readdirSync2, statSync } from "fs";
21985
- import { isAbsolute, join as join7, normalize } from "path";
21985
+ import { existsSync as existsSync8, lstatSync, readFileSync as readFileSync7, readdirSync as readdirSync2, statSync } from "fs";
21986
+ import { isAbsolute, join as join8, normalize } from "path";
21986
21987
  function add(target, code, message) {
21987
21988
  target.push({ code, message });
21988
21989
  }
@@ -22075,7 +22076,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
22075
22076
  binCommands: [],
22076
22077
  docFiles: []
22077
22078
  };
22078
- if (!existsSync7(skillPath)) {
22079
+ if (!existsSync8(skillPath)) {
22079
22080
  add(issues, "skill.dir_missing", `Skill directory not found: ${skillPath}`);
22080
22081
  return {
22081
22082
  name: bareName,
@@ -22090,7 +22091,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
22090
22091
  add(issues, "skill.name_invalid", `Skill name '${bareName}' must use lowercase letters, numbers, dots, underscores, or hyphens`);
22091
22092
  }
22092
22093
  for (const entry of readdirSync2(skillPath).sort()) {
22093
- const entryPath = join7(skillPath, entry);
22094
+ const entryPath = join8(skillPath, entry);
22094
22095
  if (RESERVED_SKILL_ENTRIES.has(entry)) {
22095
22096
  add(issues, "skill.reserved_file", `Reserved file '${entry}' is not allowed in skill packages`);
22096
22097
  }
@@ -22102,14 +22103,14 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
22102
22103
  }
22103
22104
  }
22104
22105
  for (const docFile of DOC_FILES) {
22105
- if (existsSync7(join7(skillPath, docFile)))
22106
+ if (existsSync8(join8(skillPath, docFile)))
22106
22107
  metadata.docFiles.push(docFile);
22107
22108
  }
22108
22109
  if (metadata.docFiles.length === 0) {
22109
22110
  add(issues, "skill.docs_missing", "Missing documentation file: expected SKILL.md, README.md, or CLAUDE.md");
22110
22111
  }
22111
- const skillMdPath = join7(skillPath, "SKILL.md");
22112
- if (existsSync7(skillMdPath)) {
22112
+ const skillMdPath = join8(skillPath, "SKILL.md");
22113
+ if (existsSync8(skillMdPath)) {
22113
22114
  const frontmatter = parseSkillFrontmatter(readFileSync7(skillMdPath, "utf-8"));
22114
22115
  if (!frontmatter) {
22115
22116
  add(warnings, "skill.frontmatter_missing", "SKILL.md has no YAML frontmatter");
@@ -22143,8 +22144,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
22143
22144
  } else {
22144
22145
  add(warnings, "skill.skill_md_missing", "Missing SKILL.md; registry docs may need generated agent-facing instructions");
22145
22146
  }
22146
- const pkgPath = join7(skillPath, "package.json");
22147
- if (!existsSync7(pkgPath)) {
22147
+ const pkgPath = join8(skillPath, "package.json");
22148
+ if (!existsSync8(pkgPath)) {
22148
22149
  add(issues, "package.missing", "Missing package.json");
22149
22150
  } else {
22150
22151
  try {
@@ -22201,8 +22202,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
22201
22202
  add(issues, "package.bin_target_unsafe", `package.json bin '${command}' target '${target}' must stay inside the skill directory`);
22202
22203
  continue;
22203
22204
  }
22204
- const targetPath = join7(skillPath, target);
22205
- if (!existsSync7(targetPath)) {
22205
+ const targetPath = join8(skillPath, target);
22206
+ if (!existsSync8(targetPath)) {
22206
22207
  add(warnings, "package.bin_target_missing", `package.json bin '${command}' target '${target}' is not present before build`);
22207
22208
  } else if (statSync(targetPath).isDirectory()) {
22208
22209
  add(issues, "package.bin_target_directory", `package.json bin '${command}' target '${target}' must point to a file, not a directory`);
@@ -22216,17 +22217,17 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
22216
22217
  }
22217
22218
  const hostedMetadata = isHostedMetadataSkill(bareName, metadata.skillMdFrontmatter, registryMeta, packageDeclaresHosted);
22218
22219
  metadata.runtime = hostedMetadata ? "hosted" : "local";
22219
- const srcDir = join7(skillPath, "src");
22220
+ const srcDir = join8(skillPath, "src");
22220
22221
  if (hostedMetadata) {
22221
- if (existsSync7(srcDir)) {
22222
+ if (existsSync8(srcDir)) {
22222
22223
  add(issues, "skill.hosted_source_forbidden", "Hosted metadata skills must not include local implementation source");
22223
22224
  }
22224
- } else if (!existsSync7(srcDir)) {
22225
+ } else if (!existsSync8(srcDir)) {
22225
22226
  add(issues, "skill.src_missing", "Missing src/ directory");
22226
- } else if (!existsSync7(join7(srcDir, "index.ts")) && !existsSync7(join7(srcDir, "index.js"))) {
22227
+ } else if (!existsSync8(join8(srcDir, "index.ts")) && !existsSync8(join8(srcDir, "index.js"))) {
22227
22228
  add(issues, "skill.src_index_missing", "Missing src/index.ts or src/index.js");
22228
22229
  } else {
22229
- const indexPath = existsSync7(join7(srcDir, "index.ts")) ? join7(srcDir, "index.ts") : join7(srcDir, "index.js");
22230
+ const indexPath = existsSync8(join8(srcDir, "index.ts")) ? join8(srcDir, "index.ts") : join8(srcDir, "index.js");
22230
22231
  const size = statSync(indexPath).size;
22231
22232
  if (size < 50)
22232
22233
  add(warnings, "skill.src_index_minimal", `Source entry point is very small (${size}B)`);
@@ -22295,8 +22296,8 @@ __export(exports_introspect, {
22295
22296
  registerIntrospect: () => registerIntrospect
22296
22297
  });
22297
22298
  import chalk4 from "chalk";
22298
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
22299
- import { join as join8 } from "path";
22299
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
22300
+ import { join as join9 } from "path";
22300
22301
  import { execSync } from "child_process";
22301
22302
  function registerIntrospect(parent) {
22302
22303
  parent.command("info").argument("<skill>", "Skill name").option("--json", "Output as JSON", false).option("--brief", "Single line: name \u2014 description [category] (tags: ...)", false).option("--remote", "Use remote registry from SKILLS_API_URL or config apiUrl", false).description("Show details about a specific skill").action((name, options) => {
@@ -22507,7 +22508,7 @@ function handleValidate(name, options) {
22507
22508
  function handleDiff(name, options) {
22508
22509
  const bare = name;
22509
22510
  const sourcePath = getSkillPath(bare);
22510
- if (!existsSync8(sourcePath)) {
22511
+ if (!existsSync9(sourcePath)) {
22511
22512
  if (options.json)
22512
22513
  console.log(JSON.stringify({ error: `Skill '${bare}' not found in registry` }));
22513
22514
  else
@@ -22524,9 +22525,9 @@ function handleDiff(name, options) {
22524
22525
  }
22525
22526
  const installMeta = getInstallMeta();
22526
22527
  const installedVersion = installMeta.skills[bare]?.version ?? "unknown";
22527
- const registryPkgPath = join8(sourcePath, "package.json");
22528
+ const registryPkgPath = join9(sourcePath, "package.json");
22528
22529
  let registryVersion = "unknown";
22529
- if (existsSync8(registryPkgPath)) {
22530
+ if (existsSync9(registryPkgPath)) {
22530
22531
  try {
22531
22532
  registryVersion = JSON.parse(readFileSync8(registryPkgPath, "utf-8")).version || "unknown";
22532
22533
  } catch {}
@@ -22559,8 +22560,8 @@ __export(exports_init, {
22559
22560
  registerSetup: () => registerSetup
22560
22561
  });
22561
22562
  import chalk5 from "chalk";
22562
- import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync4, appendFileSync } from "fs";
22563
- import { join as join9 } from "path";
22563
+ import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync4, appendFileSync } from "fs";
22564
+ import { join as join10 } from "path";
22564
22565
  function registerSetup(parent) {
22565
22566
  parent.command("init").option("--json", "Output as JSON", false).option("--for <agent>", "Detect project type and show MCP registration guidance for agent").option("--scope <scope>", "Deprecated; agent skill-folder installs are disabled", "global").description("Initialize project for pinned skills (.env.example, .gitignore)").action((options) => handleInit(options));
22566
22567
  parent.command("export").option("--json", "Output as JSON (default behavior)", false).description("Export pinned skills to JSON for sharing or backup").action((_options) => handleExport());
@@ -22644,7 +22645,7 @@ Use: skills mcp --register ${options.for}`));
22644
22645
  lines.push(`# Used by: ${skills.join(", ")}`);
22645
22646
  lines.push(`${envVar}=`);
22646
22647
  }
22647
- writeFileSync4(join9(cwd, ".env.example"), lines.join(`
22648
+ writeFileSync4(join10(cwd, ".env.example"), lines.join(`
22648
22649
  `) + `
22649
22650
  `);
22650
22651
  envVarCount = envMap.size;
@@ -22652,9 +22653,9 @@ Use: skills mcp --register ${options.for}`));
22652
22653
  console.log(chalk5.green(`\u2713 Generated .env.example (${envVarCount} variables from ${installed.length} skills)`));
22653
22654
  } else if (!options.json)
22654
22655
  console.log(chalk5.dim(" No environment variables detected across pinned skills"));
22655
- const gitignorePath = join9(cwd, ".gitignore");
22656
+ const gitignorePath = join10(cwd, ".gitignore");
22656
22657
  const gitignoreEntries = [".skills/runs/", ".skills/exports/", ".skills/tmp/"];
22657
- let gitignoreContent = existsSync9(gitignorePath) ? readFileSync9(gitignorePath, "utf-8") : "";
22658
+ let gitignoreContent = existsSync10(gitignorePath) ? readFileSync9(gitignorePath, "utf-8") : "";
22658
22659
  let gitignoreUpdated = false;
22659
22660
  const missingEntries = gitignoreEntries.filter((entry) => !gitignoreContent.includes(entry));
22660
22661
  if (missingEntries.length > 0) {
@@ -22700,7 +22701,7 @@ async function handleImport(file2, options) {
22700
22701
  if (file2 === "-")
22701
22702
  raw = await new Response(process.stdin).text();
22702
22703
  else {
22703
- if (!existsSync9(file2)) {
22704
+ if (!existsSync10(file2)) {
22704
22705
  const error48 = `File not found: ${file2}`;
22705
22706
  if (options.json)
22706
22707
  console.log(JSON.stringify({ imported: 0, error: error48 }));
@@ -22810,8 +22811,8 @@ __export(exports_diagnostic, {
22810
22811
  registerDiagnostic: () => registerDiagnostic
22811
22812
  });
22812
22813
  import chalk6 from "chalk";
22813
- import { existsSync as existsSync10, readFileSync as readFileSync10, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
22814
- import { join as join10 } from "path";
22814
+ import { existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
22815
+ import { join as join11 } from "path";
22815
22816
  import { execSync as execSync2 } from "child_process";
22816
22817
  function registerDiagnostic(parent) {
22817
22818
  parent.command("doctor").option("--json", "Output as JSON", false).description("Check env vars, system deps, and readiness for pinned skills").action((options) => handleDoctor(options));
@@ -22924,7 +22925,7 @@ Skills Test (${results.length} skill${results.length === 1 ? "" : "s"}):
22924
22925
  }
22925
22926
  function handleAuth(name, options) {
22926
22927
  const cwd = process.cwd();
22927
- const envFilePath = join10(cwd, ".env");
22928
+ const envFilePath = join11(cwd, ".env");
22928
22929
  if (options.set) {
22929
22930
  const eqIdx = options.set.indexOf("=");
22930
22931
  if (eqIdx === -1) {
@@ -22946,7 +22947,7 @@ function handleAuth(name, options) {
22946
22947
  process.exitCode = 1;
22947
22948
  return;
22948
22949
  }
22949
- let existing = existsSync10(envFilePath) ? readFileSync10(envFilePath, "utf-8") : "";
22950
+ let existing = existsSync11(envFilePath) ? readFileSync10(envFilePath, "utf-8") : "";
22950
22951
  const keyPattern = new RegExp(`^${key}=.*$`, "m");
22951
22952
  const updated = keyPattern.test(existing) ? existing.replace(keyPattern, `${key}=${value}`) : existing.endsWith(`
22952
22953
  `) || existing === "" ? existing + `${key}=${value}
@@ -23012,11 +23013,11 @@ function handleWhoami(options) {
23012
23013
  const agentConfigs = [];
23013
23014
  for (const agent of AGENT_TARGETS) {
23014
23015
  const agentSkillsPath = getAgentSkillsDir(agent, "global");
23015
- const exists = existsSync10(agentSkillsPath);
23016
+ const exists = existsSync11(agentSkillsPath);
23016
23017
  let skillCount = 0;
23017
23018
  if (exists)
23018
23019
  try {
23019
- skillCount = readdirSync3(agentSkillsPath).filter((f) => !f.startsWith(".") && statSync2(join10(agentSkillsPath, f)).isDirectory()).length;
23020
+ skillCount = readdirSync3(agentSkillsPath).filter((f) => !f.startsWith(".") && statSync2(join11(agentSkillsPath, f)).isDirectory()).length;
23020
23021
  } catch {}
23021
23022
  agentConfigs.push({ agent, label: AGENT_LABELS[agent], path: agentSkillsPath, exists, skillCount });
23022
23023
  }
@@ -23055,9 +23056,9 @@ function handleOutdated(options) {
23055
23056
  for (const name of installed) {
23056
23057
  const installedVersion = meta3.skills[name]?.version ?? "unknown";
23057
23058
  const registryPath = getSkillPath(name);
23058
- const registryPkgPath = join10(registryPath, "package.json");
23059
+ const registryPkgPath = join11(registryPath, "package.json");
23059
23060
  let registryVersion = "unknown";
23060
- if (existsSync10(registryPkgPath))
23061
+ if (existsSync11(registryPkgPath))
23061
23062
  try {
23062
23063
  registryVersion = JSON.parse(readFileSync10(registryPkgPath, "utf-8")).version || "unknown";
23063
23064
  } catch {}
@@ -23148,27 +23149,27 @@ var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
23148
23149
 
23149
23150
  // src/lib/run-state.ts
23150
23151
  import { createHash, randomBytes } from "crypto";
23151
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync11, readdirSync as readdirSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
23152
- import { extname, join as join11, relative } from "path";
23152
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync11, readdirSync as readdirSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
23153
+ import { extname, join as join12, relative } from "path";
23153
23154
  function createSkillRun(params, targetDir = process.cwd()) {
23154
- const now = new Date;
23155
- const id = createRunId(now);
23156
- const day = now.toISOString().slice(0, 10);
23155
+ const now2 = new Date;
23156
+ const id = createRunId(now2);
23157
+ const day = now2.toISOString().slice(0, 10);
23157
23158
  const skillName = normalizeSkillName(params.skill);
23158
23159
  const root = getProjectStateDir(targetDir);
23159
- const runDir = join11(root, "runs", day, id);
23160
- const logsDir = join11(runDir, "logs");
23161
- const exportDir = join11(root, "exports", skillName, id);
23160
+ const runDir = join12(root, "runs", day, id);
23161
+ const logsDir = join12(runDir, "logs");
23162
+ const exportDir = join12(root, "exports", skillName, id);
23162
23163
  mkdirSync4(logsDir, { recursive: true });
23163
23164
  mkdirSync4(exportDir, { recursive: true });
23164
- mkdirSync4(join11(root, "tmp"), { recursive: true });
23165
+ mkdirSync4(join12(root, "tmp"), { recursive: true });
23165
23166
  const record2 = {
23166
23167
  id,
23167
23168
  skill: skillName,
23168
23169
  status: params.status ?? "running",
23169
23170
  ...params.prompt ? { prompt: params.prompt } : {},
23170
23171
  args: params.args ?? [],
23171
- startedAt: now.toISOString(),
23172
+ startedAt: now2.toISOString(),
23172
23173
  remote: params.remote ?? false,
23173
23174
  ...params.remoteRunId ? { remoteRunId: params.remoteRunId } : {},
23174
23175
  ...params.costCents !== undefined ? { costCents: params.costCents } : {},
@@ -23208,27 +23209,27 @@ function updateSkillRun(context, patch) {
23208
23209
  return context.record;
23209
23210
  }
23210
23211
  function writeRunLogs(context, stdout = "", stderr = "") {
23211
- writeFileSync6(join11(context.logsDir, "stdout.log"), stdout);
23212
- writeFileSync6(join11(context.logsDir, "stderr.log"), stderr);
23212
+ writeFileSync6(join12(context.logsDir, "stdout.log"), stdout);
23213
+ writeFileSync6(join12(context.logsDir, "stderr.log"), stderr);
23213
23214
  }
23214
23215
  function appendRunEvent(context, event, data = {}) {
23215
23216
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
23216
23217
  `;
23217
- const path = join11(context.runDir, "events.ndjson");
23218
- const previous = existsSync11(path) ? readFileSync11(path, "utf-8") : "";
23218
+ const path = join12(context.runDir, "events.ndjson");
23219
+ const previous = existsSync12(path) ? readFileSync11(path, "utf-8") : "";
23219
23220
  writeFileSync6(path, previous + line);
23220
23221
  }
23221
23222
  function listSkillRuns(targetDir = process.cwd(), limit = 50) {
23222
- const runsRoot = join11(getProjectStateDir(targetDir), "runs");
23223
- if (!existsSync11(runsRoot))
23223
+ const runsRoot = join12(getProjectStateDir(targetDir), "runs");
23224
+ if (!existsSync12(runsRoot))
23224
23225
  return [];
23225
23226
  const records = [];
23226
23227
  for (const day of readdirSync4(runsRoot).sort().reverse()) {
23227
- const dayDir = join11(runsRoot, day);
23228
+ const dayDir = join12(runsRoot, day);
23228
23229
  if (!statSync3(dayDir).isDirectory())
23229
23230
  continue;
23230
23231
  for (const runId of readdirSync4(dayDir).sort().reverse()) {
23231
- const record2 = readRunRecord(join11(dayDir, runId));
23232
+ const record2 = readRunRecord(join12(dayDir, runId));
23232
23233
  if (record2)
23233
23234
  records.push(record2);
23234
23235
  if (records.length >= limit)
@@ -23238,29 +23239,29 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
23238
23239
  return records;
23239
23240
  }
23240
23241
  function findSkillRun(runId, targetDir = process.cwd()) {
23241
- const runsRoot = join11(getProjectStateDir(targetDir), "runs");
23242
- if (!existsSync11(runsRoot))
23242
+ const runsRoot = join12(getProjectStateDir(targetDir), "runs");
23243
+ if (!existsSync12(runsRoot))
23243
23244
  return null;
23244
23245
  for (const day of readdirSync4(runsRoot)) {
23245
- const record2 = readRunRecord(join11(runsRoot, day, runId));
23246
+ const record2 = readRunRecord(join12(runsRoot, day, runId));
23246
23247
  if (record2)
23247
23248
  return record2;
23248
23249
  }
23249
23250
  return null;
23250
23251
  }
23251
23252
  function getRunExportDir(runId, skill, targetDir = process.cwd()) {
23252
- return join11(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
23253
+ return join12(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
23253
23254
  }
23254
23255
  function writeRunRecord(context) {
23255
- writeFileSync6(join11(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
23256
+ writeFileSync6(join12(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
23256
23257
  `);
23257
23258
  }
23258
23259
  function writeArtifactsManifest(context, artifacts) {
23259
- writeFileSync6(join11(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
23260
+ writeFileSync6(join12(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
23260
23261
  `);
23261
23262
  }
23262
23263
  function collectRunArtifacts(context) {
23263
- if (!existsSync11(context.exportDir))
23264
+ if (!existsSync12(context.exportDir))
23264
23265
  return [];
23265
23266
  const artifacts = [];
23266
23267
  for (const path of walkFiles(context.exportDir)) {
@@ -23276,8 +23277,8 @@ function collectRunArtifacts(context) {
23276
23277
  return artifacts.sort((a, b) => a.path.localeCompare(b.path));
23277
23278
  }
23278
23279
  function readRunRecord(runDir) {
23279
- const path = join11(runDir, "run.json");
23280
- if (!existsSync11(path))
23280
+ const path = join12(runDir, "run.json");
23281
+ if (!existsSync12(path))
23281
23282
  return null;
23282
23283
  try {
23283
23284
  return JSON.parse(readFileSync11(path, "utf-8"));
@@ -23288,7 +23289,7 @@ function readRunRecord(runDir) {
23288
23289
  function walkFiles(dir) {
23289
23290
  const files = [];
23290
23291
  for (const entry of readdirSync4(dir)) {
23291
- const full = join11(dir, entry);
23292
+ const full = join12(dir, entry);
23292
23293
  if (statSync3(full).isDirectory())
23293
23294
  files.push(...walkFiles(full));
23294
23295
  else
@@ -23296,8 +23297,8 @@ function walkFiles(dir) {
23296
23297
  }
23297
23298
  return files;
23298
23299
  }
23299
- function createRunId(now) {
23300
- return `run_${now.getTime().toString(36)}_${randomBytes(4).toString("hex")}`;
23300
+ function createRunId(now2) {
23301
+ return `run_${now2.getTime().toString(36)}_${randomBytes(4).toString("hex")}`;
23301
23302
  }
23302
23303
  function toProjectRelative(targetDir, path) {
23303
23304
  const rel = relative(targetDir, path).split(/[\\/]/).join("/");
@@ -39375,8 +39376,8 @@ var init_remote_client = __esm(() => {
39375
39376
  });
39376
39377
 
39377
39378
  // src/mcp/operation-tools.ts
39378
- import { existsSync as existsSync12, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
39379
- import { join as join12 } from "path";
39379
+ import { existsSync as existsSync13, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
39380
+ import { join as join13 } from "path";
39380
39381
  function registerOperationTools(server) {
39381
39382
  server.registerTool("pin_skill", {
39382
39383
  title: "Pin Skill",
@@ -39770,12 +39771,12 @@ function registerOperationTools(server) {
39770
39771
  const agents = [];
39771
39772
  for (const agent of AGENT_TARGETS) {
39772
39773
  const agentSkillsPath = getAgentSkillsDir(agent, "global");
39773
- const exists = existsSync12(agentSkillsPath);
39774
+ const exists = existsSync13(agentSkillsPath);
39774
39775
  let skillCount = 0;
39775
39776
  if (exists) {
39776
39777
  try {
39777
39778
  skillCount = readdirSync5(agentSkillsPath).filter((f) => {
39778
- const full = join12(agentSkillsPath, f);
39779
+ const full = join13(agentSkillsPath, f);
39779
39780
  return !f.startsWith(".") && statSync4(full).isDirectory();
39780
39781
  }).length;
39781
39782
  } catch {}
@@ -39805,17 +39806,17 @@ var init_operation_tools = __esm(() => {
39805
39806
  });
39806
39807
 
39807
39808
  // src/lib/feedback.ts
39808
- import { existsSync as existsSync13, mkdirSync as mkdirSync5 } from "fs";
39809
- import { homedir as homedir5 } from "os";
39810
- import { dirname as dirname3, join as join13 } from "path";
39809
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "fs";
39810
+ import { homedir as homedir6 } from "os";
39811
+ import { dirname as dirname3, join as join14 } from "path";
39811
39812
  import { Database } from "bun:sqlite";
39812
39813
  function getFeedbackDbPath() {
39813
- return join13(homedir5(), ".hasna", "skills", "skills.db");
39814
+ return join14(homedir6(), ".hasna", "skills", "skills.db");
39814
39815
  }
39815
39816
  function getFeedbackDb() {
39816
39817
  const dbPath = getFeedbackDbPath();
39817
39818
  const dir = dirname3(dbPath);
39818
- if (!existsSync13(dir))
39819
+ if (!existsSync14(dir))
39819
39820
  mkdirSync5(dir, { recursive: true });
39820
39821
  const db = new Database(dbPath);
39821
39822
  db.exec("PRAGMA journal_mode = WAL");
@@ -39976,14 +39977,14 @@ var init_resource_meta_tools = __esm(() => {
39976
39977
  });
39977
39978
 
39978
39979
  // src/lib/scheduler.ts
39979
- import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6 } from "fs";
39980
- import { join as join14 } from "path";
39980
+ import { existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6 } from "fs";
39981
+ import { join as join15 } from "path";
39981
39982
  function getSchedulesPath(targetDir = process.cwd()) {
39982
- return join14(targetDir, ".skills", "schedules.json");
39983
+ return join15(targetDir, ".skills", "schedules.json");
39983
39984
  }
39984
39985
  function loadSchedules(targetDir = process.cwd()) {
39985
39986
  const path = getSchedulesPath(targetDir);
39986
- if (existsSync14(path)) {
39987
+ if (existsSync15(path)) {
39987
39988
  try {
39988
39989
  return JSON.parse(readFileSync12(path, "utf-8"));
39989
39990
  } catch {}
@@ -39992,8 +39993,8 @@ function loadSchedules(targetDir = process.cwd()) {
39992
39993
  }
39993
39994
  function saveSchedules(data, targetDir = process.cwd()) {
39994
39995
  const path = getSchedulesPath(targetDir);
39995
- const dir = join14(targetDir, ".skills");
39996
- if (!existsSync14(dir))
39996
+ const dir = join15(targetDir, ".skills");
39997
+ if (!existsSync15(dir))
39997
39998
  mkdirSync6(dir, { recursive: true });
39998
39999
  writeFileSync7(path, JSON.stringify(data, null, 2));
39999
40000
  }
@@ -40124,8 +40125,8 @@ function addSchedule(skill, cron, options = {}) {
40124
40125
  return { schedule: null, error: error48 };
40125
40126
  const data = loadSchedules(options.targetDir);
40126
40127
  const id = `${skill}-${Date.now()}`;
40127
- const now = new Date;
40128
- const nextRun = getNextRun(cron, now);
40128
+ const now2 = new Date;
40129
+ const nextRun = getNextRun(cron, now2);
40129
40130
  const schedule = {
40130
40131
  id,
40131
40132
  name: options.name || `${skill} (${cron})`,
@@ -40133,7 +40134,7 @@ function addSchedule(skill, cron, options = {}) {
40133
40134
  cron,
40134
40135
  args: options.args,
40135
40136
  enabled: true,
40136
- createdAt: now.toISOString(),
40137
+ createdAt: now2.toISOString(),
40137
40138
  nextRun: nextRun?.toISOString()
40138
40139
  };
40139
40140
  data.schedules.push(schedule);
@@ -40165,18 +40166,18 @@ function setScheduleEnabled(idOrName, enabled, targetDir) {
40165
40166
  return true;
40166
40167
  }
40167
40168
  function getDueSchedules(targetDir) {
40168
- const now = new Date;
40169
- return listSchedules(targetDir).filter((s) => s.enabled && s.nextRun && new Date(s.nextRun) <= now);
40169
+ const now2 = new Date;
40170
+ return listSchedules(targetDir).filter((s) => s.enabled && s.nextRun && new Date(s.nextRun) <= now2);
40170
40171
  }
40171
40172
  function recordScheduleRun(id, status, targetDir) {
40172
40173
  const data = loadSchedules(targetDir);
40173
40174
  const schedule = data.schedules.find((s) => s.id === id);
40174
40175
  if (!schedule)
40175
40176
  return;
40176
- const now = new Date;
40177
- schedule.lastRun = now.toISOString();
40177
+ const now2 = new Date;
40178
+ schedule.lastRun = now2.toISOString();
40178
40179
  schedule.lastRunStatus = status;
40179
- schedule.nextRun = getNextRun(schedule.cron, now)?.toISOString();
40180
+ schedule.nextRun = getNextRun(schedule.cron, now2)?.toISOString();
40180
40181
  saveSchedules(data, targetDir);
40181
40182
  }
40182
40183
  var init_scheduler = () => {};
@@ -40538,9 +40539,9 @@ var init_mcp2 = __esm(() => {
40538
40539
 
40539
40540
  // src/cli/commands/runtime-mcp.ts
40540
40541
  import chalk7 from "chalk";
40541
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
40542
- import { homedir as homedir6 } from "os";
40543
- import { dirname as dirname4, join as join15 } from "path";
40542
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
40543
+ import { homedir as homedir7 } from "os";
40544
+ import { dirname as dirname4, join as join16 } from "path";
40544
40545
  async function handleMcp(options) {
40545
40546
  if (options.register) {
40546
40547
  let agents;
@@ -40582,24 +40583,24 @@ async function registerMcpForAgent(agent, command) {
40582
40583
  case "codex":
40583
40584
  return registerCodexMcp(command);
40584
40585
  case "gemini":
40585
- return registerJsonMcpServer(agent, join15(homedir6(), ".gemini", "settings.json"), "mcpServers", {
40586
+ return registerJsonMcpServer(agent, join16(homedir7(), ".gemini", "settings.json"), "mcpServers", {
40586
40587
  command,
40587
40588
  args: []
40588
40589
  });
40589
40590
  case "pi":
40590
- return registerJsonMcpServer(agent, join15(homedir6(), ".pi", "agent", "mcp.json"), "mcpServers", {
40591
+ return registerJsonMcpServer(agent, join16(homedir7(), ".pi", "agent", "mcp.json"), "mcpServers", {
40591
40592
  command,
40592
40593
  args: []
40593
40594
  });
40594
40595
  case "opencode":
40595
40596
  return registerOpenCodeMcp(command);
40596
40597
  case "cursor":
40597
- return registerJsonMcpServer(agent, join15(homedir6(), ".cursor", "mcp.json"), "mcpServers", {
40598
+ return registerJsonMcpServer(agent, join16(homedir7(), ".cursor", "mcp.json"), "mcpServers", {
40598
40599
  command,
40599
40600
  args: []
40600
40601
  });
40601
40602
  case "windsurf":
40602
- return registerJsonMcpServer(agent, join15(homedir6(), ".windsurf", "mcp.json"), "mcpServers", {
40603
+ return registerJsonMcpServer(agent, join16(homedir7(), ".windsurf", "mcp.json"), "mcpServers", {
40603
40604
  command,
40604
40605
  args: []
40605
40606
  });
@@ -40622,7 +40623,7 @@ async function registerClaudeMcp(command) {
40622
40623
  if (exitCode === 0) {
40623
40624
  return { agent: "claude", success: true, command: cliCommand };
40624
40625
  }
40625
- const fallback = registerJsonMcpServer("claude", join15(homedir6(), ".claude", ".mcp.json"), "mcpServers", {
40626
+ const fallback = registerJsonMcpServer("claude", join16(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
40626
40627
  command,
40627
40628
  args: []
40628
40629
  });
@@ -40632,7 +40633,7 @@ async function registerClaudeMcp(command) {
40632
40633
  error: fallback.success ? undefined : `claude exited with ${exitCode}: ${(stderr || stdout).trim()}`
40633
40634
  };
40634
40635
  } catch (err) {
40635
- const fallback = registerJsonMcpServer("claude", join15(homedir6(), ".claude", ".mcp.json"), "mcpServers", {
40636
+ const fallback = registerJsonMcpServer("claude", join16(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
40636
40637
  command,
40637
40638
  args: []
40638
40639
  });
@@ -40644,11 +40645,11 @@ async function registerClaudeMcp(command) {
40644
40645
  }
40645
40646
  }
40646
40647
  function registerCodexMcp(command) {
40647
- const path = join15(homedir6(), ".codex", "config.toml");
40648
+ const path = join16(homedir7(), ".codex", "config.toml");
40648
40649
  const config2 = `[mcp_servers.${MCP_SERVER_NAME}]
40649
40650
  command = ${JSON.stringify(command)}`;
40650
40651
  try {
40651
- const current = existsSync15(path) ? readFileSync13(path, "utf-8") : "";
40652
+ const current = existsSync16(path) ? readFileSync13(path, "utf-8") : "";
40652
40653
  writeTextFile(path, upsertTomlSection(current, `[mcp_servers.${MCP_SERVER_NAME}]`, `command = ${JSON.stringify(command)}`));
40653
40654
  return { agent: "codex", success: true, path, config: config2 };
40654
40655
  } catch (err) {
@@ -40656,7 +40657,7 @@ command = ${JSON.stringify(command)}`;
40656
40657
  }
40657
40658
  }
40658
40659
  function registerOpenCodeMcp(command) {
40659
- const path = join15(homedir6(), ".config", "opencode", "opencode.json");
40660
+ const path = join16(homedir7(), ".config", "opencode", "opencode.json");
40660
40661
  const config2 = JSON.stringify({
40661
40662
  $schema: "https://opencode.ai/config.json",
40662
40663
  mcp: {
@@ -40698,7 +40699,7 @@ function registerJsonMcpServer(agent, path, containerKey, server2) {
40698
40699
  }
40699
40700
  }
40700
40701
  function readJsonObject(path) {
40701
- if (!existsSync15(path))
40702
+ if (!existsSync16(path))
40702
40703
  return {};
40703
40704
  const raw = readFileSync13(path, "utf-8").trim();
40704
40705
  if (!raw)
@@ -40744,8 +40745,8 @@ function findCommandOnPath(command) {
40744
40745
  for (const dir of pathValue.split(":")) {
40745
40746
  if (!dir)
40746
40747
  continue;
40747
- const candidate = join15(dir, command);
40748
- if (existsSync15(candidate))
40748
+ const candidate = join16(dir, command);
40749
+ if (existsSync16(candidate))
40749
40750
  return candidate;
40750
40751
  }
40751
40752
  return command;
@@ -40765,7 +40766,7 @@ __export(exports_runtime, {
40765
40766
  });
40766
40767
  import chalk8 from "chalk";
40767
40768
  import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
40768
- import { dirname as dirname5, join as join16 } from "path";
40769
+ import { dirname as dirname5, join as join17 } from "path";
40769
40770
  import { createInterface } from "readline";
40770
40771
  function registerRuntime(parent) {
40771
40772
  parent.command("quote").argument("<skill>", "Skill name").argument("[args...]", "Arguments that affect pricing, such as --count 8").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output quote as JSON", false).description("Quote a skill run before spending account balance").action((name, args2, options) => handleQuote(name, args2, options));
@@ -41317,7 +41318,7 @@ async function handleExportsDownload(runId, options) {
41317
41318
  if (!response.ok)
41318
41319
  throw new Error(`download failed for artifact ${artifactId}: ${response.status}`);
41319
41320
  const relativePath = safeArtifactRelativePath(typeof artifact.relativePath === "string" ? artifact.relativePath : artifact.fileName, String(artifact.fileName || artifactId));
41320
- const outputPath = join16(exportDir, relativePath);
41321
+ const outputPath = join17(exportDir, relativePath);
41321
41322
  mkdirSync8(dirname5(outputPath), { recursive: true });
41322
41323
  const bytes = new Uint8Array(await response.arrayBuffer());
41323
41324
  writeFileSync9(outputPath, bytes);
@@ -41711,9 +41712,9 @@ __export(exports_create_sync_config, {
41711
41712
  registerCreateSync: () => registerCreateSync
41712
41713
  });
41713
41714
  import chalk9 from "chalk";
41714
- import { existsSync as existsSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9 } from "fs";
41715
- import { join as join17 } from "path";
41716
- import { homedir as homedir7 } from "os";
41715
+ import { existsSync as existsSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9 } from "fs";
41716
+ import { join as join18 } from "path";
41717
+ import { homedir as homedir8 } from "os";
41717
41718
  function registerCreateSync(parent) {
41718
41719
  const configCmd = parent.command("config").description("Manage skills configuration");
41719
41720
  configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
@@ -41761,13 +41762,13 @@ function registerCreateSync(parent) {
41761
41762
  const pp = getConfigPath("project");
41762
41763
  if (options.json) {
41763
41764
  console.log(JSON.stringify({
41764
- global: { path: gp, exists: existsSync16(gp) },
41765
- project: { path: pp, exists: existsSync16(pp) }
41765
+ global: { path: gp, exists: existsSync17(gp) },
41766
+ project: { path: pp, exists: existsSync17(pp) }
41766
41767
  }, null, 2));
41767
41768
  return;
41768
41769
  }
41769
- console.log(`${chalk9.cyan("global")}: ${gp}${existsSync16(gp) ? chalk9.green(" (exists)") : chalk9.dim(" (not found)")}`);
41770
- console.log(`${chalk9.cyan("project")}: ${pp}${existsSync16(pp) ? chalk9.green(" (exists)") : chalk9.dim(" (not found)")}`);
41770
+ console.log(`${chalk9.cyan("global")}: ${gp}${existsSync17(gp) ? chalk9.green(" (exists)") : chalk9.dim(" (not found)")}`);
41771
+ console.log(`${chalk9.cyan("project")}: ${pp}${existsSync17(pp) ? chalk9.green(" (exists)") : chalk9.dim(" (not found)")}`);
41771
41772
  });
41772
41773
  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", "Deprecated; custom skills are always global", false).option("--json", "Output result as JSON", false).description("Scaffold a new custom skill directory").action((name, options) => handleCreate(name, options));
41773
41774
  parent.command("sync").option("--to <agent>", "Deprecated; use skills mcp --register <agent|all>").option("--from <agent>", "Deprecated; agent skill-folder sync is disabled").option("--register", "Deprecated; agent skill-folder imports are disabled", false).option("--scope <scope>", "Deprecated; ignored", "global").option("--json", "Output as JSON", false).description("Disabled legacy agent skill-folder sync").action((options) => handleSync(options));
@@ -41775,9 +41776,9 @@ function registerCreateSync(parent) {
41775
41776
  function handleCreate(name, options) {
41776
41777
  const bare = name.trim();
41777
41778
  const dirName = bare;
41778
- const baseDir = join17(homedir7(), ".hasna", "skills", "custom");
41779
- const skillDir = join17(baseDir, dirName);
41780
- if (existsSync16(skillDir)) {
41779
+ const baseDir = join18(homedir8(), ".hasna", "skills", "custom");
41780
+ const skillDir = join18(baseDir, dirName);
41781
+ if (existsSync17(skillDir)) {
41781
41782
  console.log(options.json ? JSON.stringify({ error: `Skill '${bare}' already exists at ${skillDir}` }) : chalk9.red(`Skill '${bare}' already exists at ${skillDir}`));
41782
41783
  process.exitCode = 1;
41783
41784
  return;
@@ -41785,8 +41786,8 @@ function handleCreate(name, options) {
41785
41786
  const description = options.description || `${bare} skill`;
41786
41787
  const tags = options.tags ? options.tags.split(",").map((t) => t.trim()).filter(Boolean) : [bare];
41787
41788
  const displayName = bare.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
41788
- mkdirSync9(join17(skillDir, "src"), { recursive: true });
41789
- writeFileSync10(join17(skillDir, "SKILL.md"), [
41789
+ mkdirSync9(join18(skillDir, "src"), { recursive: true });
41790
+ writeFileSync10(join18(skillDir, "SKILL.md"), [
41790
41791
  "---",
41791
41792
  `name: ${bare}`,
41792
41793
  `description: ${description}`,
@@ -41806,11 +41807,11 @@ function handleCreate(name, options) {
41806
41807
  ""
41807
41808
  ].join(`
41808
41809
  `));
41809
- writeFileSync10(join17(skillDir, "src", "index.ts"), [`#!/usr/bin/env bun`, `/**`, ` * ${displayName} \u2014 ${description}`, ` */`, "", `console.log("${displayName}");`, ""].join(`
41810
+ writeFileSync10(join18(skillDir, "src", "index.ts"), [`#!/usr/bin/env bun`, `/**`, ` * ${displayName} \u2014 ${description}`, ` */`, "", `console.log("${displayName}");`, ""].join(`
41810
41811
  `));
41811
- writeFileSync10(join17(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) + `
41812
+ writeFileSync10(join18(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) + `
41812
41813
  `);
41813
- writeFileSync10(join17(skillDir, "tsconfig.json"), JSON.stringify({ compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, outDir: "dist" }, include: ["src/**/*.ts"] }, null, 2) + `
41814
+ writeFileSync10(join18(skillDir, "tsconfig.json"), JSON.stringify({ compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, outDir: "dist" }, include: ["src/**/*.ts"] }, null, 2) + `
41814
41815
  `);
41815
41816
  clearRegistryCache();
41816
41817
  if (options.json)
@@ -41819,8 +41820,8 @@ function handleCreate(name, options) {
41819
41820
  console.log(chalk9.green(`\u2713 Created custom skill '${bare}' at ${skillDir}`));
41820
41821
  console.log(chalk9.dim(` Category: ${options.category}`));
41821
41822
  console.log(chalk9.dim(` Tags: ${tags.join(", ")}`));
41822
- console.log(` ${chalk9.cyan("Edit:")} ${join17(skillDir, "src", "index.ts")}`);
41823
- console.log(` ${chalk9.cyan("Run:")} bun ${join17(skillDir, "src", "index.ts")}`);
41823
+ console.log(` ${chalk9.cyan("Edit:")} ${join18(skillDir, "src", "index.ts")}`);
41824
+ console.log(` ${chalk9.cyan("Run:")} bun ${join18(skillDir, "src", "index.ts")}`);
41824
41825
  }
41825
41826
  }
41826
41827
  function handleSync(options) {
@@ -42147,11 +42148,23 @@ function registerRegistry(parent) {
42147
42148
  const registry2 = parent.command("registry").description("Generate registry artifacts for hosted skills services");
42148
42149
  registry2.command("sync").description("Generate a deterministic registry sync artifact").option("--profile <profile>", "Registry profile: basic or all", "all").option("--output <path>", "Write artifact to a JSON file").option("--no-docs", "Exclude skill documentation content").option("--no-requirements", "Exclude extracted skill requirements").option("--no-validation", "Exclude validation results").option("--json", "Print artifact JSON to stdout", false).action((options) => handleRegistrySync(options));
42149
42150
  }
42150
- function handleRegistrySync(options) {
42151
+ async function writeJson2(value, space) {
42152
+ const text = `${JSON.stringify(value, null, space)}
42153
+ `;
42154
+ await new Promise((resolve, reject) => {
42155
+ process.stdout.write(text, (error48) => {
42156
+ if (error48)
42157
+ reject(error48);
42158
+ else
42159
+ resolve();
42160
+ });
42161
+ });
42162
+ }
42163
+ async function handleRegistrySync(options) {
42151
42164
  if (options.profile !== "basic" && options.profile !== "all") {
42152
42165
  const error48 = `Unknown registry profile: ${options.profile}. Available: basic, all`;
42153
42166
  if (options.json)
42154
- console.log(JSON.stringify({ error: error48 }));
42167
+ await writeJson2({ error: error48 });
42155
42168
  else
42156
42169
  console.error(chalk11.red(error48));
42157
42170
  process.exitCode = 1;
@@ -42168,7 +42181,7 @@ function handleRegistrySync(options) {
42168
42181
  writeRegistrySyncArtifact(options.output, artifact);
42169
42182
  }
42170
42183
  if (options.json || !options.output) {
42171
- console.log(JSON.stringify(artifact, null, 2));
42184
+ await writeJson2(artifact, 2);
42172
42185
  return;
42173
42186
  }
42174
42187
  const invalid = artifact.summary.invalidSkillCount ?? "not checked";
@@ -42751,8 +42764,688 @@ var init_feedback2 = __esm(() => {
42751
42764
 
42752
42765
  // src/cli/index.tsx
42753
42766
  init_esm();
42754
- init_package();
42755
42767
  import { render } from "ink";
42768
+
42769
+ // node_modules/@hasna/events/dist/commander.js
42770
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
42771
+ import { existsSync } from "fs";
42772
+ import { homedir } from "os";
42773
+ import { join } from "path";
42774
+ import { createHmac, timingSafeEqual } from "crypto";
42775
+ import { randomUUID } from "crypto";
42776
+ import { spawn } from "child_process";
42777
+ import { randomUUID as randomUUID2 } from "crypto";
42778
+ function getPathValue(input, path) {
42779
+ return path.split(".").reduce((value, part) => {
42780
+ if (value && typeof value === "object" && part in value) {
42781
+ return value[part];
42782
+ }
42783
+ return;
42784
+ }, input);
42785
+ }
42786
+ function wildcardToRegExp(pattern) {
42787
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
42788
+ return new RegExp(`^${escaped}$`);
42789
+ }
42790
+ function matchString(value, matcher) {
42791
+ if (matcher === undefined)
42792
+ return true;
42793
+ if (value === undefined)
42794
+ return false;
42795
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
42796
+ return matchers.some((item) => wildcardToRegExp(item).test(value));
42797
+ }
42798
+ function matchRecord(input, matcher) {
42799
+ if (!matcher)
42800
+ return true;
42801
+ return Object.entries(matcher).every(([path, expected]) => {
42802
+ const actual = getPathValue(input, path);
42803
+ if (typeof expected === "string" || Array.isArray(expected)) {
42804
+ return matchString(actual === undefined ? undefined : String(actual), expected);
42805
+ }
42806
+ return actual === expected;
42807
+ });
42808
+ }
42809
+ function eventMatchesFilter(event, filter) {
42810
+ return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
42811
+ }
42812
+ function channelMatchesEvent(channel, event) {
42813
+ if (!channel.enabled)
42814
+ return false;
42815
+ if (!channel.filters || channel.filters.length === 0)
42816
+ return true;
42817
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
42818
+ }
42819
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
42820
+ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
42821
+ function getEventsDataDir(override) {
42822
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
42823
+ }
42824
+
42825
+ class JsonEventsStore {
42826
+ dataDir;
42827
+ channelsPath;
42828
+ eventsPath;
42829
+ deliveriesPath;
42830
+ constructor(dataDir = getEventsDataDir()) {
42831
+ this.dataDir = dataDir;
42832
+ this.channelsPath = join(dataDir, "channels.json");
42833
+ this.eventsPath = join(dataDir, "events.json");
42834
+ this.deliveriesPath = join(dataDir, "deliveries.json");
42835
+ }
42836
+ async init() {
42837
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
42838
+ await chmod(this.dataDir, 448).catch(() => {
42839
+ return;
42840
+ });
42841
+ await this.ensureArrayFile(this.channelsPath);
42842
+ await this.ensureArrayFile(this.eventsPath);
42843
+ await this.ensureArrayFile(this.deliveriesPath);
42844
+ }
42845
+ async addChannel(channel) {
42846
+ await this.init();
42847
+ const channels = await this.readJson(this.channelsPath, []);
42848
+ const index = channels.findIndex((item) => item.id === channel.id);
42849
+ if (index >= 0) {
42850
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
42851
+ } else {
42852
+ channels.push(channel);
42853
+ }
42854
+ await this.writeJson(this.channelsPath, channels);
42855
+ return index >= 0 ? channels[index] : channel;
42856
+ }
42857
+ async listChannels() {
42858
+ await this.init();
42859
+ return this.readJson(this.channelsPath, []);
42860
+ }
42861
+ async getChannel(id) {
42862
+ const channels = await this.listChannels();
42863
+ return channels.find((channel) => channel.id === id);
42864
+ }
42865
+ async removeChannel(id) {
42866
+ await this.init();
42867
+ const channels = await this.readJson(this.channelsPath, []);
42868
+ const next = channels.filter((channel) => channel.id !== id);
42869
+ await this.writeJson(this.channelsPath, next);
42870
+ return next.length !== channels.length;
42871
+ }
42872
+ async appendEvent(event) {
42873
+ await this.init();
42874
+ const events = await this.readJson(this.eventsPath, []);
42875
+ events.push(event);
42876
+ await this.writeJson(this.eventsPath, events);
42877
+ return event;
42878
+ }
42879
+ async listEvents() {
42880
+ await this.init();
42881
+ return this.readJson(this.eventsPath, []);
42882
+ }
42883
+ async findEventByIdentity(identity) {
42884
+ const events = await this.listEvents();
42885
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
42886
+ }
42887
+ async appendDelivery(result) {
42888
+ await this.init();
42889
+ const deliveries = await this.readJson(this.deliveriesPath, []);
42890
+ deliveries.push(result);
42891
+ await this.writeJson(this.deliveriesPath, deliveries);
42892
+ return result;
42893
+ }
42894
+ async listDeliveries() {
42895
+ await this.init();
42896
+ return this.readJson(this.deliveriesPath, []);
42897
+ }
42898
+ async exportData() {
42899
+ return {
42900
+ channels: await this.listChannels(),
42901
+ events: await this.listEvents(),
42902
+ deliveries: await this.listDeliveries()
42903
+ };
42904
+ }
42905
+ async ensureArrayFile(path) {
42906
+ if (!existsSync(path)) {
42907
+ await writeFile(path, `[]
42908
+ `, { encoding: "utf-8", mode: 384 });
42909
+ }
42910
+ await chmod(path, 384).catch(() => {
42911
+ return;
42912
+ });
42913
+ }
42914
+ async readJson(path, fallback) {
42915
+ try {
42916
+ const raw = await readFile(path, "utf-8");
42917
+ if (!raw.trim())
42918
+ return fallback;
42919
+ return JSON.parse(raw);
42920
+ } catch (error) {
42921
+ if (error.code === "ENOENT")
42922
+ return fallback;
42923
+ throw error;
42924
+ }
42925
+ }
42926
+ async writeJson(path, value) {
42927
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
42928
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
42929
+ `, { encoding: "utf-8", mode: 384 });
42930
+ await rename(tempPath, path);
42931
+ await chmod(path, 384).catch(() => {
42932
+ return;
42933
+ });
42934
+ }
42935
+ }
42936
+ var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
42937
+ function buildSignatureBase(timestamp, body) {
42938
+ return `${timestamp}.${body}`;
42939
+ }
42940
+ function signPayload(secret, timestamp, body) {
42941
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
42942
+ return `sha256=${digest}`;
42943
+ }
42944
+ function now() {
42945
+ return new Date().toISOString();
42946
+ }
42947
+ function truncate(value, max = 4096) {
42948
+ return value.length > max ? `${value.slice(0, max)}...` : value;
42949
+ }
42950
+ function buildWebhookRequest(event, channel) {
42951
+ if (!channel.webhook)
42952
+ throw new Error(`Channel ${channel.id} has no webhook config`);
42953
+ const body = JSON.stringify(event);
42954
+ const timestamp = event.time;
42955
+ const headers = {
42956
+ "Content-Type": "application/json",
42957
+ "User-Agent": "@hasna/events",
42958
+ "X-Hasna-Event-Id": event.id,
42959
+ "X-Hasna-Event-Type": event.type,
42960
+ "X-Hasna-Timestamp": timestamp,
42961
+ ...channel.webhook.headers
42962
+ };
42963
+ if (channel.webhook.secret) {
42964
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
42965
+ }
42966
+ return { body, headers };
42967
+ }
42968
+ async function dispatchWebhook(event, channel, options = {}) {
42969
+ if (!channel.webhook)
42970
+ throw new Error(`Channel ${channel.id} has no webhook config`);
42971
+ const startedAt = now();
42972
+ const { body, headers } = buildWebhookRequest(event, channel);
42973
+ const controller = new AbortController;
42974
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
42975
+ try {
42976
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
42977
+ method: "POST",
42978
+ headers,
42979
+ body,
42980
+ signal: controller.signal
42981
+ });
42982
+ const responseBody = truncate(await response.text());
42983
+ return {
42984
+ attempt: 1,
42985
+ status: response.ok ? "success" : "failed",
42986
+ startedAt,
42987
+ completedAt: now(),
42988
+ responseStatus: response.status,
42989
+ responseBody,
42990
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
42991
+ };
42992
+ } catch (error) {
42993
+ return {
42994
+ attempt: 1,
42995
+ status: "failed",
42996
+ startedAt,
42997
+ completedAt: now(),
42998
+ error: error instanceof Error ? error.message : String(error)
42999
+ };
43000
+ } finally {
43001
+ clearTimeout(timeout);
43002
+ }
43003
+ }
43004
+ async function dispatchCommand(event, channel) {
43005
+ if (!channel.command)
43006
+ throw new Error(`Channel ${channel.id} has no command config`);
43007
+ const startedAt = now();
43008
+ const eventJson = JSON.stringify(event);
43009
+ const env = {
43010
+ ...process.env,
43011
+ ...channel.command.env,
43012
+ HASNA_CHANNEL_ID: channel.id,
43013
+ HASNA_EVENT_ID: event.id,
43014
+ HASNA_EVENT_TYPE: event.type,
43015
+ HASNA_EVENT_SOURCE: event.source,
43016
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
43017
+ HASNA_EVENT_SEVERITY: event.severity,
43018
+ HASNA_EVENT_TIME: event.time,
43019
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
43020
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
43021
+ HASNA_EVENT_JSON: eventJson
43022
+ };
43023
+ return new Promise((resolve) => {
43024
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
43025
+ cwd: channel.command.cwd,
43026
+ env,
43027
+ stdio: ["pipe", "pipe", "pipe"]
43028
+ });
43029
+ let stdout = "";
43030
+ let stderr = "";
43031
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
43032
+ child.stdin.end(eventJson);
43033
+ child.stdout.on("data", (chunk) => {
43034
+ stdout += chunk.toString();
43035
+ });
43036
+ child.stderr.on("data", (chunk) => {
43037
+ stderr += chunk.toString();
43038
+ });
43039
+ child.on("error", (error) => {
43040
+ clearTimeout(timeout);
43041
+ resolve({
43042
+ attempt: 1,
43043
+ status: "failed",
43044
+ startedAt,
43045
+ completedAt: now(),
43046
+ stdout: truncate(stdout),
43047
+ stderr: truncate(stderr),
43048
+ error: error.message
43049
+ });
43050
+ });
43051
+ child.on("close", (code, signal) => {
43052
+ clearTimeout(timeout);
43053
+ const success = code === 0;
43054
+ resolve({
43055
+ attempt: 1,
43056
+ status: success ? "success" : "failed",
43057
+ startedAt,
43058
+ completedAt: now(),
43059
+ stdout: truncate(stdout),
43060
+ stderr: truncate(stderr),
43061
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
43062
+ });
43063
+ });
43064
+ });
43065
+ }
43066
+ async function dispatchChannel(event, channel, options = {}) {
43067
+ if (channel.transport === "webhook")
43068
+ return dispatchWebhook(event, channel, options);
43069
+ if (channel.transport === "command")
43070
+ return dispatchCommand(event, channel);
43071
+ return {
43072
+ attempt: 1,
43073
+ status: "skipped",
43074
+ startedAt: now(),
43075
+ completedAt: now(),
43076
+ error: `Unsupported transport: ${channel.transport}`
43077
+ };
43078
+ }
43079
+ function createDeliveryResult(event, channel, attempts) {
43080
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
43081
+ return {
43082
+ id: randomUUID(),
43083
+ eventId: event.id,
43084
+ channelId: channel.id,
43085
+ transport: channel.transport,
43086
+ status,
43087
+ attempts,
43088
+ createdAt: attempts[0]?.startedAt ?? now(),
43089
+ completedAt: attempts.at(-1)?.completedAt ?? now()
43090
+ };
43091
+ }
43092
+ function createEvent(input) {
43093
+ return {
43094
+ id: input.id ?? randomUUID2(),
43095
+ source: input.source,
43096
+ type: input.type,
43097
+ time: normalizeTime(input.time),
43098
+ subject: input.subject,
43099
+ severity: input.severity ?? "info",
43100
+ data: input.data ?? {},
43101
+ message: input.message,
43102
+ dedupeKey: input.dedupeKey,
43103
+ schemaVersion: input.schemaVersion ?? "1.0",
43104
+ metadata: input.metadata ?? {}
43105
+ };
43106
+ }
43107
+
43108
+ class EventsClient {
43109
+ store;
43110
+ redactors;
43111
+ transportOptions;
43112
+ constructor(options = {}) {
43113
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
43114
+ this.redactors = options.redactors ?? [];
43115
+ this.transportOptions = { fetchImpl: options.fetchImpl };
43116
+ }
43117
+ async addChannel(input) {
43118
+ const timestamp = new Date().toISOString();
43119
+ return this.store.addChannel({
43120
+ ...input,
43121
+ createdAt: input.createdAt ?? timestamp,
43122
+ updatedAt: input.updatedAt ?? timestamp
43123
+ });
43124
+ }
43125
+ async listChannels() {
43126
+ return this.store.listChannels();
43127
+ }
43128
+ async removeChannel(id) {
43129
+ return this.store.removeChannel(id);
43130
+ }
43131
+ async emit(input, options = {}) {
43132
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
43133
+ if (options.dedupe !== false) {
43134
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
43135
+ if (existing) {
43136
+ return { event: existing, deliveries: [], deduped: true };
43137
+ }
43138
+ }
43139
+ await this.store.appendEvent(event);
43140
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
43141
+ return { event, deliveries, deduped: false };
43142
+ }
43143
+ async listEvents() {
43144
+ return this.store.listEvents();
43145
+ }
43146
+ async listDeliveries() {
43147
+ return this.store.listDeliveries();
43148
+ }
43149
+ async deliver(event) {
43150
+ const channels = await this.store.listChannels();
43151
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
43152
+ const deliveries = [];
43153
+ for (const channel of selected) {
43154
+ const eventForChannel = await this.applyRedaction(event, channel);
43155
+ const result = await this.deliverWithRetry(eventForChannel, channel);
43156
+ await this.store.appendDelivery(result);
43157
+ deliveries.push(result);
43158
+ }
43159
+ return deliveries;
43160
+ }
43161
+ async testChannel(id, input = {}) {
43162
+ const channel = await this.store.getChannel(id);
43163
+ if (!channel)
43164
+ throw new Error(`Channel not found: ${id}`);
43165
+ const event = createEvent({
43166
+ source: input.source ?? "hasna.events",
43167
+ type: input.type ?? "events.test",
43168
+ subject: input.subject ?? id,
43169
+ severity: input.severity ?? "info",
43170
+ data: input.data ?? { test: true },
43171
+ message: input.message ?? "Hasna events test delivery",
43172
+ dedupeKey: input.dedupeKey,
43173
+ schemaVersion: input.schemaVersion,
43174
+ metadata: input.metadata,
43175
+ time: input.time,
43176
+ id: input.id
43177
+ });
43178
+ const eventForChannel = await this.applyRedaction(event, channel);
43179
+ const result = await this.deliverWithRetry(eventForChannel, channel);
43180
+ await this.store.appendDelivery(result);
43181
+ return result;
43182
+ }
43183
+ async replay(options = {}) {
43184
+ const events = (await this.store.listEvents()).filter((event) => {
43185
+ if (options.eventId && event.id !== options.eventId)
43186
+ return false;
43187
+ if (options.source && event.source !== options.source)
43188
+ return false;
43189
+ if (options.type && event.type !== options.type)
43190
+ return false;
43191
+ return true;
43192
+ });
43193
+ if (options.dryRun)
43194
+ return { events, deliveries: [] };
43195
+ const deliveries = [];
43196
+ for (const event of events) {
43197
+ deliveries.push(...await this.deliver(event));
43198
+ }
43199
+ return { events, deliveries };
43200
+ }
43201
+ async applyRedaction(event, channel) {
43202
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
43203
+ for (const redactor of this.redactors) {
43204
+ next = await redactor(next, channel);
43205
+ }
43206
+ return next;
43207
+ }
43208
+ async deliverWithRetry(event, channel) {
43209
+ const policy = normalizeRetryPolicy(channel.retry);
43210
+ const attempts = [];
43211
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
43212
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
43213
+ attempt.attempt = index + 1;
43214
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
43215
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
43216
+ }
43217
+ attempts.push(attempt);
43218
+ if (attempt.status !== "failed")
43219
+ break;
43220
+ if (attempt.nextBackoffMs)
43221
+ await Bun.sleep(attempt.nextBackoffMs);
43222
+ }
43223
+ return createDeliveryResult(event, channel, attempts);
43224
+ }
43225
+ }
43226
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
43227
+ if (paths.length === 0)
43228
+ return event;
43229
+ const copy = structuredClone(event);
43230
+ for (const path of paths) {
43231
+ setPath(copy, path, replacement);
43232
+ }
43233
+ return copy;
43234
+ }
43235
+ function sanitizeChannelForOutput(channel) {
43236
+ const copy = structuredClone(channel);
43237
+ if (copy.webhook?.secret)
43238
+ copy.webhook.secret = "[REDACTED]";
43239
+ if (copy.command?.env) {
43240
+ copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
43241
+ }
43242
+ return copy;
43243
+ }
43244
+ function sanitizeChannelsForOutput(channels) {
43245
+ return channels.map(sanitizeChannelForOutput);
43246
+ }
43247
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
43248
+ return redactValue(event, replacement);
43249
+ }
43250
+ function shouldRedactKey(key) {
43251
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
43252
+ }
43253
+ function redactValue(value, replacement) {
43254
+ if (Array.isArray(value))
43255
+ return value.map((item) => redactValue(item, replacement));
43256
+ if (!value || typeof value !== "object")
43257
+ return value;
43258
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
43259
+ key,
43260
+ shouldRedactKey(key) ? replacement : redactValue(item, replacement)
43261
+ ]));
43262
+ }
43263
+ function setPath(input, path, replacement) {
43264
+ const parts = path.split(".");
43265
+ let cursor = input;
43266
+ for (const part of parts.slice(0, -1)) {
43267
+ const next = cursor[part];
43268
+ if (!next || typeof next !== "object")
43269
+ return;
43270
+ cursor = next;
43271
+ }
43272
+ const last = parts.at(-1);
43273
+ if (last && last in cursor)
43274
+ cursor[last] = replacement;
43275
+ }
43276
+ function normalizeTime(value) {
43277
+ if (!value)
43278
+ return new Date().toISOString();
43279
+ return value instanceof Date ? value.toISOString() : value;
43280
+ }
43281
+ function normalizeRetryPolicy(policy) {
43282
+ return {
43283
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
43284
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
43285
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
43286
+ };
43287
+ }
43288
+ function parseJsonObject(value, fallback) {
43289
+ if (!value)
43290
+ return fallback;
43291
+ const parsed = JSON.parse(value);
43292
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
43293
+ throw new Error("Expected a JSON object");
43294
+ }
43295
+ return parsed;
43296
+ }
43297
+ function parseHeaders(values) {
43298
+ if (!values?.length)
43299
+ return;
43300
+ const headers = {};
43301
+ for (const value of values) {
43302
+ const separator = value.indexOf("=");
43303
+ if (separator === -1)
43304
+ throw new Error(`Invalid header, expected name=value: ${value}`);
43305
+ headers[value.slice(0, separator)] = value.slice(separator + 1);
43306
+ }
43307
+ return headers;
43308
+ }
43309
+ function parseFilter(options) {
43310
+ const filter2 = {};
43311
+ if (options.source)
43312
+ filter2.source = options.source;
43313
+ if (options.type)
43314
+ filter2.type = options.type;
43315
+ if (options.subject)
43316
+ filter2.subject = options.subject;
43317
+ if (options.severity)
43318
+ filter2.severity = options.severity;
43319
+ return Object.keys(filter2).length > 0 ? [filter2] : undefined;
43320
+ }
43321
+ function createClient(options) {
43322
+ if (options.createClient)
43323
+ return options.createClient();
43324
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
43325
+ }
43326
+ function print(value, json, text) {
43327
+ if (json)
43328
+ console.log(JSON.stringify(value, null, 2));
43329
+ else
43330
+ console.log(text);
43331
+ }
43332
+ function registerWebhookCommands(program2, options) {
43333
+ const webhooks = program2.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
43334
+ webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions) => {
43335
+ const timestamp = new Date().toISOString();
43336
+ const channel = {
43337
+ id: actionOptions.id,
43338
+ name: actionOptions.name,
43339
+ enabled: !actionOptions.disabled,
43340
+ transport: actionOptions.transport,
43341
+ filters: parseFilter(actionOptions),
43342
+ retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
43343
+ redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
43344
+ createdAt: timestamp,
43345
+ updatedAt: timestamp
43346
+ };
43347
+ if (actionOptions.transport === "webhook") {
43348
+ channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
43349
+ } else if (actionOptions.transport === "command") {
43350
+ channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
43351
+ } else {
43352
+ throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
43353
+ }
43354
+ const saved = await createClient(options).addChannel(channel);
43355
+ print(sanitizeChannelForOutput(saved), Boolean(actionOptions.json), `Added ${saved.transport} channel ${saved.id}`);
43356
+ });
43357
+ webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
43358
+ const channels = await createClient(options).listChannels();
43359
+ if (actionOptions.json) {
43360
+ console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
43361
+ return;
43362
+ }
43363
+ if (!channels.length) {
43364
+ console.log("No channels configured.");
43365
+ return;
43366
+ }
43367
+ for (const channel of channels) {
43368
+ console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
43369
+ }
43370
+ });
43371
+ webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
43372
+ const removed = await createClient(options).removeChannel(id);
43373
+ print({ removed }, Boolean(actionOptions.json), removed ? `Removed ${id}` : `Channel not found: ${id}`);
43374
+ });
43375
+ webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
43376
+ const result = await createClient(options).testChannel(id, {
43377
+ source: options.source,
43378
+ type: actionOptions.type,
43379
+ subject: actionOptions.subject ?? id,
43380
+ message: actionOptions.message,
43381
+ data: parseJsonObject(actionOptions.data, { test: true })
43382
+ });
43383
+ print(result, Boolean(actionOptions.json), `${result.status}: ${result.channelId}`);
43384
+ });
43385
+ return webhooks;
43386
+ }
43387
+ function registerEventCommands(program2, options) {
43388
+ const events = program2.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
43389
+ events.command("emit").description("Emit an event from this app").argument("<type>", "Event type").option("--source <source>", "Event source override").option("--subject <subject>", "Event subject").option("--severity <severity>", "Event severity", "info").option("--message <message>", "Event message").option("--dedupe-key <key>", "Dedupe key").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--no-deliver", "Record without delivering").option("--no-dedupe", "Allow duplicate id/dedupeKey events").option("-j, --json", "Print JSON output", false).action(async (type, actionOptions) => {
43390
+ const result = await createClient(options).emit({
43391
+ source: actionOptions.source ?? options.source,
43392
+ type,
43393
+ subject: actionOptions.subject,
43394
+ severity: actionOptions.severity,
43395
+ message: actionOptions.message,
43396
+ dedupeKey: actionOptions.dedupeKey,
43397
+ data: parseJsonObject(actionOptions.data, {}),
43398
+ metadata: parseJsonObject(actionOptions.metadata, {})
43399
+ }, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
43400
+ print(result, Boolean(actionOptions.json), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
43401
+ });
43402
+ events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
43403
+ let rows = await createClient(options).listEvents();
43404
+ if (actionOptions.source)
43405
+ rows = rows.filter((event) => event.source === actionOptions.source);
43406
+ if (actionOptions.type)
43407
+ rows = rows.filter((event) => event.type === actionOptions.type);
43408
+ if (actionOptions.limit)
43409
+ rows = rows.slice(-actionOptions.limit);
43410
+ if (actionOptions.json) {
43411
+ console.log(JSON.stringify(rows, null, 2));
43412
+ return;
43413
+ }
43414
+ if (!rows.length) {
43415
+ console.log("No events recorded.");
43416
+ return;
43417
+ }
43418
+ for (const event of rows)
43419
+ console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
43420
+ });
43421
+ events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
43422
+ const result = await createClient(options).replay({
43423
+ eventId: actionOptions.id,
43424
+ source: actionOptions.source,
43425
+ type: actionOptions.type,
43426
+ dryRun: actionOptions.dryRun
43427
+ });
43428
+ print(result, Boolean(actionOptions.json), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
43429
+ });
43430
+ return events;
43431
+ }
43432
+ function registerEventsCommands(program2, options) {
43433
+ registerWebhookCommands(program2, options);
43434
+ registerEventCommands(program2, options);
43435
+ }
43436
+ function parseNumber(value) {
43437
+ const parsed = Number(value);
43438
+ if (!Number.isFinite(parsed))
43439
+ throw new Error(`Expected a number, got ${value}`);
43440
+ return parsed;
43441
+ }
43442
+ function collectValues(value, previous) {
43443
+ previous.push(value);
43444
+ return previous;
43445
+ }
43446
+
43447
+ // src/cli/index.tsx
43448
+ init_package();
42756
43449
  import chalk14 from "chalk";
42757
43450
 
42758
43451
  // src/cli/components/App.tsx
@@ -43963,6 +44656,7 @@ var { registerAuth: registerAuth2 } = await Promise.resolve().then(() => (init_a
43963
44656
  registerAuth2(program2);
43964
44657
  var { registerFeedback: registerFeedback2 } = await Promise.resolve().then(() => (init_feedback2(), exports_feedback));
43965
44658
  registerFeedback2(program2);
44659
+ registerEventsCommands(program2, { source: "skills" });
43966
44660
  program2.hook("preAction", (_thisCommand, actionCommand) => {
43967
44661
  maybePrintFirstRunOnboarding(actionCommand, process.argv.slice(2), isTTY2);
43968
44662
  });