@hasna/skills 0.1.71 → 0.2.0

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/dist/index.js CHANGED
@@ -47,13 +47,12 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
47
47
  var __require = import.meta.require;
48
48
 
49
49
  // src/lib/registry.ts
50
- import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync6 } from "fs";
51
- import { join as join7 } from "path";
50
+ import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync6 } from "fs";
51
+ import { join as join8 } from "path";
52
52
 
53
53
  // src/lib/config.ts
54
- import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
55
- import { join, dirname } from "path";
56
- import { homedir } from "os";
54
+ import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
55
+ import { join as join2, dirname } from "path";
57
56
 
58
57
  // src/lib/retired-settings.ts
59
58
  var RETIRED_ENV_SUFFIXES = ["_STORAGE_MODE", "_DEPLOYMENT_MODE", "_CLOUD_MODE"];
@@ -101,6 +100,124 @@ function assertNoRetiredConfigKeys(config, source) {
101
100
  }
102
101
  }
103
102
 
103
+ // src/lib/app-home.ts
104
+ import { existsSync } from "fs";
105
+ import { homedir } from "os";
106
+ import { join, resolve } from "path";
107
+ import { homedir as pathsResolverHomedir } from "os";
108
+ import { join as pathsResolverJoin } from "path";
109
+ var PATHS_RESOLVER_KIND_ENV = {
110
+ config: "HASNA_CONFIG_HOME",
111
+ data: "HASNA_DATA_HOME",
112
+ state: "HASNA_STATE_HOME",
113
+ cache: "HASNA_CACHE_HOME"
114
+ };
115
+ var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
116
+ function pathsResolverAssertApp(app) {
117
+ if (typeof app !== "string" || app.length === 0) {
118
+ throw new TypeError("paths: app must be a non-empty string");
119
+ }
120
+ if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
121
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
122
+ }
123
+ }
124
+ function pathsResolverAssertKind(kind) {
125
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
126
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
127
+ }
128
+ }
129
+ function pathsResolverBaseDir(kind, options) {
130
+ pathsResolverAssertKind(kind);
131
+ const env = options.env ?? process.env;
132
+ const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
133
+ if (typeof override === "string" && override.length > 0)
134
+ return override;
135
+ const home = options.home ?? pathsResolverHomedir();
136
+ const platform = options.platform ?? process.platform;
137
+ if (platform === "darwin") {
138
+ switch (kind) {
139
+ case "config":
140
+ case "data":
141
+ return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
142
+ case "cache":
143
+ return pathsResolverJoin(home, "Library", "Caches", "Hasna");
144
+ case "state":
145
+ return pathsResolverJoin(home, "Library", "Logs", "Hasna");
146
+ }
147
+ }
148
+ switch (kind) {
149
+ case "config":
150
+ return pathsResolverJoin(home, ".config", "hasna");
151
+ case "data":
152
+ return pathsResolverJoin(home, ".local", "share", "hasna");
153
+ case "state":
154
+ return pathsResolverJoin(home, ".local", "state", "hasna");
155
+ case "cache":
156
+ return pathsResolverJoin(home, ".cache", "hasna");
157
+ }
158
+ }
159
+ function pathsResolverResolve(kind, options) {
160
+ pathsResolverAssertApp(options.app);
161
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
162
+ return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
163
+ }
164
+ function dataDir(options) {
165
+ return pathsResolverResolve("data", options);
166
+ }
167
+ var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
168
+ var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
169
+ var SKILLS_HOME_ENV = "SKILLS_HOME";
170
+ var DEFAULT_SQLITE_FILENAME = "server.db";
171
+ var GLOBAL_CONFIG_FILENAME = "config.json";
172
+ function effectiveHome() {
173
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir() || "/tmp";
174
+ }
175
+ function legacyDataRoot() {
176
+ return join(effectiveHome(), ".hasna", "skills");
177
+ }
178
+ function resolverDataRoot(home = effectiveHome(), env) {
179
+ return dataDir({ app: "skills", home, env });
180
+ }
181
+ function adoptResolverDataRoot(resolved, env = process.env) {
182
+ const dataOverride = env.HASNA_DATA_HOME;
183
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
184
+ return true;
185
+ return existsSync(join(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync(join(resolved, GLOBAL_CONFIG_FILENAME));
186
+ }
187
+ function exactDataRoot() {
188
+ for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
189
+ const dir = process.env[key]?.trim();
190
+ if (dir)
191
+ return resolve(dir);
192
+ }
193
+ return;
194
+ }
195
+ function hasExactOverride(env = process.env) {
196
+ return Boolean(env[DATA_DIR_ENV]?.trim()) || Boolean(env[HASNA_SKILLS_HOME_ENV]?.trim()) || Boolean(env[SKILLS_HOME_ENV]?.trim());
197
+ }
198
+ function hasOperatorOverride(env = process.env) {
199
+ return hasExactOverride(env) || Boolean(env.HASNA_DATA_HOME?.trim());
200
+ }
201
+ function getDataRoot() {
202
+ const exact = exactDataRoot();
203
+ if (exact)
204
+ return exact;
205
+ const resolved = resolverDataRoot();
206
+ return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
207
+ }
208
+ function skillsDataRootForHome(home) {
209
+ const isOwnHome = resolve(home) === resolve(effectiveHome()) || resolve(home) === resolve(homedir());
210
+ if (isOwnHome) {
211
+ const exact = exactDataRoot();
212
+ if (exact)
213
+ return exact;
214
+ const resolved2 = resolverDataRoot(home);
215
+ return adoptResolverDataRoot(resolved2) ? resolve(resolved2) : resolve(join(home, ".hasna", "skills"));
216
+ }
217
+ const resolved = resolverDataRoot(home, {});
218
+ return adoptResolverDataRoot(resolved, {}) ? resolve(resolved) : resolve(join(home, ".hasna", "skills"));
219
+ }
220
+
104
221
  // src/lib/config.ts
105
222
  var ENUM_KEYS = {
106
223
  defaultAgent: ["claude", "codex", "gemini", "pi", "opencode", "all"],
@@ -115,19 +232,19 @@ function allowedValues(key) {
115
232
  return ENUM_KEYS[key];
116
233
  }
117
234
  function mergeDirectoryContents(sourceDir, targetDir) {
118
- if (!existsSync(sourceDir))
235
+ if (!existsSync2(sourceDir))
119
236
  return;
120
237
  mkdirSync(targetDir, { recursive: true });
121
238
  for (const entry of readdirSync(sourceDir)) {
122
- const sourcePath = join(sourceDir, entry);
123
- const targetPath = join(targetDir, entry);
239
+ const sourcePath = join2(sourceDir, entry);
240
+ const targetPath = join2(targetDir, entry);
124
241
  try {
125
242
  const sourceStat = statSync(sourcePath);
126
243
  if (sourceStat.isDirectory()) {
127
244
  mergeDirectoryContents(sourcePath, targetPath);
128
245
  continue;
129
246
  }
130
- if (!existsSync(targetPath))
247
+ if (!existsSync2(targetPath))
131
248
  copyFileSync(sourcePath, targetPath);
132
249
  } catch {}
133
250
  }
@@ -152,44 +269,40 @@ function normalizeConfigValue(key, value) {
152
269
  return value.trim() ? value : undefined;
153
270
  return;
154
271
  }
155
- var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
156
272
  var INSTALLED_SKILLS_DIRNAME = "installed";
157
273
  var SKILLS_CACHE_DIRNAME = "skills";
158
274
  var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
159
275
  function isOwnerLayoutMigrated(appDir) {
160
- return existsSync(join(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
276
+ return existsSync2(join2(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
161
277
  }
162
278
  function getDataDir() {
163
- const override = process.env[DATA_DIR_ENV];
164
- if (override) {
165
- try {
166
- mkdirSync(override, { recursive: true });
167
- } catch {}
168
- return override;
169
- }
170
- const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
171
- const newDir = join(home, ".hasna", "skills");
172
- const oldDir = join(home, ".skills");
173
- const oldConfigFile = join(home, ".skillsrc");
174
- mkdirSync(newDir, { recursive: true });
279
+ const root = getDataRoot();
175
280
  try {
176
- mergeDirectoryContents(oldDir, newDir);
281
+ mkdirSync(root, { recursive: true });
177
282
  } catch {}
178
- if (existsSync(oldConfigFile) && !existsSync(join(newDir, "config.json"))) {
283
+ if (hasOperatorOverride())
284
+ return root;
285
+ const home = effectiveHome();
286
+ const oldDir = join2(home, ".skills");
287
+ const oldConfigFile = join2(home, ".skillsrc");
288
+ try {
289
+ mergeDirectoryContents(oldDir, root);
290
+ } catch {}
291
+ if (existsSync2(oldConfigFile) && !existsSync2(join2(root, "config.json"))) {
179
292
  try {
180
- copyFileSync(oldConfigFile, join(newDir, "config.json"));
293
+ copyFileSync(oldConfigFile, join2(root, "config.json"));
181
294
  } catch {}
182
295
  }
183
- return newDir;
296
+ return root;
184
297
  }
185
298
  function getConfigPath(scope) {
186
299
  if (scope === "global") {
187
- return join(getDataDir(), "config.json");
300
+ return join2(getDataDir(), "config.json");
188
301
  }
189
- return join(process.cwd(), "skills.config.json");
302
+ return join2(process.cwd(), "skills.config.json");
190
303
  }
191
304
  function readConfigFile(path) {
192
- if (!existsSync(path))
305
+ if (!existsSync2(path))
193
306
  return {};
194
307
  let parsed;
195
308
  try {
@@ -225,7 +338,7 @@ function saveConfig(key, value, scope = "project") {
225
338
  }
226
339
  const filePath = getConfigPath(scope);
227
340
  let existing = {};
228
- if (existsSync(filePath)) {
341
+ if (existsSync2(filePath)) {
229
342
  try {
230
343
  existing = JSON.parse(readFileSync(filePath, "utf-8"));
231
344
  if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
@@ -236,7 +349,7 @@ function saveConfig(key, value, scope = "project") {
236
349
  }
237
350
  } else {
238
351
  const dir = dirname(filePath);
239
- if (!existsSync(dir)) {
352
+ if (!existsSync2(dir)) {
240
353
  mkdirSync(dir, { recursive: true });
241
354
  }
242
355
  }
@@ -250,7 +363,7 @@ function unsetConfig(key, scope = "project") {
250
363
  throw new Error(`Unknown config key: ${key}. Valid keys: ${validKeys().join(", ")}`);
251
364
  }
252
365
  const filePath = getConfigPath(scope);
253
- if (!existsSync(filePath))
366
+ if (!existsSync2(filePath))
254
367
  return false;
255
368
  let existing;
256
369
  try {
@@ -272,7 +385,7 @@ function unsetConfig(key, scope = "project") {
272
385
  // src/lib/portable-skills.ts
273
386
  import {
274
387
  cpSync as cpSync2,
275
- existsSync as existsSync6,
388
+ existsSync as existsSync7,
276
389
  mkdirSync as mkdirSync3,
277
390
  mkdtempSync,
278
391
  readdirSync as readdirSync5,
@@ -281,7 +394,7 @@ import {
281
394
  statSync as statSync6,
282
395
  writeFileSync as writeFileSync3
283
396
  } from "fs";
284
- import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join6, normalize as normalize2 } from "path";
397
+ import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join7, normalize as normalize2 } from "path";
285
398
 
286
399
  // src/lib/registry-data/development-tools.ts
287
400
  var DEVELOPMENT_TOOLS_SKILLS = [
@@ -495,14 +608,6 @@ var DEVELOPMENT_TOOLS_SKILLS = [
495
608
  category: "Development Tools",
496
609
  tags: ["storage", "backend", "postgresql", "sqlite", "two-backend", "oss-app"],
497
610
  kind: "instruction"
498
- },
499
- {
500
- name: "session-inject-monitor",
501
- displayName: "Session Inject Monitor",
502
- description: "Set up a declarative monitor that injects a prompt into a live coding-agent session when a watched source (conversations, email, todos, knowledge, command output) has new content",
503
- category: "Development Tools",
504
- tags: ["monitor", "session", "injection", "automation", "wake"],
505
- kind: "instruction"
506
611
  }
507
612
  ];
508
613
 
@@ -1001,8 +1106,8 @@ var SKILLS = [
1001
1106
  ];
1002
1107
 
1003
1108
  // src/lib/hosted-skill-set.ts
1004
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1005
- import { join as join2 } from "path";
1109
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1110
+ import { join as join3 } from "path";
1006
1111
  var HOSTED_RUNTIMES = new Set(["hosted"]);
1007
1112
  var HOSTED_SOURCES = new Set(["remote", "private-hosted"]);
1008
1113
  function normalizeMarker(value) {
@@ -1015,8 +1120,8 @@ function isHostedMetadataPackage(pkg) {
1015
1120
  return HOSTED_RUNTIMES.has(normalizeMarker(skills.runtime)) || HOSTED_SOURCES.has(normalizeMarker(skills.source));
1016
1121
  }
1017
1122
  function isHostedMetadataSkillDir(skillDir) {
1018
- const pkgPath = join2(skillDir, "package.json");
1019
- if (!existsSync2(pkgPath))
1123
+ const pkgPath = join3(skillDir, "package.json");
1124
+ if (!existsSync3(pkgPath))
1020
1125
  return false;
1021
1126
  try {
1022
1127
  return isHostedMetadataPackage(JSON.parse(readFileSync2(pkgPath, "utf8")));
@@ -1040,8 +1145,8 @@ var BRACE_SOURCE_EXCLUSION = new RegExp(`^!skills/\\{(${SLUG}(?:,${SLUG})+)\\}/s
1040
1145
  var SINGLE_SOURCE_EXCLUSION = new RegExp(`^!skills/(${SLUG})/src$`);
1041
1146
 
1042
1147
  // src/lib/skill-validation.ts
1043
- import { existsSync as existsSync3, lstatSync, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
1044
- import { isAbsolute, join as join3, normalize } from "path";
1148
+ import { existsSync as existsSync4, lstatSync, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
1149
+ import { isAbsolute, join as join4, normalize } from "path";
1045
1150
  var VALID_SKILL_KINDS = ["executable", "instruction"];
1046
1151
  var DOC_FILES = ["SKILL.md", "README.md", "CLAUDE.md"];
1047
1152
  var RESERVED_SKILL_ENTRIES = new Set([
@@ -1179,7 +1284,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1179
1284
  binCommands: [],
1180
1285
  docFiles: []
1181
1286
  };
1182
- if (!existsSync3(skillPath)) {
1287
+ if (!existsSync4(skillPath)) {
1183
1288
  add(issues, "skill.dir_missing", `Skill directory not found: ${skillPath}`);
1184
1289
  return {
1185
1290
  name: bareName,
@@ -1194,7 +1299,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1194
1299
  add(issues, "skill.name_invalid", `Skill name '${bareName}' must use lowercase letters, numbers, dots, underscores, or hyphens`);
1195
1300
  }
1196
1301
  for (const entry of readdirSync3(skillPath).sort()) {
1197
- const entryPath = join3(skillPath, entry);
1302
+ const entryPath = join4(skillPath, entry);
1198
1303
  if (RESERVED_SKILL_ENTRIES.has(entry)) {
1199
1304
  add(issues, "skill.reserved_file", `Reserved file '${entry}' is not allowed in skill packages`);
1200
1305
  }
@@ -1206,14 +1311,14 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1206
1311
  }
1207
1312
  }
1208
1313
  for (const docFile of DOC_FILES) {
1209
- if (existsSync3(join3(skillPath, docFile)))
1314
+ if (existsSync4(join4(skillPath, docFile)))
1210
1315
  metadata.docFiles.push(docFile);
1211
1316
  }
1212
1317
  if (metadata.docFiles.length === 0) {
1213
1318
  add(issues, "skill.docs_missing", "Missing documentation file: expected SKILL.md, README.md, or CLAUDE.md");
1214
1319
  }
1215
- const skillMdPath = join3(skillPath, "SKILL.md");
1216
- if (existsSync3(skillMdPath)) {
1320
+ const skillMdPath = join4(skillPath, "SKILL.md");
1321
+ if (existsSync4(skillMdPath)) {
1217
1322
  const frontmatter = parseSkillFrontmatter(readFileSync3(skillMdPath, "utf-8"));
1218
1323
  if (!frontmatter) {
1219
1324
  add(warnings, "skill.frontmatter_missing", "SKILL.md has no YAML frontmatter");
@@ -1256,8 +1361,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1256
1361
  }
1257
1362
  metadata.kind = resolvedKind;
1258
1363
  const isInstruction = resolvedKind === "instruction";
1259
- const pkgPath = join3(skillPath, "package.json");
1260
- if (!existsSync3(pkgPath)) {
1364
+ const pkgPath = join4(skillPath, "package.json");
1365
+ if (!existsSync4(pkgPath)) {
1261
1366
  if (!isInstruction)
1262
1367
  add(issues, "package.missing", "Missing package.json");
1263
1368
  } else {
@@ -1315,8 +1420,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1315
1420
  add(issues, "package.bin_target_unsafe", `package.json bin '${command}' target '${target}' must stay inside the skill directory`);
1316
1421
  continue;
1317
1422
  }
1318
- const targetPath = join3(skillPath, target);
1319
- if (!existsSync3(targetPath)) {
1423
+ const targetPath = join4(skillPath, target);
1424
+ if (!existsSync4(targetPath)) {
1320
1425
  add(warnings, "package.bin_target_missing", `package.json bin '${command}' target '${target}' is not present before build`);
1321
1426
  } else if (statSync3(targetPath).isDirectory()) {
1322
1427
  add(issues, "package.bin_target_directory", `package.json bin '${command}' target '${target}' must point to a file, not a directory`);
@@ -1333,17 +1438,17 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1333
1438
  metadata.runtime = "none";
1334
1439
  } else {
1335
1440
  metadata.runtime = hostedMetadata ? "hosted" : "local";
1336
- const srcDir = join3(skillPath, "src");
1441
+ const srcDir = join4(skillPath, "src");
1337
1442
  if (hostedMetadata) {
1338
- if (existsSync3(srcDir)) {
1443
+ if (existsSync4(srcDir)) {
1339
1444
  add(issues, "skill.hosted_source_forbidden", "Hosted metadata skills must not include local implementation source");
1340
1445
  }
1341
- } else if (!existsSync3(srcDir)) {
1446
+ } else if (!existsSync4(srcDir)) {
1342
1447
  add(issues, "skill.src_missing", "Missing src/ directory");
1343
- } else if (!existsSync3(join3(srcDir, "index.ts")) && !existsSync3(join3(srcDir, "index.js"))) {
1448
+ } else if (!existsSync4(join4(srcDir, "index.ts")) && !existsSync4(join4(srcDir, "index.js"))) {
1344
1449
  add(issues, "skill.src_index_missing", "Missing src/index.ts or src/index.js");
1345
1450
  } else {
1346
- const indexPath = existsSync3(join3(srcDir, "index.ts")) ? join3(srcDir, "index.ts") : join3(srcDir, "index.js");
1451
+ const indexPath = existsSync4(join4(srcDir, "index.ts")) ? join4(srcDir, "index.ts") : join4(srcDir, "index.js");
1347
1452
  const size = statSync3(indexPath).size;
1348
1453
  if (size < 50)
1349
1454
  add(warnings, "skill.src_index_minimal", `Source entry point is very small (${size}B)`);
@@ -1367,8 +1472,8 @@ function validateRegistryConsistency(registry, skillsDir) {
1367
1472
  seen.add(name);
1368
1473
  return false;
1369
1474
  })));
1370
- const skillDirs = existsSync3(skillsDir) ? readdirSync3(skillsDir).filter((entry) => {
1371
- const fullPath = join3(skillsDir, entry);
1475
+ const skillDirs = existsSync4(skillsDir) ? readdirSync3(skillsDir).filter((entry) => {
1476
+ const fullPath = join4(skillsDir, entry);
1372
1477
  return !entry.startsWith(".") && entry !== "_common" && statSync3(fullPath).isDirectory();
1373
1478
  }) : [];
1374
1479
  const directoryNames = new Set(skillDirs);
@@ -1385,8 +1490,8 @@ function validateRegistryConsistency(registry, skillsDir) {
1385
1490
 
1386
1491
  // src/lib/skill-hash.ts
1387
1492
  import { createHash } from "crypto";
1388
- import { existsSync as existsSync4, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
1389
- import { join as join4, sep } from "path";
1493
+ import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
1494
+ import { join as join5, sep } from "path";
1390
1495
  var CONTENT_HASH_ALGORITHM = "sha256";
1391
1496
  var HASH_EXCLUDE_DIRS = new Set([".git", "node_modules", "dist", "build", ".turbo"]);
1392
1497
  var HASH_COVERAGE = [
@@ -1445,8 +1550,8 @@ function collectBundleFiles(skillPath) {
1445
1550
  if (seen.has(entry))
1446
1551
  continue;
1447
1552
  seen.add(entry);
1448
- const absolute = join4(skillPath, entry);
1449
- if (!existsSync4(absolute))
1553
+ const absolute = join5(skillPath, entry);
1554
+ if (!existsSync5(absolute))
1450
1555
  continue;
1451
1556
  if (statSync4(absolute).isDirectory())
1452
1557
  collectDirectory(files, absolute, entry);
@@ -1459,7 +1564,7 @@ function collectDirectory(files, dir, rel) {
1459
1564
  for (const entry of readdirSync4(dir).sort()) {
1460
1565
  if (entry.startsWith("."))
1461
1566
  continue;
1462
- const absolute = join4(dir, entry);
1567
+ const absolute = join5(dir, entry);
1463
1568
  const childRel = `${rel}/${entry}`;
1464
1569
  let stats;
1465
1570
  try {
@@ -1679,14 +1784,14 @@ function validateRuntimeContract(manifest, issues, strict) {
1679
1784
  // src/lib/portable-skills-files.ts
1680
1785
  import {
1681
1786
  cpSync,
1682
- existsSync as existsSync5,
1787
+ existsSync as existsSync6,
1683
1788
  lstatSync as lstatSync2,
1684
1789
  mkdirSync as mkdirSync2,
1685
1790
  readFileSync as readFileSync5,
1686
1791
  realpathSync,
1687
1792
  writeFileSync as writeFileSync2
1688
1793
  } from "fs";
1689
- import { basename, dirname as dirname2, join as join5, relative } from "path";
1794
+ import { basename, dirname as dirname2, join as join6, relative } from "path";
1690
1795
  var ANY_SEGMENT_COPY_EXCLUDES = new Set([
1691
1796
  ".git",
1692
1797
  ".DS_Store",
@@ -1729,12 +1834,12 @@ function normalizePortableSkillName(name) {
1729
1834
  return normalized;
1730
1835
  }
1731
1836
  function readPortableSkillManifest(skillPath, fallbackName = basename(skillPath)) {
1732
- const skillJsonPath = join5(skillPath, "skill.json");
1733
- const skillMdPath = join5(skillPath, "SKILL.md");
1734
- const pkgPath = join5(skillPath, "package.json");
1735
- const jsonManifest = existsSync5(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
1736
- const frontmatter = existsSync5(skillMdPath) ? parseSkillFrontmatter(readFileSync5(skillMdPath, "utf-8")) ?? undefined : undefined;
1737
- const pkg = existsSync5(pkgPath) ? readJsonObject(pkgPath) : undefined;
1837
+ const skillJsonPath = join6(skillPath, "skill.json");
1838
+ const skillMdPath = join6(skillPath, "SKILL.md");
1839
+ const pkgPath = join6(skillPath, "package.json");
1840
+ const jsonManifest = existsSync6(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
1841
+ const frontmatter = existsSync6(skillMdPath) ? parseSkillFrontmatter(readFileSync5(skillMdPath, "utf-8")) ?? undefined : undefined;
1842
+ const pkg = existsSync6(pkgPath) ? readJsonObject(pkgPath) : undefined;
1738
1843
  const name = normalizePortableSkillName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
1739
1844
  const description = stringField(jsonManifest, "description") ?? frontmatter?.description ?? stringValue(pkg?.description) ?? `${name} skill`;
1740
1845
  const version = stringField(jsonManifest, "version") ?? frontmatter?.version ?? stringValue(pkg?.version) ?? PORTABLE_SKILL_DEFAULT_VERSION;
@@ -1780,7 +1885,7 @@ function createInstructionManifest(name, options) {
1780
1885
  }
1781
1886
  function writeInstructionSkillTemplate(skillPath, manifest) {
1782
1887
  mkdirSync2(skillPath, { recursive: true });
1783
- writeFileSync2(join5(skillPath, "SKILL.md"), renderInstructionSkillMd(manifest));
1888
+ writeFileSync2(join6(skillPath, "SKILL.md"), renderInstructionSkillMd(manifest));
1784
1889
  writeSkillJsonWithHash(skillPath, manifest);
1785
1890
  }
1786
1891
  function renderInstructionSkillMd(manifest) {
@@ -1829,12 +1934,12 @@ function createPortableManifest(name, options) {
1829
1934
  };
1830
1935
  }
1831
1936
  function writePortableSkillTemplate(skillPath, manifest) {
1832
- mkdirSync2(join5(skillPath, "src"), { recursive: true });
1833
- writeFileSync2(join5(skillPath, "SKILL.md"), renderSkillMd(manifest));
1834
- writeFileSync2(join5(skillPath, "AGENTS.md"), renderAgentsMd(manifest));
1835
- writeFileSync2(join5(skillPath, "package.json"), renderPackageJson(manifest));
1836
- writeFileSync2(join5(skillPath, "tsconfig.json"), renderTsconfig());
1837
- writeFileSync2(join5(skillPath, "src", "index.ts"), renderEntrypoint(manifest));
1937
+ mkdirSync2(join6(skillPath, "src"), { recursive: true });
1938
+ writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(manifest));
1939
+ writeFileSync2(join6(skillPath, "AGENTS.md"), renderAgentsMd(manifest));
1940
+ writeFileSync2(join6(skillPath, "package.json"), renderPackageJson(manifest));
1941
+ writeFileSync2(join6(skillPath, "tsconfig.json"), renderTsconfig());
1942
+ writeFileSync2(join6(skillPath, "src", "index.ts"), renderEntrypoint(manifest));
1838
1943
  writeSkillJsonWithHash(skillPath, manifest);
1839
1944
  }
1840
1945
  function fillContractDefaults(manifest, entrypoint) {
@@ -1859,7 +1964,7 @@ function writeSkillJsonWithHash(skillPath, manifest) {
1859
1964
  content_hash: undefined
1860
1965
  }
1861
1966
  };
1862
- writeFileSync2(join5(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withoutHash) }, null, 2)}
1967
+ writeFileSync2(join6(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withoutHash) }, null, 2)}
1863
1968
  `);
1864
1969
  const hash = computeContentHash(skillPath);
1865
1970
  const withHash = {
@@ -1869,13 +1974,13 @@ function writeSkillJsonWithHash(skillPath, manifest) {
1869
1974
  content_hash: hash
1870
1975
  }
1871
1976
  };
1872
- writeFileSync2(join5(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withHash) }, null, 2)}
1977
+ writeFileSync2(join6(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withHash) }, null, 2)}
1873
1978
  `);
1874
1979
  return withHash;
1875
1980
  }
1876
1981
  function readExistingSkillJson(skillPath) {
1877
- const path = join5(skillPath, "skill.json");
1878
- if (!existsSync5(path))
1982
+ const path = join6(skillPath, "skill.json");
1983
+ if (!existsSync6(path))
1879
1984
  return {};
1880
1985
  try {
1881
1986
  const parsed = JSON.parse(readFileSync5(path, "utf-8"));
@@ -1908,28 +2013,28 @@ function ensurePortableSkillFiles(skillPath, manifest) {
1908
2013
  tags: next.tags?.length ? next.tags : ["custom", next.name]
1909
2014
  };
1910
2015
  const entry = next.commands[0]?.entry ?? "src/index.ts";
1911
- if (entry && !existsSync5(join5(skillPath, entry))) {
1912
- mkdirSync2(dirname2(join5(skillPath, entry)), { recursive: true });
1913
- writeFileSync2(join5(skillPath, entry), renderEntrypoint(next));
2016
+ if (entry && !existsSync6(join6(skillPath, entry))) {
2017
+ mkdirSync2(dirname2(join6(skillPath, entry)), { recursive: true });
2018
+ writeFileSync2(join6(skillPath, entry), renderEntrypoint(next));
1914
2019
  }
1915
- if (!existsSync5(join5(skillPath, "SKILL.md")))
1916
- writeFileSync2(join5(skillPath, "SKILL.md"), renderSkillMd(next));
2020
+ if (!existsSync6(join6(skillPath, "SKILL.md")))
2021
+ writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
1917
2022
  else
1918
- writeFileSync2(join5(skillPath, "SKILL.md"), ensureSkillMdFrontmatter(readFileSync5(join5(skillPath, "SKILL.md"), "utf-8"), next));
1919
- if (!existsSync5(join5(skillPath, "AGENTS.md")))
1920
- writeFileSync2(join5(skillPath, "AGENTS.md"), renderAgentsMd(next));
2023
+ writeFileSync2(join6(skillPath, "SKILL.md"), ensureSkillMdFrontmatter(readFileSync5(join6(skillPath, "SKILL.md"), "utf-8"), next));
2024
+ if (!existsSync6(join6(skillPath, "AGENTS.md")))
2025
+ writeFileSync2(join6(skillPath, "AGENTS.md"), renderAgentsMd(next));
1921
2026
  ensurePackageJson(skillPath, next);
1922
- if (!existsSync5(join5(skillPath, "tsconfig.json")))
1923
- writeFileSync2(join5(skillPath, "tsconfig.json"), renderTsconfig());
2027
+ if (!existsSync6(join6(skillPath, "tsconfig.json")))
2028
+ writeFileSync2(join6(skillPath, "tsconfig.json"), renderTsconfig());
1924
2029
  writeSkillJsonWithHash(skillPath, next);
1925
2030
  return readPortableSkillManifest(skillPath, next.name);
1926
2031
  }
1927
2032
  function ensurePackageJson(skillPath, manifest) {
1928
- const pkgPath = join5(skillPath, "package.json");
2033
+ const pkgPath = join6(skillPath, "package.json");
1929
2034
  const first = manifest.commands[0] ?? { name: manifest.name, entry: "src/index.ts" };
1930
2035
  const commandName = normalizePortableSkillName(first.name || manifest.name);
1931
2036
  const entry = (first.entry ?? "src/index.ts").replace(/^\.\//, "");
1932
- if (!existsSync5(pkgPath)) {
2037
+ if (!existsSync6(pkgPath)) {
1933
2038
  writeFileSync2(pkgPath, renderPackageJson(manifest));
1934
2039
  return;
1935
2040
  }
@@ -1972,8 +2077,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
1972
2077
  inputs: [],
1973
2078
  commands: []
1974
2079
  };
1975
- if (!existsSync5(join5(skillPath, "SKILL.md"))) {
1976
- writeFileSync2(join5(skillPath, "SKILL.md"), renderSkillMd(next));
2080
+ if (!existsSync6(join6(skillPath, "SKILL.md"))) {
2081
+ writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
1977
2082
  }
1978
2083
  writeSkillJsonWithHash(skillPath, next);
1979
2084
  return readPortableSkillManifest(skillPath, next.name);
@@ -2265,18 +2370,18 @@ var LEGACY_CUSTOM_DIRNAME = "custom";
2265
2370
  function getPortableSkillsRoot(options = {}) {
2266
2371
  if (options.rootDir)
2267
2372
  return options.rootDir;
2268
- const appDir = options.homeDir ? join6(options.homeDir, ".hasna", "skills") : getDataDir();
2269
- const cache = join6(appDir, SKILLS_CACHE_DIRNAME);
2373
+ const appDir = options.homeDir ? join7(options.homeDir, ".hasna", "skills") : getDataDir();
2374
+ const cache = join7(appDir, SKILLS_CACHE_DIRNAME);
2270
2375
  if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
2271
2376
  return cache;
2272
- const installed = join6(appDir, INSTALLED_SKILLS_DIRNAME);
2377
+ const installed = join7(appDir, INSTALLED_SKILLS_DIRNAME);
2273
2378
  migrateLegacySkillLayout(appDir, installed);
2274
2379
  return installed;
2275
2380
  }
2276
2381
  function looksLikeSkillDirectory(path) {
2277
2382
  if (!safeIsDirectory(path))
2278
2383
  return false;
2279
- return existsSync6(join6(path, "SKILL.md")) || existsSync6(join6(path, "skill.json")) || existsSync6(join6(path, "package.json"));
2384
+ return existsSync7(join7(path, "SKILL.md")) || existsSync7(join7(path, "skill.json")) || existsSync7(join7(path, "package.json"));
2280
2385
  }
2281
2386
  function migrateLegacySkillLayout(appDir, installed) {
2282
2387
  if (!safeIsDirectory(appDir))
@@ -2286,7 +2391,7 @@ function migrateLegacySkillLayout(appDir, installed) {
2286
2391
  for (const entry of readdirSync5(appDir)) {
2287
2392
  if (entry.startsWith(".") || entry === INSTALLED_SKILLS_DIRNAME)
2288
2393
  continue;
2289
- const path = join6(appDir, entry);
2394
+ const path = join7(appDir, entry);
2290
2395
  if (entry === LEGACY_CUSTOM_DIRNAME) {
2291
2396
  if (!safeIsDirectory(path))
2292
2397
  continue;
@@ -2294,7 +2399,7 @@ function migrateLegacySkillLayout(appDir, installed) {
2294
2399
  for (const nested of readdirSync5(path)) {
2295
2400
  if (nested.startsWith("."))
2296
2401
  continue;
2297
- const nestedPath = join6(path, nested);
2402
+ const nestedPath = join7(path, nested);
2298
2403
  if (looksLikeSkillDirectory(nestedPath))
2299
2404
  candidates.push({ from: nestedPath, name: nested });
2300
2405
  }
@@ -2308,10 +2413,10 @@ function migrateLegacySkillLayout(appDir, installed) {
2308
2413
  return;
2309
2414
  }
2310
2415
  for (const { from, name } of candidates) {
2311
- const target = join6(installed, name);
2312
- if (existsSync6(target))
2416
+ const target = join7(installed, name);
2417
+ if (existsSync7(target))
2313
2418
  continue;
2314
- const staging = join6(installed, `.migrating-${name}-${process.pid}`);
2419
+ const staging = join7(installed, `.migrating-${name}-${process.pid}`);
2315
2420
  try {
2316
2421
  rmSync(staging, { recursive: true, force: true });
2317
2422
  cpSync2(from, staging, { recursive: true, errorOnExist: false });
@@ -2324,7 +2429,7 @@ function migrateLegacySkillLayout(appDir, installed) {
2324
2429
  }
2325
2430
  }
2326
2431
  function getPortableSkillPath(name, options = {}) {
2327
- return join6(getPortableSkillsRoot(options), normalizePortableSkillName(name));
2432
+ return join7(getPortableSkillsRoot(options), normalizePortableSkillName(name));
2328
2433
  }
2329
2434
  function findPortableSkill(name, options = {}) {
2330
2435
  let normalized;
@@ -2334,7 +2439,7 @@ function findPortableSkill(name, options = {}) {
2334
2439
  return null;
2335
2440
  }
2336
2441
  const path = getPortableSkillPath(normalized, options);
2337
- if (!existsSync6(path) || !statSync6(path).isDirectory())
2442
+ if (!existsSync7(path) || !statSync6(path).isDirectory())
2338
2443
  return null;
2339
2444
  try {
2340
2445
  return summarizePortableSkill(path, normalized);
@@ -2350,7 +2455,7 @@ function listPortableSkills(options = {}) {
2350
2455
  for (const entry of readdirSync5(root).sort()) {
2351
2456
  if (entry.startsWith("."))
2352
2457
  continue;
2353
- const path = join6(root, entry);
2458
+ const path = join7(root, entry);
2354
2459
  if (!safeIsDirectory(path))
2355
2460
  continue;
2356
2461
  try {
@@ -2384,8 +2489,8 @@ function isOfficialSkillName(name) {
2384
2489
  function scaffoldPortableSkill(name, options = {}) {
2385
2490
  const skillName = normalizePortableSkillName(name);
2386
2491
  const root = getPortableSkillsRoot(options);
2387
- const skillPath = join6(root, skillName);
2388
- if (existsSync6(skillPath)) {
2492
+ const skillPath = join7(root, skillName);
2493
+ if (existsSync7(skillPath)) {
2389
2494
  if (!options.overwrite)
2390
2495
  throw new Error(`Skill '${skillName}' already exists at ${skillPath}`);
2391
2496
  rmSync(skillPath, { recursive: true, force: true });
@@ -2403,7 +2508,7 @@ function scaffoldPortableSkill(name, options = {}) {
2403
2508
  }
2404
2509
  function portPortableSkillDirectory(sourceDir, options = {}) {
2405
2510
  const absoluteSource = normalize2(sourceDir);
2406
- if (!existsSync6(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
2511
+ if (!existsSync7(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
2407
2512
  throw new Error(`Import directory not found: ${sourceDir}`);
2408
2513
  }
2409
2514
  const continueOnError = options.continueOnError ?? true;
@@ -2416,7 +2521,7 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
2416
2521
  const skipped = [];
2417
2522
  const entries = readdirSync5(absoluteSource, { withFileTypes: true }).map((entry) => entry.name).filter((entryName) => !entryName.startsWith(".")).sort();
2418
2523
  for (const entryName of entries) {
2419
- const childPath = join6(absoluteSource, entryName);
2524
+ const childPath = join7(absoluteSource, entryName);
2420
2525
  if (!safeIsDirectory(childPath))
2421
2526
  continue;
2422
2527
  if (!isSkillCandidate(childPath)) {
@@ -2445,11 +2550,11 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
2445
2550
  };
2446
2551
  }
2447
2552
  function isSkillCandidate(dir) {
2448
- return existsSync6(join6(dir, "SKILL.md")) || existsSync6(join6(dir, "skill.json")) || existsSync6(join6(dir, "package.json"));
2553
+ return existsSync7(join7(dir, "SKILL.md")) || existsSync7(join7(dir, "skill.json")) || existsSync7(join7(dir, "package.json"));
2449
2554
  }
2450
2555
  function portPortableSkill(sourcePath, options = {}) {
2451
2556
  const absoluteSource = normalize2(sourcePath);
2452
- if (!existsSync6(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
2557
+ if (!existsSync7(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
2453
2558
  throw new Error(`Skill source directory not found: ${sourcePath}`);
2454
2559
  }
2455
2560
  const inferred = readPortableSkillManifest(absoluteSource, basename2(absoluteSource));
@@ -2461,8 +2566,8 @@ function portPortableSkill(sourcePath, options = {}) {
2461
2566
  throw new Error(`${via} Importing it would shadow the official '${skillName}'. ` + `Pass --name to choose a different name, or --allow-shadow to override deliberately.`);
2462
2567
  }
2463
2568
  const root = getPortableSkillsRoot(options);
2464
- const destination = join6(root, skillName);
2465
- if (existsSync6(destination)) {
2569
+ const destination = join7(root, skillName);
2570
+ if (existsSync7(destination)) {
2466
2571
  if (!options.overwrite)
2467
2572
  throw new Error(`Skill '${skillName}' already exists at ${destination}`);
2468
2573
  rmSync(destination, { recursive: true, force: true });
@@ -2504,10 +2609,10 @@ function buildCorpusManifest(input, name) {
2504
2609
  function writeCorpusSkill(input, options = {}) {
2505
2610
  const name = normalizePortableSkillName(input.name);
2506
2611
  const root = getPortableSkillsRoot(options);
2507
- const skillPath = join6(root, name);
2508
- const created = !existsSync6(skillPath);
2612
+ const skillPath = join7(root, name);
2613
+ const created = !existsSync7(skillPath);
2509
2614
  mkdirSync3(skillPath, { recursive: true });
2510
- writeFileSync3(join6(skillPath, "SKILL.md"), input.skillMd);
2615
+ writeFileSync3(join7(skillPath, "SKILL.md"), input.skillMd);
2511
2616
  const manifest = buildCorpusManifest(input, name);
2512
2617
  writeSkillJsonWithHash(skillPath, manifest);
2513
2618
  return { name, path: skillPath, manifest, created };
@@ -2515,19 +2620,19 @@ function writeCorpusSkill(input, options = {}) {
2515
2620
  function installCorpusSkillAtomically(input, options = {}) {
2516
2621
  const name = normalizePortableSkillName(input.name);
2517
2622
  const root = getPortableSkillsRoot(options);
2518
- const target = join6(root, name);
2519
- const created = !existsSync6(target);
2623
+ const target = join7(root, name);
2624
+ const created = !existsSync7(target);
2520
2625
  mkdirSync3(root, { recursive: true });
2521
- const staging = mkdtempSync(join6(root, `.pull-${name}-`));
2626
+ const staging = mkdtempSync(join7(root, `.pull-${name}-`));
2522
2627
  let moved = false;
2523
2628
  let backup = null;
2524
2629
  try {
2525
- writeFileSync3(join6(staging, "SKILL.md"), input.skillMd);
2630
+ writeFileSync3(join7(staging, "SKILL.md"), input.skillMd);
2526
2631
  const manifest = buildCorpusManifest(input, name);
2527
2632
  writeSkillJsonWithHash(staging, manifest);
2528
- if (existsSync6(target)) {
2529
- backup = mkdtempSync(join6(root, `.pull-backup-${name}-`));
2530
- renameSync(target, join6(backup, name));
2633
+ if (existsSync7(target)) {
2634
+ backup = mkdtempSync(join7(root, `.pull-backup-${name}-`));
2635
+ renameSync(target, join7(backup, name));
2531
2636
  moved = true;
2532
2637
  }
2533
2638
  renameSync(staging, target);
@@ -2536,9 +2641,9 @@ function installCorpusSkillAtomically(input, options = {}) {
2536
2641
  return { name, path: target, manifest, created };
2537
2642
  } catch (error) {
2538
2643
  rmSync(staging, { recursive: true, force: true });
2539
- if (moved && backup && existsSync6(join6(backup, name))) {
2644
+ if (moved && backup && existsSync7(join7(backup, name))) {
2540
2645
  try {
2541
- renameSync(join6(backup, name), target);
2646
+ renameSync(join7(backup, name), target);
2542
2647
  } catch {}
2543
2648
  }
2544
2649
  throw error;
@@ -2550,10 +2655,10 @@ function validatePortableSkillDirectory(name, skillPath) {
2550
2655
  const issues = [...base.issues];
2551
2656
  const warnings = [...base.warnings];
2552
2657
  let manifest;
2553
- if (existsSync6(skillPath)) {
2554
- const skillJsonPath = join6(skillPath, "skill.json");
2555
- const skillMdPath = join6(skillPath, "SKILL.md");
2556
- if (!existsSync6(skillJsonPath) && !existsSync6(skillMdPath)) {
2658
+ if (existsSync7(skillPath)) {
2659
+ const skillJsonPath = join7(skillPath, "skill.json");
2660
+ const skillMdPath = join7(skillPath, "SKILL.md");
2661
+ if (!existsSync7(skillJsonPath) && !existsSync7(skillMdPath)) {
2557
2662
  add3(issues, "portable.manifest_missing", "Missing portable manifest: expected SKILL.md frontmatter and/or skill.json");
2558
2663
  }
2559
2664
  try {
@@ -2572,7 +2677,7 @@ function validatePortableSkillDirectory(name, skillPath) {
2572
2677
  add3(issues, "portable.version_missing", "Portable manifest missing version");
2573
2678
  }
2574
2679
  const contractIssues = validatePortableManifestContract(manifest, {
2575
- strict: existsSync6(join6(skillPath, "skill.json")),
2680
+ strict: existsSync7(join7(skillPath, "skill.json")),
2576
2681
  skillPath
2577
2682
  });
2578
2683
  for (const issue of contractIssues)
@@ -2608,8 +2713,8 @@ function validatePortableSkillDirectory(name, skillPath) {
2608
2713
  add3(issues, "portable.command_entry_unsafe", `Command '${command.name}' entry '${command.entry}' must stay inside the skill directory`);
2609
2714
  continue;
2610
2715
  }
2611
- const entryPath = join6(skillPath, command.entry);
2612
- if (!existsSync6(entryPath))
2716
+ const entryPath = join7(skillPath, command.entry);
2717
+ if (!existsSync7(entryPath))
2613
2718
  add3(issues, "portable.command_entry_missing", `Command '${command.name}' entry '${command.entry}' is missing`);
2614
2719
  else if (statSync6(entryPath).isDirectory())
2615
2720
  add3(issues, "portable.command_entry_directory", `Command '${command.name}' entry '${command.entry}' must be a file`);
@@ -2619,7 +2724,7 @@ function validatePortableSkillDirectory(name, skillPath) {
2619
2724
  } catch (error) {
2620
2725
  add3(issues, "portable.manifest_invalid", error.message);
2621
2726
  }
2622
- if (manifest?.kind !== "instruction" && !existsSync6(join6(skillPath, "AGENTS.md"))) {
2727
+ if (manifest?.kind !== "instruction" && !existsSync7(join7(skillPath, "AGENTS.md"))) {
2623
2728
  add3(issues, "portable.agents_missing", "Missing AGENTS.md with build-out instructions for coding agents");
2624
2729
  }
2625
2730
  }
@@ -2655,13 +2760,13 @@ async function runPortableSkill(name, args, options = {}) {
2655
2760
  if (!isSafeRelativePath2(command.entry)) {
2656
2761
  return { exitCode: 1, error: `Portable skill '${name}' command entry is unsafe` };
2657
2762
  }
2658
- const entryPath = join6(skill.path, command.entry);
2659
- if (!existsSync6(entryPath)) {
2763
+ const entryPath = join7(skill.path, command.entry);
2764
+ if (!existsSync7(entryPath)) {
2660
2765
  return { exitCode: 1, error: `Entry point '${command.entry}' not found in portable skill '${name}'` };
2661
2766
  }
2662
- const pkgPath = join6(skill.path, "package.json");
2663
- const nodeModules = join6(skill.path, "node_modules");
2664
- if (existsSync6(pkgPath) && !existsSync6(nodeModules) && hasPackageDependencies(pkgPath)) {
2767
+ const pkgPath = join7(skill.path, "package.json");
2768
+ const nodeModules = join7(skill.path, "node_modules");
2769
+ if (existsSync7(pkgPath) && !existsSync7(nodeModules) && hasPackageDependencies(pkgPath)) {
2665
2770
  const install = Bun.spawn(["bun", "install", "--no-save"], {
2666
2771
  cwd: skill.path,
2667
2772
  stdout: "pipe",
@@ -2930,7 +3035,7 @@ function parseSkillMdFrontmatter(content) {
2930
3035
  return Object.keys(result).length > 0 ? result : null;
2931
3036
  }
2932
3037
  function discoverSkillsInDir(dir, source = "custom") {
2933
- if (!existsSync7(dir))
3038
+ if (!existsSync8(dir))
2934
3039
  return [];
2935
3040
  const result = [];
2936
3041
  try {
@@ -2938,8 +3043,8 @@ function discoverSkillsInDir(dir, source = "custom") {
2938
3043
  for (const entry of entries) {
2939
3044
  if (!entry.isDirectory())
2940
3045
  continue;
2941
- const skillMdPath = join7(dir, entry.name, "SKILL.md");
2942
- if (!existsSync7(skillMdPath))
3046
+ const skillMdPath = join8(dir, entry.name, "SKILL.md");
3047
+ if (!existsSync8(skillMdPath))
2943
3048
  continue;
2944
3049
  let content;
2945
3050
  try {
@@ -2958,7 +3063,7 @@ function discoverSkillsInDir(dir, source = "custom") {
2958
3063
  category: fm.category || "Development Tools",
2959
3064
  tags: fm.tags || [],
2960
3065
  ...fm.kind ? { kind: fm.kind } : {},
2961
- ...isHostedMetadataSkillDir(join7(dir, entry.name)) ? { serverOwned: true } : {},
3066
+ ...isHostedMetadataSkillDir(join8(dir, entry.name)) ? { serverOwned: true } : {},
2962
3067
  source
2963
3068
  });
2964
3069
  }
@@ -2967,16 +3072,16 @@ function discoverSkillsInDir(dir, source = "custom") {
2967
3072
  }
2968
3073
  function findExtensionSkillPath(name) {
2969
3074
  const config = loadConfig();
2970
- if (!config.extensionsDir || !existsSync7(config.extensionsDir))
3075
+ if (!config.extensionsDir || !existsSync8(config.extensionsDir))
2971
3076
  return null;
2972
3077
  try {
2973
3078
  const entries = readdirSync6(config.extensionsDir, { withFileTypes: true });
2974
3079
  for (const entry of entries) {
2975
3080
  if (!entry.isDirectory())
2976
3081
  continue;
2977
- const skillDir = join7(config.extensionsDir, entry.name);
2978
- const skillMdPath = join7(skillDir, "SKILL.md");
2979
- if (!existsSync7(skillMdPath))
3082
+ const skillDir = join8(config.extensionsDir, entry.name);
3083
+ const skillMdPath = join8(skillDir, "SKILL.md");
3084
+ if (!existsSync8(skillMdPath))
2980
3085
  continue;
2981
3086
  let content;
2982
3087
  try {
@@ -3008,12 +3113,12 @@ function loadRegistry(cwd) {
3008
3113
  if (registryCache && registryCacheKey === rootKey && now - registryCacheTime < REGISTRY_CACHE_TTL) {
3009
3114
  return registryCache;
3010
3115
  }
3011
- const dataDir = getDataDir();
3116
+ const dataDir2 = getDataDir();
3012
3117
  const config = loadConfig();
3013
3118
  const official = SKILLS.map((s) => ({ ...s, source: "official" }));
3014
3119
  const extensions = config.extensionsDir ? discoverSkillsInDir(config.extensionsDir, "extension") : [];
3015
3120
  const portableCustom = listPortableSkillMetas();
3016
- const legacyCustom = discoverSkillsInDir(join7(dataDir, "custom"));
3121
+ const legacyCustom = discoverSkillsInDir(join8(dataDir2, "custom"));
3017
3122
  const globalCustom = mergeCustomSkills([...legacyCustom, ...portableCustom]);
3018
3123
  registryCache = mergeSkillRegistryLists(official, extensions, globalCustom);
3019
3124
  registryCacheTime = now;
@@ -3060,15 +3165,15 @@ function getAllTags() {
3060
3165
  return Array.from(tagSet).sort();
3061
3166
  }
3062
3167
  // src/lib/installer.ts
3063
- import { existsSync as existsSync10, readFileSync as readFileSync9, rmSync as rmSync3 } from "fs";
3064
- import { dirname as dirname5, join as join10 } from "path";
3168
+ import { existsSync as existsSync11, readFileSync as readFileSync9, rmSync as rmSync3 } from "fs";
3169
+ import { dirname as dirname5, join as join11 } from "path";
3065
3170
  import { homedir as homedir3 } from "os";
3066
3171
  import { fileURLToPath } from "url";
3067
3172
 
3068
3173
  // src/lib/agent-sync.ts
3069
3174
  import {
3070
3175
  cpSync as cpSync3,
3071
- existsSync as existsSync8,
3176
+ existsSync as existsSync9,
3072
3177
  mkdirSync as mkdirSync4,
3073
3178
  mkdtempSync as mkdtempSync2,
3074
3179
  readFileSync as readFileSync7,
@@ -3079,7 +3184,7 @@ import {
3079
3184
  writeFileSync as writeFileSync4
3080
3185
  } from "fs";
3081
3186
  import { homedir as homedir2 } from "os";
3082
- import { basename as basename3, dirname as dirname4, join as join8 } from "path";
3187
+ import { basename as basename3, dirname as dirname4, join as join9 } from "path";
3083
3188
  // src/lib/home-migration.ts
3084
3189
  function resolveCorpusRoot(options = {}) {
3085
3190
  return getPortableSkillsRoot(options);
@@ -3104,9 +3209,9 @@ function resolveSyncAgents(arg) {
3104
3209
  function agentGlobalSkillsDir(agent, homeDir = homedir2()) {
3105
3210
  switch (agent) {
3106
3211
  case "opencode":
3107
- return join8(homeDir, ".config", "opencode", "skills");
3212
+ return join9(homeDir, ".config", "opencode", "skills");
3108
3213
  default:
3109
- return join8(homeDir, `.${agent}`, "skills");
3214
+ return join9(homeDir, `.${agent}`, "skills");
3110
3215
  }
3111
3216
  }
3112
3217
  function adaptSkillMdForAgent(skillMd, agent) {
@@ -3165,8 +3270,8 @@ function resolveSyncCorpus(options = {}) {
3165
3270
  function packageSourceRoots(source) {
3166
3271
  const roots = [];
3167
3272
  for (const sub of ["skills"]) {
3168
- const candidate = join8(source, sub);
3169
- if (existsSync8(candidate) && isDirectory(candidate))
3273
+ const candidate = join9(source, sub);
3274
+ if (existsSync9(candidate) && isDirectory(candidate))
3170
3275
  roots.push(candidate);
3171
3276
  }
3172
3277
  if (roots.length > 0)
@@ -3181,10 +3286,10 @@ function containsSkillDirectories(path) {
3181
3286
  return false;
3182
3287
  }
3183
3288
  return entries.some((entry) => {
3184
- const candidate = join8(path, entry);
3289
+ const candidate = join9(path, entry);
3185
3290
  if (!isDirectory(candidate))
3186
3291
  return false;
3187
- return existsSync8(join8(candidate, "SKILL.md")) || existsSync8(join8(candidate, "skill.json")) || existsSync8(join8(candidate, "package.json"));
3292
+ return existsSync9(join9(candidate, "SKILL.md")) || existsSync9(join9(candidate, "skill.json")) || existsSync9(join9(candidate, "package.json"));
3188
3293
  });
3189
3294
  }
3190
3295
  function isDirectory(path) {
@@ -3223,7 +3328,7 @@ function syncSkillsToAgents(options = {}) {
3223
3328
  actions.push({
3224
3329
  skill: name,
3225
3330
  agent,
3226
- path: join8(agentGlobalSkillsDir(agent, homeDir), name, "SKILL.md"),
3331
+ path: join9(agentGlobalSkillsDir(agent, homeDir), name, "SKILL.md"),
3227
3332
  action: "skip",
3228
3333
  reason: "not found in this machine's corpus"
3229
3334
  });
@@ -3253,7 +3358,7 @@ function syncSkillsToAgents(options = {}) {
3253
3358
  }
3254
3359
  function writeManagedAgentSkill(params) {
3255
3360
  const homeDir = params.homeDir ?? homedir2();
3256
- const dir = join8(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
3361
+ const dir = join9(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
3257
3362
  const result = writeManagedSkillDir(dir, params.skillMd, {
3258
3363
  skill: params.skill,
3259
3364
  source: params.source,
@@ -3270,11 +3375,11 @@ function writeManagedAgentSkill(params) {
3270
3375
  };
3271
3376
  }
3272
3377
  function writeManagedSkillDir(dir, skillMd, options) {
3273
- const skillMdPath = join8(dir, "SKILL.md");
3274
- const markerPath = join8(dir, SYNC_MARKER_FILE);
3275
- const dirExists = existsSync8(dir);
3276
- const managed = existsSync8(markerPath);
3277
- const hasSkillMd = existsSync8(skillMdPath);
3378
+ const skillMdPath = join9(dir, "SKILL.md");
3379
+ const markerPath = join9(dir, SYNC_MARKER_FILE);
3380
+ const dirExists = existsSync9(dir);
3381
+ const managed = existsSync9(markerPath);
3382
+ const hasSkillMd = existsSync9(skillMdPath);
3278
3383
  if (dirExists && !managed && !hasSkillMd) {
3279
3384
  return {
3280
3385
  action: "skip",
@@ -3309,11 +3414,11 @@ function writeManagedSkillDir(dir, skillMd, options) {
3309
3414
  return { action, path: skillMdPath };
3310
3415
  const parentDir = dirname4(dir);
3311
3416
  mkdirSync4(parentDir, { recursive: true });
3312
- const transactionDir = mkdtempSync2(join8(parentDir, `.hasna-skills-write-${basename3(dir)}-`));
3313
- const candidateDir = join8(transactionDir, "candidate");
3314
- const backupDir = join8(transactionDir, "backup");
3315
- const candidateSkillMdPath = join8(candidateDir, "SKILL.md");
3316
- const candidateMarkerPath = join8(candidateDir, SYNC_MARKER_FILE);
3417
+ const transactionDir = mkdtempSync2(join9(parentDir, `.hasna-skills-write-${basename3(dir)}-`));
3418
+ const candidateDir = join9(transactionDir, "candidate");
3419
+ const backupDir = join9(transactionDir, "backup");
3420
+ const candidateSkillMdPath = join9(candidateDir, "SKILL.md");
3421
+ const candidateMarkerPath = join9(candidateDir, SYNC_MARKER_FILE);
3317
3422
  const marker = {
3318
3423
  managedBy: SYNC_MARKER_MANAGED_BY,
3319
3424
  skill: options.skill,
@@ -3340,9 +3445,9 @@ function writeManagedSkillDir(dir, skillMd, options) {
3340
3445
  }
3341
3446
  renameDirectory(candidateDir, dir);
3342
3447
  } catch (error) {
3343
- if (originalMoved && existsSync8(backupDir)) {
3448
+ if (originalMoved && existsSync9(backupDir)) {
3344
3449
  try {
3345
- if (existsSync8(dir))
3450
+ if (existsSync9(dir))
3346
3451
  rmSync2(dir, { recursive: true, force: true });
3347
3452
  renameDirectory(backupDir, dir);
3348
3453
  originalMoved = false;
@@ -3362,16 +3467,16 @@ function writeManagedSkillDir(dir, skillMd, options) {
3362
3467
  return { action, path: skillMdPath };
3363
3468
  }
3364
3469
  function removeManagedAgentSkill(skill, agent, homeDir = homedir2()) {
3365
- const dir = join8(agentGlobalSkillsDir(agent, homeDir), skill);
3366
- if (!existsSync8(join8(dir, SYNC_MARKER_FILE)))
3470
+ const dir = join9(agentGlobalSkillsDir(agent, homeDir), skill);
3471
+ if (!existsSync9(join9(dir, SYNC_MARKER_FILE)))
3367
3472
  return false;
3368
3473
  rmSync2(dir, { recursive: true, force: true });
3369
3474
  return true;
3370
3475
  }
3371
3476
  function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
3372
3477
  if (kind === undefined || kind === "instruction" || preferBundledDocs) {
3373
- const skillMdPath = join8(skillPath, "SKILL.md");
3374
- if (existsSync8(skillMdPath))
3478
+ const skillMdPath = join9(skillPath, "SKILL.md");
3479
+ if (existsSync9(skillMdPath))
3375
3480
  return readFileSync7(skillMdPath, "utf-8");
3376
3481
  }
3377
3482
  return pointerSkillMd(name, description);
@@ -3402,8 +3507,8 @@ function normalizeSkillName(name) {
3402
3507
  }
3403
3508
 
3404
3509
  // src/lib/project-state.ts
3405
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
3406
- import { join as join9 } from "path";
3510
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
3511
+ import { join as join10 } from "path";
3407
3512
  var VALID_PIN_SOURCES = [
3408
3513
  "official",
3409
3514
  "custom",
@@ -3418,14 +3523,14 @@ var SKILLS_PROJECT_DIR = ".skills";
3418
3523
  var PROJECT_CONFIG_FILE = "project.json";
3419
3524
  var DEFAULT_EXPORT_DIR = ".skills/exports";
3420
3525
  function getProjectStateDir(targetDir = process.cwd()) {
3421
- return join9(targetDir, SKILLS_PROJECT_DIR);
3526
+ return join10(targetDir, SKILLS_PROJECT_DIR);
3422
3527
  }
3423
3528
  function getProjectConfigPath(targetDir = process.cwd()) {
3424
- return join9(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
3529
+ return join10(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
3425
3530
  }
3426
3531
  function loadProjectConfig(targetDir = process.cwd()) {
3427
3532
  const path = getProjectConfigPath(targetDir);
3428
- if (!existsSync9(path))
3533
+ if (!existsSync10(path))
3429
3534
  return null;
3430
3535
  try {
3431
3536
  return normalizeProjectConfig(JSON.parse(readFileSync8(path, "utf-8")));
@@ -3546,12 +3651,12 @@ var __dirname2 = dirname5(fileURLToPath(import.meta.url));
3546
3651
  function findSkillsDir() {
3547
3652
  let dir = __dirname2;
3548
3653
  for (let i = 0;i < 5; i++) {
3549
- const candidate = join10(dir, "skills");
3550
- if (existsSync10(candidate) && !dir.includes(".skills"))
3654
+ const candidate = join11(dir, "skills");
3655
+ if (existsSync11(candidate) && !dir.includes(".skills"))
3551
3656
  return candidate;
3552
3657
  dir = dirname5(dir);
3553
3658
  }
3554
- return join10(__dirname2, "..", "skills");
3659
+ return join11(__dirname2, "..", "skills");
3555
3660
  }
3556
3661
  var SKILLS_DIR = findSkillsDir();
3557
3662
  function getSkillPath(name) {
@@ -3559,25 +3664,25 @@ function getSkillPath(name) {
3559
3664
  const portable = findPortableSkill(skillName);
3560
3665
  if (portable)
3561
3666
  return portable.path;
3562
- const legacyCustomPath = join10(getDataDir(), "custom", skillName);
3563
- if (existsSync10(legacyCustomPath))
3667
+ const legacyCustomPath = join11(getDataDir(), "custom", skillName);
3668
+ if (existsSync11(legacyCustomPath))
3564
3669
  return legacyCustomPath;
3565
3670
  const extensionPath = findExtensionSkillPath(skillName);
3566
3671
  if (extensionPath)
3567
3672
  return extensionPath;
3568
- return join10(SKILLS_DIR, skillName);
3673
+ return join11(SKILLS_DIR, skillName);
3569
3674
  }
3570
3675
  function getCanonicalSkillName(name) {
3571
3676
  return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
3572
3677
  }
3573
3678
  function skillExists(name) {
3574
- return existsSync10(getSkillPath(name));
3679
+ return existsSync11(getSkillPath(name));
3575
3680
  }
3576
3681
  function installSkill(name, options = {}) {
3577
3682
  const { targetDir = process.cwd(), overwrite = false } = options;
3578
3683
  const canonicalName = getCanonicalSkillName(name);
3579
3684
  const skillName = normalizeSkillName(canonicalName);
3580
- if (!existsSync10(getSkillPath(name))) {
3685
+ if (!existsSync11(getSkillPath(name))) {
3581
3686
  const knownOfficial = Boolean(getSkill(name));
3582
3687
  return {
3583
3688
  skill: canonicalName,
@@ -3604,7 +3709,7 @@ function installSkill(name, options = {}) {
3604
3709
  }
3605
3710
  function installSkillSource(name, _options = {}) {
3606
3711
  const canonicalName = getCanonicalSkillName(name);
3607
- if (!existsSync10(getSkillPath(name))) {
3712
+ if (!existsSync11(getSkillPath(name))) {
3608
3713
  return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "source" };
3609
3714
  }
3610
3715
  return {
@@ -3625,11 +3730,11 @@ function installSkillManifest(manifest, _options = {}) {
3625
3730
  }
3626
3731
  function createLocalSkillManifest(name, generateSkillMd) {
3627
3732
  const sourcePath = getSkillPath(name);
3628
- if (!existsSync10(sourcePath))
3733
+ if (!existsSync11(sourcePath))
3629
3734
  return null;
3630
3735
  let skillMd = "";
3631
- const skillMdPath = join10(sourcePath, "SKILL.md");
3632
- if (existsSync10(skillMdPath)) {
3736
+ const skillMdPath = join11(sourcePath, "SKILL.md");
3737
+ if (existsSync11(skillMdPath)) {
3633
3738
  skillMd = readFileSync9(skillMdPath, "utf-8");
3634
3739
  } else if (generateSkillMd) {
3635
3740
  skillMd = generateSkillMd(name) ?? "";
@@ -3702,20 +3807,20 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
3702
3807
  const base = projectDir || process.cwd();
3703
3808
  switch (agent) {
3704
3809
  case "pi":
3705
- return scope === "project" ? join10(base, ".pi", "skills") : join10(homedir3(), ".pi", "agent", "skills");
3810
+ return scope === "project" ? join11(base, ".pi", "skills") : join11(homedir3(), ".pi", "agent", "skills");
3706
3811
  case "opencode":
3707
- return scope === "project" ? join10(base, ".opencode", "skills") : join10(homedir3(), ".config", "opencode", "skills");
3812
+ return scope === "project" ? join11(base, ".opencode", "skills") : join11(homedir3(), ".config", "opencode", "skills");
3708
3813
  default:
3709
- return scope === "project" ? join10(base, `.${agent}`, "skills") : join10(homedir3(), `.${agent}`, "skills");
3814
+ return scope === "project" ? join11(base, `.${agent}`, "skills") : join11(homedir3(), `.${agent}`, "skills");
3710
3815
  }
3711
3816
  }
3712
3817
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
3713
3818
  const skillName = normalizeSkillName(getCanonicalSkillName(name));
3714
- return join10(getAgentSkillsDir(agent, scope, projectDir), skillName);
3819
+ return join11(getAgentSkillsDir(agent, scope, projectDir), skillName);
3715
3820
  }
3716
3821
  function installSkillForAgent(name, options, generateSkillMd) {
3717
3822
  const canonicalName = getCanonicalSkillName(name);
3718
- if (!existsSync10(getSkillPath(name))) {
3823
+ if (!existsSync11(getSkillPath(name))) {
3719
3824
  return { skill: canonicalName, success: false, error: `Skill '${name}' not found` };
3720
3825
  }
3721
3826
  const scope = options.scope ?? "global";
@@ -3739,15 +3844,15 @@ function removeSkillForAgent(name, options) {
3739
3844
  const canonicalName = getCanonicalSkillName(name);
3740
3845
  const scope = options.scope ?? "global";
3741
3846
  const dir = getAgentSkillPath(canonicalName, options.agent, scope, options.projectDir);
3742
- if (!existsSync10(join10(dir, SYNC_MARKER_FILE)))
3847
+ if (!existsSync11(join11(dir, SYNC_MARKER_FILE)))
3743
3848
  return false;
3744
3849
  rmSync3(dir, { recursive: true, force: true });
3745
3850
  return true;
3746
3851
  }
3747
3852
  function resolveAgentSkillMd(name, generateSkillMd) {
3748
3853
  const sourcePath = getSkillPath(name);
3749
- const skillMdPath = join10(sourcePath, "SKILL.md");
3750
- if (existsSync10(skillMdPath))
3854
+ const skillMdPath = join11(sourcePath, "SKILL.md");
3855
+ if (existsSync11(skillMdPath))
3751
3856
  return readFileSync9(skillMdPath, "utf-8");
3752
3857
  if (generateSkillMd)
3753
3858
  return generateSkillMd(name);
@@ -3766,7 +3871,7 @@ function warnMissingDependencies(name, targetDir) {
3766
3871
  }
3767
3872
  function generateMinimalSkillMd(name) {
3768
3873
  const sourcePath = getSkillPath(name);
3769
- if (!existsSync10(sourcePath))
3874
+ if (!existsSync11(sourcePath))
3770
3875
  return null;
3771
3876
  const canonicalName = getCanonicalSkillName(name);
3772
3877
  const meta = getSkill(canonicalName);
@@ -3781,7 +3886,7 @@ function generateMinimalSkillMd(name) {
3781
3886
  "---",
3782
3887
  ""
3783
3888
  ].filter(Boolean);
3784
- const fallbackDoc = readFileIfExists(join10(sourcePath, "README.md")) || readFileIfExists(join10(sourcePath, "CLAUDE.md"));
3889
+ const fallbackDoc = readFileIfExists(join11(sourcePath, "README.md")) || readFileIfExists(join11(sourcePath, "CLAUDE.md"));
3785
3890
  if (fallbackDoc)
3786
3891
  return `${frontmatter.join(`
3787
3892
  `)}${fallbackDoc.trim()}
@@ -3800,8 +3905,8 @@ skills run ${canonicalName}
3800
3905
  `;
3801
3906
  }
3802
3907
  function readBundledSkillVersion(name) {
3803
- const pkgPath = join10(getSkillPath(name), "package.json");
3804
- if (!existsSync10(pkgPath))
3908
+ const pkgPath = join11(getSkillPath(name), "package.json");
3909
+ if (!existsSync11(pkgPath))
3805
3910
  return "unknown";
3806
3911
  try {
3807
3912
  const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
@@ -3811,27 +3916,27 @@ function readBundledSkillVersion(name) {
3811
3916
  }
3812
3917
  }
3813
3918
  function readFileIfExists(path) {
3814
- return existsSync10(path) ? readFileSync9(path, "utf-8") : null;
3919
+ return existsSync11(path) ? readFileSync9(path, "utf-8") : null;
3815
3920
  }
3816
3921
  function loadProjectConfigCompat(targetDir) {
3817
3922
  return loadProjectConfig(targetDir);
3818
3923
  }
3819
3924
  // src/lib/run-state.ts
3820
3925
  import { createHash as createHash2, randomBytes } from "crypto";
3821
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync10, readdirSync as readdirSync8, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
3822
- import { extname, join as join11, relative as relative2 } from "path";
3926
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync10, readdirSync as readdirSync8, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
3927
+ import { extname, join as join12, relative as relative2 } from "path";
3823
3928
  function createSkillRun(params, targetDir = process.cwd()) {
3824
3929
  const now = new Date;
3825
3930
  const id = createRunId(now);
3826
3931
  const day = now.toISOString().slice(0, 10);
3827
3932
  const skillName = normalizeSkillName(params.skill);
3828
3933
  const root = getProjectStateDir(targetDir);
3829
- const runDir = join11(root, "runs", day, id);
3830
- const logsDir = join11(runDir, "logs");
3831
- const exportDir = join11(root, "exports", skillName, id);
3934
+ const runDir = join12(root, "runs", day, id);
3935
+ const logsDir = join12(runDir, "logs");
3936
+ const exportDir = join12(root, "exports", skillName, id);
3832
3937
  mkdirSync6(logsDir, { recursive: true });
3833
3938
  mkdirSync6(exportDir, { recursive: true });
3834
- mkdirSync6(join11(root, "tmp"), { recursive: true });
3939
+ mkdirSync6(join12(root, "tmp"), { recursive: true });
3835
3940
  const record = {
3836
3941
  id,
3837
3942
  skill: skillName,
@@ -3882,27 +3987,27 @@ function updateSkillRun(context, patch) {
3882
3987
  return context.record;
3883
3988
  }
3884
3989
  function writeRunLogs(context, stdout = "", stderr = "") {
3885
- writeFileSync6(join11(context.logsDir, "stdout.log"), stdout);
3886
- writeFileSync6(join11(context.logsDir, "stderr.log"), stderr);
3990
+ writeFileSync6(join12(context.logsDir, "stdout.log"), stdout);
3991
+ writeFileSync6(join12(context.logsDir, "stderr.log"), stderr);
3887
3992
  }
3888
3993
  function appendRunEvent(context, event, data = {}) {
3889
3994
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
3890
3995
  `;
3891
- const path = join11(context.runDir, "events.ndjson");
3892
- const previous = existsSync11(path) ? readFileSync10(path, "utf-8") : "";
3996
+ const path = join12(context.runDir, "events.ndjson");
3997
+ const previous = existsSync12(path) ? readFileSync10(path, "utf-8") : "";
3893
3998
  writeFileSync6(path, previous + line);
3894
3999
  }
3895
4000
  function listSkillRuns(targetDir = process.cwd(), limit = 50) {
3896
- const runsRoot = join11(getProjectStateDir(targetDir), "runs");
3897
- if (!existsSync11(runsRoot))
4001
+ const runsRoot = join12(getProjectStateDir(targetDir), "runs");
4002
+ if (!existsSync12(runsRoot))
3898
4003
  return [];
3899
4004
  const records = [];
3900
4005
  for (const day of readdirSync8(runsRoot).sort().reverse()) {
3901
- const dayDir = join11(runsRoot, day);
4006
+ const dayDir = join12(runsRoot, day);
3902
4007
  if (!statSync8(dayDir).isDirectory())
3903
4008
  continue;
3904
4009
  for (const runId of readdirSync8(dayDir).sort().reverse()) {
3905
- const record = readRunRecord(join11(dayDir, runId));
4010
+ const record = readRunRecord(join12(dayDir, runId));
3906
4011
  if (record)
3907
4012
  records.push(record);
3908
4013
  if (records.length >= limit)
@@ -3912,29 +4017,29 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
3912
4017
  return records;
3913
4018
  }
3914
4019
  function findSkillRun(runId, targetDir = process.cwd()) {
3915
- const runsRoot = join11(getProjectStateDir(targetDir), "runs");
3916
- if (!existsSync11(runsRoot))
4020
+ const runsRoot = join12(getProjectStateDir(targetDir), "runs");
4021
+ if (!existsSync12(runsRoot))
3917
4022
  return null;
3918
4023
  for (const day of readdirSync8(runsRoot)) {
3919
- const record = readRunRecord(join11(runsRoot, day, runId));
4024
+ const record = readRunRecord(join12(runsRoot, day, runId));
3920
4025
  if (record)
3921
4026
  return record;
3922
4027
  }
3923
4028
  return null;
3924
4029
  }
3925
4030
  function getRunExportDir(runId, skill, targetDir = process.cwd()) {
3926
- return join11(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
4031
+ return join12(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
3927
4032
  }
3928
4033
  function writeRunRecord(context) {
3929
- writeFileSync6(join11(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
4034
+ writeFileSync6(join12(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
3930
4035
  `);
3931
4036
  }
3932
4037
  function writeArtifactsManifest(context, artifacts) {
3933
- writeFileSync6(join11(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
4038
+ writeFileSync6(join12(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
3934
4039
  `);
3935
4040
  }
3936
4041
  function collectRunArtifacts(context) {
3937
- if (!existsSync11(context.exportDir))
4042
+ if (!existsSync12(context.exportDir))
3938
4043
  return [];
3939
4044
  const artifacts = [];
3940
4045
  for (const path of walkFiles(context.exportDir)) {
@@ -3950,8 +4055,8 @@ function collectRunArtifacts(context) {
3950
4055
  return artifacts.sort((a, b) => a.path.localeCompare(b.path));
3951
4056
  }
3952
4057
  function readRunRecord(runDir) {
3953
- const path = join11(runDir, "run.json");
3954
- if (!existsSync11(path))
4058
+ const path = join12(runDir, "run.json");
4059
+ if (!existsSync12(path))
3955
4060
  return null;
3956
4061
  try {
3957
4062
  return JSON.parse(readFileSync10(path, "utf-8"));
@@ -3962,7 +4067,7 @@ function readRunRecord(runDir) {
3962
4067
  function walkFiles(dir) {
3963
4068
  const files = [];
3964
4069
  for (const entry of readdirSync8(dir)) {
3965
- const full = join11(dir, entry);
4070
+ const full = join12(dir, entry);
3966
4071
  if (statSync8(full).isDirectory())
3967
4072
  files.push(...walkFiles(full));
3968
4073
  else
@@ -4008,13 +4113,13 @@ function mimeForPath(path) {
4008
4113
  }
4009
4114
  }
4010
4115
  // src/lib/skillinfo.ts
4011
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
4012
- import { join as join12 } from "path";
4116
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
4117
+ import { join as join13 } from "path";
4013
4118
  function isInstructionSkillDir(skillPath, meta) {
4014
4119
  if (meta?.kind === "instruction")
4015
4120
  return true;
4016
- const skillMdPath = join12(skillPath, "SKILL.md");
4017
- if (!existsSync12(skillMdPath))
4121
+ const skillMdPath = join13(skillPath, "SKILL.md");
4122
+ if (!existsSync13(skillMdPath))
4018
4123
  return false;
4019
4124
  try {
4020
4125
  return parseSkillFrontmatter(readFileSync11(skillMdPath, "utf-8"))?.kind === "instruction";
@@ -4040,12 +4145,12 @@ var HOSTED_PROVIDER_ENV_PREFIXES = [
4040
4145
  ];
4041
4146
  function getSkillDocs(name) {
4042
4147
  const skillPath = getSkillPath(name);
4043
- if (!existsSync12(skillPath))
4148
+ if (!existsSync13(skillPath))
4044
4149
  return null;
4045
4150
  return {
4046
- skillMd: readIfExists(join12(skillPath, "SKILL.md")),
4047
- readme: readIfExists(join12(skillPath, "README.md")),
4048
- claudeMd: readIfExists(join12(skillPath, "CLAUDE.md"))
4151
+ skillMd: readIfExists(join13(skillPath, "SKILL.md")),
4152
+ readme: readIfExists(join13(skillPath, "README.md")),
4153
+ claudeMd: readIfExists(join13(skillPath, "CLAUDE.md"))
4049
4154
  };
4050
4155
  }
4051
4156
  function getSkillBestDoc(name) {
@@ -4056,11 +4161,11 @@ function getSkillBestDoc(name) {
4056
4161
  }
4057
4162
  function getSkillRequirements(name) {
4058
4163
  const skillPath = getSkillPath(name);
4059
- if (!existsSync12(skillPath))
4164
+ if (!existsSync13(skillPath))
4060
4165
  return null;
4061
4166
  const texts = [];
4062
4167
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
4063
- const content = readIfExists(join12(skillPath, file));
4168
+ const content = readIfExists(join13(skillPath, file));
4064
4169
  if (content)
4065
4170
  texts.push(content);
4066
4171
  }
@@ -4099,8 +4204,8 @@ function getSkillRequirements(name) {
4099
4204
  const skillName = normalizeSkillName(name);
4100
4205
  let cliCommand = `skills run ${skillName}`;
4101
4206
  let dependencies = {};
4102
- const pkgPath = join12(skillPath, "package.json");
4103
- if (existsSync12(pkgPath)) {
4207
+ const pkgPath = join13(skillPath, "package.json");
4208
+ if (existsSync13(pkgPath)) {
4104
4209
  try {
4105
4210
  const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
4106
4211
  dependencies = pkg.dependencies || {};
@@ -4120,7 +4225,7 @@ async function runSkill(name, args, options = {}) {
4120
4225
  const meta = getSkill(name);
4121
4226
  const canonicalName = meta?.name ?? name;
4122
4227
  const skillPath = getSkillPath(canonicalName);
4123
- if (!existsSync12(skillPath)) {
4228
+ if (!existsSync13(skillPath)) {
4124
4229
  return { exitCode: 1, error: `Skill '${name}' not found` };
4125
4230
  }
4126
4231
  if (isInstructionSkillDir(skillPath, meta)) {
@@ -4129,8 +4234,8 @@ async function runSkill(name, args, options = {}) {
4129
4234
  error: `Skill '${name}' is an instruction skill (kind: instruction) and is not runnable. Instruction skills are consumed by coding agents via SKILL.md, not executed with 'skills run'.`
4130
4235
  };
4131
4236
  }
4132
- const pkgPath = join12(skillPath, "package.json");
4133
- if (!existsSync12(pkgPath)) {
4237
+ const pkgPath = join13(skillPath, "package.json");
4238
+ if (!existsSync13(pkgPath)) {
4134
4239
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
4135
4240
  }
4136
4241
  let entryPoint;
@@ -4149,12 +4254,12 @@ async function runSkill(name, args, options = {}) {
4149
4254
  } catch {
4150
4255
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
4151
4256
  }
4152
- const entryPath = join12(skillPath, entryPoint);
4153
- if (!existsSync12(entryPath)) {
4257
+ const entryPath = join13(skillPath, entryPoint);
4258
+ if (!existsSync13(entryPath)) {
4154
4259
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
4155
4260
  }
4156
- const nodeModules = join12(skillPath, "node_modules");
4157
- if (!existsSync12(nodeModules)) {
4261
+ const nodeModules = join13(skillPath, "node_modules");
4262
+ if (!existsSync13(nodeModules)) {
4158
4263
  const install = Bun.spawn(["bun", "install", "--no-save"], {
4159
4264
  cwd: skillPath,
4160
4265
  stdout: "pipe",
@@ -4226,7 +4331,7 @@ function generateSkillMd(name) {
4226
4331
  if (!meta)
4227
4332
  return null;
4228
4333
  const skillPath = getSkillPath(name);
4229
- if (!existsSync12(skillPath))
4334
+ if (!existsSync13(skillPath))
4230
4335
  return null;
4231
4336
  const frontmatter = [
4232
4337
  "---",
@@ -4235,11 +4340,11 @@ function generateSkillMd(name) {
4235
4340
  "---"
4236
4341
  ].join(`
4237
4342
  `);
4238
- const readme = readIfExists(join12(skillPath, "README.md"));
4239
- const claudeMd = readIfExists(join12(skillPath, "CLAUDE.md"));
4343
+ const readme = readIfExists(join13(skillPath, "README.md"));
4344
+ const claudeMd = readIfExists(join13(skillPath, "CLAUDE.md"));
4240
4345
  let cliCommand = null;
4241
- const pkgPath = join12(skillPath, "package.json");
4242
- if (existsSync12(pkgPath)) {
4346
+ const pkgPath = join13(skillPath, "package.json");
4347
+ if (existsSync13(pkgPath)) {
4243
4348
  try {
4244
4349
  const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
4245
4350
  if (pkg.bin) {
@@ -4316,7 +4421,7 @@ function extractEnvVars(text) {
4316
4421
  }
4317
4422
  function readIfExists(path) {
4318
4423
  try {
4319
- if (existsSync12(path)) {
4424
+ if (existsSync13(path)) {
4320
4425
  return readFileSync11(path, "utf-8");
4321
4426
  }
4322
4427
  } catch {}
@@ -8320,21 +8425,21 @@ function requireApiUrl(action = "This command", config, env) {
8320
8425
  }
8321
8426
 
8322
8427
  // src/lib/auth-store.ts
8323
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync7, unlinkSync } from "fs";
8324
- import { dirname as dirname6, join as join13 } from "path";
8428
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync7, unlinkSync } from "fs";
8429
+ import { dirname as dirname6, join as join14 } from "path";
8325
8430
  import { homedir as homedir4 } from "os";
8326
8431
  function getAuthFilePath() {
8327
- return join13(getDataDir(), "auth.json");
8432
+ return join14(getDataDir(), "auth.json");
8328
8433
  }
8329
8434
  function legacyAuthFilePath() {
8330
- return join13(process.env["HOME"] || process.env["USERPROFILE"] || homedir4(), ".skills", "auth.json");
8435
+ return join14(process.env["HOME"] || process.env["USERPROFILE"] || homedir4(), ".skills", "auth.json");
8331
8436
  }
8332
8437
  var cachedConfig;
8333
8438
  function getAuthConfig() {
8334
8439
  if (cachedConfig !== undefined)
8335
8440
  return cachedConfig;
8336
8441
  try {
8337
- const file = existsSync13(getAuthFilePath()) ? getAuthFilePath() : legacyAuthFilePath();
8442
+ const file = existsSync14(getAuthFilePath()) ? getAuthFilePath() : legacyAuthFilePath();
8338
8443
  const raw = readFileSync12(file, "utf-8");
8339
8444
  const config = JSON.parse(raw);
8340
8445
  if (!config.apiKey) {
@@ -9292,12 +9397,30 @@ class RemoteSkillsClient {
9292
9397
  async downloadSkillBundle(slug) {
9293
9398
  return this.request(`/api/v1/skills/${encodeURIComponent(slug)}/bundle`, { method: "GET" });
9294
9399
  }
9295
- async getBundle(slug) {
9296
- const response = await this.request(`/api/v1/skills/${encodeURIComponent(slug)}/bundle`, { method: "GET" });
9400
+ async getBundle(slug, version) {
9401
+ const path = version ? `/api/v1/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/bundle` : `/api/v1/skills/${encodeURIComponent(slug)}/bundle`;
9402
+ const response = await this.request(path, { method: "GET" });
9297
9403
  if (response.status === 404)
9298
9404
  return null;
9299
9405
  return response;
9300
9406
  }
9407
+ async listSkillVersions(slug) {
9408
+ const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/versions`, undefined, { domainNotFoundCodes: ["SKILL_NOT_FOUND"] });
9409
+ if (response.status === 404)
9410
+ return [];
9411
+ if (!response.ok)
9412
+ throw new Error(`versions request failed: ${response.status}`);
9413
+ const body = await response.json();
9414
+ return Array.isArray(body.versions) ? body.versions : [];
9415
+ }
9416
+ async getSkillVersion(slug, version) {
9417
+ const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}`, undefined, { domainNotFoundCodes: ["SKILL_NOT_FOUND", "SKILL_VERSION_NOT_FOUND"] });
9418
+ if (response.status === 404)
9419
+ return null;
9420
+ if (!response.ok)
9421
+ throw new Error(`version request failed: ${response.status}`);
9422
+ return await response.json();
9423
+ }
9301
9424
  async listPins() {
9302
9425
  const response = await this.requestNewRoute("/api/v1/pins");
9303
9426
  return normalizePinList(await response.json());
@@ -9435,14 +9558,14 @@ function createRemoteSkillsClient() {
9435
9558
  return new RemoteSkillsClient(apiKey);
9436
9559
  }
9437
9560
  // src/lib/scheduler.ts
9438
- import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
9439
- import { join as join14 } from "path";
9561
+ import { existsSync as existsSync15, readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
9562
+ import { join as join15 } from "path";
9440
9563
  function getSchedulesPath(targetDir = process.cwd()) {
9441
- return join14(targetDir, ".skills", "schedules.json");
9564
+ return join15(targetDir, ".skills", "schedules.json");
9442
9565
  }
9443
9566
  function loadSchedules(targetDir = process.cwd()) {
9444
9567
  const path = getSchedulesPath(targetDir);
9445
- if (existsSync14(path)) {
9568
+ if (existsSync15(path)) {
9446
9569
  try {
9447
9570
  return JSON.parse(readFileSync13(path, "utf-8"));
9448
9571
  } catch {}
@@ -9451,8 +9574,8 @@ function loadSchedules(targetDir = process.cwd()) {
9451
9574
  }
9452
9575
  function saveSchedules(data, targetDir = process.cwd()) {
9453
9576
  const path = getSchedulesPath(targetDir);
9454
- const dir = join14(targetDir, ".skills");
9455
- if (!existsSync14(dir))
9577
+ const dir = join15(targetDir, ".skills");
9578
+ if (!existsSync15(dir))
9456
9579
  mkdirSync8(dir, { recursive: true });
9457
9580
  writeFileSync8(path, JSON.stringify(data, null, 2));
9458
9581
  }
@@ -9639,8 +9762,8 @@ function recordScheduleRun(id, status, targetDir) {
9639
9762
  saveSchedules(data, targetDir);
9640
9763
  }
9641
9764
  // src/lib/pull.ts
9642
- import { existsSync as existsSync15, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
9643
- import { dirname as dirname7, join as join15 } from "path";
9765
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readFileSync as readFileSync15, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
9766
+ import { dirname as dirname7, join as join17 } from "path";
9644
9767
 
9645
9768
  // src/lib/revision.ts
9646
9769
  import { createHash as createHash3 } from "crypto";
@@ -9667,6 +9790,8 @@ function revisionIdOfRecord(record) {
9667
9790
 
9668
9791
  // src/lib/skill-bundle.ts
9669
9792
  import { createHash as createHash4 } from "crypto";
9793
+ import { readFileSync as readFileSync14, readdirSync as readdirSync9, statSync as statSync9 } from "fs";
9794
+ import { join as join16, relative as relative3 } from "path";
9670
9795
  var BLOCK = 512;
9671
9796
  var ANY_SEGMENT_EXCLUDES = new Set([
9672
9797
  ".git",
@@ -9693,6 +9818,7 @@ var CREDENTIAL_FILENAMES = new Set([
9693
9818
  "id_ecdsa",
9694
9819
  "id_ed25519"
9695
9820
  ]);
9821
+ var CREDENTIAL_EXTENSIONS = [".pem", ".key", ".p12", ".pfx", ".keystore", ".jks"];
9696
9822
  var ENV_TEMPLATE_NAMES = new Set([".env.example", ".env.sample", ".env.template", ".env.dist"]);
9697
9823
  var NON_DOTENV_EXTENSIONS = new Set([
9698
9824
  "ts",
@@ -9789,6 +9915,27 @@ var NON_DOTENV_EXTENSIONS = new Set([
9789
9915
  "tar",
9790
9916
  "wasm"
9791
9917
  ]);
9918
+ function isDotenvFile(lower) {
9919
+ if (lower === ".env" || lower.startsWith(".env."))
9920
+ return true;
9921
+ if (lower === "env")
9922
+ return true;
9923
+ if (lower.startsWith("env.")) {
9924
+ const extension = lower.slice(lower.lastIndexOf(".") + 1);
9925
+ return !NON_DOTENV_EXTENSIONS.has(extension);
9926
+ }
9927
+ return false;
9928
+ }
9929
+ function isCredentialFile(name) {
9930
+ const lower = name.toLowerCase();
9931
+ if (ENV_TEMPLATE_NAMES.has(lower))
9932
+ return false;
9933
+ if (isDotenvFile(lower))
9934
+ return true;
9935
+ if (CREDENTIAL_FILENAMES.has(lower))
9936
+ return true;
9937
+ return CREDENTIAL_EXTENSIONS.some((extension) => lower.endsWith(extension));
9938
+ }
9792
9939
  function ownBytes(view) {
9793
9940
  const source = view instanceof ArrayBuffer ? new Uint8Array(view) : view;
9794
9941
  const out = new Uint8Array(new ArrayBuffer(source.byteLength));
@@ -9798,9 +9945,117 @@ function ownBytes(view) {
9798
9945
  function sha256Hex(bytes) {
9799
9946
  return createHash4("sha256").update(bytes).digest("hex");
9800
9947
  }
9948
+ function collectSkillBundleEntries(dir) {
9949
+ const entries = [];
9950
+ walk(dir, dir, entries);
9951
+ return entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
9952
+ }
9953
+ function walk(root, current, out) {
9954
+ for (const entry of readdirSync9(current, { withFileTypes: true })) {
9955
+ const absolute = join16(current, entry.name);
9956
+ const rel = relative3(root, absolute).split("\\").join("/");
9957
+ const isRootLevel = !rel.includes("/");
9958
+ if (ANY_SEGMENT_EXCLUDES.has(entry.name.toLowerCase()))
9959
+ continue;
9960
+ if (isRootLevel && ROOT_EXCLUDES.has(entry.name.toLowerCase()))
9961
+ continue;
9962
+ if (isRootLevel && TOOL_SIDECAR_FILENAMES.has(entry.name.toLowerCase()))
9963
+ continue;
9964
+ if (entry.name.startsWith("._"))
9965
+ continue;
9966
+ if (entry.isSymbolicLink())
9967
+ continue;
9968
+ if (entry.isDirectory()) {
9969
+ walk(root, absolute, out);
9970
+ continue;
9971
+ }
9972
+ if (!entry.isFile())
9973
+ continue;
9974
+ if (isCredentialFile(entry.name))
9975
+ continue;
9976
+ const stats = statSync9(absolute);
9977
+ out.push({
9978
+ path: rel,
9979
+ bytes: ownBytes(readFileSync14(absolute)),
9980
+ mode: stats.mode & 64 ? 493 : 420
9981
+ });
9982
+ }
9983
+ }
9984
+ function packSkillBundle(dir, options = {}) {
9985
+ const entries = collectSkillBundleEntries(dir);
9986
+ if (entries.length === 0) {
9987
+ throw new Error(`Nothing to pack: ${dir} contains no files after exclusions (.git, node_modules, dist, .env)`);
9988
+ }
9989
+ const unpackedByteSize = entries.reduce((sum, entry) => sum + entry.bytes.byteLength, 0);
9990
+ const max = options.maxUnpackedBytes ?? 0;
9991
+ if (max > 0 && unpackedByteSize > max) {
9992
+ throw new Error(`Skill sources are ${unpackedByteSize} bytes, over the ${max} byte limit. Remove build output or large fixtures.`);
9993
+ }
9994
+ const tar = writeTar(entries);
9995
+ const bytes = canonicalGzip(tar);
9996
+ return {
9997
+ bytes,
9998
+ sha256: sha256Hex(bytes),
9999
+ fileCount: entries.length,
10000
+ unpackedByteSize,
10001
+ paths: entries.map((entry) => entry.path)
10002
+ };
10003
+ }
10004
+ function canonicalGzip(tar) {
10005
+ const bytes = ownBytes(Bun.gzipSync(tar, { level: 6 }));
10006
+ if (bytes.byteLength >= 10) {
10007
+ bytes[4] = 0;
10008
+ bytes[5] = 0;
10009
+ bytes[6] = 0;
10010
+ bytes[7] = 0;
10011
+ bytes[9] = 255;
10012
+ }
10013
+ return bytes;
10014
+ }
9801
10015
  function unpackSkillBundle(bundle) {
9802
10016
  return readTar(ownBytes(Bun.gunzipSync(ownBytes(bundle))));
9803
10017
  }
10018
+ function writeTar(entries) {
10019
+ const blocks = [];
10020
+ for (const entry of entries) {
10021
+ blocks.push(ustarHeader(entry));
10022
+ blocks.push(entry.bytes);
10023
+ const remainder = entry.bytes.byteLength % BLOCK;
10024
+ if (remainder !== 0)
10025
+ blocks.push(new Uint8Array(new ArrayBuffer(BLOCK - remainder)));
10026
+ }
10027
+ blocks.push(new Uint8Array(new ArrayBuffer(BLOCK * 2)));
10028
+ return concat(blocks);
10029
+ }
10030
+ function ustarHeader(entry) {
10031
+ const header = new Uint8Array(new ArrayBuffer(BLOCK));
10032
+ const encoder = new TextEncoder;
10033
+ const put = (offset, length, value) => {
10034
+ const encoded = encoder.encode(value);
10035
+ if (encoded.byteLength > length) {
10036
+ throw new Error(`Cannot pack '${entry.path}': field does not fit in a ustar header (${encoded.byteLength} > ${length})`);
10037
+ }
10038
+ header.set(encoded, offset);
10039
+ };
10040
+ if (encoder.encode(entry.path).byteLength > 100) {
10041
+ throw new Error(`Cannot pack '${entry.path}': path is longer than the 100 bytes a ustar header holds`);
10042
+ }
10043
+ put(0, 100, entry.path);
10044
+ put(100, 8, `${entry.mode.toString(8).padStart(7, "0")}\x00`);
10045
+ put(108, 8, "0000000\x00");
10046
+ put(116, 8, "0000000\x00");
10047
+ put(124, 12, `${entry.bytes.byteLength.toString(8).padStart(11, "0")}\x00`);
10048
+ put(136, 12, `${0 .toString(8).padStart(11, "0")}\x00`);
10049
+ put(148, 8, " ");
10050
+ put(156, 1, "0");
10051
+ put(257, 6, "ustar\x00");
10052
+ put(263, 2, "00");
10053
+ let checksum = 0;
10054
+ for (const byte of header)
10055
+ checksum += byte;
10056
+ put(148, 8, `${checksum.toString(8).padStart(6, "0")}\x00 `);
10057
+ return header;
10058
+ }
9804
10059
  function readTar(tar) {
9805
10060
  const decoder = new TextDecoder;
9806
10061
  const entries = [];
@@ -9849,6 +10104,23 @@ function trimNul(value) {
9849
10104
  const end = value.indexOf("\x00");
9850
10105
  return end === -1 ? value : value.slice(0, end);
9851
10106
  }
10107
+ function concat(chunks) {
10108
+ const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
10109
+ const merged = new Uint8Array(new ArrayBuffer(total));
10110
+ let offset = 0;
10111
+ for (const chunk of chunks) {
10112
+ merged.set(chunk, offset);
10113
+ offset += chunk.byteLength;
10114
+ }
10115
+ return merged;
10116
+ }
10117
+
10118
+ // src/lib/skill-version.ts
10119
+ var SKILL_VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
10120
+ function isValidSkillVersion(value) {
10121
+ return typeof value === "string" && SKILL_VERSION_PATTERN.test(value) && !/^\.+$/.test(value) && !value.includes("..");
10122
+ }
10123
+ var SKILL_VERSION_RULE = "1-128 characters: letters, digits, '.', '_', '+', '-'; must start with a letter or digit; no '..'";
9852
10124
 
9853
10125
  // src/lib/skill-bundles.ts
9854
10126
  import { createHmac, timingSafeEqual } from "crypto";
@@ -9913,25 +10185,36 @@ async function resolveTargetSlugs(client, options) {
9913
10185
  return explicit;
9914
10186
  }
9915
10187
  async function pullOne(client, rawName, corpusOptions, verify) {
10188
+ const { name: bareName, version: requestedVersion } = splitNameVersion(rawName);
9916
10189
  let slug;
9917
10190
  try {
9918
- slug = normalizePortableSkillName(rawName);
10191
+ slug = normalizePortableSkillName(bareName);
9919
10192
  } catch (error) {
9920
10193
  return { name: rawName, success: false, error: error.message };
9921
10194
  }
10195
+ if (requestedVersion !== undefined && !isValidSkillVersion(requestedVersion)) {
10196
+ return { name: rawName, success: false, error: `'${requestedVersion}' is not a valid skill version (${SKILL_VERSION_RULE}).` };
10197
+ }
9922
10198
  const meta = await safeMeta(client, slug);
9923
10199
  let bundleResponse;
9924
10200
  try {
9925
- bundleResponse = await client.getBundle(slug);
10201
+ bundleResponse = await client.getBundle(slug, requestedVersion);
9926
10202
  } catch (error) {
9927
10203
  return { name: slug, success: false, error: `Failed to fetch '${slug}': ${error.message}` };
9928
10204
  }
10205
+ if (requestedVersion && (!bundleResponse || bundleResponse.status === 404)) {
10206
+ return {
10207
+ name: rawName,
10208
+ success: false,
10209
+ error: `Version '${requestedVersion}' of '${slug}' is not published on the configured instance (run 'skills versions ${slug}' to list what exists).`
10210
+ };
10211
+ }
9929
10212
  if (bundleResponse && !bundleResponse.ok) {
9930
10213
  if (bundleResponse.status === 410) {
9931
10214
  return reconcileTombstone(slug, corpusOptions);
9932
10215
  }
9933
10216
  if (bundleResponse.status === 404) {
9934
- const marker = readPullMarker(join15(getPortableSkillsRoot(corpusOptions), slug));
10217
+ const marker = readPullMarker(join17(getPortableSkillsRoot(corpusOptions), slug));
9935
10218
  if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
9936
10219
  return { name: slug, success: true, purged: true, removed: false };
9937
10220
  }
@@ -9944,7 +10227,15 @@ async function pullOne(client, rawName, corpusOptions, verify) {
9944
10227
  }
9945
10228
  if (bundleResponse) {
9946
10229
  try {
9947
- return await installVerifiedBundle(slug, bundleResponse, meta, corpusOptions, verify);
10230
+ let expectedSha256;
10231
+ if (requestedVersion && client.getSkillVersion) {
10232
+ const recorded = await client.getSkillVersion(slug, requestedVersion);
10233
+ if (!recorded) {
10234
+ return { name: rawName, success: false, error: `Version '${requestedVersion}' of '${slug}' is not published on the configured instance (run 'skills versions ${slug}' to list what exists).` };
10235
+ }
10236
+ expectedSha256 = typeof recorded.bundleSha256 === "string" ? recorded.bundleSha256 : undefined;
10237
+ }
10238
+ return await installVerifiedBundle(slug, bundleResponse, meta, corpusOptions, verify, requestedVersion ? { version: requestedVersion, expectedSha256 } : undefined);
9948
10239
  } catch (error) {
9949
10240
  if (error instanceof PullSkillError) {
9950
10241
  return { name: slug, success: false, error: error.message };
@@ -9962,7 +10253,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
9962
10253
  return { name: slug, success: false, error: `Skill '${slug}' was not found on the configured Skills instance.` };
9963
10254
  }
9964
10255
  if (!meta?.revisionId) {
9965
- const marker = readPullMarker(join15(getPortableSkillsRoot(corpusOptions), slug));
10256
+ const marker = readPullMarker(join17(getPortableSkillsRoot(corpusOptions), slug));
9966
10257
  if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
9967
10258
  return { name: slug, success: true, purged: true, removed: false };
9968
10259
  }
@@ -10019,8 +10310,8 @@ function provenRevision(meta, slug, bundle) {
10019
10310
  return declared;
10020
10311
  }
10021
10312
  function reconcileTombstone(slug, corpusOptions) {
10022
- const target = join15(getPortableSkillsRoot(corpusOptions), slug);
10023
- if (!existsSync15(join15(target, PULL_MARKER_FILE))) {
10313
+ const target = join17(getPortableSkillsRoot(corpusOptions), slug);
10314
+ if (!existsSync16(join17(target, PULL_MARKER_FILE))) {
10024
10315
  return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
10025
10316
  }
10026
10317
  rmSync4(target, { recursive: true, force: true });
@@ -10028,23 +10319,30 @@ function reconcileTombstone(slug, corpusOptions) {
10028
10319
  }
10029
10320
  function readPullMarker(dir) {
10030
10321
  try {
10031
- return JSON.parse(readFileSync14(join15(dir, PULL_MARKER_FILE), "utf-8"));
10322
+ return JSON.parse(readFileSync15(join17(dir, PULL_MARKER_FILE), "utf-8"));
10032
10323
  } catch {
10033
10324
  return null;
10034
10325
  }
10035
10326
  }
10036
- function installVerifiedBundle(slug, response, meta, corpusOptions, verify) {
10327
+ function installVerifiedBundle(slug, response, meta, corpusOptions, verify, exact) {
10037
10328
  return response.arrayBuffer().then((buffer) => {
10038
10329
  const verified = verifyBundleResponseBytes(buffer, response, verify);
10330
+ if (exact?.expectedSha256 && exact.expectedSha256 !== verified.contentHash) {
10331
+ throw new PullSkillError(`Digest proof failed for '${slug}@${exact.version}': the registry records bundle ${exact.expectedSha256.slice(0, 12)}\u2026 but the received bytes hash to ${verified.contentHash.slice(0, 12)}\u2026. Nothing was installed.`);
10332
+ }
10333
+ const served = str(response.headers.get("X-Skill-Version"));
10334
+ if (exact && served && served !== exact.version) {
10335
+ throw new PullSkillError(`Version proof failed for '${slug}': asked for '${exact.version}', the instance served '${served}'. Nothing was installed.`);
10336
+ }
10039
10337
  let entries;
10040
10338
  try {
10041
10339
  entries = unpackSkillBundle(verified.bytes);
10042
10340
  } catch (error) {
10043
10341
  throw new PullSkillError(`Bundle for '${slug}' could not be unpacked: ${error.message}`);
10044
10342
  }
10045
- const version = str(meta?.version) ?? versionFromEntries(entries) ?? "unknown";
10343
+ const version = exact?.version ?? served ?? str(meta?.version) ?? versionFromEntries(entries) ?? "unknown";
10046
10344
  const sourceCommit = sourceCommitFromEntries(entries);
10047
- const declaredRevision = verified.revisionId ?? meta?.revisionId;
10345
+ const declaredRevision = exact ? undefined : verified.revisionId ?? meta?.revisionId;
10048
10346
  if (declaredRevision && meta?.revisionId && verified.revisionId && declaredRevision !== meta.revisionId) {
10049
10347
  throw new PullSkillError(`Revision proof failed for '${slug}': the bundle header declares '${verified.revisionId.slice(0, 12)}\u2026' but the metadata declares '${meta.revisionId.slice(0, 12)}\u2026'. The instance is inconsistent. Nothing was installed.`);
10050
10348
  }
@@ -10149,14 +10447,14 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
10149
10447
  function installBundleAtomically(name, entries, options = {}, marker = {}) {
10150
10448
  const root = getPortableSkillsRoot(options);
10151
10449
  mkdirSync9(root, { recursive: true });
10152
- const target = join15(root, name);
10153
- const created = !existsSync15(target);
10154
- const staging = mkdtempSync3(join15(root, `.pull-${name}-`));
10450
+ const target = join17(root, name);
10451
+ const created = !existsSync16(target);
10452
+ const staging = mkdtempSync3(join17(root, `.pull-${name}-`));
10155
10453
  let moved = false;
10156
10454
  let backup = null;
10157
10455
  try {
10158
10456
  for (const entry of entries) {
10159
- const destination = join15(staging, entry.path);
10457
+ const destination = join17(staging, entry.path);
10160
10458
  mkdirSync9(dirname7(destination), { recursive: true });
10161
10459
  writeFileSync9(destination, entry.bytes, { mode: entry.mode });
10162
10460
  }
@@ -10168,9 +10466,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
10168
10466
  ...marker.signature ? { signature: marker.signature } : {},
10169
10467
  ...marker.revisionId ? { revisionId: marker.revisionId } : {}
10170
10468
  });
10171
- if (existsSync15(target)) {
10172
- backup = mkdtempSync3(join15(root, `.pull-backup-${name}-`));
10173
- renameSync3(target, join15(backup, name));
10469
+ if (existsSync16(target)) {
10470
+ backup = mkdtempSync3(join17(root, `.pull-backup-${name}-`));
10471
+ renameSync3(target, join17(backup, name));
10174
10472
  moved = true;
10175
10473
  }
10176
10474
  renameSync3(staging, target);
@@ -10178,9 +10476,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
10178
10476
  rmSync4(backup, { recursive: true, force: true });
10179
10477
  } catch (error) {
10180
10478
  rmSync4(staging, { recursive: true, force: true });
10181
- if (moved && backup && existsSync15(join15(backup, name))) {
10479
+ if (moved && backup && existsSync16(join17(backup, name))) {
10182
10480
  try {
10183
- renameSync3(join15(backup, name), target);
10481
+ renameSync3(join17(backup, name), target);
10184
10482
  } catch {}
10185
10483
  }
10186
10484
  throw error;
@@ -10199,7 +10497,7 @@ function writePullMarker(dir, record) {
10199
10497
  ...record.revisionId ? { revisionId: record.revisionId } : {},
10200
10498
  syncedAt: new Date().toISOString()
10201
10499
  };
10202
- writeFileSync9(join15(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
10500
+ writeFileSync9(join17(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
10203
10501
  `);
10204
10502
  }
10205
10503
  async function safeMeta(client, slug) {
@@ -10241,6 +10539,15 @@ function dedupe(values) {
10241
10539
  function str(value) {
10242
10540
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
10243
10541
  }
10542
+ function splitNameVersion(raw) {
10543
+ const trimmed = raw.trim();
10544
+ const at = trimmed.indexOf("@");
10545
+ if (at <= 0)
10546
+ return { name: trimmed };
10547
+ const name = trimmed.slice(0, at);
10548
+ const version = trimmed.slice(at + 1);
10549
+ return version ? { name, version } : { name };
10550
+ }
10244
10551
  // src/lib/cli-mcp-parity.ts
10245
10552
  var SKILLS_CLI_MCP_PARITY = [
10246
10553
  {
@@ -10346,11 +10653,11 @@ function findSkillsParityForMcpTool(tool) {
10346
10653
  }
10347
10654
  // src/lib/registry-sync.ts
10348
10655
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
10349
- import { dirname as dirname8, relative as relative3 } from "path";
10656
+ import { dirname as dirname8, relative as relative4 } from "path";
10350
10657
  // package.json
10351
10658
  var package_default = {
10352
10659
  name: "@hasna/skills",
10353
- version: "0.1.71",
10660
+ version: "0.2.0",
10354
10661
  description: "Skills library for AI coding agents",
10355
10662
  type: "module",
10356
10663
  bin: {
@@ -10430,7 +10737,7 @@ var package_default = {
10430
10737
  author: "Hasna",
10431
10738
  license: "Apache-2.0",
10432
10739
  devDependencies: {
10433
- "@types/bun": "latest",
10740
+ "@types/bun": "1.3.14",
10434
10741
  "@types/node": "25.2.3",
10435
10742
  "@types/react": "^18.2.0",
10436
10743
  "bun-types": "1.3.14",
@@ -10481,7 +10788,7 @@ function createRegistrySyncArtifact(options = {}) {
10481
10788
  const registry = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
10482
10789
  const skills = registry.map((skill) => {
10483
10790
  const skillPath = getSkillPath(skill.name);
10484
- const directory = relative3(process.cwd(), skillPath) || skillPath;
10791
+ const directory = relative4(process.cwd(), skillPath) || skillPath;
10485
10792
  const validation = includeValidation ? validateSkillDirectory(skill.name, skillPath, skill) : undefined;
10486
10793
  const docs = includeDocs ? buildDocs(skill.name) : undefined;
10487
10794
  return {
@@ -11366,16 +11673,16 @@ function clone2(value) {
11366
11673
  return JSON.parse(JSON.stringify(value));
11367
11674
  }
11368
11675
  // src/lib/feedback.ts
11369
- import { existsSync as existsSync16, mkdirSync as mkdirSync11 } from "fs";
11370
- import { dirname as dirname9, join as join16 } from "path";
11676
+ import { appendFileSync, existsSync as existsSync17, mkdirSync as mkdirSync11 } from "fs";
11677
+ import { dirname as dirname9, join as join18 } from "path";
11371
11678
  import { Database } from "bun:sqlite";
11372
11679
  function getFeedbackDbPath() {
11373
- return join16(getDataDir(), "skills.db");
11680
+ return join18(getDataDir(), "skills.db");
11374
11681
  }
11375
11682
  function getFeedbackDb() {
11376
11683
  const dbPath = getFeedbackDbPath();
11377
11684
  const dir = dirname9(dbPath);
11378
- if (!existsSync16(dir))
11685
+ if (!existsSync17(dir))
11379
11686
  mkdirSync11(dir, { recursive: true });
11380
11687
  const db = new Database(dbPath);
11381
11688
  db.exec("PRAGMA journal_mode = WAL");
@@ -11401,6 +11708,15 @@ function saveFeedback(input) {
11401
11708
  if (!message)
11402
11709
  throw new Error("Feedback message is required");
11403
11710
  const category = input.category ?? "general";
11711
+ if (isApiMode()) {
11712
+ const path = join18(getDataDir(), "feedback.jsonl");
11713
+ const dir = dirname9(path);
11714
+ if (!existsSync17(dir))
11715
+ mkdirSync11(dir, { recursive: true });
11716
+ appendFileSync(path, JSON.stringify({ message, category, email: input.email ?? null, agent: input.agent ?? null, version: input.version ?? null, createdAt: new Date().toISOString() }) + `
11717
+ `);
11718
+ return { saved: true, category, path };
11719
+ }
11404
11720
  const db = getFeedbackDb();
11405
11721
  try {
11406
11722
  db.run("INSERT INTO feedback (message, email, category, agent, version) VALUES (?, ?, ?, ?, ?)", [message, input.email || null, category, input.agent || null, input.version || null]);
@@ -11409,17 +11725,26 @@ function saveFeedback(input) {
11409
11725
  }
11410
11726
  return { saved: true, category, path: getFeedbackDbPath() };
11411
11727
  }
11728
+ function isApiMode(env = process.env) {
11729
+ if (env.HASNA_SKILLS_API_URL?.trim())
11730
+ return true;
11731
+ try {
11732
+ return Boolean(resolveApiUrl(undefined, env));
11733
+ } catch {
11734
+ return Boolean(env.SKILLS_API_URL?.trim());
11735
+ }
11736
+ }
11412
11737
  // src/lib/native-storage.ts
11413
11738
  import { createHash as createHash5, createHmac as createHmac2 } from "crypto";
11414
11739
  import {
11415
- existsSync as existsSync17,
11740
+ existsSync as existsSync18,
11416
11741
  mkdirSync as mkdirSync12,
11417
- readFileSync as readFileSync15,
11418
- readdirSync as readdirSync9,
11419
- statSync as statSync9,
11742
+ readFileSync as readFileSync16,
11743
+ readdirSync as readdirSync10,
11744
+ statSync as statSync10,
11420
11745
  writeFileSync as writeFileSync11
11421
11746
  } from "fs";
11422
- import { dirname as dirname10, join as join17, normalize as normalize3, relative as relative4, sep as sep2 } from "path";
11747
+ import { dirname as dirname10, join as join19, normalize as normalize3, relative as relative5, sep as sep2 } from "path";
11423
11748
  var SKILLS_STORAGE_TABLES = [
11424
11749
  "skills_sync_records",
11425
11750
  "skills_sync_cursors"
@@ -11556,7 +11881,7 @@ function getSkillsNativeStorageStatus(options = {}) {
11556
11881
  local: {
11557
11882
  dataDir: getDataDir(),
11558
11883
  projectStateDir: getProjectStateDir(targetDir),
11559
- feedbackDbPath: join17(getDataDir(), "skills.db")
11884
+ feedbackDbPath: join19(getDataDir(), "skills.db")
11560
11885
  },
11561
11886
  remote: {
11562
11887
  databaseConfigured: Boolean(config.databaseUrl),
@@ -11579,10 +11904,10 @@ function getStorageStatus(options = {}) {
11579
11904
  function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
11580
11905
  const projectStateDir = getProjectStateDir(targetDir);
11581
11906
  const files = [];
11582
- if (existsSync17(projectStateDir)) {
11907
+ if (existsSync18(projectStateDir)) {
11583
11908
  for (const filePath of walkFiles2(projectStateDir)) {
11584
- const bytes = readFileSync15(filePath);
11585
- const relativePath = toPosix(relative4(targetDir, filePath));
11909
+ const bytes = readFileSync16(filePath);
11910
+ const relativePath = toPosix(relative5(targetDir, filePath));
11586
11911
  files.push({
11587
11912
  path: relativePath,
11588
11913
  sizeBytes: bytes.byteLength,
@@ -11606,7 +11931,7 @@ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options
11606
11931
  continue;
11607
11932
  }
11608
11933
  const absolutePath = resolveSnapshotPath(targetDir, file.path);
11609
- if (existsSync17(absolutePath) && !options.overwrite) {
11934
+ if (existsSync18(absolutePath) && !options.overwrite) {
11610
11935
  skipped += 1;
11611
11936
  continue;
11612
11937
  }
@@ -11912,9 +12237,9 @@ function parsePositiveInteger(value) {
11912
12237
  }
11913
12238
  function walkFiles2(dir) {
11914
12239
  const files = [];
11915
- for (const entry of readdirSync9(dir)) {
11916
- const full = join17(dir, entry);
11917
- const stats = statSync9(full);
12240
+ for (const entry of readdirSync10(dir)) {
12241
+ const full = join19(dir, entry);
12242
+ const stats = statSync10(full);
11918
12243
  if (stats.isDirectory())
11919
12244
  files.push(...walkFiles2(full));
11920
12245
  else
@@ -11930,7 +12255,7 @@ function resolveSnapshotPath(targetDir, snapshotPath) {
11930
12255
  if (!toPosix(normalizedPath).startsWith(".skills/")) {
11931
12256
  throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
11932
12257
  }
11933
- return join17(targetDir, normalizedPath);
12258
+ return join19(targetDir, normalizedPath);
11934
12259
  }
11935
12260
  function normalizeS3Prefix(prefix) {
11936
12261
  return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
@@ -11995,16 +12320,16 @@ import { createHash as createHash6 } from "crypto";
11995
12320
  import {
11996
12321
  copyFileSync as copyFileSync2,
11997
12322
  mkdirSync as mkdirSync13,
11998
- readFileSync as readFileSync16,
11999
- statSync as statSync11,
12323
+ readFileSync as readFileSync17,
12324
+ statSync as statSync12,
12000
12325
  writeFileSync as writeFileSync12
12001
12326
  } from "fs";
12002
- import { dirname as dirname11, isAbsolute as isAbsolute3, relative as relative5, resolve, sep as sep4 } from "path";
12327
+ import { dirname as dirname11, isAbsolute as isAbsolute3, relative as relative6, resolve as resolve2, sep as sep4 } from "path";
12003
12328
 
12004
12329
  // src/lib/portable-snapshot-filter.ts
12005
- import { readdirSync as readdirSync10, statSync as statSync10 } from "fs";
12330
+ import { readdirSync as readdirSync11, statSync as statSync11 } from "fs";
12006
12331
  import { homedir as homedir5 } from "os";
12007
- import { join as join18, sep as sep3 } from "path";
12332
+ import { join as join20, sep as sep3 } from "path";
12008
12333
  var SYNC_HOMES = [
12009
12334
  { name: "skills", subClass: "skills", agent: null },
12010
12335
  { name: "custom", subClass: "custom", agent: null },
@@ -12097,27 +12422,27 @@ function isPortableWithinSkill(relativeParts) {
12097
12422
  function homePathFor(definition, homesRoot) {
12098
12423
  const home = homesRoot ?? homedir5();
12099
12424
  if (definition.subClass === "skills" || definition.subClass === "custom") {
12100
- return join18(home, ".hasna", "skills", definition.name);
12425
+ return join20(skillsDataRootForHome(home), definition.name);
12101
12426
  }
12102
12427
  if (definition.agent === "opencode") {
12103
- return join18(home, ".config", "opencode", "skills");
12428
+ return join20(home, ".config", "opencode", "skills");
12104
12429
  }
12105
- return join18(home, `.${definition.agent}`, "skills");
12430
+ return join20(home, `.${definition.agent}`, "skills");
12106
12431
  }
12107
12432
  function destinationFor(definition, stationId, relativePath) {
12108
- const category = definition.subClass === "agent-homes" ? join18("agent-homes", definition.agent ?? "") : definition.name;
12109
- return join18("resources", stationId, "skills", category, ...relativePath.split(sep3));
12433
+ const category = definition.subClass === "agent-homes" ? join20("agent-homes", definition.agent ?? "") : definition.name;
12434
+ return join20("resources", stationId, "skills", category, ...relativePath.split(sep3));
12110
12435
  }
12111
12436
  function walkEntries(absoluteRoot) {
12112
12437
  let entries;
12113
12438
  try {
12114
- entries = readdirSync10(absoluteRoot, { withFileTypes: true });
12439
+ entries = readdirSync11(absoluteRoot, { withFileTypes: true });
12115
12440
  } catch {
12116
12441
  return [];
12117
12442
  }
12118
12443
  const output = [];
12119
12444
  for (const entry of entries) {
12120
- const childFull = join18(absoluteRoot, entry.name);
12445
+ const childFull = join20(absoluteRoot, entry.name);
12121
12446
  if (entry.isSymbolicLink()) {
12122
12447
  output.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
12123
12448
  continue;
@@ -12128,7 +12453,7 @@ function walkEntries(absoluteRoot) {
12128
12453
  }
12129
12454
  const nested = walkEntries(childFull);
12130
12455
  for (const item of nested) {
12131
- output.push({ ...item, relativePath: join18(entry.name, item.relativePath) });
12456
+ output.push({ ...item, relativePath: join20(entry.name, item.relativePath) });
12132
12457
  }
12133
12458
  continue;
12134
12459
  }
@@ -12140,7 +12465,7 @@ function walkEntries(absoluteRoot) {
12140
12465
  }
12141
12466
  function isRegularFile(filePath) {
12142
12467
  try {
12143
- return statSync10(filePath).isFile();
12468
+ return statSync11(filePath).isFile();
12144
12469
  } catch {
12145
12470
  return false;
12146
12471
  }
@@ -12166,7 +12491,7 @@ function validateStationId(stationId) {
12166
12491
  }
12167
12492
  }
12168
12493
  function sha256File(filePath) {
12169
- return createHash6("sha256").update(readFileSync16(filePath)).digest("hex");
12494
+ return createHash6("sha256").update(readFileSync17(filePath)).digest("hex");
12170
12495
  }
12171
12496
  function scanHome(definition, homesRoot) {
12172
12497
  const homePath = homePathFor(definition, homesRoot);
@@ -12196,7 +12521,7 @@ function scanHome(definition, homesRoot) {
12196
12521
  skipped.push({ relativePath: entry.relativePath, reason: "not-regular-file" });
12197
12522
  continue;
12198
12523
  }
12199
- const info = statSync11(entry.fullPath);
12524
+ const info = statSync12(entry.fullPath);
12200
12525
  portable.push({
12201
12526
  relativePath: entry.relativePath,
12202
12527
  fullPath: entry.fullPath,
@@ -12237,7 +12562,7 @@ function humanHomes(scanned) {
12237
12562
  }));
12238
12563
  }
12239
12564
  function writeStationSnapshot(options) {
12240
- const repoRoot = resolve(options.repoRoot ?? process.cwd());
12565
+ const repoRoot = resolve2(options.repoRoot ?? process.cwd());
12241
12566
  const { scanned, plans, totalBytes } = planStationSnapshot(options);
12242
12567
  const manifestFiles = plans.map((plan) => ({
12243
12568
  relativePath: plan.source.relativePath,
@@ -12263,8 +12588,8 @@ function writeStationSnapshot(options) {
12263
12588
  const conflicts = [];
12264
12589
  const untouched = [];
12265
12590
  for (const plan of plans) {
12266
- const destination = resolve(repoRoot, plan.destination);
12267
- const destinationRelative = relative5(repoRoot, destination);
12591
+ const destination = resolve2(repoRoot, plan.destination);
12592
+ const destinationRelative = relative6(repoRoot, destination);
12268
12593
  if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep4) || isAbsolute3(destinationRelative)) {
12269
12594
  throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
12270
12595
  }
@@ -12286,7 +12611,7 @@ function writeStationSnapshot(options) {
12286
12611
  }
12287
12612
  let written = 0;
12288
12613
  for (const plan of untouched) {
12289
- const destination = resolve(repoRoot, plan.destination);
12614
+ const destination = resolve2(repoRoot, plan.destination);
12290
12615
  mkdirSync13(dirname11(destination), { recursive: true });
12291
12616
  copyFileSync2(plan.source.fullPath, destination);
12292
12617
  written += 1;
@@ -12305,7 +12630,7 @@ function writeStationSnapshot(options) {
12305
12630
  },
12306
12631
  files: manifestFiles
12307
12632
  };
12308
- const manifestPath = resolve(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
12633
+ const manifestPath = resolve2(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
12309
12634
  mkdirSync13(dirname11(manifestPath), { recursive: true });
12310
12635
  writeFileSync12(manifestPath, `${JSON.stringify(manifest, null, 2)}
12311
12636
  `);
@@ -12321,12 +12646,12 @@ import { createHash as createHash7 } from "crypto";
12321
12646
  import {
12322
12647
  copyFileSync as copyFileSync3,
12323
12648
  mkdirSync as mkdirSync14,
12324
- readdirSync as readdirSync11,
12325
- readFileSync as readFileSync17,
12326
- statSync as statSync12,
12649
+ readdirSync as readdirSync12,
12650
+ readFileSync as readFileSync18,
12651
+ statSync as statSync13,
12327
12652
  writeFileSync as writeFileSync13
12328
12653
  } from "fs";
12329
- import { dirname as dirname12, join as join19, resolve as resolve2, sep as sep5 } from "path";
12654
+ import { dirname as dirname12, join as join21, resolve as resolve3, sep as sep5 } from "path";
12330
12655
  var STATION_HYDRATION_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-hydration-manifest/v1";
12331
12656
  var STATION_HYDRATION_PRODUCER = { name: "@hasna/skills", version: package_default.version };
12332
12657
  var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
@@ -12334,14 +12659,14 @@ function fail(code, message, detail = []) {
12334
12659
  throw new StationSnapshotError(code, message, detail);
12335
12660
  }
12336
12661
  function snapshotRootFor(repoRoot, stationId) {
12337
- return join19(repoRoot, "resources", stationId, "skills");
12662
+ return join21(repoRoot, "resources", stationId, "skills");
12338
12663
  }
12339
12664
  function readSnapshotManifest(repoRoot, stationId) {
12340
12665
  const snapshotRoot = snapshotRootFor(repoRoot, stationId);
12341
- const manifestPath = join19(snapshotRoot, "sync-manifest.json");
12666
+ const manifestPath = join21(snapshotRoot, "sync-manifest.json");
12342
12667
  let manifest;
12343
12668
  try {
12344
- manifest = JSON.parse(readFileSync17(manifestPath, "utf8"));
12669
+ manifest = JSON.parse(readFileSync18(manifestPath, "utf8"));
12345
12670
  } catch (error) {
12346
12671
  fail("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error.message}`);
12347
12672
  }
@@ -12362,12 +12687,13 @@ function planStationHydration(stationId, repoRoot) {
12362
12687
  }
12363
12688
  const candidates = [];
12364
12689
  const symlinks = [];
12690
+ const hashMismatches = [];
12365
12691
  const skippedByRule = [];
12366
12692
  for (const agent of SYNC_AGENTS) {
12367
- const agentRoot = join19(snapshotRoot, "agent-homes", agent);
12693
+ const agentRoot = join21(snapshotRoot, "agent-homes", agent);
12368
12694
  let identEntries;
12369
12695
  try {
12370
- identEntries = readdirSync11(agentRoot, { withFileTypes: true });
12696
+ identEntries = readdirSync12(agentRoot, { withFileTypes: true });
12371
12697
  } catch {
12372
12698
  continue;
12373
12699
  }
@@ -12375,7 +12701,7 @@ function planStationHydration(stationId, repoRoot) {
12375
12701
  if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
12376
12702
  continue;
12377
12703
  }
12378
- const identRoot = join19(agentRoot, identEntry.name);
12704
+ const identRoot = join21(agentRoot, identEntry.name);
12379
12705
  const entries = walkEntries(identRoot);
12380
12706
  for (const entry of entries) {
12381
12707
  const relativeParts = [identEntry.name, ...entry.relativePath.split(sep5)];
@@ -12422,7 +12748,19 @@ function planStationHydration(stationId, repoRoot) {
12422
12748
  });
12423
12749
  continue;
12424
12750
  }
12425
- const info = statSync12(entry.fullPath);
12751
+ const info = statSync13(entry.fullPath);
12752
+ const manifestHash = manifestHashes.get(`${agent}${MANIFEST_HASH_KEY_SEP}${homeRelative}`) ?? null;
12753
+ let verified = false;
12754
+ if (manifestHash !== null) {
12755
+ verified = sha256File(entry.fullPath) === manifestHash;
12756
+ if (!verified) {
12757
+ hashMismatches.push({
12758
+ ident: identEntry.name,
12759
+ agent,
12760
+ relativePath: entry.relativePath
12761
+ });
12762
+ }
12763
+ }
12426
12764
  candidates.push({
12427
12765
  ident: identEntry.name,
12428
12766
  agent,
@@ -12430,7 +12768,8 @@ function planStationHydration(stationId, repoRoot) {
12430
12768
  fullPath: entry.fullPath,
12431
12769
  size: info.size,
12432
12770
  mtimeMs: info.mtimeMs,
12433
- manifestHash: manifestHashes.get(`${agent}${MANIFEST_HASH_KEY_SEP}${homeRelative}`) ?? null
12771
+ manifestHash,
12772
+ verified
12434
12773
  });
12435
12774
  }
12436
12775
  }
@@ -12438,6 +12777,9 @@ function planStationHydration(stationId, repoRoot) {
12438
12777
  if (symlinks.length > 0) {
12439
12778
  fail("SYMLINKS_REFUSED", `${symlinks.length} symlink(s) inside the snapshot; symlinks are refused (fail closed)`);
12440
12779
  }
12780
+ if (hashMismatches.length > 0) {
12781
+ fail("MANIFEST_HASH_MISMATCH", `${hashMismatches.length} snapshot file(s) no longer match their sync-manifest sha256; ` + "stale or tampered content is refused (fail closed), nothing written \u2014 re-run sync to refresh the manifest", hashMismatches.map((mismatch) => `agent-homes/${mismatch.agent}/${mismatch.ident}/${mismatch.relativePath}`));
12782
+ }
12441
12783
  const byIdent = new Map;
12442
12784
  for (const candidate of candidates) {
12443
12785
  const group = byIdent.get(candidate.ident) ?? [];
@@ -12460,7 +12802,7 @@ function planStationHydration(stationId, repoRoot) {
12460
12802
  for (const copy of copies) {
12461
12803
  let isStub = false;
12462
12804
  try {
12463
- isStub = isPointerSkillMd(readFileSync17(copy.fullPath, "utf8"));
12805
+ isStub = isPointerSkillMd(readFileSync18(copy.fullPath, "utf8"));
12464
12806
  } catch {
12465
12807
  isStub = false;
12466
12808
  }
@@ -12472,8 +12814,8 @@ function planStationHydration(stationId, repoRoot) {
12472
12814
  }
12473
12815
  }
12474
12816
  eligible.sort((left, right) => {
12475
- const leftHash = left.manifestHash !== null;
12476
- const rightHash = right.manifestHash !== null;
12817
+ const leftHash = left.verified;
12818
+ const rightHash = right.verified;
12477
12819
  if (leftHash !== rightHash) {
12478
12820
  return leftHash ? -1 : 1;
12479
12821
  }
@@ -12510,8 +12852,8 @@ function skillSha256(skill) {
12510
12852
  `)).digest("hex");
12511
12853
  }
12512
12854
  function writeStationHydration(options) {
12513
- const repoRoot = resolve2(options.repoRoot ?? process.cwd());
12514
- const cacheRoot = resolve2(options.cacheRoot ?? resolveCorpusRoot());
12855
+ const repoRoot = resolve3(options.repoRoot ?? process.cwd());
12856
+ const cacheRoot = resolve3(options.cacheRoot ?? resolveCorpusRoot());
12515
12857
  const plan = planStationHydration(options.stationId, repoRoot);
12516
12858
  const resultSkills = plan.winners.map((skill) => ({
12517
12859
  ident: skill.ident,
@@ -12544,7 +12886,7 @@ function writeStationHydration(options) {
12544
12886
  const toWrite = [];
12545
12887
  for (const skill of plan.winners) {
12546
12888
  for (const file of skill.files) {
12547
- const destination = join19(cacheRoot, skill.ident, file.withinIdent);
12889
+ const destination = join21(cacheRoot, skill.ident, file.withinIdent);
12548
12890
  const digest = sha256File(file.winner.fullPath);
12549
12891
  let existingDigest = null;
12550
12892
  try {
@@ -12586,7 +12928,7 @@ function writeStationHydration(options) {
12586
12928
  },
12587
12929
  skills: resultSkills
12588
12930
  };
12589
- const hydrationManifestPath = join19(dirname12(cacheRoot), `hydration-${options.stationId}.json`);
12931
+ const hydrationManifestPath = join21(dirname12(cacheRoot), `hydration-${options.stationId}.json`);
12590
12932
  mkdirSync14(dirname12(hydrationManifestPath), { recursive: true });
12591
12933
  writeFileSync13(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
12592
12934
  `);