@hasna/skills 0.1.70 → 0.1.72

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 join9 } 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 join3, 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,123 @@ function assertNoRetiredConfigKeys(config, source) {
101
100
  }
102
101
  }
103
102
 
103
+ // src/lib/app-home.ts
104
+ import { existsSync } from "fs";
105
+ import { homedir as homedir2 } from "os";
106
+ import { join as join2, resolve } from "path";
107
+
108
+ // ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
109
+ import { homedir } from "os";
110
+ import { join } from "path";
111
+ var KIND_ENV = {
112
+ config: "HASNA_CONFIG_HOME",
113
+ data: "HASNA_DATA_HOME",
114
+ state: "HASNA_STATE_HOME",
115
+ cache: "HASNA_CACHE_HOME"
116
+ };
117
+ var APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
118
+ function assertApp(app) {
119
+ if (typeof app !== "string" || app.length === 0) {
120
+ throw new TypeError("paths: app must be a non-empty string");
121
+ }
122
+ if (!APP_SLUG_RE.test(app)) {
123
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
124
+ }
125
+ }
126
+ function envOf(options) {
127
+ return options.env ?? process.env;
128
+ }
129
+ function envValue(options, kind) {
130
+ const value = envOf(options)[KIND_ENV[kind]];
131
+ return typeof value === "string" && value.length > 0 ? value : undefined;
132
+ }
133
+ function isMacOS(platform) {
134
+ return platform === "darwin";
135
+ }
136
+ function baseDir(kind, options) {
137
+ const override = envValue(options, kind);
138
+ if (override)
139
+ return override;
140
+ const home = options.home ?? homedir();
141
+ const platform = options.platform ?? process.platform;
142
+ if (isMacOS(platform)) {
143
+ switch (kind) {
144
+ case "config":
145
+ case "data":
146
+ return join(home, "Library", "Application Support", "Hasna");
147
+ case "cache":
148
+ return join(home, "Library", "Caches", "Hasna");
149
+ case "state":
150
+ return join(home, "Library", "Logs", "Hasna");
151
+ }
152
+ }
153
+ switch (kind) {
154
+ case "config":
155
+ return join(home, ".config", "hasna");
156
+ case "data":
157
+ return join(home, ".local", "share", "hasna");
158
+ case "state":
159
+ return join(home, ".local", "state", "hasna");
160
+ case "cache":
161
+ return join(home, ".cache", "hasna");
162
+ }
163
+ }
164
+ function resolvePath(kind, options) {
165
+ assertApp(options.app);
166
+ const appSegment = options.internal === true ? join("internal", options.app) : options.app;
167
+ return join(baseDir(kind, options), appSegment);
168
+ }
169
+ function dataDir(options) {
170
+ return resolvePath("data", options);
171
+ }
172
+
173
+ // src/lib/app-home.ts
174
+ var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
175
+ var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
176
+ var SKILLS_HOME_ENV = "SKILLS_HOME";
177
+ var DEFAULT_SQLITE_FILENAME = "server.db";
178
+ var GLOBAL_CONFIG_FILENAME = "config.json";
179
+ function effectiveHome() {
180
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir2() || "/tmp";
181
+ }
182
+ function legacyDataRoot() {
183
+ return join2(effectiveHome(), ".hasna", "skills");
184
+ }
185
+ function resolverDataRoot(home = effectiveHome()) {
186
+ return dataDir({ app: "skills", home });
187
+ }
188
+ function adoptResolverDataRoot(resolved, env = process.env) {
189
+ const dataOverride = env.HASNA_DATA_HOME;
190
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
191
+ return true;
192
+ return existsSync(join2(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync(join2(resolved, GLOBAL_CONFIG_FILENAME));
193
+ }
194
+ function exactDataRoot() {
195
+ for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
196
+ const dir = process.env[key]?.trim();
197
+ if (dir)
198
+ return resolve(dir);
199
+ }
200
+ return;
201
+ }
202
+ function hasExactOverride(env = process.env) {
203
+ return Boolean(env[DATA_DIR_ENV]?.trim()) || Boolean(env[HASNA_SKILLS_HOME_ENV]?.trim()) || Boolean(env[SKILLS_HOME_ENV]?.trim());
204
+ }
205
+ function hasOperatorOverride(env = process.env) {
206
+ return hasExactOverride(env) || Boolean(env.HASNA_DATA_HOME?.trim());
207
+ }
208
+ function getDataRoot() {
209
+ const exact = exactDataRoot();
210
+ if (exact)
211
+ return exact;
212
+ const resolved = resolverDataRoot();
213
+ return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
214
+ }
215
+ function skillsDataRootForHome(home) {
216
+ const resolved = resolverDataRoot(home);
217
+ return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(join2(home, ".hasna", "skills"));
218
+ }
219
+
104
220
  // src/lib/config.ts
105
221
  var ENUM_KEYS = {
106
222
  defaultAgent: ["claude", "codex", "gemini", "pi", "opencode", "all"],
@@ -115,19 +231,19 @@ function allowedValues(key) {
115
231
  return ENUM_KEYS[key];
116
232
  }
117
233
  function mergeDirectoryContents(sourceDir, targetDir) {
118
- if (!existsSync(sourceDir))
234
+ if (!existsSync2(sourceDir))
119
235
  return;
120
236
  mkdirSync(targetDir, { recursive: true });
121
237
  for (const entry of readdirSync(sourceDir)) {
122
- const sourcePath = join(sourceDir, entry);
123
- const targetPath = join(targetDir, entry);
238
+ const sourcePath = join3(sourceDir, entry);
239
+ const targetPath = join3(targetDir, entry);
124
240
  try {
125
241
  const sourceStat = statSync(sourcePath);
126
242
  if (sourceStat.isDirectory()) {
127
243
  mergeDirectoryContents(sourcePath, targetPath);
128
244
  continue;
129
245
  }
130
- if (!existsSync(targetPath))
246
+ if (!existsSync2(targetPath))
131
247
  copyFileSync(sourcePath, targetPath);
132
248
  } catch {}
133
249
  }
@@ -152,44 +268,40 @@ function normalizeConfigValue(key, value) {
152
268
  return value.trim() ? value : undefined;
153
269
  return;
154
270
  }
155
- var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
156
271
  var INSTALLED_SKILLS_DIRNAME = "installed";
157
272
  var SKILLS_CACHE_DIRNAME = "skills";
158
273
  var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
159
274
  function isOwnerLayoutMigrated(appDir) {
160
- return existsSync(join(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
275
+ return existsSync2(join3(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
161
276
  }
162
277
  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 });
278
+ const root = getDataRoot();
175
279
  try {
176
- mergeDirectoryContents(oldDir, newDir);
280
+ mkdirSync(root, { recursive: true });
177
281
  } catch {}
178
- if (existsSync(oldConfigFile) && !existsSync(join(newDir, "config.json"))) {
282
+ if (hasOperatorOverride())
283
+ return root;
284
+ const home = effectiveHome();
285
+ const oldDir = join3(home, ".skills");
286
+ const oldConfigFile = join3(home, ".skillsrc");
287
+ try {
288
+ mergeDirectoryContents(oldDir, root);
289
+ } catch {}
290
+ if (existsSync2(oldConfigFile) && !existsSync2(join3(root, "config.json"))) {
179
291
  try {
180
- copyFileSync(oldConfigFile, join(newDir, "config.json"));
292
+ copyFileSync(oldConfigFile, join3(root, "config.json"));
181
293
  } catch {}
182
294
  }
183
- return newDir;
295
+ return root;
184
296
  }
185
297
  function getConfigPath(scope) {
186
298
  if (scope === "global") {
187
- return join(getDataDir(), "config.json");
299
+ return join3(getDataDir(), "config.json");
188
300
  }
189
- return join(process.cwd(), "skills.config.json");
301
+ return join3(process.cwd(), "skills.config.json");
190
302
  }
191
303
  function readConfigFile(path) {
192
- if (!existsSync(path))
304
+ if (!existsSync2(path))
193
305
  return {};
194
306
  let parsed;
195
307
  try {
@@ -225,7 +337,7 @@ function saveConfig(key, value, scope = "project") {
225
337
  }
226
338
  const filePath = getConfigPath(scope);
227
339
  let existing = {};
228
- if (existsSync(filePath)) {
340
+ if (existsSync2(filePath)) {
229
341
  try {
230
342
  existing = JSON.parse(readFileSync(filePath, "utf-8"));
231
343
  if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
@@ -236,7 +348,7 @@ function saveConfig(key, value, scope = "project") {
236
348
  }
237
349
  } else {
238
350
  const dir = dirname(filePath);
239
- if (!existsSync(dir)) {
351
+ if (!existsSync2(dir)) {
240
352
  mkdirSync(dir, { recursive: true });
241
353
  }
242
354
  }
@@ -250,7 +362,7 @@ function unsetConfig(key, scope = "project") {
250
362
  throw new Error(`Unknown config key: ${key}. Valid keys: ${validKeys().join(", ")}`);
251
363
  }
252
364
  const filePath = getConfigPath(scope);
253
- if (!existsSync(filePath))
365
+ if (!existsSync2(filePath))
254
366
  return false;
255
367
  let existing;
256
368
  try {
@@ -272,7 +384,7 @@ function unsetConfig(key, scope = "project") {
272
384
  // src/lib/portable-skills.ts
273
385
  import {
274
386
  cpSync as cpSync2,
275
- existsSync as existsSync6,
387
+ existsSync as existsSync7,
276
388
  mkdirSync as mkdirSync3,
277
389
  mkdtempSync,
278
390
  readdirSync as readdirSync5,
@@ -281,7 +393,7 @@ import {
281
393
  statSync as statSync6,
282
394
  writeFileSync as writeFileSync3
283
395
  } from "fs";
284
- import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join6, normalize as normalize2 } from "path";
396
+ import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join8, normalize as normalize2 } from "path";
285
397
 
286
398
  // src/lib/registry-data/development-tools.ts
287
399
  var DEVELOPMENT_TOOLS_SKILLS = [
@@ -1001,8 +1113,8 @@ var SKILLS = [
1001
1113
  ];
1002
1114
 
1003
1115
  // 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";
1116
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1117
+ import { join as join4 } from "path";
1006
1118
  var HOSTED_RUNTIMES = new Set(["hosted"]);
1007
1119
  var HOSTED_SOURCES = new Set(["remote", "private-hosted"]);
1008
1120
  function normalizeMarker(value) {
@@ -1015,8 +1127,8 @@ function isHostedMetadataPackage(pkg) {
1015
1127
  return HOSTED_RUNTIMES.has(normalizeMarker(skills.runtime)) || HOSTED_SOURCES.has(normalizeMarker(skills.source));
1016
1128
  }
1017
1129
  function isHostedMetadataSkillDir(skillDir) {
1018
- const pkgPath = join2(skillDir, "package.json");
1019
- if (!existsSync2(pkgPath))
1130
+ const pkgPath = join4(skillDir, "package.json");
1131
+ if (!existsSync3(pkgPath))
1020
1132
  return false;
1021
1133
  try {
1022
1134
  return isHostedMetadataPackage(JSON.parse(readFileSync2(pkgPath, "utf8")));
@@ -1040,8 +1152,8 @@ var BRACE_SOURCE_EXCLUSION = new RegExp(`^!skills/\\{(${SLUG}(?:,${SLUG})+)\\}/s
1040
1152
  var SINGLE_SOURCE_EXCLUSION = new RegExp(`^!skills/(${SLUG})/src$`);
1041
1153
 
1042
1154
  // 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";
1155
+ import { existsSync as existsSync4, lstatSync, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
1156
+ import { isAbsolute, join as join5, normalize } from "path";
1045
1157
  var VALID_SKILL_KINDS = ["executable", "instruction"];
1046
1158
  var DOC_FILES = ["SKILL.md", "README.md", "CLAUDE.md"];
1047
1159
  var RESERVED_SKILL_ENTRIES = new Set([
@@ -1179,7 +1291,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1179
1291
  binCommands: [],
1180
1292
  docFiles: []
1181
1293
  };
1182
- if (!existsSync3(skillPath)) {
1294
+ if (!existsSync4(skillPath)) {
1183
1295
  add(issues, "skill.dir_missing", `Skill directory not found: ${skillPath}`);
1184
1296
  return {
1185
1297
  name: bareName,
@@ -1194,7 +1306,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1194
1306
  add(issues, "skill.name_invalid", `Skill name '${bareName}' must use lowercase letters, numbers, dots, underscores, or hyphens`);
1195
1307
  }
1196
1308
  for (const entry of readdirSync3(skillPath).sort()) {
1197
- const entryPath = join3(skillPath, entry);
1309
+ const entryPath = join5(skillPath, entry);
1198
1310
  if (RESERVED_SKILL_ENTRIES.has(entry)) {
1199
1311
  add(issues, "skill.reserved_file", `Reserved file '${entry}' is not allowed in skill packages`);
1200
1312
  }
@@ -1206,14 +1318,14 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1206
1318
  }
1207
1319
  }
1208
1320
  for (const docFile of DOC_FILES) {
1209
- if (existsSync3(join3(skillPath, docFile)))
1321
+ if (existsSync4(join5(skillPath, docFile)))
1210
1322
  metadata.docFiles.push(docFile);
1211
1323
  }
1212
1324
  if (metadata.docFiles.length === 0) {
1213
1325
  add(issues, "skill.docs_missing", "Missing documentation file: expected SKILL.md, README.md, or CLAUDE.md");
1214
1326
  }
1215
- const skillMdPath = join3(skillPath, "SKILL.md");
1216
- if (existsSync3(skillMdPath)) {
1327
+ const skillMdPath = join5(skillPath, "SKILL.md");
1328
+ if (existsSync4(skillMdPath)) {
1217
1329
  const frontmatter = parseSkillFrontmatter(readFileSync3(skillMdPath, "utf-8"));
1218
1330
  if (!frontmatter) {
1219
1331
  add(warnings, "skill.frontmatter_missing", "SKILL.md has no YAML frontmatter");
@@ -1256,8 +1368,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1256
1368
  }
1257
1369
  metadata.kind = resolvedKind;
1258
1370
  const isInstruction = resolvedKind === "instruction";
1259
- const pkgPath = join3(skillPath, "package.json");
1260
- if (!existsSync3(pkgPath)) {
1371
+ const pkgPath = join5(skillPath, "package.json");
1372
+ if (!existsSync4(pkgPath)) {
1261
1373
  if (!isInstruction)
1262
1374
  add(issues, "package.missing", "Missing package.json");
1263
1375
  } else {
@@ -1315,8 +1427,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1315
1427
  add(issues, "package.bin_target_unsafe", `package.json bin '${command}' target '${target}' must stay inside the skill directory`);
1316
1428
  continue;
1317
1429
  }
1318
- const targetPath = join3(skillPath, target);
1319
- if (!existsSync3(targetPath)) {
1430
+ const targetPath = join5(skillPath, target);
1431
+ if (!existsSync4(targetPath)) {
1320
1432
  add(warnings, "package.bin_target_missing", `package.json bin '${command}' target '${target}' is not present before build`);
1321
1433
  } else if (statSync3(targetPath).isDirectory()) {
1322
1434
  add(issues, "package.bin_target_directory", `package.json bin '${command}' target '${target}' must point to a file, not a directory`);
@@ -1333,17 +1445,17 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
1333
1445
  metadata.runtime = "none";
1334
1446
  } else {
1335
1447
  metadata.runtime = hostedMetadata ? "hosted" : "local";
1336
- const srcDir = join3(skillPath, "src");
1448
+ const srcDir = join5(skillPath, "src");
1337
1449
  if (hostedMetadata) {
1338
- if (existsSync3(srcDir)) {
1450
+ if (existsSync4(srcDir)) {
1339
1451
  add(issues, "skill.hosted_source_forbidden", "Hosted metadata skills must not include local implementation source");
1340
1452
  }
1341
- } else if (!existsSync3(srcDir)) {
1453
+ } else if (!existsSync4(srcDir)) {
1342
1454
  add(issues, "skill.src_missing", "Missing src/ directory");
1343
- } else if (!existsSync3(join3(srcDir, "index.ts")) && !existsSync3(join3(srcDir, "index.js"))) {
1455
+ } else if (!existsSync4(join5(srcDir, "index.ts")) && !existsSync4(join5(srcDir, "index.js"))) {
1344
1456
  add(issues, "skill.src_index_missing", "Missing src/index.ts or src/index.js");
1345
1457
  } else {
1346
- const indexPath = existsSync3(join3(srcDir, "index.ts")) ? join3(srcDir, "index.ts") : join3(srcDir, "index.js");
1458
+ const indexPath = existsSync4(join5(srcDir, "index.ts")) ? join5(srcDir, "index.ts") : join5(srcDir, "index.js");
1347
1459
  const size = statSync3(indexPath).size;
1348
1460
  if (size < 50)
1349
1461
  add(warnings, "skill.src_index_minimal", `Source entry point is very small (${size}B)`);
@@ -1367,8 +1479,8 @@ function validateRegistryConsistency(registry, skillsDir) {
1367
1479
  seen.add(name);
1368
1480
  return false;
1369
1481
  })));
1370
- const skillDirs = existsSync3(skillsDir) ? readdirSync3(skillsDir).filter((entry) => {
1371
- const fullPath = join3(skillsDir, entry);
1482
+ const skillDirs = existsSync4(skillsDir) ? readdirSync3(skillsDir).filter((entry) => {
1483
+ const fullPath = join5(skillsDir, entry);
1372
1484
  return !entry.startsWith(".") && entry !== "_common" && statSync3(fullPath).isDirectory();
1373
1485
  }) : [];
1374
1486
  const directoryNames = new Set(skillDirs);
@@ -1385,8 +1497,8 @@ function validateRegistryConsistency(registry, skillsDir) {
1385
1497
 
1386
1498
  // src/lib/skill-hash.ts
1387
1499
  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";
1500
+ import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
1501
+ import { join as join6, sep } from "path";
1390
1502
  var CONTENT_HASH_ALGORITHM = "sha256";
1391
1503
  var HASH_EXCLUDE_DIRS = new Set([".git", "node_modules", "dist", "build", ".turbo"]);
1392
1504
  var HASH_COVERAGE = [
@@ -1445,8 +1557,8 @@ function collectBundleFiles(skillPath) {
1445
1557
  if (seen.has(entry))
1446
1558
  continue;
1447
1559
  seen.add(entry);
1448
- const absolute = join4(skillPath, entry);
1449
- if (!existsSync4(absolute))
1560
+ const absolute = join6(skillPath, entry);
1561
+ if (!existsSync5(absolute))
1450
1562
  continue;
1451
1563
  if (statSync4(absolute).isDirectory())
1452
1564
  collectDirectory(files, absolute, entry);
@@ -1459,7 +1571,7 @@ function collectDirectory(files, dir, rel) {
1459
1571
  for (const entry of readdirSync4(dir).sort()) {
1460
1572
  if (entry.startsWith("."))
1461
1573
  continue;
1462
- const absolute = join4(dir, entry);
1574
+ const absolute = join6(dir, entry);
1463
1575
  const childRel = `${rel}/${entry}`;
1464
1576
  let stats;
1465
1577
  try {
@@ -1679,14 +1791,14 @@ function validateRuntimeContract(manifest, issues, strict) {
1679
1791
  // src/lib/portable-skills-files.ts
1680
1792
  import {
1681
1793
  cpSync,
1682
- existsSync as existsSync5,
1794
+ existsSync as existsSync6,
1683
1795
  lstatSync as lstatSync2,
1684
1796
  mkdirSync as mkdirSync2,
1685
1797
  readFileSync as readFileSync5,
1686
1798
  realpathSync,
1687
1799
  writeFileSync as writeFileSync2
1688
1800
  } from "fs";
1689
- import { basename, dirname as dirname2, join as join5, relative } from "path";
1801
+ import { basename, dirname as dirname2, join as join7, relative } from "path";
1690
1802
  var ANY_SEGMENT_COPY_EXCLUDES = new Set([
1691
1803
  ".git",
1692
1804
  ".DS_Store",
@@ -1729,12 +1841,12 @@ function normalizePortableSkillName(name) {
1729
1841
  return normalized;
1730
1842
  }
1731
1843
  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;
1844
+ const skillJsonPath = join7(skillPath, "skill.json");
1845
+ const skillMdPath = join7(skillPath, "SKILL.md");
1846
+ const pkgPath = join7(skillPath, "package.json");
1847
+ const jsonManifest = existsSync6(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
1848
+ const frontmatter = existsSync6(skillMdPath) ? parseSkillFrontmatter(readFileSync5(skillMdPath, "utf-8")) ?? undefined : undefined;
1849
+ const pkg = existsSync6(pkgPath) ? readJsonObject(pkgPath) : undefined;
1738
1850
  const name = normalizePortableSkillName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
1739
1851
  const description = stringField(jsonManifest, "description") ?? frontmatter?.description ?? stringValue(pkg?.description) ?? `${name} skill`;
1740
1852
  const version = stringField(jsonManifest, "version") ?? frontmatter?.version ?? stringValue(pkg?.version) ?? PORTABLE_SKILL_DEFAULT_VERSION;
@@ -1780,7 +1892,7 @@ function createInstructionManifest(name, options) {
1780
1892
  }
1781
1893
  function writeInstructionSkillTemplate(skillPath, manifest) {
1782
1894
  mkdirSync2(skillPath, { recursive: true });
1783
- writeFileSync2(join5(skillPath, "SKILL.md"), renderInstructionSkillMd(manifest));
1895
+ writeFileSync2(join7(skillPath, "SKILL.md"), renderInstructionSkillMd(manifest));
1784
1896
  writeSkillJsonWithHash(skillPath, manifest);
1785
1897
  }
1786
1898
  function renderInstructionSkillMd(manifest) {
@@ -1829,12 +1941,12 @@ function createPortableManifest(name, options) {
1829
1941
  };
1830
1942
  }
1831
1943
  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));
1944
+ mkdirSync2(join7(skillPath, "src"), { recursive: true });
1945
+ writeFileSync2(join7(skillPath, "SKILL.md"), renderSkillMd(manifest));
1946
+ writeFileSync2(join7(skillPath, "AGENTS.md"), renderAgentsMd(manifest));
1947
+ writeFileSync2(join7(skillPath, "package.json"), renderPackageJson(manifest));
1948
+ writeFileSync2(join7(skillPath, "tsconfig.json"), renderTsconfig());
1949
+ writeFileSync2(join7(skillPath, "src", "index.ts"), renderEntrypoint(manifest));
1838
1950
  writeSkillJsonWithHash(skillPath, manifest);
1839
1951
  }
1840
1952
  function fillContractDefaults(manifest, entrypoint) {
@@ -1859,7 +1971,7 @@ function writeSkillJsonWithHash(skillPath, manifest) {
1859
1971
  content_hash: undefined
1860
1972
  }
1861
1973
  };
1862
- writeFileSync2(join5(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withoutHash) }, null, 2)}
1974
+ writeFileSync2(join7(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withoutHash) }, null, 2)}
1863
1975
  `);
1864
1976
  const hash = computeContentHash(skillPath);
1865
1977
  const withHash = {
@@ -1869,13 +1981,13 @@ function writeSkillJsonWithHash(skillPath, manifest) {
1869
1981
  content_hash: hash
1870
1982
  }
1871
1983
  };
1872
- writeFileSync2(join5(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withHash) }, null, 2)}
1984
+ writeFileSync2(join7(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withHash) }, null, 2)}
1873
1985
  `);
1874
1986
  return withHash;
1875
1987
  }
1876
1988
  function readExistingSkillJson(skillPath) {
1877
- const path = join5(skillPath, "skill.json");
1878
- if (!existsSync5(path))
1989
+ const path = join7(skillPath, "skill.json");
1990
+ if (!existsSync6(path))
1879
1991
  return {};
1880
1992
  try {
1881
1993
  const parsed = JSON.parse(readFileSync5(path, "utf-8"));
@@ -1908,28 +2020,28 @@ function ensurePortableSkillFiles(skillPath, manifest) {
1908
2020
  tags: next.tags?.length ? next.tags : ["custom", next.name]
1909
2021
  };
1910
2022
  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));
2023
+ if (entry && !existsSync6(join7(skillPath, entry))) {
2024
+ mkdirSync2(dirname2(join7(skillPath, entry)), { recursive: true });
2025
+ writeFileSync2(join7(skillPath, entry), renderEntrypoint(next));
1914
2026
  }
1915
- if (!existsSync5(join5(skillPath, "SKILL.md")))
1916
- writeFileSync2(join5(skillPath, "SKILL.md"), renderSkillMd(next));
2027
+ if (!existsSync6(join7(skillPath, "SKILL.md")))
2028
+ writeFileSync2(join7(skillPath, "SKILL.md"), renderSkillMd(next));
1917
2029
  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));
2030
+ writeFileSync2(join7(skillPath, "SKILL.md"), ensureSkillMdFrontmatter(readFileSync5(join7(skillPath, "SKILL.md"), "utf-8"), next));
2031
+ if (!existsSync6(join7(skillPath, "AGENTS.md")))
2032
+ writeFileSync2(join7(skillPath, "AGENTS.md"), renderAgentsMd(next));
1921
2033
  ensurePackageJson(skillPath, next);
1922
- if (!existsSync5(join5(skillPath, "tsconfig.json")))
1923
- writeFileSync2(join5(skillPath, "tsconfig.json"), renderTsconfig());
2034
+ if (!existsSync6(join7(skillPath, "tsconfig.json")))
2035
+ writeFileSync2(join7(skillPath, "tsconfig.json"), renderTsconfig());
1924
2036
  writeSkillJsonWithHash(skillPath, next);
1925
2037
  return readPortableSkillManifest(skillPath, next.name);
1926
2038
  }
1927
2039
  function ensurePackageJson(skillPath, manifest) {
1928
- const pkgPath = join5(skillPath, "package.json");
2040
+ const pkgPath = join7(skillPath, "package.json");
1929
2041
  const first = manifest.commands[0] ?? { name: manifest.name, entry: "src/index.ts" };
1930
2042
  const commandName = normalizePortableSkillName(first.name || manifest.name);
1931
2043
  const entry = (first.entry ?? "src/index.ts").replace(/^\.\//, "");
1932
- if (!existsSync5(pkgPath)) {
2044
+ if (!existsSync6(pkgPath)) {
1933
2045
  writeFileSync2(pkgPath, renderPackageJson(manifest));
1934
2046
  return;
1935
2047
  }
@@ -1972,8 +2084,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
1972
2084
  inputs: [],
1973
2085
  commands: []
1974
2086
  };
1975
- if (!existsSync5(join5(skillPath, "SKILL.md"))) {
1976
- writeFileSync2(join5(skillPath, "SKILL.md"), renderSkillMd(next));
2087
+ if (!existsSync6(join7(skillPath, "SKILL.md"))) {
2088
+ writeFileSync2(join7(skillPath, "SKILL.md"), renderSkillMd(next));
1977
2089
  }
1978
2090
  writeSkillJsonWithHash(skillPath, next);
1979
2091
  return readPortableSkillManifest(skillPath, next.name);
@@ -2265,18 +2377,18 @@ var LEGACY_CUSTOM_DIRNAME = "custom";
2265
2377
  function getPortableSkillsRoot(options = {}) {
2266
2378
  if (options.rootDir)
2267
2379
  return options.rootDir;
2268
- const appDir = options.homeDir ? join6(options.homeDir, ".hasna", "skills") : getDataDir();
2269
- const cache = join6(appDir, SKILLS_CACHE_DIRNAME);
2380
+ const appDir = options.homeDir ? join8(options.homeDir, ".hasna", "skills") : getDataDir();
2381
+ const cache = join8(appDir, SKILLS_CACHE_DIRNAME);
2270
2382
  if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
2271
2383
  return cache;
2272
- const installed = join6(appDir, INSTALLED_SKILLS_DIRNAME);
2384
+ const installed = join8(appDir, INSTALLED_SKILLS_DIRNAME);
2273
2385
  migrateLegacySkillLayout(appDir, installed);
2274
2386
  return installed;
2275
2387
  }
2276
2388
  function looksLikeSkillDirectory(path) {
2277
2389
  if (!safeIsDirectory(path))
2278
2390
  return false;
2279
- return existsSync6(join6(path, "SKILL.md")) || existsSync6(join6(path, "skill.json")) || existsSync6(join6(path, "package.json"));
2391
+ return existsSync7(join8(path, "SKILL.md")) || existsSync7(join8(path, "skill.json")) || existsSync7(join8(path, "package.json"));
2280
2392
  }
2281
2393
  function migrateLegacySkillLayout(appDir, installed) {
2282
2394
  if (!safeIsDirectory(appDir))
@@ -2286,7 +2398,7 @@ function migrateLegacySkillLayout(appDir, installed) {
2286
2398
  for (const entry of readdirSync5(appDir)) {
2287
2399
  if (entry.startsWith(".") || entry === INSTALLED_SKILLS_DIRNAME)
2288
2400
  continue;
2289
- const path = join6(appDir, entry);
2401
+ const path = join8(appDir, entry);
2290
2402
  if (entry === LEGACY_CUSTOM_DIRNAME) {
2291
2403
  if (!safeIsDirectory(path))
2292
2404
  continue;
@@ -2294,7 +2406,7 @@ function migrateLegacySkillLayout(appDir, installed) {
2294
2406
  for (const nested of readdirSync5(path)) {
2295
2407
  if (nested.startsWith("."))
2296
2408
  continue;
2297
- const nestedPath = join6(path, nested);
2409
+ const nestedPath = join8(path, nested);
2298
2410
  if (looksLikeSkillDirectory(nestedPath))
2299
2411
  candidates.push({ from: nestedPath, name: nested });
2300
2412
  }
@@ -2308,10 +2420,10 @@ function migrateLegacySkillLayout(appDir, installed) {
2308
2420
  return;
2309
2421
  }
2310
2422
  for (const { from, name } of candidates) {
2311
- const target = join6(installed, name);
2312
- if (existsSync6(target))
2423
+ const target = join8(installed, name);
2424
+ if (existsSync7(target))
2313
2425
  continue;
2314
- const staging = join6(installed, `.migrating-${name}-${process.pid}`);
2426
+ const staging = join8(installed, `.migrating-${name}-${process.pid}`);
2315
2427
  try {
2316
2428
  rmSync(staging, { recursive: true, force: true });
2317
2429
  cpSync2(from, staging, { recursive: true, errorOnExist: false });
@@ -2324,7 +2436,7 @@ function migrateLegacySkillLayout(appDir, installed) {
2324
2436
  }
2325
2437
  }
2326
2438
  function getPortableSkillPath(name, options = {}) {
2327
- return join6(getPortableSkillsRoot(options), normalizePortableSkillName(name));
2439
+ return join8(getPortableSkillsRoot(options), normalizePortableSkillName(name));
2328
2440
  }
2329
2441
  function findPortableSkill(name, options = {}) {
2330
2442
  let normalized;
@@ -2334,7 +2446,7 @@ function findPortableSkill(name, options = {}) {
2334
2446
  return null;
2335
2447
  }
2336
2448
  const path = getPortableSkillPath(normalized, options);
2337
- if (!existsSync6(path) || !statSync6(path).isDirectory())
2449
+ if (!existsSync7(path) || !statSync6(path).isDirectory())
2338
2450
  return null;
2339
2451
  try {
2340
2452
  return summarizePortableSkill(path, normalized);
@@ -2350,7 +2462,7 @@ function listPortableSkills(options = {}) {
2350
2462
  for (const entry of readdirSync5(root).sort()) {
2351
2463
  if (entry.startsWith("."))
2352
2464
  continue;
2353
- const path = join6(root, entry);
2465
+ const path = join8(root, entry);
2354
2466
  if (!safeIsDirectory(path))
2355
2467
  continue;
2356
2468
  try {
@@ -2384,8 +2496,8 @@ function isOfficialSkillName(name) {
2384
2496
  function scaffoldPortableSkill(name, options = {}) {
2385
2497
  const skillName = normalizePortableSkillName(name);
2386
2498
  const root = getPortableSkillsRoot(options);
2387
- const skillPath = join6(root, skillName);
2388
- if (existsSync6(skillPath)) {
2499
+ const skillPath = join8(root, skillName);
2500
+ if (existsSync7(skillPath)) {
2389
2501
  if (!options.overwrite)
2390
2502
  throw new Error(`Skill '${skillName}' already exists at ${skillPath}`);
2391
2503
  rmSync(skillPath, { recursive: true, force: true });
@@ -2403,7 +2515,7 @@ function scaffoldPortableSkill(name, options = {}) {
2403
2515
  }
2404
2516
  function portPortableSkillDirectory(sourceDir, options = {}) {
2405
2517
  const absoluteSource = normalize2(sourceDir);
2406
- if (!existsSync6(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
2518
+ if (!existsSync7(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
2407
2519
  throw new Error(`Import directory not found: ${sourceDir}`);
2408
2520
  }
2409
2521
  const continueOnError = options.continueOnError ?? true;
@@ -2416,7 +2528,7 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
2416
2528
  const skipped = [];
2417
2529
  const entries = readdirSync5(absoluteSource, { withFileTypes: true }).map((entry) => entry.name).filter((entryName) => !entryName.startsWith(".")).sort();
2418
2530
  for (const entryName of entries) {
2419
- const childPath = join6(absoluteSource, entryName);
2531
+ const childPath = join8(absoluteSource, entryName);
2420
2532
  if (!safeIsDirectory(childPath))
2421
2533
  continue;
2422
2534
  if (!isSkillCandidate(childPath)) {
@@ -2445,11 +2557,11 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
2445
2557
  };
2446
2558
  }
2447
2559
  function isSkillCandidate(dir) {
2448
- return existsSync6(join6(dir, "SKILL.md")) || existsSync6(join6(dir, "skill.json")) || existsSync6(join6(dir, "package.json"));
2560
+ return existsSync7(join8(dir, "SKILL.md")) || existsSync7(join8(dir, "skill.json")) || existsSync7(join8(dir, "package.json"));
2449
2561
  }
2450
2562
  function portPortableSkill(sourcePath, options = {}) {
2451
2563
  const absoluteSource = normalize2(sourcePath);
2452
- if (!existsSync6(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
2564
+ if (!existsSync7(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
2453
2565
  throw new Error(`Skill source directory not found: ${sourcePath}`);
2454
2566
  }
2455
2567
  const inferred = readPortableSkillManifest(absoluteSource, basename2(absoluteSource));
@@ -2461,8 +2573,8 @@ function portPortableSkill(sourcePath, options = {}) {
2461
2573
  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
2574
  }
2463
2575
  const root = getPortableSkillsRoot(options);
2464
- const destination = join6(root, skillName);
2465
- if (existsSync6(destination)) {
2576
+ const destination = join8(root, skillName);
2577
+ if (existsSync7(destination)) {
2466
2578
  if (!options.overwrite)
2467
2579
  throw new Error(`Skill '${skillName}' already exists at ${destination}`);
2468
2580
  rmSync(destination, { recursive: true, force: true });
@@ -2504,10 +2616,10 @@ function buildCorpusManifest(input, name) {
2504
2616
  function writeCorpusSkill(input, options = {}) {
2505
2617
  const name = normalizePortableSkillName(input.name);
2506
2618
  const root = getPortableSkillsRoot(options);
2507
- const skillPath = join6(root, name);
2508
- const created = !existsSync6(skillPath);
2619
+ const skillPath = join8(root, name);
2620
+ const created = !existsSync7(skillPath);
2509
2621
  mkdirSync3(skillPath, { recursive: true });
2510
- writeFileSync3(join6(skillPath, "SKILL.md"), input.skillMd);
2622
+ writeFileSync3(join8(skillPath, "SKILL.md"), input.skillMd);
2511
2623
  const manifest = buildCorpusManifest(input, name);
2512
2624
  writeSkillJsonWithHash(skillPath, manifest);
2513
2625
  return { name, path: skillPath, manifest, created };
@@ -2515,19 +2627,19 @@ function writeCorpusSkill(input, options = {}) {
2515
2627
  function installCorpusSkillAtomically(input, options = {}) {
2516
2628
  const name = normalizePortableSkillName(input.name);
2517
2629
  const root = getPortableSkillsRoot(options);
2518
- const target = join6(root, name);
2519
- const created = !existsSync6(target);
2630
+ const target = join8(root, name);
2631
+ const created = !existsSync7(target);
2520
2632
  mkdirSync3(root, { recursive: true });
2521
- const staging = mkdtempSync(join6(root, `.pull-${name}-`));
2633
+ const staging = mkdtempSync(join8(root, `.pull-${name}-`));
2522
2634
  let moved = false;
2523
2635
  let backup = null;
2524
2636
  try {
2525
- writeFileSync3(join6(staging, "SKILL.md"), input.skillMd);
2637
+ writeFileSync3(join8(staging, "SKILL.md"), input.skillMd);
2526
2638
  const manifest = buildCorpusManifest(input, name);
2527
2639
  writeSkillJsonWithHash(staging, manifest);
2528
- if (existsSync6(target)) {
2529
- backup = mkdtempSync(join6(root, `.pull-backup-${name}-`));
2530
- renameSync(target, join6(backup, name));
2640
+ if (existsSync7(target)) {
2641
+ backup = mkdtempSync(join8(root, `.pull-backup-${name}-`));
2642
+ renameSync(target, join8(backup, name));
2531
2643
  moved = true;
2532
2644
  }
2533
2645
  renameSync(staging, target);
@@ -2536,9 +2648,9 @@ function installCorpusSkillAtomically(input, options = {}) {
2536
2648
  return { name, path: target, manifest, created };
2537
2649
  } catch (error) {
2538
2650
  rmSync(staging, { recursive: true, force: true });
2539
- if (moved && backup && existsSync6(join6(backup, name))) {
2651
+ if (moved && backup && existsSync7(join8(backup, name))) {
2540
2652
  try {
2541
- renameSync(join6(backup, name), target);
2653
+ renameSync(join8(backup, name), target);
2542
2654
  } catch {}
2543
2655
  }
2544
2656
  throw error;
@@ -2550,10 +2662,10 @@ function validatePortableSkillDirectory(name, skillPath) {
2550
2662
  const issues = [...base.issues];
2551
2663
  const warnings = [...base.warnings];
2552
2664
  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)) {
2665
+ if (existsSync7(skillPath)) {
2666
+ const skillJsonPath = join8(skillPath, "skill.json");
2667
+ const skillMdPath = join8(skillPath, "SKILL.md");
2668
+ if (!existsSync7(skillJsonPath) && !existsSync7(skillMdPath)) {
2557
2669
  add3(issues, "portable.manifest_missing", "Missing portable manifest: expected SKILL.md frontmatter and/or skill.json");
2558
2670
  }
2559
2671
  try {
@@ -2572,7 +2684,7 @@ function validatePortableSkillDirectory(name, skillPath) {
2572
2684
  add3(issues, "portable.version_missing", "Portable manifest missing version");
2573
2685
  }
2574
2686
  const contractIssues = validatePortableManifestContract(manifest, {
2575
- strict: existsSync6(join6(skillPath, "skill.json")),
2687
+ strict: existsSync7(join8(skillPath, "skill.json")),
2576
2688
  skillPath
2577
2689
  });
2578
2690
  for (const issue of contractIssues)
@@ -2608,8 +2720,8 @@ function validatePortableSkillDirectory(name, skillPath) {
2608
2720
  add3(issues, "portable.command_entry_unsafe", `Command '${command.name}' entry '${command.entry}' must stay inside the skill directory`);
2609
2721
  continue;
2610
2722
  }
2611
- const entryPath = join6(skillPath, command.entry);
2612
- if (!existsSync6(entryPath))
2723
+ const entryPath = join8(skillPath, command.entry);
2724
+ if (!existsSync7(entryPath))
2613
2725
  add3(issues, "portable.command_entry_missing", `Command '${command.name}' entry '${command.entry}' is missing`);
2614
2726
  else if (statSync6(entryPath).isDirectory())
2615
2727
  add3(issues, "portable.command_entry_directory", `Command '${command.name}' entry '${command.entry}' must be a file`);
@@ -2619,7 +2731,7 @@ function validatePortableSkillDirectory(name, skillPath) {
2619
2731
  } catch (error) {
2620
2732
  add3(issues, "portable.manifest_invalid", error.message);
2621
2733
  }
2622
- if (manifest?.kind !== "instruction" && !existsSync6(join6(skillPath, "AGENTS.md"))) {
2734
+ if (manifest?.kind !== "instruction" && !existsSync7(join8(skillPath, "AGENTS.md"))) {
2623
2735
  add3(issues, "portable.agents_missing", "Missing AGENTS.md with build-out instructions for coding agents");
2624
2736
  }
2625
2737
  }
@@ -2655,13 +2767,13 @@ async function runPortableSkill(name, args, options = {}) {
2655
2767
  if (!isSafeRelativePath2(command.entry)) {
2656
2768
  return { exitCode: 1, error: `Portable skill '${name}' command entry is unsafe` };
2657
2769
  }
2658
- const entryPath = join6(skill.path, command.entry);
2659
- if (!existsSync6(entryPath)) {
2770
+ const entryPath = join8(skill.path, command.entry);
2771
+ if (!existsSync7(entryPath)) {
2660
2772
  return { exitCode: 1, error: `Entry point '${command.entry}' not found in portable skill '${name}'` };
2661
2773
  }
2662
- const pkgPath = join6(skill.path, "package.json");
2663
- const nodeModules = join6(skill.path, "node_modules");
2664
- if (existsSync6(pkgPath) && !existsSync6(nodeModules) && hasPackageDependencies(pkgPath)) {
2774
+ const pkgPath = join8(skill.path, "package.json");
2775
+ const nodeModules = join8(skill.path, "node_modules");
2776
+ if (existsSync7(pkgPath) && !existsSync7(nodeModules) && hasPackageDependencies(pkgPath)) {
2665
2777
  const install = Bun.spawn(["bun", "install", "--no-save"], {
2666
2778
  cwd: skill.path,
2667
2779
  stdout: "pipe",
@@ -2930,7 +3042,7 @@ function parseSkillMdFrontmatter(content) {
2930
3042
  return Object.keys(result).length > 0 ? result : null;
2931
3043
  }
2932
3044
  function discoverSkillsInDir(dir, source = "custom") {
2933
- if (!existsSync7(dir))
3045
+ if (!existsSync8(dir))
2934
3046
  return [];
2935
3047
  const result = [];
2936
3048
  try {
@@ -2938,8 +3050,8 @@ function discoverSkillsInDir(dir, source = "custom") {
2938
3050
  for (const entry of entries) {
2939
3051
  if (!entry.isDirectory())
2940
3052
  continue;
2941
- const skillMdPath = join7(dir, entry.name, "SKILL.md");
2942
- if (!existsSync7(skillMdPath))
3053
+ const skillMdPath = join9(dir, entry.name, "SKILL.md");
3054
+ if (!existsSync8(skillMdPath))
2943
3055
  continue;
2944
3056
  let content;
2945
3057
  try {
@@ -2958,7 +3070,7 @@ function discoverSkillsInDir(dir, source = "custom") {
2958
3070
  category: fm.category || "Development Tools",
2959
3071
  tags: fm.tags || [],
2960
3072
  ...fm.kind ? { kind: fm.kind } : {},
2961
- ...isHostedMetadataSkillDir(join7(dir, entry.name)) ? { serverOwned: true } : {},
3073
+ ...isHostedMetadataSkillDir(join9(dir, entry.name)) ? { serverOwned: true } : {},
2962
3074
  source
2963
3075
  });
2964
3076
  }
@@ -2967,16 +3079,16 @@ function discoverSkillsInDir(dir, source = "custom") {
2967
3079
  }
2968
3080
  function findExtensionSkillPath(name) {
2969
3081
  const config = loadConfig();
2970
- if (!config.extensionsDir || !existsSync7(config.extensionsDir))
3082
+ if (!config.extensionsDir || !existsSync8(config.extensionsDir))
2971
3083
  return null;
2972
3084
  try {
2973
3085
  const entries = readdirSync6(config.extensionsDir, { withFileTypes: true });
2974
3086
  for (const entry of entries) {
2975
3087
  if (!entry.isDirectory())
2976
3088
  continue;
2977
- const skillDir = join7(config.extensionsDir, entry.name);
2978
- const skillMdPath = join7(skillDir, "SKILL.md");
2979
- if (!existsSync7(skillMdPath))
3089
+ const skillDir = join9(config.extensionsDir, entry.name);
3090
+ const skillMdPath = join9(skillDir, "SKILL.md");
3091
+ if (!existsSync8(skillMdPath))
2980
3092
  continue;
2981
3093
  let content;
2982
3094
  try {
@@ -3008,12 +3120,12 @@ function loadRegistry(cwd) {
3008
3120
  if (registryCache && registryCacheKey === rootKey && now - registryCacheTime < REGISTRY_CACHE_TTL) {
3009
3121
  return registryCache;
3010
3122
  }
3011
- const dataDir = getDataDir();
3123
+ const dataDir2 = getDataDir();
3012
3124
  const config = loadConfig();
3013
3125
  const official = SKILLS.map((s) => ({ ...s, source: "official" }));
3014
3126
  const extensions = config.extensionsDir ? discoverSkillsInDir(config.extensionsDir, "extension") : [];
3015
3127
  const portableCustom = listPortableSkillMetas();
3016
- const legacyCustom = discoverSkillsInDir(join7(dataDir, "custom"));
3128
+ const legacyCustom = discoverSkillsInDir(join9(dataDir2, "custom"));
3017
3129
  const globalCustom = mergeCustomSkills([...legacyCustom, ...portableCustom]);
3018
3130
  registryCache = mergeSkillRegistryLists(official, extensions, globalCustom);
3019
3131
  registryCacheTime = now;
@@ -3060,15 +3172,15 @@ function getAllTags() {
3060
3172
  return Array.from(tagSet).sort();
3061
3173
  }
3062
3174
  // 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";
3065
- import { homedir as homedir3 } from "os";
3175
+ import { existsSync as existsSync11, readFileSync as readFileSync9, rmSync as rmSync3 } from "fs";
3176
+ import { dirname as dirname5, join as join12 } from "path";
3177
+ import { homedir as homedir4 } from "os";
3066
3178
  import { fileURLToPath } from "url";
3067
3179
 
3068
3180
  // src/lib/agent-sync.ts
3069
3181
  import {
3070
3182
  cpSync as cpSync3,
3071
- existsSync as existsSync8,
3183
+ existsSync as existsSync9,
3072
3184
  mkdirSync as mkdirSync4,
3073
3185
  mkdtempSync as mkdtempSync2,
3074
3186
  readFileSync as readFileSync7,
@@ -3078,8 +3190,8 @@ import {
3078
3190
  statSync as statSync7,
3079
3191
  writeFileSync as writeFileSync4
3080
3192
  } from "fs";
3081
- import { homedir as homedir2 } from "os";
3082
- import { basename as basename3, dirname as dirname4, join as join8 } from "path";
3193
+ import { homedir as homedir3 } from "os";
3194
+ import { basename as basename3, dirname as dirname4, join as join10 } from "path";
3083
3195
  // src/lib/home-migration.ts
3084
3196
  function resolveCorpusRoot(options = {}) {
3085
3197
  return getPortableSkillsRoot(options);
@@ -3101,12 +3213,12 @@ function resolveSyncAgents(arg) {
3101
3213
  }
3102
3214
  return [arg];
3103
3215
  }
3104
- function agentGlobalSkillsDir(agent, homeDir = homedir2()) {
3216
+ function agentGlobalSkillsDir(agent, homeDir = homedir3()) {
3105
3217
  switch (agent) {
3106
3218
  case "opencode":
3107
- return join8(homeDir, ".config", "opencode", "skills");
3219
+ return join10(homeDir, ".config", "opencode", "skills");
3108
3220
  default:
3109
- return join8(homeDir, `.${agent}`, "skills");
3221
+ return join10(homeDir, `.${agent}`, "skills");
3110
3222
  }
3111
3223
  }
3112
3224
  function adaptSkillMdForAgent(skillMd, agent) {
@@ -3165,8 +3277,8 @@ function resolveSyncCorpus(options = {}) {
3165
3277
  function packageSourceRoots(source) {
3166
3278
  const roots = [];
3167
3279
  for (const sub of ["skills"]) {
3168
- const candidate = join8(source, sub);
3169
- if (existsSync8(candidate) && isDirectory(candidate))
3280
+ const candidate = join10(source, sub);
3281
+ if (existsSync9(candidate) && isDirectory(candidate))
3170
3282
  roots.push(candidate);
3171
3283
  }
3172
3284
  if (roots.length > 0)
@@ -3181,10 +3293,10 @@ function containsSkillDirectories(path) {
3181
3293
  return false;
3182
3294
  }
3183
3295
  return entries.some((entry) => {
3184
- const candidate = join8(path, entry);
3296
+ const candidate = join10(path, entry);
3185
3297
  if (!isDirectory(candidate))
3186
3298
  return false;
3187
- return existsSync8(join8(candidate, "SKILL.md")) || existsSync8(join8(candidate, "skill.json")) || existsSync8(join8(candidate, "package.json"));
3299
+ return existsSync9(join10(candidate, "SKILL.md")) || existsSync9(join10(candidate, "skill.json")) || existsSync9(join10(candidate, "package.json"));
3188
3300
  });
3189
3301
  }
3190
3302
  function isDirectory(path) {
@@ -3197,7 +3309,7 @@ function isDirectory(path) {
3197
3309
  function syncSkillsToAgents(options = {}) {
3198
3310
  const requested = normalizeRequested(options.names);
3199
3311
  const agents = options.agents?.length ? options.agents : [...SYNC_AGENTS];
3200
- const homeDir = options.homeDir ?? homedir2();
3312
+ const homeDir = options.homeDir ?? homedir3();
3201
3313
  const { roots, source } = resolveSyncCorpus(options);
3202
3314
  const corpus = listPortableSkillsAcrossRoots(roots);
3203
3315
  const byName = new Map(corpus.map((skill) => [skill.name, skill]));
@@ -3223,7 +3335,7 @@ function syncSkillsToAgents(options = {}) {
3223
3335
  actions.push({
3224
3336
  skill: name,
3225
3337
  agent,
3226
- path: join8(agentGlobalSkillsDir(agent, homeDir), name, "SKILL.md"),
3338
+ path: join10(agentGlobalSkillsDir(agent, homeDir), name, "SKILL.md"),
3227
3339
  action: "skip",
3228
3340
  reason: "not found in this machine's corpus"
3229
3341
  });
@@ -3252,8 +3364,8 @@ function syncSkillsToAgents(options = {}) {
3252
3364
  return { actions };
3253
3365
  }
3254
3366
  function writeManagedAgentSkill(params) {
3255
- const homeDir = params.homeDir ?? homedir2();
3256
- const dir = join8(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
3367
+ const homeDir = params.homeDir ?? homedir3();
3368
+ const dir = join10(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
3257
3369
  const result = writeManagedSkillDir(dir, params.skillMd, {
3258
3370
  skill: params.skill,
3259
3371
  source: params.source,
@@ -3270,11 +3382,11 @@ function writeManagedAgentSkill(params) {
3270
3382
  };
3271
3383
  }
3272
3384
  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);
3385
+ const skillMdPath = join10(dir, "SKILL.md");
3386
+ const markerPath = join10(dir, SYNC_MARKER_FILE);
3387
+ const dirExists = existsSync9(dir);
3388
+ const managed = existsSync9(markerPath);
3389
+ const hasSkillMd = existsSync9(skillMdPath);
3278
3390
  if (dirExists && !managed && !hasSkillMd) {
3279
3391
  return {
3280
3392
  action: "skip",
@@ -3309,11 +3421,11 @@ function writeManagedSkillDir(dir, skillMd, options) {
3309
3421
  return { action, path: skillMdPath };
3310
3422
  const parentDir = dirname4(dir);
3311
3423
  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);
3424
+ const transactionDir = mkdtempSync2(join10(parentDir, `.hasna-skills-write-${basename3(dir)}-`));
3425
+ const candidateDir = join10(transactionDir, "candidate");
3426
+ const backupDir = join10(transactionDir, "backup");
3427
+ const candidateSkillMdPath = join10(candidateDir, "SKILL.md");
3428
+ const candidateMarkerPath = join10(candidateDir, SYNC_MARKER_FILE);
3317
3429
  const marker = {
3318
3430
  managedBy: SYNC_MARKER_MANAGED_BY,
3319
3431
  skill: options.skill,
@@ -3340,9 +3452,9 @@ function writeManagedSkillDir(dir, skillMd, options) {
3340
3452
  }
3341
3453
  renameDirectory(candidateDir, dir);
3342
3454
  } catch (error) {
3343
- if (originalMoved && existsSync8(backupDir)) {
3455
+ if (originalMoved && existsSync9(backupDir)) {
3344
3456
  try {
3345
- if (existsSync8(dir))
3457
+ if (existsSync9(dir))
3346
3458
  rmSync2(dir, { recursive: true, force: true });
3347
3459
  renameDirectory(backupDir, dir);
3348
3460
  originalMoved = false;
@@ -3361,17 +3473,17 @@ function writeManagedSkillDir(dir, skillMd, options) {
3361
3473
  }
3362
3474
  return { action, path: skillMdPath };
3363
3475
  }
3364
- function removeManagedAgentSkill(skill, agent, homeDir = homedir2()) {
3365
- const dir = join8(agentGlobalSkillsDir(agent, homeDir), skill);
3366
- if (!existsSync8(join8(dir, SYNC_MARKER_FILE)))
3476
+ function removeManagedAgentSkill(skill, agent, homeDir = homedir3()) {
3477
+ const dir = join10(agentGlobalSkillsDir(agent, homeDir), skill);
3478
+ if (!existsSync9(join10(dir, SYNC_MARKER_FILE)))
3367
3479
  return false;
3368
3480
  rmSync2(dir, { recursive: true, force: true });
3369
3481
  return true;
3370
3482
  }
3371
3483
  function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
3372
3484
  if (kind === undefined || kind === "instruction" || preferBundledDocs) {
3373
- const skillMdPath = join8(skillPath, "SKILL.md");
3374
- if (existsSync8(skillMdPath))
3485
+ const skillMdPath = join10(skillPath, "SKILL.md");
3486
+ if (existsSync9(skillMdPath))
3375
3487
  return readFileSync7(skillMdPath, "utf-8");
3376
3488
  }
3377
3489
  return pointerSkillMd(name, description);
@@ -3402,8 +3514,8 @@ function normalizeSkillName(name) {
3402
3514
  }
3403
3515
 
3404
3516
  // 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";
3517
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
3518
+ import { join as join11 } from "path";
3407
3519
  var VALID_PIN_SOURCES = [
3408
3520
  "official",
3409
3521
  "custom",
@@ -3418,14 +3530,14 @@ var SKILLS_PROJECT_DIR = ".skills";
3418
3530
  var PROJECT_CONFIG_FILE = "project.json";
3419
3531
  var DEFAULT_EXPORT_DIR = ".skills/exports";
3420
3532
  function getProjectStateDir(targetDir = process.cwd()) {
3421
- return join9(targetDir, SKILLS_PROJECT_DIR);
3533
+ return join11(targetDir, SKILLS_PROJECT_DIR);
3422
3534
  }
3423
3535
  function getProjectConfigPath(targetDir = process.cwd()) {
3424
- return join9(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
3536
+ return join11(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
3425
3537
  }
3426
3538
  function loadProjectConfig(targetDir = process.cwd()) {
3427
3539
  const path = getProjectConfigPath(targetDir);
3428
- if (!existsSync9(path))
3540
+ if (!existsSync10(path))
3429
3541
  return null;
3430
3542
  try {
3431
3543
  return normalizeProjectConfig(JSON.parse(readFileSync8(path, "utf-8")));
@@ -3546,12 +3658,12 @@ var __dirname2 = dirname5(fileURLToPath(import.meta.url));
3546
3658
  function findSkillsDir() {
3547
3659
  let dir = __dirname2;
3548
3660
  for (let i = 0;i < 5; i++) {
3549
- const candidate = join10(dir, "skills");
3550
- if (existsSync10(candidate) && !dir.includes(".skills"))
3661
+ const candidate = join12(dir, "skills");
3662
+ if (existsSync11(candidate) && !dir.includes(".skills"))
3551
3663
  return candidate;
3552
3664
  dir = dirname5(dir);
3553
3665
  }
3554
- return join10(__dirname2, "..", "skills");
3666
+ return join12(__dirname2, "..", "skills");
3555
3667
  }
3556
3668
  var SKILLS_DIR = findSkillsDir();
3557
3669
  function getSkillPath(name) {
@@ -3559,25 +3671,25 @@ function getSkillPath(name) {
3559
3671
  const portable = findPortableSkill(skillName);
3560
3672
  if (portable)
3561
3673
  return portable.path;
3562
- const legacyCustomPath = join10(getDataDir(), "custom", skillName);
3563
- if (existsSync10(legacyCustomPath))
3674
+ const legacyCustomPath = join12(getDataDir(), "custom", skillName);
3675
+ if (existsSync11(legacyCustomPath))
3564
3676
  return legacyCustomPath;
3565
3677
  const extensionPath = findExtensionSkillPath(skillName);
3566
3678
  if (extensionPath)
3567
3679
  return extensionPath;
3568
- return join10(SKILLS_DIR, skillName);
3680
+ return join12(SKILLS_DIR, skillName);
3569
3681
  }
3570
3682
  function getCanonicalSkillName(name) {
3571
3683
  return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
3572
3684
  }
3573
3685
  function skillExists(name) {
3574
- return existsSync10(getSkillPath(name));
3686
+ return existsSync11(getSkillPath(name));
3575
3687
  }
3576
3688
  function installSkill(name, options = {}) {
3577
3689
  const { targetDir = process.cwd(), overwrite = false } = options;
3578
3690
  const canonicalName = getCanonicalSkillName(name);
3579
3691
  const skillName = normalizeSkillName(canonicalName);
3580
- if (!existsSync10(getSkillPath(name))) {
3692
+ if (!existsSync11(getSkillPath(name))) {
3581
3693
  const knownOfficial = Boolean(getSkill(name));
3582
3694
  return {
3583
3695
  skill: canonicalName,
@@ -3604,7 +3716,7 @@ function installSkill(name, options = {}) {
3604
3716
  }
3605
3717
  function installSkillSource(name, _options = {}) {
3606
3718
  const canonicalName = getCanonicalSkillName(name);
3607
- if (!existsSync10(getSkillPath(name))) {
3719
+ if (!existsSync11(getSkillPath(name))) {
3608
3720
  return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "source" };
3609
3721
  }
3610
3722
  return {
@@ -3625,11 +3737,11 @@ function installSkillManifest(manifest, _options = {}) {
3625
3737
  }
3626
3738
  function createLocalSkillManifest(name, generateSkillMd) {
3627
3739
  const sourcePath = getSkillPath(name);
3628
- if (!existsSync10(sourcePath))
3740
+ if (!existsSync11(sourcePath))
3629
3741
  return null;
3630
3742
  let skillMd = "";
3631
- const skillMdPath = join10(sourcePath, "SKILL.md");
3632
- if (existsSync10(skillMdPath)) {
3743
+ const skillMdPath = join12(sourcePath, "SKILL.md");
3744
+ if (existsSync11(skillMdPath)) {
3633
3745
  skillMd = readFileSync9(skillMdPath, "utf-8");
3634
3746
  } else if (generateSkillMd) {
3635
3747
  skillMd = generateSkillMd(name) ?? "";
@@ -3702,20 +3814,20 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
3702
3814
  const base = projectDir || process.cwd();
3703
3815
  switch (agent) {
3704
3816
  case "pi":
3705
- return scope === "project" ? join10(base, ".pi", "skills") : join10(homedir3(), ".pi", "agent", "skills");
3817
+ return scope === "project" ? join12(base, ".pi", "skills") : join12(homedir4(), ".pi", "agent", "skills");
3706
3818
  case "opencode":
3707
- return scope === "project" ? join10(base, ".opencode", "skills") : join10(homedir3(), ".config", "opencode", "skills");
3819
+ return scope === "project" ? join12(base, ".opencode", "skills") : join12(homedir4(), ".config", "opencode", "skills");
3708
3820
  default:
3709
- return scope === "project" ? join10(base, `.${agent}`, "skills") : join10(homedir3(), `.${agent}`, "skills");
3821
+ return scope === "project" ? join12(base, `.${agent}`, "skills") : join12(homedir4(), `.${agent}`, "skills");
3710
3822
  }
3711
3823
  }
3712
3824
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
3713
3825
  const skillName = normalizeSkillName(getCanonicalSkillName(name));
3714
- return join10(getAgentSkillsDir(agent, scope, projectDir), skillName);
3826
+ return join12(getAgentSkillsDir(agent, scope, projectDir), skillName);
3715
3827
  }
3716
3828
  function installSkillForAgent(name, options, generateSkillMd) {
3717
3829
  const canonicalName = getCanonicalSkillName(name);
3718
- if (!existsSync10(getSkillPath(name))) {
3830
+ if (!existsSync11(getSkillPath(name))) {
3719
3831
  return { skill: canonicalName, success: false, error: `Skill '${name}' not found` };
3720
3832
  }
3721
3833
  const scope = options.scope ?? "global";
@@ -3739,15 +3851,15 @@ function removeSkillForAgent(name, options) {
3739
3851
  const canonicalName = getCanonicalSkillName(name);
3740
3852
  const scope = options.scope ?? "global";
3741
3853
  const dir = getAgentSkillPath(canonicalName, options.agent, scope, options.projectDir);
3742
- if (!existsSync10(join10(dir, SYNC_MARKER_FILE)))
3854
+ if (!existsSync11(join12(dir, SYNC_MARKER_FILE)))
3743
3855
  return false;
3744
3856
  rmSync3(dir, { recursive: true, force: true });
3745
3857
  return true;
3746
3858
  }
3747
3859
  function resolveAgentSkillMd(name, generateSkillMd) {
3748
3860
  const sourcePath = getSkillPath(name);
3749
- const skillMdPath = join10(sourcePath, "SKILL.md");
3750
- if (existsSync10(skillMdPath))
3861
+ const skillMdPath = join12(sourcePath, "SKILL.md");
3862
+ if (existsSync11(skillMdPath))
3751
3863
  return readFileSync9(skillMdPath, "utf-8");
3752
3864
  if (generateSkillMd)
3753
3865
  return generateSkillMd(name);
@@ -3766,7 +3878,7 @@ function warnMissingDependencies(name, targetDir) {
3766
3878
  }
3767
3879
  function generateMinimalSkillMd(name) {
3768
3880
  const sourcePath = getSkillPath(name);
3769
- if (!existsSync10(sourcePath))
3881
+ if (!existsSync11(sourcePath))
3770
3882
  return null;
3771
3883
  const canonicalName = getCanonicalSkillName(name);
3772
3884
  const meta = getSkill(canonicalName);
@@ -3781,7 +3893,7 @@ function generateMinimalSkillMd(name) {
3781
3893
  "---",
3782
3894
  ""
3783
3895
  ].filter(Boolean);
3784
- const fallbackDoc = readFileIfExists(join10(sourcePath, "README.md")) || readFileIfExists(join10(sourcePath, "CLAUDE.md"));
3896
+ const fallbackDoc = readFileIfExists(join12(sourcePath, "README.md")) || readFileIfExists(join12(sourcePath, "CLAUDE.md"));
3785
3897
  if (fallbackDoc)
3786
3898
  return `${frontmatter.join(`
3787
3899
  `)}${fallbackDoc.trim()}
@@ -3800,8 +3912,8 @@ skills run ${canonicalName}
3800
3912
  `;
3801
3913
  }
3802
3914
  function readBundledSkillVersion(name) {
3803
- const pkgPath = join10(getSkillPath(name), "package.json");
3804
- if (!existsSync10(pkgPath))
3915
+ const pkgPath = join12(getSkillPath(name), "package.json");
3916
+ if (!existsSync11(pkgPath))
3805
3917
  return "unknown";
3806
3918
  try {
3807
3919
  const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
@@ -3811,27 +3923,27 @@ function readBundledSkillVersion(name) {
3811
3923
  }
3812
3924
  }
3813
3925
  function readFileIfExists(path) {
3814
- return existsSync10(path) ? readFileSync9(path, "utf-8") : null;
3926
+ return existsSync11(path) ? readFileSync9(path, "utf-8") : null;
3815
3927
  }
3816
3928
  function loadProjectConfigCompat(targetDir) {
3817
3929
  return loadProjectConfig(targetDir);
3818
3930
  }
3819
3931
  // src/lib/run-state.ts
3820
3932
  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";
3933
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync10, readdirSync as readdirSync8, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
3934
+ import { extname, join as join13, relative as relative2 } from "path";
3823
3935
  function createSkillRun(params, targetDir = process.cwd()) {
3824
3936
  const now = new Date;
3825
3937
  const id = createRunId(now);
3826
3938
  const day = now.toISOString().slice(0, 10);
3827
3939
  const skillName = normalizeSkillName(params.skill);
3828
3940
  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);
3941
+ const runDir = join13(root, "runs", day, id);
3942
+ const logsDir = join13(runDir, "logs");
3943
+ const exportDir = join13(root, "exports", skillName, id);
3832
3944
  mkdirSync6(logsDir, { recursive: true });
3833
3945
  mkdirSync6(exportDir, { recursive: true });
3834
- mkdirSync6(join11(root, "tmp"), { recursive: true });
3946
+ mkdirSync6(join13(root, "tmp"), { recursive: true });
3835
3947
  const record = {
3836
3948
  id,
3837
3949
  skill: skillName,
@@ -3882,27 +3994,27 @@ function updateSkillRun(context, patch) {
3882
3994
  return context.record;
3883
3995
  }
3884
3996
  function writeRunLogs(context, stdout = "", stderr = "") {
3885
- writeFileSync6(join11(context.logsDir, "stdout.log"), stdout);
3886
- writeFileSync6(join11(context.logsDir, "stderr.log"), stderr);
3997
+ writeFileSync6(join13(context.logsDir, "stdout.log"), stdout);
3998
+ writeFileSync6(join13(context.logsDir, "stderr.log"), stderr);
3887
3999
  }
3888
4000
  function appendRunEvent(context, event, data = {}) {
3889
4001
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
3890
4002
  `;
3891
- const path = join11(context.runDir, "events.ndjson");
3892
- const previous = existsSync11(path) ? readFileSync10(path, "utf-8") : "";
4003
+ const path = join13(context.runDir, "events.ndjson");
4004
+ const previous = existsSync12(path) ? readFileSync10(path, "utf-8") : "";
3893
4005
  writeFileSync6(path, previous + line);
3894
4006
  }
3895
4007
  function listSkillRuns(targetDir = process.cwd(), limit = 50) {
3896
- const runsRoot = join11(getProjectStateDir(targetDir), "runs");
3897
- if (!existsSync11(runsRoot))
4008
+ const runsRoot = join13(getProjectStateDir(targetDir), "runs");
4009
+ if (!existsSync12(runsRoot))
3898
4010
  return [];
3899
4011
  const records = [];
3900
4012
  for (const day of readdirSync8(runsRoot).sort().reverse()) {
3901
- const dayDir = join11(runsRoot, day);
4013
+ const dayDir = join13(runsRoot, day);
3902
4014
  if (!statSync8(dayDir).isDirectory())
3903
4015
  continue;
3904
4016
  for (const runId of readdirSync8(dayDir).sort().reverse()) {
3905
- const record = readRunRecord(join11(dayDir, runId));
4017
+ const record = readRunRecord(join13(dayDir, runId));
3906
4018
  if (record)
3907
4019
  records.push(record);
3908
4020
  if (records.length >= limit)
@@ -3912,29 +4024,29 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
3912
4024
  return records;
3913
4025
  }
3914
4026
  function findSkillRun(runId, targetDir = process.cwd()) {
3915
- const runsRoot = join11(getProjectStateDir(targetDir), "runs");
3916
- if (!existsSync11(runsRoot))
4027
+ const runsRoot = join13(getProjectStateDir(targetDir), "runs");
4028
+ if (!existsSync12(runsRoot))
3917
4029
  return null;
3918
4030
  for (const day of readdirSync8(runsRoot)) {
3919
- const record = readRunRecord(join11(runsRoot, day, runId));
4031
+ const record = readRunRecord(join13(runsRoot, day, runId));
3920
4032
  if (record)
3921
4033
  return record;
3922
4034
  }
3923
4035
  return null;
3924
4036
  }
3925
4037
  function getRunExportDir(runId, skill, targetDir = process.cwd()) {
3926
- return join11(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
4038
+ return join13(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
3927
4039
  }
3928
4040
  function writeRunRecord(context) {
3929
- writeFileSync6(join11(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
4041
+ writeFileSync6(join13(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
3930
4042
  `);
3931
4043
  }
3932
4044
  function writeArtifactsManifest(context, artifacts) {
3933
- writeFileSync6(join11(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
4045
+ writeFileSync6(join13(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
3934
4046
  `);
3935
4047
  }
3936
4048
  function collectRunArtifacts(context) {
3937
- if (!existsSync11(context.exportDir))
4049
+ if (!existsSync12(context.exportDir))
3938
4050
  return [];
3939
4051
  const artifacts = [];
3940
4052
  for (const path of walkFiles(context.exportDir)) {
@@ -3950,8 +4062,8 @@ function collectRunArtifacts(context) {
3950
4062
  return artifacts.sort((a, b) => a.path.localeCompare(b.path));
3951
4063
  }
3952
4064
  function readRunRecord(runDir) {
3953
- const path = join11(runDir, "run.json");
3954
- if (!existsSync11(path))
4065
+ const path = join13(runDir, "run.json");
4066
+ if (!existsSync12(path))
3955
4067
  return null;
3956
4068
  try {
3957
4069
  return JSON.parse(readFileSync10(path, "utf-8"));
@@ -3962,7 +4074,7 @@ function readRunRecord(runDir) {
3962
4074
  function walkFiles(dir) {
3963
4075
  const files = [];
3964
4076
  for (const entry of readdirSync8(dir)) {
3965
- const full = join11(dir, entry);
4077
+ const full = join13(dir, entry);
3966
4078
  if (statSync8(full).isDirectory())
3967
4079
  files.push(...walkFiles(full));
3968
4080
  else
@@ -4008,13 +4120,13 @@ function mimeForPath(path) {
4008
4120
  }
4009
4121
  }
4010
4122
  // src/lib/skillinfo.ts
4011
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
4012
- import { join as join12 } from "path";
4123
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
4124
+ import { join as join14 } from "path";
4013
4125
  function isInstructionSkillDir(skillPath, meta) {
4014
4126
  if (meta?.kind === "instruction")
4015
4127
  return true;
4016
- const skillMdPath = join12(skillPath, "SKILL.md");
4017
- if (!existsSync12(skillMdPath))
4128
+ const skillMdPath = join14(skillPath, "SKILL.md");
4129
+ if (!existsSync13(skillMdPath))
4018
4130
  return false;
4019
4131
  try {
4020
4132
  return parseSkillFrontmatter(readFileSync11(skillMdPath, "utf-8"))?.kind === "instruction";
@@ -4040,12 +4152,12 @@ var HOSTED_PROVIDER_ENV_PREFIXES = [
4040
4152
  ];
4041
4153
  function getSkillDocs(name) {
4042
4154
  const skillPath = getSkillPath(name);
4043
- if (!existsSync12(skillPath))
4155
+ if (!existsSync13(skillPath))
4044
4156
  return null;
4045
4157
  return {
4046
- skillMd: readIfExists(join12(skillPath, "SKILL.md")),
4047
- readme: readIfExists(join12(skillPath, "README.md")),
4048
- claudeMd: readIfExists(join12(skillPath, "CLAUDE.md"))
4158
+ skillMd: readIfExists(join14(skillPath, "SKILL.md")),
4159
+ readme: readIfExists(join14(skillPath, "README.md")),
4160
+ claudeMd: readIfExists(join14(skillPath, "CLAUDE.md"))
4049
4161
  };
4050
4162
  }
4051
4163
  function getSkillBestDoc(name) {
@@ -4056,11 +4168,11 @@ function getSkillBestDoc(name) {
4056
4168
  }
4057
4169
  function getSkillRequirements(name) {
4058
4170
  const skillPath = getSkillPath(name);
4059
- if (!existsSync12(skillPath))
4171
+ if (!existsSync13(skillPath))
4060
4172
  return null;
4061
4173
  const texts = [];
4062
4174
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
4063
- const content = readIfExists(join12(skillPath, file));
4175
+ const content = readIfExists(join14(skillPath, file));
4064
4176
  if (content)
4065
4177
  texts.push(content);
4066
4178
  }
@@ -4099,8 +4211,8 @@ function getSkillRequirements(name) {
4099
4211
  const skillName = normalizeSkillName(name);
4100
4212
  let cliCommand = `skills run ${skillName}`;
4101
4213
  let dependencies = {};
4102
- const pkgPath = join12(skillPath, "package.json");
4103
- if (existsSync12(pkgPath)) {
4214
+ const pkgPath = join14(skillPath, "package.json");
4215
+ if (existsSync13(pkgPath)) {
4104
4216
  try {
4105
4217
  const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
4106
4218
  dependencies = pkg.dependencies || {};
@@ -4120,7 +4232,7 @@ async function runSkill(name, args, options = {}) {
4120
4232
  const meta = getSkill(name);
4121
4233
  const canonicalName = meta?.name ?? name;
4122
4234
  const skillPath = getSkillPath(canonicalName);
4123
- if (!existsSync12(skillPath)) {
4235
+ if (!existsSync13(skillPath)) {
4124
4236
  return { exitCode: 1, error: `Skill '${name}' not found` };
4125
4237
  }
4126
4238
  if (isInstructionSkillDir(skillPath, meta)) {
@@ -4129,8 +4241,8 @@ async function runSkill(name, args, options = {}) {
4129
4241
  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
4242
  };
4131
4243
  }
4132
- const pkgPath = join12(skillPath, "package.json");
4133
- if (!existsSync12(pkgPath)) {
4244
+ const pkgPath = join14(skillPath, "package.json");
4245
+ if (!existsSync13(pkgPath)) {
4134
4246
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
4135
4247
  }
4136
4248
  let entryPoint;
@@ -4149,12 +4261,12 @@ async function runSkill(name, args, options = {}) {
4149
4261
  } catch {
4150
4262
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
4151
4263
  }
4152
- const entryPath = join12(skillPath, entryPoint);
4153
- if (!existsSync12(entryPath)) {
4264
+ const entryPath = join14(skillPath, entryPoint);
4265
+ if (!existsSync13(entryPath)) {
4154
4266
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
4155
4267
  }
4156
- const nodeModules = join12(skillPath, "node_modules");
4157
- if (!existsSync12(nodeModules)) {
4268
+ const nodeModules = join14(skillPath, "node_modules");
4269
+ if (!existsSync13(nodeModules)) {
4158
4270
  const install = Bun.spawn(["bun", "install", "--no-save"], {
4159
4271
  cwd: skillPath,
4160
4272
  stdout: "pipe",
@@ -4226,7 +4338,7 @@ function generateSkillMd(name) {
4226
4338
  if (!meta)
4227
4339
  return null;
4228
4340
  const skillPath = getSkillPath(name);
4229
- if (!existsSync12(skillPath))
4341
+ if (!existsSync13(skillPath))
4230
4342
  return null;
4231
4343
  const frontmatter = [
4232
4344
  "---",
@@ -4235,11 +4347,11 @@ function generateSkillMd(name) {
4235
4347
  "---"
4236
4348
  ].join(`
4237
4349
  `);
4238
- const readme = readIfExists(join12(skillPath, "README.md"));
4239
- const claudeMd = readIfExists(join12(skillPath, "CLAUDE.md"));
4350
+ const readme = readIfExists(join14(skillPath, "README.md"));
4351
+ const claudeMd = readIfExists(join14(skillPath, "CLAUDE.md"));
4240
4352
  let cliCommand = null;
4241
- const pkgPath = join12(skillPath, "package.json");
4242
- if (existsSync12(pkgPath)) {
4353
+ const pkgPath = join14(skillPath, "package.json");
4354
+ if (existsSync13(pkgPath)) {
4243
4355
  try {
4244
4356
  const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
4245
4357
  if (pkg.bin) {
@@ -4316,7 +4428,7 @@ function extractEnvVars(text) {
4316
4428
  }
4317
4429
  function readIfExists(path) {
4318
4430
  try {
4319
- if (existsSync12(path)) {
4431
+ if (existsSync13(path)) {
4320
4432
  return readFileSync11(path, "utf-8");
4321
4433
  }
4322
4434
  } catch {}
@@ -8320,21 +8432,21 @@ function requireApiUrl(action = "This command", config, env) {
8320
8432
  }
8321
8433
 
8322
8434
  // 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";
8325
- import { homedir as homedir4 } from "os";
8435
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync7, unlinkSync } from "fs";
8436
+ import { dirname as dirname6, join as join15 } from "path";
8437
+ import { homedir as homedir5 } from "os";
8326
8438
  function getAuthFilePath() {
8327
- return join13(getDataDir(), "auth.json");
8439
+ return join15(getDataDir(), "auth.json");
8328
8440
  }
8329
8441
  function legacyAuthFilePath() {
8330
- return join13(process.env["HOME"] || process.env["USERPROFILE"] || homedir4(), ".skills", "auth.json");
8442
+ return join15(process.env["HOME"] || process.env["USERPROFILE"] || homedir5(), ".skills", "auth.json");
8331
8443
  }
8332
8444
  var cachedConfig;
8333
8445
  function getAuthConfig() {
8334
8446
  if (cachedConfig !== undefined)
8335
8447
  return cachedConfig;
8336
8448
  try {
8337
- const file = existsSync13(getAuthFilePath()) ? getAuthFilePath() : legacyAuthFilePath();
8449
+ const file = existsSync14(getAuthFilePath()) ? getAuthFilePath() : legacyAuthFilePath();
8338
8450
  const raw = readFileSync12(file, "utf-8");
8339
8451
  const config = JSON.parse(raw);
8340
8452
  if (!config.apiKey) {
@@ -9435,14 +9547,14 @@ function createRemoteSkillsClient() {
9435
9547
  return new RemoteSkillsClient(apiKey);
9436
9548
  }
9437
9549
  // 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";
9550
+ import { existsSync as existsSync15, readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
9551
+ import { join as join16 } from "path";
9440
9552
  function getSchedulesPath(targetDir = process.cwd()) {
9441
- return join14(targetDir, ".skills", "schedules.json");
9553
+ return join16(targetDir, ".skills", "schedules.json");
9442
9554
  }
9443
9555
  function loadSchedules(targetDir = process.cwd()) {
9444
9556
  const path = getSchedulesPath(targetDir);
9445
- if (existsSync14(path)) {
9557
+ if (existsSync15(path)) {
9446
9558
  try {
9447
9559
  return JSON.parse(readFileSync13(path, "utf-8"));
9448
9560
  } catch {}
@@ -9451,8 +9563,8 @@ function loadSchedules(targetDir = process.cwd()) {
9451
9563
  }
9452
9564
  function saveSchedules(data, targetDir = process.cwd()) {
9453
9565
  const path = getSchedulesPath(targetDir);
9454
- const dir = join14(targetDir, ".skills");
9455
- if (!existsSync14(dir))
9566
+ const dir = join16(targetDir, ".skills");
9567
+ if (!existsSync15(dir))
9456
9568
  mkdirSync8(dir, { recursive: true });
9457
9569
  writeFileSync8(path, JSON.stringify(data, null, 2));
9458
9570
  }
@@ -9639,8 +9751,8 @@ function recordScheduleRun(id, status, targetDir) {
9639
9751
  saveSchedules(data, targetDir);
9640
9752
  }
9641
9753
  // 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";
9754
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
9755
+ import { dirname as dirname7, join as join17 } from "path";
9644
9756
 
9645
9757
  // src/lib/revision.ts
9646
9758
  import { createHash as createHash3 } from "crypto";
@@ -9931,7 +10043,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
9931
10043
  return reconcileTombstone(slug, corpusOptions);
9932
10044
  }
9933
10045
  if (bundleResponse.status === 404) {
9934
- const marker = readPullMarker(join15(getPortableSkillsRoot(corpusOptions), slug));
10046
+ const marker = readPullMarker(join17(getPortableSkillsRoot(corpusOptions), slug));
9935
10047
  if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
9936
10048
  return { name: slug, success: true, purged: true, removed: false };
9937
10049
  }
@@ -9962,7 +10074,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
9962
10074
  return { name: slug, success: false, error: `Skill '${slug}' was not found on the configured Skills instance.` };
9963
10075
  }
9964
10076
  if (!meta?.revisionId) {
9965
- const marker = readPullMarker(join15(getPortableSkillsRoot(corpusOptions), slug));
10077
+ const marker = readPullMarker(join17(getPortableSkillsRoot(corpusOptions), slug));
9966
10078
  if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
9967
10079
  return { name: slug, success: true, purged: true, removed: false };
9968
10080
  }
@@ -10019,8 +10131,8 @@ function provenRevision(meta, slug, bundle) {
10019
10131
  return declared;
10020
10132
  }
10021
10133
  function reconcileTombstone(slug, corpusOptions) {
10022
- const target = join15(getPortableSkillsRoot(corpusOptions), slug);
10023
- if (!existsSync15(join15(target, PULL_MARKER_FILE))) {
10134
+ const target = join17(getPortableSkillsRoot(corpusOptions), slug);
10135
+ if (!existsSync16(join17(target, PULL_MARKER_FILE))) {
10024
10136
  return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
10025
10137
  }
10026
10138
  rmSync4(target, { recursive: true, force: true });
@@ -10028,7 +10140,7 @@ function reconcileTombstone(slug, corpusOptions) {
10028
10140
  }
10029
10141
  function readPullMarker(dir) {
10030
10142
  try {
10031
- return JSON.parse(readFileSync14(join15(dir, PULL_MARKER_FILE), "utf-8"));
10143
+ return JSON.parse(readFileSync14(join17(dir, PULL_MARKER_FILE), "utf-8"));
10032
10144
  } catch {
10033
10145
  return null;
10034
10146
  }
@@ -10149,14 +10261,14 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
10149
10261
  function installBundleAtomically(name, entries, options = {}, marker = {}) {
10150
10262
  const root = getPortableSkillsRoot(options);
10151
10263
  mkdirSync9(root, { recursive: true });
10152
- const target = join15(root, name);
10153
- const created = !existsSync15(target);
10154
- const staging = mkdtempSync3(join15(root, `.pull-${name}-`));
10264
+ const target = join17(root, name);
10265
+ const created = !existsSync16(target);
10266
+ const staging = mkdtempSync3(join17(root, `.pull-${name}-`));
10155
10267
  let moved = false;
10156
10268
  let backup = null;
10157
10269
  try {
10158
10270
  for (const entry of entries) {
10159
- const destination = join15(staging, entry.path);
10271
+ const destination = join17(staging, entry.path);
10160
10272
  mkdirSync9(dirname7(destination), { recursive: true });
10161
10273
  writeFileSync9(destination, entry.bytes, { mode: entry.mode });
10162
10274
  }
@@ -10168,9 +10280,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
10168
10280
  ...marker.signature ? { signature: marker.signature } : {},
10169
10281
  ...marker.revisionId ? { revisionId: marker.revisionId } : {}
10170
10282
  });
10171
- if (existsSync15(target)) {
10172
- backup = mkdtempSync3(join15(root, `.pull-backup-${name}-`));
10173
- renameSync3(target, join15(backup, name));
10283
+ if (existsSync16(target)) {
10284
+ backup = mkdtempSync3(join17(root, `.pull-backup-${name}-`));
10285
+ renameSync3(target, join17(backup, name));
10174
10286
  moved = true;
10175
10287
  }
10176
10288
  renameSync3(staging, target);
@@ -10178,9 +10290,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
10178
10290
  rmSync4(backup, { recursive: true, force: true });
10179
10291
  } catch (error) {
10180
10292
  rmSync4(staging, { recursive: true, force: true });
10181
- if (moved && backup && existsSync15(join15(backup, name))) {
10293
+ if (moved && backup && existsSync16(join17(backup, name))) {
10182
10294
  try {
10183
- renameSync3(join15(backup, name), target);
10295
+ renameSync3(join17(backup, name), target);
10184
10296
  } catch {}
10185
10297
  }
10186
10298
  throw error;
@@ -10199,7 +10311,7 @@ function writePullMarker(dir, record) {
10199
10311
  ...record.revisionId ? { revisionId: record.revisionId } : {},
10200
10312
  syncedAt: new Date().toISOString()
10201
10313
  };
10202
- writeFileSync9(join15(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
10314
+ writeFileSync9(join17(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
10203
10315
  `);
10204
10316
  }
10205
10317
  async function safeMeta(client, slug) {
@@ -10350,7 +10462,7 @@ import { dirname as dirname8, relative as relative3 } from "path";
10350
10462
  // package.json
10351
10463
  var package_default = {
10352
10464
  name: "@hasna/skills",
10353
- version: "0.1.70",
10465
+ version: "0.1.72",
10354
10466
  description: "Skills library for AI coding agents",
10355
10467
  type: "module",
10356
10468
  bin: {
@@ -10442,6 +10554,7 @@ var package_default = {
10442
10554
  "@aws-sdk/client-ecs": "^3.1079.0",
10443
10555
  "@aws-sdk/client-s3": "^3.1079.0",
10444
10556
  "@hasna/events": "0.1.16",
10557
+ "@hasna/paths": "0.1.0",
10445
10558
  "@modelcontextprotocol/sdk": "^1.26.0",
10446
10559
  chalk: "^5.3.0",
10447
10560
  commander: "^12.1.0",
@@ -11366,16 +11479,16 @@ function clone2(value) {
11366
11479
  return JSON.parse(JSON.stringify(value));
11367
11480
  }
11368
11481
  // src/lib/feedback.ts
11369
- import { existsSync as existsSync16, mkdirSync as mkdirSync11 } from "fs";
11370
- import { dirname as dirname9, join as join16 } from "path";
11482
+ import { existsSync as existsSync17, mkdirSync as mkdirSync11 } from "fs";
11483
+ import { dirname as dirname9, join as join18 } from "path";
11371
11484
  import { Database } from "bun:sqlite";
11372
11485
  function getFeedbackDbPath() {
11373
- return join16(getDataDir(), "skills.db");
11486
+ return join18(getDataDir(), "skills.db");
11374
11487
  }
11375
11488
  function getFeedbackDb() {
11376
11489
  const dbPath = getFeedbackDbPath();
11377
11490
  const dir = dirname9(dbPath);
11378
- if (!existsSync16(dir))
11491
+ if (!existsSync17(dir))
11379
11492
  mkdirSync11(dir, { recursive: true });
11380
11493
  const db = new Database(dbPath);
11381
11494
  db.exec("PRAGMA journal_mode = WAL");
@@ -11412,14 +11525,14 @@ function saveFeedback(input) {
11412
11525
  // src/lib/native-storage.ts
11413
11526
  import { createHash as createHash5, createHmac as createHmac2 } from "crypto";
11414
11527
  import {
11415
- existsSync as existsSync17,
11528
+ existsSync as existsSync18,
11416
11529
  mkdirSync as mkdirSync12,
11417
11530
  readFileSync as readFileSync15,
11418
11531
  readdirSync as readdirSync9,
11419
11532
  statSync as statSync9,
11420
11533
  writeFileSync as writeFileSync11
11421
11534
  } from "fs";
11422
- import { dirname as dirname10, join as join17, normalize as normalize3, relative as relative4, sep as sep2 } from "path";
11535
+ import { dirname as dirname10, join as join19, normalize as normalize3, relative as relative4, sep as sep2 } from "path";
11423
11536
  var SKILLS_STORAGE_TABLES = [
11424
11537
  "skills_sync_records",
11425
11538
  "skills_sync_cursors"
@@ -11556,7 +11669,7 @@ function getSkillsNativeStorageStatus(options = {}) {
11556
11669
  local: {
11557
11670
  dataDir: getDataDir(),
11558
11671
  projectStateDir: getProjectStateDir(targetDir),
11559
- feedbackDbPath: join17(getDataDir(), "skills.db")
11672
+ feedbackDbPath: join19(getDataDir(), "skills.db")
11560
11673
  },
11561
11674
  remote: {
11562
11675
  databaseConfigured: Boolean(config.databaseUrl),
@@ -11579,7 +11692,7 @@ function getStorageStatus(options = {}) {
11579
11692
  function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
11580
11693
  const projectStateDir = getProjectStateDir(targetDir);
11581
11694
  const files = [];
11582
- if (existsSync17(projectStateDir)) {
11695
+ if (existsSync18(projectStateDir)) {
11583
11696
  for (const filePath of walkFiles2(projectStateDir)) {
11584
11697
  const bytes = readFileSync15(filePath);
11585
11698
  const relativePath = toPosix(relative4(targetDir, filePath));
@@ -11606,7 +11719,7 @@ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options
11606
11719
  continue;
11607
11720
  }
11608
11721
  const absolutePath = resolveSnapshotPath(targetDir, file.path);
11609
- if (existsSync17(absolutePath) && !options.overwrite) {
11722
+ if (existsSync18(absolutePath) && !options.overwrite) {
11610
11723
  skipped += 1;
11611
11724
  continue;
11612
11725
  }
@@ -11913,7 +12026,7 @@ function parsePositiveInteger(value) {
11913
12026
  function walkFiles2(dir) {
11914
12027
  const files = [];
11915
12028
  for (const entry of readdirSync9(dir)) {
11916
- const full = join17(dir, entry);
12029
+ const full = join19(dir, entry);
11917
12030
  const stats = statSync9(full);
11918
12031
  if (stats.isDirectory())
11919
12032
  files.push(...walkFiles2(full));
@@ -11930,7 +12043,7 @@ function resolveSnapshotPath(targetDir, snapshotPath) {
11930
12043
  if (!toPosix(normalizedPath).startsWith(".skills/")) {
11931
12044
  throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
11932
12045
  }
11933
- return join17(targetDir, normalizedPath);
12046
+ return join19(targetDir, normalizedPath);
11934
12047
  }
11935
12048
  function normalizeS3Prefix(prefix) {
11936
12049
  return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
@@ -11990,14 +12103,642 @@ function toArrayBuffer(bytes) {
11990
12103
  new Uint8Array(buffer).set(bytes);
11991
12104
  return buffer;
11992
12105
  }
12106
+ // src/lib/station-snapshot.ts
12107
+ import { createHash as createHash6 } from "crypto";
12108
+ import {
12109
+ copyFileSync as copyFileSync2,
12110
+ mkdirSync as mkdirSync13,
12111
+ readFileSync as readFileSync16,
12112
+ statSync as statSync11,
12113
+ writeFileSync as writeFileSync12
12114
+ } from "fs";
12115
+ import { dirname as dirname11, isAbsolute as isAbsolute3, relative as relative5, resolve as resolve2, sep as sep4 } from "path";
12116
+
12117
+ // src/lib/portable-snapshot-filter.ts
12118
+ import { readdirSync as readdirSync10, statSync as statSync10 } from "fs";
12119
+ import { homedir as homedir6 } from "os";
12120
+ import { join as join20, sep as sep3 } from "path";
12121
+ var SYNC_HOMES = [
12122
+ { name: "skills", subClass: "skills", agent: null },
12123
+ { name: "custom", subClass: "custom", agent: null },
12124
+ { name: "claude", subClass: "agent-homes", agent: "claude" },
12125
+ { name: "codewith", subClass: "agent-homes", agent: "codewith" },
12126
+ { name: "codex", subClass: "agent-homes", agent: "codex" },
12127
+ { name: "opencode", subClass: "agent-homes", agent: "opencode" },
12128
+ { name: "cursor", subClass: "agent-homes", agent: "cursor" }
12129
+ ];
12130
+ var EXCLUDE_DIR_NAMES = new Set([
12131
+ ".git",
12132
+ "node_modules",
12133
+ "__pycache__",
12134
+ ".cache",
12135
+ ".pytest_cache",
12136
+ ".mypy_cache",
12137
+ ".ruff_cache"
12138
+ ]);
12139
+ var EXCLUDE_DIR_PATTERNS = [
12140
+ /^\.merge-pr\.rollback-/
12141
+ ];
12142
+ var EXCLUDE_FILE_NAMES = new Set([
12143
+ ".DS_Store",
12144
+ "package-lock.json",
12145
+ "pnpm-lock.yaml",
12146
+ "yarn.lock",
12147
+ "Cargo.lock"
12148
+ ]);
12149
+ var EXCLUDE_FILE_PATTERNS = [
12150
+ /^\._/,
12151
+ /\.bak$/,
12152
+ /\.orig$/,
12153
+ /\.rej$/,
12154
+ /~$/,
12155
+ /\.pyc$/,
12156
+ /\.pyo$/,
12157
+ /\.log$/,
12158
+ /\.db$/,
12159
+ /\.sqlite(\d)?$/,
12160
+ /^bun\.lock/,
12161
+ /\.env($|\.)/,
12162
+ /\.pem$/,
12163
+ /\.key$/,
12164
+ /\.p12$/,
12165
+ /\.pfx$/,
12166
+ /\.jks$/,
12167
+ /^id_rsa/,
12168
+ /^id_ed25519/,
12169
+ /^credentials/
12170
+ ];
12171
+ var PORTABLE_TOP_LEVEL = new Set(["SKILL.md", "skill.json"]);
12172
+ var PORTABLE_SUBDIRS = new Set(["scripts", "assets", "references"]);
12173
+ var REFUSED_SCANNER_FLAGGED = new Set([
12174
+ "aws-cross-account-app-migration/SKILL.md",
12175
+ "aws-cross-account-app-migration/scripts/selftest.sh",
12176
+ "gateway-serve/SKILL.md",
12177
+ "infinity-drain/SKILL.md",
12178
+ "infinity-run/SKILL.md",
12179
+ "oss-saas-code-cleanup/SKILL.md",
12180
+ "repo-project-familiarization/scripts/repo_shape.py",
12181
+ "repo-project-familiarization/scripts/session_history.py",
12182
+ "scale-check/SKILL.md",
12183
+ "standard-align-repo/SKILL.md",
12184
+ "standard-build-iapp/SKILL.md",
12185
+ "standard-build-oss/SKILL.md",
12186
+ "skill-image/SKILL.md",
12187
+ "skill-scale-check/SKILL.md",
12188
+ "sqlite-to-rds-parity-migrate/scripts/parity-migrate.ts",
12189
+ "pdf-operations/scripts/pdf_ops.py"
12190
+ ]);
12191
+ function isExcludedSkillFileName(fileName) {
12192
+ if (EXCLUDE_FILE_NAMES.has(fileName)) {
12193
+ return true;
12194
+ }
12195
+ return EXCLUDE_FILE_PATTERNS.some((pattern) => pattern.test(fileName));
12196
+ }
12197
+ function isPortableWithinSkill(relativeParts) {
12198
+ if (relativeParts.length < 2) {
12199
+ return false;
12200
+ }
12201
+ const [, second] = relativeParts;
12202
+ if (PORTABLE_TOP_LEVEL.has(second)) {
12203
+ return relativeParts.length === 2;
12204
+ }
12205
+ if (relativeParts.length < 3) {
12206
+ return false;
12207
+ }
12208
+ return PORTABLE_SUBDIRS.has(second);
12209
+ }
12210
+ function homePathFor(definition, homesRoot) {
12211
+ const home = homesRoot ?? homedir6();
12212
+ if (definition.subClass === "skills" || definition.subClass === "custom") {
12213
+ return join20(skillsDataRootForHome(home), definition.name);
12214
+ }
12215
+ if (definition.agent === "opencode") {
12216
+ return join20(home, ".config", "opencode", "skills");
12217
+ }
12218
+ return join20(home, `.${definition.agent}`, "skills");
12219
+ }
12220
+ function destinationFor(definition, stationId, relativePath) {
12221
+ const category = definition.subClass === "agent-homes" ? join20("agent-homes", definition.agent ?? "") : definition.name;
12222
+ return join20("resources", stationId, "skills", category, ...relativePath.split(sep3));
12223
+ }
12224
+ function walkEntries(absoluteRoot) {
12225
+ let entries;
12226
+ try {
12227
+ entries = readdirSync10(absoluteRoot, { withFileTypes: true });
12228
+ } catch {
12229
+ return [];
12230
+ }
12231
+ const output = [];
12232
+ for (const entry of entries) {
12233
+ const childFull = join20(absoluteRoot, entry.name);
12234
+ if (entry.isSymbolicLink()) {
12235
+ output.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
12236
+ continue;
12237
+ }
12238
+ if (entry.isDirectory()) {
12239
+ if (EXCLUDE_DIR_NAMES.has(entry.name) || EXCLUDE_DIR_PATTERNS.some((pattern) => pattern.test(entry.name))) {
12240
+ continue;
12241
+ }
12242
+ const nested = walkEntries(childFull);
12243
+ for (const item of nested) {
12244
+ output.push({ ...item, relativePath: join20(entry.name, item.relativePath) });
12245
+ }
12246
+ continue;
12247
+ }
12248
+ if (entry.isFile()) {
12249
+ output.push({ kind: "file", relativePath: entry.name, fullPath: childFull });
12250
+ }
12251
+ }
12252
+ return output;
12253
+ }
12254
+ function isRegularFile(filePath) {
12255
+ try {
12256
+ return statSync10(filePath).isFile();
12257
+ } catch {
12258
+ return false;
12259
+ }
12260
+ }
12261
+
12262
+ // src/lib/station-snapshot.ts
12263
+ var STATION_SYNC_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-sync-manifest/v1";
12264
+ var STATION_SNAPSHOT_PRODUCER = { name: "@hasna/skills", version: package_default.version };
12265
+
12266
+ class StationSnapshotError extends Error {
12267
+ code;
12268
+ detail;
12269
+ constructor(code, message, detail = []) {
12270
+ super(message);
12271
+ this.name = "StationSnapshotError";
12272
+ this.code = code;
12273
+ this.detail = detail;
12274
+ }
12275
+ }
12276
+ function validateStationId(stationId) {
12277
+ if (!/^[a-z0-9-]+$/.test(stationId)) {
12278
+ throw new StationSnapshotError("INVALID_STATION", `station id must be a slug, got: ${stationId}`);
12279
+ }
12280
+ }
12281
+ function sha256File(filePath) {
12282
+ return createHash6("sha256").update(readFileSync16(filePath)).digest("hex");
12283
+ }
12284
+ function scanHome(definition, homesRoot) {
12285
+ const homePath = homePathFor(definition, homesRoot);
12286
+ const entries = walkEntries(homePath);
12287
+ const portable = [];
12288
+ const skipped = [];
12289
+ for (const entry of entries) {
12290
+ const relativeParts = entry.relativePath.split(sep4);
12291
+ const fileName = relativeParts[relativeParts.length - 1];
12292
+ if (entry.kind === "symlink") {
12293
+ skipped.push({ relativePath: entry.relativePath, reason: "symlink" });
12294
+ continue;
12295
+ }
12296
+ if (!isPortableWithinSkill(relativeParts)) {
12297
+ skipped.push({ relativePath: entry.relativePath, reason: "not-portable" });
12298
+ continue;
12299
+ }
12300
+ if (isExcludedSkillFileName(fileName)) {
12301
+ skipped.push({ relativePath: entry.relativePath, reason: "excluded" });
12302
+ continue;
12303
+ }
12304
+ if (REFUSED_SCANNER_FLAGGED.has(entry.relativePath)) {
12305
+ skipped.push({ relativePath: entry.relativePath, reason: "refused-scanner-flagged" });
12306
+ continue;
12307
+ }
12308
+ if (!isRegularFile(entry.fullPath)) {
12309
+ skipped.push({ relativePath: entry.relativePath, reason: "not-regular-file" });
12310
+ continue;
12311
+ }
12312
+ const info = statSync11(entry.fullPath);
12313
+ portable.push({
12314
+ relativePath: entry.relativePath,
12315
+ fullPath: entry.fullPath,
12316
+ size: info.size,
12317
+ mtimeMs: info.mtimeMs,
12318
+ mtimeIso: info.mtime.toISOString()
12319
+ });
12320
+ }
12321
+ return { definition, homePath, portable, skipped };
12322
+ }
12323
+ function planStationSnapshot(options) {
12324
+ validateStationId(options.stationId);
12325
+ const scanned = SYNC_HOMES.map((definition) => scanHome(definition, options.homesRoot));
12326
+ const symlinks = scanned.reduce((sum, item) => sum + item.skipped.filter((entry) => entry.reason === "symlink").length, 0);
12327
+ if (symlinks > 0) {
12328
+ throw new StationSnapshotError("SYMLINKS_REFUSED", `${symlinks} symlink(s) inside skill homes; symlinks are refused (fail closed)`);
12329
+ }
12330
+ const plans = [];
12331
+ for (const item of scanned) {
12332
+ for (const file of item.portable) {
12333
+ plans.push({
12334
+ definition: item.definition,
12335
+ source: file,
12336
+ destination: destinationFor(item.definition, options.stationId, file.relativePath),
12337
+ digest: sha256File(file.fullPath)
12338
+ });
12339
+ }
12340
+ }
12341
+ const totalBytes = plans.reduce((sum, plan) => sum + plan.source.size, 0);
12342
+ return { scanned, plans, totalBytes };
12343
+ }
12344
+ function humanHomes(scanned) {
12345
+ return scanned.map((item) => ({
12346
+ name: item.definition.name,
12347
+ homePath: item.homePath,
12348
+ files: item.portable.length,
12349
+ skipped: item.skipped.length
12350
+ }));
12351
+ }
12352
+ function writeStationSnapshot(options) {
12353
+ const repoRoot = resolve2(options.repoRoot ?? process.cwd());
12354
+ const { scanned, plans, totalBytes } = planStationSnapshot(options);
12355
+ const manifestFiles = plans.map((plan) => ({
12356
+ relativePath: plan.source.relativePath,
12357
+ destination: plan.destination,
12358
+ subClass: plan.definition.subClass,
12359
+ agent: plan.definition.agent,
12360
+ sha256: plan.digest,
12361
+ sourceMtimeMs: plan.source.mtimeMs,
12362
+ sourceMtimeIso: plan.source.mtimeIso,
12363
+ size: plan.source.size
12364
+ }));
12365
+ const base = {
12366
+ stationId: options.stationId,
12367
+ mode: "dry-run",
12368
+ repoRoot,
12369
+ stats: { files: plans.length, bytes: totalBytes },
12370
+ homes: humanHomes(scanned),
12371
+ files: manifestFiles
12372
+ };
12373
+ if (options.dryRun !== false) {
12374
+ return base;
12375
+ }
12376
+ const conflicts = [];
12377
+ const untouched = [];
12378
+ for (const plan of plans) {
12379
+ const destination = resolve2(repoRoot, plan.destination);
12380
+ const destinationRelative = relative5(repoRoot, destination);
12381
+ if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep4) || isAbsolute3(destinationRelative)) {
12382
+ throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
12383
+ }
12384
+ let existingDigest = null;
12385
+ try {
12386
+ existingDigest = sha256File(destination);
12387
+ } catch {}
12388
+ if (existingDigest !== null) {
12389
+ if (existingDigest === plan.digest) {
12390
+ continue;
12391
+ }
12392
+ conflicts.push(`existing destination differs from staged source: ${plan.destination}`);
12393
+ continue;
12394
+ }
12395
+ untouched.push(plan);
12396
+ }
12397
+ if (conflicts.length > 0) {
12398
+ throw new StationSnapshotError("CONFLICT", `${conflicts.length} conflict(s); terminal non-acceptance, nothing written`, conflicts);
12399
+ }
12400
+ let written = 0;
12401
+ for (const plan of untouched) {
12402
+ const destination = resolve2(repoRoot, plan.destination);
12403
+ mkdirSync13(dirname11(destination), { recursive: true });
12404
+ copyFileSync2(plan.source.fullPath, destination);
12405
+ written += 1;
12406
+ }
12407
+ const unchanged = plans.length - untouched.length;
12408
+ const manifest = {
12409
+ schema: STATION_SYNC_MANIFEST_SCHEMA,
12410
+ stationId: options.stationId,
12411
+ syncedAt: new Date().toISOString(),
12412
+ producer: STATION_SNAPSHOT_PRODUCER,
12413
+ stats: {
12414
+ written,
12415
+ unchanged,
12416
+ files: plans.length,
12417
+ bytes: totalBytes
12418
+ },
12419
+ files: manifestFiles
12420
+ };
12421
+ const manifestPath = resolve2(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
12422
+ mkdirSync13(dirname11(manifestPath), { recursive: true });
12423
+ writeFileSync12(manifestPath, `${JSON.stringify(manifest, null, 2)}
12424
+ `);
12425
+ return {
12426
+ ...base,
12427
+ mode: "populate",
12428
+ stats: { files: plans.length, bytes: totalBytes, written, unchanged },
12429
+ manifestPath
12430
+ };
12431
+ }
12432
+ // src/lib/station-hydrate.ts
12433
+ import { createHash as createHash7 } from "crypto";
12434
+ import {
12435
+ copyFileSync as copyFileSync3,
12436
+ mkdirSync as mkdirSync14,
12437
+ readdirSync as readdirSync11,
12438
+ readFileSync as readFileSync17,
12439
+ statSync as statSync12,
12440
+ writeFileSync as writeFileSync13
12441
+ } from "fs";
12442
+ import { dirname as dirname12, join as join21, resolve as resolve3, sep as sep5 } from "path";
12443
+ var STATION_HYDRATION_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-hydration-manifest/v1";
12444
+ var STATION_HYDRATION_PRODUCER = { name: "@hasna/skills", version: package_default.version };
12445
+ var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
12446
+ function fail(code, message, detail = []) {
12447
+ throw new StationSnapshotError(code, message, detail);
12448
+ }
12449
+ function snapshotRootFor(repoRoot, stationId) {
12450
+ return join21(repoRoot, "resources", stationId, "skills");
12451
+ }
12452
+ function readSnapshotManifest(repoRoot, stationId) {
12453
+ const snapshotRoot = snapshotRootFor(repoRoot, stationId);
12454
+ const manifestPath = join21(snapshotRoot, "sync-manifest.json");
12455
+ let manifest;
12456
+ try {
12457
+ manifest = JSON.parse(readFileSync17(manifestPath, "utf8"));
12458
+ } catch (error) {
12459
+ fail("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error.message}`);
12460
+ }
12461
+ const sourceSnapshotSha = sha256File(manifestPath);
12462
+ return { manifest, manifestPath, sourceSnapshotSha };
12463
+ }
12464
+ function planStationHydration(stationId, repoRoot) {
12465
+ validateStationId(stationId);
12466
+ const { manifest, sourceSnapshotSha } = readSnapshotManifest(repoRoot, stationId);
12467
+ const snapshotRoot = snapshotRootFor(repoRoot, stationId);
12468
+ const manifestHashes = new Map;
12469
+ for (const file of manifest.files ?? []) {
12470
+ const relativePath = file.relativePath;
12471
+ const agent = file.agent;
12472
+ if (agent && relativePath) {
12473
+ manifestHashes.set(`${agent}${MANIFEST_HASH_KEY_SEP}${relativePath}`, file.sha256);
12474
+ }
12475
+ }
12476
+ const candidates = [];
12477
+ const symlinks = [];
12478
+ const hashMismatches = [];
12479
+ const skippedByRule = [];
12480
+ for (const agent of SYNC_AGENTS) {
12481
+ const agentRoot = join21(snapshotRoot, "agent-homes", agent);
12482
+ let identEntries;
12483
+ try {
12484
+ identEntries = readdirSync11(agentRoot, { withFileTypes: true });
12485
+ } catch {
12486
+ continue;
12487
+ }
12488
+ for (const identEntry of identEntries) {
12489
+ if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
12490
+ continue;
12491
+ }
12492
+ const identRoot = join21(agentRoot, identEntry.name);
12493
+ const entries = walkEntries(identRoot);
12494
+ for (const entry of entries) {
12495
+ const relativeParts = [identEntry.name, ...entry.relativePath.split(sep5)];
12496
+ if (entry.kind === "symlink") {
12497
+ symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
12498
+ continue;
12499
+ }
12500
+ if (!isPortableWithinSkill(relativeParts)) {
12501
+ skippedByRule.push({
12502
+ ident: identEntry.name,
12503
+ agent,
12504
+ relativePath: entry.relativePath,
12505
+ reason: "not-portable"
12506
+ });
12507
+ continue;
12508
+ }
12509
+ const fileName = relativeParts[relativeParts.length - 1];
12510
+ if (isExcludedSkillFileName(fileName)) {
12511
+ skippedByRule.push({
12512
+ ident: identEntry.name,
12513
+ agent,
12514
+ relativePath: entry.relativePath,
12515
+ reason: "excluded"
12516
+ });
12517
+ continue;
12518
+ }
12519
+ const withinIdent = relativeParts.slice(1).join(sep5);
12520
+ const homeRelative = relativeParts.join(sep5);
12521
+ if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
12522
+ skippedByRule.push({
12523
+ ident: identEntry.name,
12524
+ agent,
12525
+ relativePath: entry.relativePath,
12526
+ reason: "refused-scanner-flagged"
12527
+ });
12528
+ continue;
12529
+ }
12530
+ if (!isRegularFile(entry.fullPath)) {
12531
+ skippedByRule.push({
12532
+ ident: identEntry.name,
12533
+ agent,
12534
+ relativePath: entry.relativePath,
12535
+ reason: "not-regular-file"
12536
+ });
12537
+ continue;
12538
+ }
12539
+ const info = statSync12(entry.fullPath);
12540
+ const manifestHash = manifestHashes.get(`${agent}${MANIFEST_HASH_KEY_SEP}${homeRelative}`) ?? null;
12541
+ let verified = false;
12542
+ if (manifestHash !== null) {
12543
+ verified = sha256File(entry.fullPath) === manifestHash;
12544
+ if (!verified) {
12545
+ hashMismatches.push({
12546
+ ident: identEntry.name,
12547
+ agent,
12548
+ relativePath: entry.relativePath
12549
+ });
12550
+ }
12551
+ }
12552
+ candidates.push({
12553
+ ident: identEntry.name,
12554
+ agent,
12555
+ withinIdent,
12556
+ fullPath: entry.fullPath,
12557
+ size: info.size,
12558
+ mtimeMs: info.mtimeMs,
12559
+ manifestHash,
12560
+ verified
12561
+ });
12562
+ }
12563
+ }
12564
+ }
12565
+ if (symlinks.length > 0) {
12566
+ fail("SYMLINKS_REFUSED", `${symlinks.length} symlink(s) inside the snapshot; symlinks are refused (fail closed)`);
12567
+ }
12568
+ if (hashMismatches.length > 0) {
12569
+ 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}`));
12570
+ }
12571
+ const byIdent = new Map;
12572
+ for (const candidate of candidates) {
12573
+ const group = byIdent.get(candidate.ident) ?? [];
12574
+ group.push(candidate);
12575
+ byIdent.set(candidate.ident, group);
12576
+ }
12577
+ const winners = [];
12578
+ for (const [ident, group] of byIdent) {
12579
+ const byFile = new Map;
12580
+ for (const candidate of group) {
12581
+ const copies = byFile.get(candidate.withinIdent) ?? [];
12582
+ copies.push(candidate);
12583
+ byFile.set(candidate.withinIdent, copies);
12584
+ }
12585
+ const files = [];
12586
+ for (const [withinIdent, copies] of byFile) {
12587
+ let eligible = copies;
12588
+ if (withinIdent === "SKILL.md") {
12589
+ const content = [];
12590
+ for (const copy of copies) {
12591
+ let isStub = false;
12592
+ try {
12593
+ isStub = isPointerSkillMd(readFileSync17(copy.fullPath, "utf8"));
12594
+ } catch {
12595
+ isStub = false;
12596
+ }
12597
+ if (!isStub)
12598
+ content.push(copy);
12599
+ }
12600
+ if (content.length > 0) {
12601
+ eligible = content;
12602
+ }
12603
+ }
12604
+ eligible.sort((left, right) => {
12605
+ const leftHash = left.verified;
12606
+ const rightHash = right.verified;
12607
+ if (leftHash !== rightHash) {
12608
+ return leftHash ? -1 : 1;
12609
+ }
12610
+ if (right.mtimeMs !== left.mtimeMs) {
12611
+ return right.mtimeMs - left.mtimeMs;
12612
+ }
12613
+ return SYNC_AGENTS.indexOf(left.agent) - SYNC_AGENTS.indexOf(right.agent);
12614
+ });
12615
+ const winner = eligible[0];
12616
+ files.push({
12617
+ withinIdent,
12618
+ winner,
12619
+ alternates: copies.filter((copy) => copy !== winner).map((copy) => copy.agent)
12620
+ });
12621
+ }
12622
+ files.sort((left, right) => left.withinIdent.localeCompare(right.withinIdent));
12623
+ winners.push({ ident, files });
12624
+ }
12625
+ winners.sort((left, right) => left.ident.localeCompare(right.ident));
12626
+ const totalFiles = winners.reduce((sum, skill) => sum + skill.files.length, 0);
12627
+ const totalBytes = winners.reduce((sum, skill) => sum + skill.files.reduce((inner, file) => inner + file.winner.size, 0), 0);
12628
+ return { manifest, sourceSnapshotSha, winners, skippedByRule, totalFiles, totalBytes };
12629
+ }
12630
+ function skillSha256(skill) {
12631
+ const skillMd = skill.files.find((file) => file.withinIdent === "SKILL.md");
12632
+ if (skillMd) {
12633
+ return sha256File(skillMd.winner.fullPath);
12634
+ }
12635
+ if (skill.files.length === 1) {
12636
+ return sha256File(skill.files[0].winner.fullPath);
12637
+ }
12638
+ const joined = skill.files.map((file) => sha256File(file.winner.fullPath));
12639
+ return createHash7("sha256").update(joined.sort().join(`
12640
+ `)).digest("hex");
12641
+ }
12642
+ function writeStationHydration(options) {
12643
+ const repoRoot = resolve3(options.repoRoot ?? process.cwd());
12644
+ const cacheRoot = resolve3(options.cacheRoot ?? resolveCorpusRoot());
12645
+ const plan = planStationHydration(options.stationId, repoRoot);
12646
+ const resultSkills = plan.winners.map((skill) => ({
12647
+ ident: skill.ident,
12648
+ files: skill.files.map((file) => ({
12649
+ relativePath: file.withinIdent,
12650
+ sourceAgent: file.winner.agent,
12651
+ sourceMtimeMs: file.winner.mtimeMs,
12652
+ size: file.winner.size
12653
+ })),
12654
+ sha256: skillSha256(skill)
12655
+ }));
12656
+ const base = {
12657
+ stationId: options.stationId,
12658
+ mode: "dry-run",
12659
+ cacheRoot,
12660
+ snapshotRoot: snapshotRootFor(repoRoot, options.stationId),
12661
+ sourceSnapshotSha: plan.sourceSnapshotSha,
12662
+ stats: {
12663
+ idents: plan.winners.length,
12664
+ files: plan.totalFiles,
12665
+ bytes: plan.totalBytes
12666
+ },
12667
+ winners: plan.winners,
12668
+ skills: resultSkills
12669
+ };
12670
+ if (options.dryRun !== false) {
12671
+ return base;
12672
+ }
12673
+ const conflicts = [];
12674
+ const toWrite = [];
12675
+ for (const skill of plan.winners) {
12676
+ for (const file of skill.files) {
12677
+ const destination = join21(cacheRoot, skill.ident, file.withinIdent);
12678
+ const digest = sha256File(file.winner.fullPath);
12679
+ let existingDigest = null;
12680
+ try {
12681
+ existingDigest = sha256File(destination);
12682
+ } catch {}
12683
+ if (existingDigest !== null) {
12684
+ if (existingDigest === digest) {
12685
+ continue;
12686
+ }
12687
+ conflicts.push(`existing destination differs from snapshot winner: ${destination}`);
12688
+ continue;
12689
+ }
12690
+ toWrite.push({ destination, fullPath: file.winner.fullPath });
12691
+ }
12692
+ }
12693
+ if (conflicts.length > 0) {
12694
+ fail("CONFLICT", `${conflicts.length} conflict(s); terminal non-acceptance, nothing written`, conflicts);
12695
+ }
12696
+ let written = 0;
12697
+ for (const entry of toWrite) {
12698
+ mkdirSync14(dirname12(entry.destination), { recursive: true });
12699
+ copyFileSync3(entry.fullPath, entry.destination);
12700
+ written += 1;
12701
+ }
12702
+ const unchanged = plan.totalFiles - written;
12703
+ const hydration = {
12704
+ schema: STATION_HYDRATION_MANIFEST_SCHEMA,
12705
+ stationId: options.stationId,
12706
+ hydratedAt: new Date().toISOString(),
12707
+ producer: STATION_HYDRATION_PRODUCER,
12708
+ sourceSnapshotSha: plan.sourceSnapshotSha,
12709
+ cacheRoot,
12710
+ stats: {
12711
+ idents: plan.winners.length,
12712
+ written,
12713
+ unchanged,
12714
+ files: plan.totalFiles,
12715
+ bytes: plan.totalBytes
12716
+ },
12717
+ skills: resultSkills
12718
+ };
12719
+ const hydrationManifestPath = join21(dirname12(cacheRoot), `hydration-${options.stationId}.json`);
12720
+ mkdirSync14(dirname12(hydrationManifestPath), { recursive: true });
12721
+ writeFileSync13(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
12722
+ `);
12723
+ return {
12724
+ ...base,
12725
+ mode: "apply",
12726
+ stats: { ...base.stats, written, unchanged },
12727
+ manifestPath: hydrationManifestPath
12728
+ };
12729
+ }
11993
12730
  export {
12731
+ writeStationSnapshot,
12732
+ writeStationHydration,
11994
12733
  writeRunLogs,
11995
12734
  writeRegistrySyncArtifact,
11996
12735
  writeManagedSkillDir,
11997
12736
  writeManagedAgentSkill,
11998
12737
  writeCorpusSkill,
12738
+ walkEntries,
11999
12739
  verifyContentHash,
12000
12740
  validateToolPrimitiveCoverage,
12741
+ validateStationId,
12001
12742
  validateSkillsCliMcpParity,
12002
12743
  validateSkillDirectory,
12003
12744
  validateRegistryConsistency,
@@ -12016,6 +12757,7 @@ export {
12016
12757
  skillsPostgresSyncSchemaSql,
12017
12758
  skillExists,
12018
12759
  signSkillsAwsV4Request,
12760
+ sha256File,
12019
12761
  setSkillDisabled,
12020
12762
  setScheduleEnabled,
12021
12763
  searchSkills,
@@ -12043,6 +12785,8 @@ export {
12043
12785
  portPortableSkillDirectory,
12044
12786
  portPortableSkill,
12045
12787
  pointerSkillMd,
12788
+ planStationSnapshot,
12789
+ planStationHydration,
12046
12790
  planSkillsS3SnapshotUpload,
12047
12791
  pinSkill,
12048
12792
  pinProjectSkill,
@@ -12068,7 +12812,10 @@ export {
12068
12812
  listPinnedSkills,
12069
12813
  listMcpToolContracts,
12070
12814
  isSyncAgent,
12815
+ isRegularFile,
12816
+ isPortableWithinSkill,
12071
12817
  isGatewayBackedSkill,
12818
+ isExcludedSkillFileName,
12072
12819
  isBasicSkillName,
12073
12820
  installSkills,
12074
12821
  installSkillSource,
@@ -12076,6 +12823,7 @@ export {
12076
12823
  installSkillForAgent,
12077
12824
  installSkill,
12078
12825
  importSkillsLocalSnapshot,
12826
+ homePathFor,
12079
12827
  getToolPrimitive,
12080
12828
  getStorageStatus,
12081
12829
  getStorageDatabaseUrl,
@@ -12125,6 +12873,7 @@ export {
12125
12873
  ensureProjectConfig,
12126
12874
  enableSkill,
12127
12875
  disableSkill,
12876
+ destinationFor,
12128
12877
  describeMcpToolContracts,
12129
12878
  createSkillsSnapshotSyncRecord,
12130
12879
  createSkillsS3ObjectStore,
@@ -12148,12 +12897,16 @@ export {
12148
12897
  adaptSkillMdForAgent,
12149
12898
  TOOL_PRIMITIVE_SCHEMA_VERSION,
12150
12899
  TOOL_PRIMITIVES,
12900
+ StationSnapshotError,
12151
12901
  SkillsS3ObjectStore,
12152
12902
  SkillsPostgresSyncStore,
12153
12903
  SYNC_MARKER_MANAGED_BY,
12154
12904
  SYNC_MARKER_FILE,
12905
+ SYNC_HOMES,
12155
12906
  SYNC_AGENTS,
12156
12907
  STORAGE_TABLES,
12908
+ STATION_SYNC_MANIFEST_SCHEMA,
12909
+ STATION_HYDRATION_MANIFEST_SCHEMA,
12157
12910
  SKILL_SYSTEM_DEPS_ALLOWLIST,
12158
12911
  SKILL_SANDBOX_MODES,
12159
12912
  SKILL_RUNTIMES,
@@ -12170,6 +12923,7 @@ export {
12170
12923
  RemoteRouteUnsupportedError,
12171
12924
  RemoteRequestError,
12172
12925
  REMOTE_SKILL_RUN_CONTRACT_VERSION,
12926
+ REFUSED_SCANNER_FLAGGED,
12173
12927
  PullSkillError,
12174
12928
  PROJECT_CONFIG_FILE,
12175
12929
  PORTABLE_SKILL_STANDARD,