@hasna/skills 0.5.10 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +95 -53
  2. package/bin/index.js +3467 -1460
  3. package/bin/mcp.js +2638 -1713
  4. package/bin/migrate.js +110 -7
  5. package/bin/server.js +5487 -216
  6. package/bin/worker.js +372 -17
  7. package/dist/admin-contract.d.ts +13 -13
  8. package/dist/admin-contract.js +13 -13
  9. package/dist/cli/commands/agent-integration.d.ts +2 -0
  10. package/dist/cli/commands/context.d.ts +19 -0
  11. package/dist/cli/commands/install.d.ts +1 -0
  12. package/dist/cli/commands/profile-sync.d.ts +10 -0
  13. package/dist/cli/commands/profiles.d.ts +2 -0
  14. package/dist/index.d.ts +5 -0
  15. package/dist/index.js +1273 -331
  16. package/dist/lib/agent-integration.d.ts +74 -0
  17. package/dist/lib/cloud-executions.d.ts +39 -0
  18. package/dist/lib/managed-policy.d.ts +6 -0
  19. package/dist/lib/profile-admin.d.ts +4 -0
  20. package/dist/lib/profile-client.d.ts +19 -0
  21. package/dist/lib/read-access.d.ts +3 -23
  22. package/dist/lib/selected-document.d.ts +6 -0
  23. package/dist/lib/selected-run.d.ts +31 -0
  24. package/dist/lib/selection-cache.d.ts +40 -0
  25. package/dist/lib/selection-resolver.d.ts +75 -0
  26. package/dist/lib/skill-context.d.ts +43 -0
  27. package/dist/lib/skillinfo.d.ts +2 -0
  28. package/dist/sdk/execution/dispatchers/ecs.d.ts +8 -0
  29. package/dist/sdk/index.d.ts +2 -0
  30. package/dist/sdk/index.js +9791 -8152
  31. package/dist/server/app.d.ts +7 -1
  32. package/dist/server/artifact-storage.d.ts +7 -10
  33. package/dist/server/auth.d.ts +2 -0
  34. package/dist/server/config.d.ts +2 -0
  35. package/dist/server/profile-api.d.ts +3 -0
  36. package/dist/server/runtime-api.d.ts +31 -0
  37. package/dist/server/runtime-policy.d.ts +25 -0
  38. package/dist/server/runtime-store.d.ts +73 -0
  39. package/dist/server/runtime-worker.d.ts +19 -0
  40. package/dist/server/selection-store.d.ts +39 -0
  41. package/dist/server/sqlite-store.d.ts +2 -0
  42. package/dist/server/store.d.ts +3 -0
  43. package/dist/server/types.d.ts +3 -0
  44. package/dist/storage.js +3 -0
  45. package/dist/types/skill-selection.d.ts +41 -0
  46. package/migrations/postgres/0007_skill_profiles.sql +19 -0
  47. package/migrations/postgres/0008_skill_runtime.sql +14 -0
  48. package/migrations/sqlite/0007_skill_profiles.sql +19 -0
  49. package/migrations/sqlite/0008_skill_runtime.sql +14 -0
  50. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -256,6 +256,9 @@ function getDataDir() {
256
256
  }
257
257
  return root;
258
258
  }
259
+ function getDataDirReadOnly() {
260
+ return getDataRoot();
261
+ }
259
262
  function getConfigPath(scope) {
260
263
  if (scope === "global") {
261
264
  return join2(getDataDir(), "config.json");
@@ -350,12 +353,13 @@ import {
350
353
  mkdirSync as mkdirSync3,
351
354
  mkdtempSync,
352
355
  readdirSync as readdirSync5,
356
+ realpathSync as realpathSync2,
353
357
  renameSync,
354
358
  rmSync,
355
359
  statSync as statSync6,
356
360
  writeFileSync as writeFileSync3
357
361
  } from "fs";
358
- import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join7, normalize as normalize2 } from "path";
362
+ import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join7, normalize as normalize2, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
359
363
 
360
364
  // src/lib/registry-data/development-tools.ts
361
365
  var DEVELOPMENT_TOOLS_SKILLS = [
@@ -2650,14 +2654,18 @@ function escapeJsString(value) {
2650
2654
  // src/lib/portable-skills.ts
2651
2655
  var LEGACY_CUSTOM_DIRNAME = "custom";
2652
2656
  function getPortableSkillsRoot(options = {}) {
2657
+ return resolvePortableSkillsRoot(options, true);
2658
+ }
2659
+ function resolvePortableSkillsRoot(options, migrate) {
2653
2660
  if (options.rootDir)
2654
2661
  return options.rootDir;
2655
- const appDir = options.homeDir ? join7(options.homeDir, ".hasna", "skills") : getDataDir();
2662
+ const appDir = options.homeDir ? join7(options.homeDir, ".hasna", "skills") : migrate ? getDataDir() : getDataDirReadOnly();
2656
2663
  const cache = join7(appDir, SKILLS_CACHE_DIRNAME);
2657
2664
  if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
2658
2665
  return cache;
2659
2666
  const installed = join7(appDir, INSTALLED_SKILLS_DIRNAME);
2660
- migrateLegacySkillLayout(appDir, installed);
2667
+ if (migrate)
2668
+ migrateLegacySkillLayout(appDir, installed);
2661
2669
  return installed;
2662
2670
  }
2663
2671
  function looksLikeSkillDirectory(path) {
@@ -2834,6 +2842,26 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
2834
2842
  function isSkillCandidate(dir) {
2835
2843
  return existsSync7(join7(dir, "SKILL.md")) || existsSync7(join7(dir, "skill.json")) || existsSync7(join7(dir, "package.json"));
2836
2844
  }
2845
+ function physicalImportPath(path) {
2846
+ let ancestor = resolve2(path);
2847
+ const suffix = [];
2848
+ while (!existsSync7(ancestor)) {
2849
+ suffix.unshift(basename2(ancestor));
2850
+ ancestor = dirname3(ancestor);
2851
+ }
2852
+ return join7(realpathSync2(ancestor), ...suffix);
2853
+ }
2854
+ function assertDisjointImportPaths(source, destination) {
2855
+ const sourcePath = realpathSync2(source);
2856
+ const destinationPath = physicalImportPath(destination);
2857
+ const contains = (parent, child) => {
2858
+ const path = relative2(parent, child);
2859
+ return path === "" || !isAbsolute2(path) && path !== ".." && !path.startsWith(`..${sep2}`);
2860
+ };
2861
+ if (contains(sourcePath, destinationPath) || contains(destinationPath, sourcePath)) {
2862
+ throw new Error("Source and destination skill directories must not overlap");
2863
+ }
2864
+ }
2837
2865
  function portPortableSkill(sourcePath, options = {}) {
2838
2866
  const absoluteSource = normalize2(sourcePath);
2839
2867
  if (!existsSync7(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
@@ -2847,8 +2875,10 @@ function portPortableSkill(sourcePath, options = {}) {
2847
2875
  const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename2(absoluteSource)}')` : ""} matches a bundled official skill.`;
2848
2876
  throw new Error(`${via} Importing it would shadow the official '${skillName}'. ` + `Pass --name to choose a different name, or --allow-shadow to override deliberately.`);
2849
2877
  }
2878
+ assertDisjointImportPaths(absoluteSource, join7(resolvePortableSkillsRoot(options, false), skillName));
2850
2879
  const root = getPortableSkillsRoot(options);
2851
2880
  const destination = join7(root, skillName);
2881
+ assertDisjointImportPaths(absoluteSource, destination);
2852
2882
  if (existsSync7(destination)) {
2853
2883
  if (!options.overwrite)
2854
2884
  throw new Error(`Skill '${skillName}' already exists at ${destination}`);
@@ -3447,27 +3477,369 @@ function getAllTags() {
3447
3477
  return Array.from(tagSet).sort();
3448
3478
  }
3449
3479
  // src/lib/installer.ts
3450
- import { existsSync as existsSync11, readFileSync as readFileSync9, rmSync as rmSync3 } from "fs";
3451
- import { dirname as dirname5, join as join11 } from "path";
3452
- import { homedir as homedir3 } from "os";
3480
+ import { existsSync as existsSync12, readFileSync as readFileSync10, rmSync as rmSync3 } from "fs";
3481
+ import { dirname as dirname6, join as join12 } from "path";
3482
+ import { homedir as homedir4 } from "os";
3453
3483
  import { fileURLToPath } from "url";
3454
3484
 
3455
3485
  // src/lib/agent-sync.ts
3456
3486
  import {
3457
3487
  cpSync as cpSync3,
3458
- existsSync as existsSync9,
3459
- lstatSync as lstatSync3,
3460
- mkdirSync as mkdirSync4,
3488
+ existsSync as existsSync10,
3489
+ lstatSync as lstatSync4,
3490
+ mkdirSync as mkdirSync5,
3461
3491
  mkdtempSync as mkdtempSync2,
3462
- readFileSync as readFileSync7,
3463
- readdirSync as readdirSync7,
3464
- renameSync as renameSync2,
3492
+ readFileSync as readFileSync8,
3493
+ readdirSync as readdirSync8,
3494
+ renameSync as renameSync3,
3465
3495
  rmSync as rmSync2,
3466
3496
  statSync as statSync7,
3467
- writeFileSync as writeFileSync4
3497
+ writeFileSync as writeFileSync5
3468
3498
  } from "fs";
3499
+ import { homedir as homedir3 } from "os";
3500
+ import { basename as basename3, dirname as dirname5, join as join10 } from "path";
3501
+
3502
+ // src/lib/agent-integration.ts
3503
+ import { createHash as createHash2, randomUUID } from "crypto";
3504
+ import { existsSync as existsSync9, lstatSync as lstatSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync as readdirSync7, readlinkSync, realpathSync as realpathSync3, renameSync as renameSync2, writeFileSync as writeFileSync4, unlinkSync, chmodSync } from "fs";
3505
+ import { dirname as dirname4, join as join9, resolve as resolve3, relative as relative3, isAbsolute as isAbsolute3, sep as sep3 } from "path";
3469
3506
  import { homedir as homedir2 } from "os";
3470
- import { basename as basename3, dirname as dirname4, join as join9 } from "path";
3507
+ var sha = (data) => createHash2("sha256").update(data).digest("hex");
3508
+ var HOOK_EVENTS = ["UserPromptSubmit", "SessionStart", "SubagentStart"];
3509
+ var ROOTS = [
3510
+ ["claude", ".claude/skills"],
3511
+ ["codex", ".codex/skills"],
3512
+ ["codex", ".agents/skills"],
3513
+ ["gemini", ".gemini/skills"],
3514
+ ["codewith", ".codewith/skills"],
3515
+ ["opencode", ".config/opencode/skills"],
3516
+ ["cursor", ".cursor/skills"]
3517
+ ];
3518
+ function assertSafePath(path) {
3519
+ let cursor = resolve3(path);
3520
+ while (true) {
3521
+ if (lstatSync3(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
3522
+ throw new Error(`Refusing symlink path: ${cursor}`);
3523
+ const parent = dirname4(cursor);
3524
+ if (parent === cursor)
3525
+ return;
3526
+ cursor = parent;
3527
+ }
3528
+ }
3529
+ function readRootAlias(home, agent) {
3530
+ assertSafePath(home);
3531
+ const realHome = realpathSync3(home), alias = join9(realHome, `.${agent}`);
3532
+ const stat = lstatSync3(alias, { throwIfNoEntry: false, bigint: true });
3533
+ if (!stat?.isSymbolicLink())
3534
+ throw new Error(`Agent root alias changed after planning: ${alias}`);
3535
+ const link = readlinkSync(alias), targetPath = resolve3(dirname4(alias), link);
3536
+ assertSafePath(targetPath);
3537
+ const targetStat = lstatSync3(targetPath, { throwIfNoEntry: false });
3538
+ if (!targetStat?.isDirectory())
3539
+ throw new Error(`Agent root alias must target an existing directory: ${alias}`);
3540
+ const target = realpathSync3(targetPath), inside = relative3(realHome, target);
3541
+ if (!inside || inside === ".." || inside.startsWith(`..${sep3}`) || isAbsolute3(inside))
3542
+ throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
3543
+ return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat.dev}:${stat.ino}:${stat.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
3544
+ }
3545
+ function rootAliases(home, allowed = false) {
3546
+ if (!allowed)
3547
+ return [];
3548
+ const result = [];
3549
+ for (const agent of ["claude", "codex"])
3550
+ if (lstatSync3(join9(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
3551
+ result.push(readRootAlias(home, agent));
3552
+ if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep3) || other.target.startsWith(item.target + sep3))))
3553
+ throw new Error("Agent root alias targets must not overlap");
3554
+ return result;
3555
+ }
3556
+ function canonicalAgentPath(path, aliases) {
3557
+ const absolute = resolve3(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep3));
3558
+ return binding ? join9(binding.target, relative3(binding.alias, absolute)) : absolute;
3559
+ }
3560
+ function recheckRootAliases(aliases) {
3561
+ for (const binding of aliases) {
3562
+ if (!["claude", "codex"].includes(binding.agent) || binding.alias !== join9(binding.home, `.${binding.agent}`))
3563
+ throw new Error("Invalid agent root alias binding");
3564
+ const current = readRootAlias(binding.home, binding.agent);
3565
+ if (JSON.stringify(current) !== JSON.stringify(binding))
3566
+ throw new Error(`Agent root alias changed after planning: ${binding.alias}`);
3567
+ }
3568
+ }
3569
+ function treeHash(root) {
3570
+ const hash = createHash2("sha256");
3571
+ let bytes = 0, files = 0;
3572
+ function visit(path) {
3573
+ const stat = lstatSync3(path);
3574
+ if (stat.isSymbolicLink())
3575
+ throw new Error(`Refusing symlink in skill: ${path}`);
3576
+ const rel = relative3(root, path);
3577
+ if (stat.isDirectory()) {
3578
+ hash.update(`d\x00${rel}\x00`);
3579
+ for (const name of readdirSync7(path).sort())
3580
+ visit(join9(path, name));
3581
+ return;
3582
+ }
3583
+ if (!stat.isFile())
3584
+ throw new Error(`Unsupported skill file: ${path}`);
3585
+ bytes += stat.size;
3586
+ files++;
3587
+ if (bytes > 64 * 1024 * 1024 || files > 1e4)
3588
+ throw new Error("Native skill exceeds migration size limits");
3589
+ hash.update(`f\x00${rel}\x00${stat.size}\x00`);
3590
+ hash.update(readFileSync7(path));
3591
+ }
3592
+ assertSafePath(root);
3593
+ visit(root);
3594
+ return hash.digest("hex");
3595
+ }
3596
+ function inventoryNativeSkills(home = homedir2(), options = {}) {
3597
+ const aliases = rootAliases(home, options.allowRootAliases);
3598
+ const roots = ROOTS.map(([agent, path]) => [agent, canonicalAgentPath(join9(home, path), aliases)]);
3599
+ if (options.projectDir)
3600
+ for (const [agent, path] of ROOTS)
3601
+ roots.push([agent, join9(resolve3(options.projectDir), path)]);
3602
+ const entries = [], seen = new Set;
3603
+ function visit(agent, path, vendor = false, depth = 0) {
3604
+ assertSafePath(path);
3605
+ if (!existsSync9(path))
3606
+ return;
3607
+ if (!lstatSync3(path).isDirectory())
3608
+ return;
3609
+ if (existsSync9(join9(path, "SKILL.md"))) {
3610
+ if (seen.has(path))
3611
+ return;
3612
+ seen.add(path);
3613
+ let managed = false;
3614
+ const marker = join9(path, ".hasna-skills.json");
3615
+ if (existsSync9(marker)) {
3616
+ try {
3617
+ managed = JSON.parse(readFileSync7(marker, "utf8")).managedBy === "@hasna/skills";
3618
+ } catch {}
3619
+ }
3620
+ const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep3));
3621
+ entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...rootAlias ? { rootAlias } : {} });
3622
+ return;
3623
+ }
3624
+ if (depth > (vendor ? 8 : 3))
3625
+ return;
3626
+ for (const name of readdirSync7(path).sort()) {
3627
+ if (name === "node_modules")
3628
+ continue;
3629
+ if (name.startsWith(".") && name !== ".system")
3630
+ continue;
3631
+ const isVendor = vendor || name === ".system";
3632
+ if (isVendor && !options.includeVendor)
3633
+ continue;
3634
+ visit(agent, join9(path, name), isVendor, depth + 1);
3635
+ }
3636
+ }
3637
+ for (const [agent, path] of roots)
3638
+ visit(agent, path);
3639
+ if (options.includeVendor) {
3640
+ for (const agent of ["codex", "claude"])
3641
+ visit(agent, canonicalAgentPath(join9(home, `.${agent}`, "plugins", "cache"), aliases), true);
3642
+ }
3643
+ recheckRootAliases(aliases);
3644
+ return entries;
3645
+ }
3646
+ function readOptional(path) {
3647
+ assertSafePath(path);
3648
+ return existsSync9(path) ? readFileSync7(path, "utf8") : null;
3649
+ }
3650
+ function jsonObject(text, path) {
3651
+ if (text === null)
3652
+ return {};
3653
+ const value = JSON.parse(text);
3654
+ if (!value || typeof value !== "object" || Array.isArray(value))
3655
+ throw new Error(`Expected configuration object: ${path}`);
3656
+ return value;
3657
+ }
3658
+ function shellQuote(value) {
3659
+ return `'${value.replace(/'/g, `'\\''`)}'`;
3660
+ }
3661
+ function configureHooks(config, agent, command, profileId) {
3662
+ config.hooks ??= {};
3663
+ if (typeof config.hooks !== "object" || Array.isArray(config.hooks))
3664
+ throw new Error("Expected hooks configuration object");
3665
+ for (const event of HOOK_EVENTS) {
3666
+ const existing = config.hooks[event] ?? [];
3667
+ if (!Array.isArray(existing))
3668
+ throw new Error(`Expected ${event} hooks array`);
3669
+ const hookCommand = `${shellQuote(command)} hook user-prompt --agent ${agent} --selection-profile ${profileId}`;
3670
+ const retained = existing.flatMap((entry) => {
3671
+ if (!entry || !Array.isArray(entry.hooks))
3672
+ throw new Error(`Malformed ${event} hook entry`);
3673
+ const hooks = entry.hooks.filter((hook) => !(hook?.type === "command" && typeof hook.command === "string" && /(?:^|\s)hook user-prompt --agent (?:claude|codex)(?: --selection-profile [A-Za-z0-9._-]+)?$/.test(hook.command)));
3674
+ return hooks.length ? [{ ...entry, hooks }] : [];
3675
+ });
3676
+ retained.push({ hooks: [{ type: "command", command: hookCommand, timeout: 15 }] });
3677
+ config.hooks[event] = retained;
3678
+ }
3679
+ if (agent === "claude") {
3680
+ config.permissions ??= {};
3681
+ if (typeof config.permissions !== "object" || Array.isArray(config.permissions))
3682
+ throw new Error("Expected permissions configuration object");
3683
+ const denied = config.permissions.deny ?? [];
3684
+ if (!Array.isArray(denied))
3685
+ throw new Error("Expected permission deny array");
3686
+ config.permissions.deny = [...new Set([...denied, "Skill"])];
3687
+ }
3688
+ }
3689
+ function disableCodexSkills(text, skills, aliases) {
3690
+ Bun.TOML.parse(text);
3691
+ let result = text;
3692
+ for (const skill of skills.filter((entry) => entry.agent === "codex")) {
3693
+ const path = join9(skill.path, "SKILL.md");
3694
+ let found = false;
3695
+ result = result.replace(/^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm, (section) => {
3696
+ const parsed = Bun.TOML.parse(section);
3697
+ const declared = parsed.skills?.config?.[0]?.path;
3698
+ if (!declared || ![resolve3(path), resolve3(skill.path)].includes(canonicalAgentPath(declared, aliases)))
3699
+ return section;
3700
+ found = true;
3701
+ return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, "enabled = false") : `${section.trimEnd()}
3702
+ enabled = false
3703
+ `;
3704
+ });
3705
+ if (!found)
3706
+ result = `${result.trimEnd()}
3707
+
3708
+ [[skills.config]]
3709
+ path = ${JSON.stringify(path)}
3710
+ enabled = false
3711
+ `;
3712
+ }
3713
+ Bun.TOML.parse(result);
3714
+ return result;
3715
+ }
3716
+ function planAgentIntegration(options) {
3717
+ const home = options.home ?? homedir2(), dataDir2 = options.dataDir ?? getDataDir();
3718
+ const profileId = options.profileId ?? "default";
3719
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(profileId) || profileId.includes(".."))
3720
+ throw new Error("Invalid selection profile id");
3721
+ const aliases = rootAliases(home, options.allowRootAliases);
3722
+ const nativeSkills = inventoryNativeSkills(home, { includeVendor: options.includeVendor, projectDir: options.projectDir, allowRootAliases: options.allowRootAliases });
3723
+ const changes = [];
3724
+ for (const agent of [...new Set(options.agents)]) {
3725
+ if (agent !== "claude" && agent !== "codex")
3726
+ throw new Error(`Unsupported agent: ${agent}`);
3727
+ const path = canonicalAgentPath(join9(home, `.${agent}`, agent === "claude" ? "settings.json" : "hooks.json"), aliases);
3728
+ const before = readOptional(path), config = jsonObject(before, path);
3729
+ configureHooks(config, agent, options.command ?? "skills", profileId);
3730
+ const after = `${JSON.stringify(config, null, 2)}
3731
+ `;
3732
+ if (before !== after)
3733
+ changes.push({ path, before, after });
3734
+ if (agent === "codex") {
3735
+ const configPath = canonicalAgentPath(join9(home, ".codex", "config.toml"), aliases), previous = readOptional(configPath);
3736
+ const next = disableCodexSkills(previous ?? "", nativeSkills, aliases);
3737
+ if (next !== (previous ?? ""))
3738
+ changes.push({ path: configPath, before: previous, after: next });
3739
+ }
3740
+ }
3741
+ recheckRootAliases(aliases);
3742
+ return { dataDir: dataDir2, profileId, changes, nativeSkills, ...aliases.length ? { rootAliases: aliases } : {} };
3743
+ }
3744
+ function atomicWrite(path, content) {
3745
+ assertSafePath(path);
3746
+ mkdirSync4(dirname4(path), { recursive: true, mode: 448 });
3747
+ const temporary = `${path}.skills-${randomUUID()}`;
3748
+ try {
3749
+ writeFileSync4(temporary, content, { mode: 384, flag: "wx" });
3750
+ renameSync2(temporary, path);
3751
+ } finally {
3752
+ if (existsSync9(temporary))
3753
+ unlinkSync(temporary);
3754
+ }
3755
+ }
3756
+ function applyAgentIntegration(plan) {
3757
+ const aliases = plan.rootAliases ?? [];
3758
+ recheckRootAliases(aliases);
3759
+ for (const change of plan.changes)
3760
+ if (readOptional(change.path) !== change.before)
3761
+ throw new Error(`Configuration changed after planning: ${change.path}`);
3762
+ const backupRoot = join9(plan.dataDir, "migration", randomUUID()), backups = [], written = [];
3763
+ if (!plan.changes.length) {
3764
+ const policyPath = join9(plan.dataDir, "agent-policy.json");
3765
+ const policy = jsonObject(readOptional(policyPath), policyPath);
3766
+ recheckRootAliases(aliases);
3767
+ if (policy.loading !== "cli" || policy.profileId !== plan.profileId)
3768
+ atomicWrite(policyPath, JSON.stringify({ version: 1, loading: "cli", profileId: plan.profileId, installedAt: new Date().toISOString() }) + `
3769
+ `);
3770
+ return { changed: [], backups, ...aliases.length ? { rootAliases: aliases } : {} };
3771
+ }
3772
+ assertSafePath(backupRoot);
3773
+ mkdirSync4(backupRoot, { recursive: true, mode: 448 });
3774
+ chmodSync(backupRoot, 448);
3775
+ for (const [index, change] of plan.changes.entries())
3776
+ if (change.before !== null) {
3777
+ const backup = join9(backupRoot, `${index}-${sha(change.path).slice(0, 12)}.backup`);
3778
+ writeFileSync4(backup, change.before, { mode: 384, flag: "wx" });
3779
+ backups.push(backup);
3780
+ }
3781
+ try {
3782
+ for (const change of plan.changes) {
3783
+ recheckRootAliases(aliases);
3784
+ atomicWrite(change.path, change.after);
3785
+ written.push(change);
3786
+ }
3787
+ recheckRootAliases(aliases);
3788
+ atomicWrite(join9(plan.dataDir, "agent-policy.json"), JSON.stringify({ version: 1, loading: "cli", profileId: plan.profileId, installedAt: new Date().toISOString() }) + `
3789
+ `);
3790
+ } catch (error) {
3791
+ for (const change of written.reverse()) {
3792
+ if (change.before === null)
3793
+ unlinkSync(change.path);
3794
+ else
3795
+ atomicWrite(change.path, change.before);
3796
+ }
3797
+ throw error;
3798
+ }
3799
+ atomicWrite(join9(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: plan.changes.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha(change.before), afterHash: sha(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
3800
+ `);
3801
+ return { changed: plan.changes.map((change) => change.path), backups, ...aliases.length ? { rootAliases: aliases } : {} };
3802
+ }
3803
+ function archiveNativeSkills(inventory, options) {
3804
+ const aliases = [...new Map(inventory.filter((entry) => entry.rootAlias).map((entry) => [entry.rootAlias.alias, entry.rootAlias])).values()];
3805
+ if (aliases.length && !options.allowRootAliases)
3806
+ throw new Error("Native migration requires explicit allowRootAliases for agent root aliases");
3807
+ recheckRootAliases(aliases);
3808
+ const selected = inventory.filter((entry) => !entry.vendor && (entry.managed || options.includeUnmanaged));
3809
+ for (const entry of selected)
3810
+ if (treeHash(entry.path) !== entry.hash)
3811
+ throw new Error(`Native skill changed after planning: ${entry.path}`);
3812
+ if (!selected.length)
3813
+ return { entries: [], ...aliases.length ? { rootAliases: aliases } : {} };
3814
+ const archiveRoot = join9(options.dataDir ?? getDataDir(), "migration", randomUUID(), "native");
3815
+ assertSafePath(archiveRoot);
3816
+ mkdirSync4(archiveRoot, { recursive: true, mode: 448 });
3817
+ const entries = [];
3818
+ try {
3819
+ for (const [index, entry] of selected.entries()) {
3820
+ recheckRootAliases(aliases);
3821
+ const archive = join9(archiveRoot, `${index}-${sha(entry.path).slice(0, 12)}`);
3822
+ renameSync2(entry.path, archive);
3823
+ entries.push({ source: entry.path, archive, hash: entry.hash });
3824
+ }
3825
+ recheckRootAliases(aliases);
3826
+ atomicWrite(join9(archiveRoot, "receipt.json"), JSON.stringify({ version: 1, entries, ...aliases.length ? { rootAliases: aliases } : {} }) + `
3827
+ `);
3828
+ } catch (error) {
3829
+ for (const entry of entries.reverse())
3830
+ renameSync2(entry.archive, entry.source);
3831
+ throw error;
3832
+ }
3833
+ return { entries, ...aliases.length ? { rootAliases: aliases } : {} };
3834
+ }
3835
+ function assertNativeExportAllowed(dataDir2 = getDataDir()) {
3836
+ const path = join9(dataDir2, "agent-policy.json");
3837
+ if (!existsSync9(path))
3838
+ return;
3839
+ const policy = jsonObject(readOptional(path), path);
3840
+ if (policy.loading === "cli")
3841
+ throw new Error("NATIVE_SKILL_EXPORT_DISABLED: this station loads skills through the Skills CLI; use skills sync --selection-profile <id>");
3842
+ }
3471
3843
  // src/lib/home-migration.ts
3472
3844
  function resolveCorpusRoot(options = {}) {
3473
3845
  return getPortableSkillsRoot(options);
@@ -3482,11 +3854,11 @@ function isSkillsOwnershipMarker(marker) {
3482
3854
  return typeof marker === "object" && marker !== null && Object.hasOwn(marker, "managedBy") && marker.managedBy === SYNC_MARKER_MANAGED_BY;
3483
3855
  }
3484
3856
  function hasSkillsOwnershipMarker(dir) {
3485
- const path = join9(dir, SYNC_MARKER_FILE);
3857
+ const path = join10(dir, SYNC_MARKER_FILE);
3486
3858
  try {
3487
- if (!lstatSync3(path).isFile())
3859
+ if (!lstatSync4(path).isFile())
3488
3860
  return false;
3489
- const marker = JSON.parse(readFileSync7(path, "utf8"));
3861
+ const marker = JSON.parse(readFileSync8(path, "utf8"));
3490
3862
  return isSkillsOwnershipMarker(marker);
3491
3863
  } catch {
3492
3864
  return false;
@@ -3503,12 +3875,12 @@ function resolveSyncAgents(arg) {
3503
3875
  }
3504
3876
  return [arg];
3505
3877
  }
3506
- function agentGlobalSkillsDir(agent, homeDir = homedir2()) {
3878
+ function agentGlobalSkillsDir(agent, homeDir = homedir3()) {
3507
3879
  switch (agent) {
3508
3880
  case "opencode":
3509
- return join9(homeDir, ".config", "opencode", "skills");
3881
+ return join10(homeDir, ".config", "opencode", "skills");
3510
3882
  default:
3511
- return join9(homeDir, `.${agent}`, "skills");
3883
+ return join10(homeDir, `.${agent}`, "skills");
3512
3884
  }
3513
3885
  }
3514
3886
  function adaptSkillMdForAgent(skillMd, agent) {
@@ -3567,8 +3939,8 @@ function resolveSyncCorpus(options = {}) {
3567
3939
  function packageSourceRoots(source) {
3568
3940
  const roots = [];
3569
3941
  for (const sub of ["skills"]) {
3570
- const candidate = join9(source, sub);
3571
- if (existsSync9(candidate) && isDirectory(candidate))
3942
+ const candidate = join10(source, sub);
3943
+ if (existsSync10(candidate) && isDirectory(candidate))
3572
3944
  roots.push(candidate);
3573
3945
  }
3574
3946
  if (roots.length > 0)
@@ -3578,15 +3950,15 @@ function packageSourceRoots(source) {
3578
3950
  function containsSkillDirectories(path) {
3579
3951
  let entries;
3580
3952
  try {
3581
- entries = readdirSync7(path);
3953
+ entries = readdirSync8(path);
3582
3954
  } catch {
3583
3955
  return false;
3584
3956
  }
3585
3957
  return entries.some((entry) => {
3586
- const candidate = join9(path, entry);
3958
+ const candidate = join10(path, entry);
3587
3959
  if (!isDirectory(candidate))
3588
3960
  return false;
3589
- return existsSync9(join9(candidate, "SKILL.md")) || existsSync9(join9(candidate, "skill.json")) || existsSync9(join9(candidate, "package.json"));
3961
+ return existsSync10(join10(candidate, "SKILL.md")) || existsSync10(join10(candidate, "skill.json")) || existsSync10(join10(candidate, "package.json"));
3590
3962
  });
3591
3963
  }
3592
3964
  function isDirectory(path) {
@@ -3597,9 +3969,10 @@ function isDirectory(path) {
3597
3969
  }
3598
3970
  }
3599
3971
  function syncSkillsToAgents(options = {}) {
3972
+ assertNativeExportAllowed();
3600
3973
  const requested = normalizeRequested(options.names);
3601
3974
  const agents = options.agents?.length ? options.agents : [...SYNC_AGENTS];
3602
- const homeDir = options.homeDir ?? homedir2();
3975
+ const homeDir = options.homeDir ?? homedir3();
3603
3976
  const { roots, source } = resolveSyncCorpus(options);
3604
3977
  const corpus = listPortableSkillsAcrossRoots(roots);
3605
3978
  const byName = new Map(corpus.map((skill) => [skill.name, skill]));
@@ -3625,7 +3998,7 @@ function syncSkillsToAgents(options = {}) {
3625
3998
  actions.push({
3626
3999
  skill: name,
3627
4000
  agent,
3628
- path: join9(agentGlobalSkillsDir(agent, homeDir), name, "SKILL.md"),
4001
+ path: join10(agentGlobalSkillsDir(agent, homeDir), name, "SKILL.md"),
3629
4002
  action: "skip",
3630
4003
  reason: "not found in this machine's corpus"
3631
4004
  });
@@ -3654,8 +4027,8 @@ function syncSkillsToAgents(options = {}) {
3654
4027
  return { actions };
3655
4028
  }
3656
4029
  function writeManagedAgentSkill(params) {
3657
- const homeDir = params.homeDir ?? homedir2();
3658
- const dir = join9(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
4030
+ const homeDir = params.homeDir ?? homedir3();
4031
+ const dir = join10(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
3659
4032
  const result = writeManagedSkillDir(dir, params.skillMd, {
3660
4033
  skill: params.skill,
3661
4034
  source: params.source,
@@ -3672,11 +4045,11 @@ function writeManagedAgentSkill(params) {
3672
4045
  };
3673
4046
  }
3674
4047
  function writeManagedSkillDir(dir, skillMd, options) {
3675
- const skillMdPath = join9(dir, "SKILL.md");
3676
- const markerPath = join9(dir, SYNC_MARKER_FILE);
3677
- const dirExists = existsSync9(dir);
4048
+ const skillMdPath = join10(dir, "SKILL.md");
4049
+ const markerPath = join10(dir, SYNC_MARKER_FILE);
4050
+ const dirExists = existsSync10(dir);
3678
4051
  const managed = hasSkillsOwnershipMarker(dir);
3679
- const hasSkillMd = existsSync9(skillMdPath);
4052
+ const hasSkillMd = existsSync10(skillMdPath);
3680
4053
  if (dirExists && !managed && !hasSkillMd) {
3681
4054
  return {
3682
4055
  action: "skip",
@@ -3688,13 +4061,13 @@ function writeManagedSkillDir(dir, skillMd, options) {
3688
4061
  return {
3689
4062
  action: "skip",
3690
4063
  path: skillMdPath,
3691
- reason: existsSync9(markerPath) ? "an unmanaged SKILL.md already exists here (invalid or foreign ownership marker); pass --force to overwrite" : "an unmanaged SKILL.md already exists here (hand-authored); pass --force to overwrite"
4064
+ reason: existsSync10(markerPath) ? "an unmanaged SKILL.md already exists here (invalid or foreign ownership marker); pass --force to overwrite" : "an unmanaged SKILL.md already exists here (hand-authored); pass --force to overwrite"
3692
4065
  };
3693
4066
  }
3694
4067
  if (dirExists && managed && hasSkillMd && !options.force && isPointerSkillMd(skillMd)) {
3695
4068
  let existingIsStub = false;
3696
4069
  try {
3697
- existingIsStub = isPointerSkillMd(readFileSync7(skillMdPath, "utf-8"));
4070
+ existingIsStub = isPointerSkillMd(readFileSync8(skillMdPath, "utf-8"));
3698
4071
  } catch {
3699
4072
  existingIsStub = false;
3700
4073
  }
@@ -3709,32 +4082,32 @@ function writeManagedSkillDir(dir, skillMd, options) {
3709
4082
  const action = dirExists ? "update" : "create";
3710
4083
  if (options.dryRun)
3711
4084
  return { action, path: skillMdPath };
3712
- const parentDir = dirname4(dir);
3713
- mkdirSync4(parentDir, { recursive: true });
3714
- const transactionDir = mkdtempSync2(join9(parentDir, `.hasna-skills-write-${basename3(dir)}-`));
3715
- const candidateDir = join9(transactionDir, "candidate");
3716
- const backupDir = join9(transactionDir, "backup");
3717
- const candidateSkillMdPath = join9(candidateDir, "SKILL.md");
3718
- const candidateMarkerPath = join9(candidateDir, SYNC_MARKER_FILE);
4085
+ const parentDir = dirname5(dir);
4086
+ mkdirSync5(parentDir, { recursive: true });
4087
+ const transactionDir = mkdtempSync2(join10(parentDir, `.hasna-skills-write-${basename3(dir)}-`));
4088
+ const candidateDir = join10(transactionDir, "candidate");
4089
+ const backupDir = join10(transactionDir, "backup");
4090
+ const candidateSkillMdPath = join10(candidateDir, "SKILL.md");
4091
+ const candidateMarkerPath = join10(candidateDir, SYNC_MARKER_FILE);
3719
4092
  const marker = {
3720
4093
  managedBy: SYNC_MARKER_MANAGED_BY,
3721
4094
  skill: options.skill,
3722
4095
  source: options.source ?? "corpus",
3723
4096
  syncedAt: new Date().toISOString()
3724
4097
  };
3725
- const renameDirectory = options.renameDirectory ?? renameSync2;
4098
+ const renameDirectory = options.renameDirectory ?? renameSync3;
3726
4099
  let originalMoved = false;
3727
4100
  let preserveTransaction = false;
3728
4101
  try {
3729
4102
  if (options.resourceDir) {
3730
4103
  cpSync3(options.resourceDir, candidateDir, { recursive: true, force: true });
3731
4104
  } else {
3732
- mkdirSync4(candidateDir, { recursive: true });
4105
+ mkdirSync5(candidateDir, { recursive: true });
3733
4106
  }
3734
- writeFileSync4(candidateSkillMdPath, skillMd.endsWith(`
4107
+ writeFileSync5(candidateSkillMdPath, skillMd.endsWith(`
3735
4108
  `) ? skillMd : `${skillMd}
3736
4109
  `);
3737
- writeFileSync4(candidateMarkerPath, `${JSON.stringify(marker, null, 2)}
4110
+ writeFileSync5(candidateMarkerPath, `${JSON.stringify(marker, null, 2)}
3738
4111
  `);
3739
4112
  if (dirExists) {
3740
4113
  originalMoved = true;
@@ -3742,9 +4115,9 @@ function writeManagedSkillDir(dir, skillMd, options) {
3742
4115
  }
3743
4116
  renameDirectory(candidateDir, dir);
3744
4117
  } catch (error) {
3745
- if (originalMoved && existsSync9(backupDir)) {
4118
+ if (originalMoved && existsSync10(backupDir)) {
3746
4119
  try {
3747
- if (existsSync9(dir))
4120
+ if (existsSync10(dir))
3748
4121
  rmSync2(dir, { recursive: true, force: true });
3749
4122
  renameDirectory(backupDir, dir);
3750
4123
  originalMoved = false;
@@ -3763,8 +4136,8 @@ function writeManagedSkillDir(dir, skillMd, options) {
3763
4136
  }
3764
4137
  return { action, path: skillMdPath };
3765
4138
  }
3766
- function removeManagedAgentSkill(skill, agent, homeDir = homedir2()) {
3767
- const dir = join9(agentGlobalSkillsDir(agent, homeDir), skill);
4139
+ function removeManagedAgentSkill(skill, agent, homeDir = homedir3()) {
4140
+ const dir = join10(agentGlobalSkillsDir(agent, homeDir), skill);
3768
4141
  if (!hasSkillsOwnershipMarker(dir))
3769
4142
  return false;
3770
4143
  rmSync2(dir, { recursive: true, force: true });
@@ -3772,9 +4145,9 @@ function removeManagedAgentSkill(skill, agent, homeDir = homedir2()) {
3772
4145
  }
3773
4146
  function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
3774
4147
  if (kind === undefined || kind === "instruction" || preferBundledDocs) {
3775
- const skillMdPath = join9(skillPath, "SKILL.md");
3776
- if (existsSync9(skillMdPath))
3777
- return readFileSync7(skillMdPath, "utf-8");
4148
+ const skillMdPath = join10(skillPath, "SKILL.md");
4149
+ if (existsSync10(skillMdPath))
4150
+ return readFileSync8(skillMdPath, "utf-8");
3778
4151
  }
3779
4152
  return pointerSkillMd(name, description);
3780
4153
  }
@@ -3804,8 +4177,8 @@ function normalizeSkillName(name) {
3804
4177
  }
3805
4178
 
3806
4179
  // src/lib/project-state.ts
3807
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
3808
- import { join as join10 } from "path";
4180
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
4181
+ import { join as join11 } from "path";
3809
4182
  var VALID_PIN_SOURCES = [
3810
4183
  "official",
3811
4184
  "custom",
@@ -3820,17 +4193,17 @@ var SKILLS_PROJECT_DIR = ".skills";
3820
4193
  var PROJECT_CONFIG_FILE = "project.json";
3821
4194
  var DEFAULT_EXPORT_DIR = ".skills/exports";
3822
4195
  function getProjectStateDir(targetDir = process.cwd()) {
3823
- return join10(targetDir, SKILLS_PROJECT_DIR);
4196
+ return join11(targetDir, SKILLS_PROJECT_DIR);
3824
4197
  }
3825
4198
  function getProjectConfigPath(targetDir = process.cwd()) {
3826
- return join10(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
4199
+ return join11(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
3827
4200
  }
3828
4201
  function loadProjectConfig(targetDir = process.cwd()) {
3829
4202
  const path = getProjectConfigPath(targetDir);
3830
- if (!existsSync10(path))
4203
+ if (!existsSync11(path))
3831
4204
  return null;
3832
4205
  try {
3833
- return normalizeProjectConfig(JSON.parse(readFileSync8(path, "utf-8")));
4206
+ return normalizeProjectConfig(JSON.parse(readFileSync9(path, "utf-8")));
3834
4207
  } catch {
3835
4208
  return null;
3836
4209
  }
@@ -3851,9 +4224,9 @@ function ensureProjectConfig(targetDir = process.cwd()) {
3851
4224
  }
3852
4225
  function saveProjectConfig(config, targetDir = process.cwd()) {
3853
4226
  const dir = getProjectStateDir(targetDir);
3854
- mkdirSync5(dir, { recursive: true });
4227
+ mkdirSync6(dir, { recursive: true });
3855
4228
  const normalized = normalizeProjectConfig({ ...config, updatedAt: new Date().toISOString() });
3856
- writeFileSync5(getProjectConfigPath(targetDir), JSON.stringify(normalized, null, 2) + `
4229
+ writeFileSync6(getProjectConfigPath(targetDir), JSON.stringify(normalized, null, 2) + `
3857
4230
  `);
3858
4231
  }
3859
4232
  function pinProjectSkill(name, details = {}, targetDir = process.cwd()) {
@@ -3944,16 +4317,16 @@ function isPinSource(value) {
3944
4317
  }
3945
4318
 
3946
4319
  // src/lib/installer.ts
3947
- var __dirname2 = dirname5(fileURLToPath(import.meta.url));
4320
+ var __dirname2 = dirname6(fileURLToPath(import.meta.url));
3948
4321
  function findSkillsDir() {
3949
4322
  let dir = __dirname2;
3950
4323
  for (let i = 0;i < 5; i++) {
3951
- const candidate = join11(dir, "skills");
3952
- if (existsSync11(candidate) && !dir.includes(".skills"))
4324
+ const candidate = join12(dir, "skills");
4325
+ if (existsSync12(candidate) && !dir.includes(".skills"))
3953
4326
  return candidate;
3954
- dir = dirname5(dir);
4327
+ dir = dirname6(dir);
3955
4328
  }
3956
- return join11(__dirname2, "..", "skills");
4329
+ return join12(__dirname2, "..", "skills");
3957
4330
  }
3958
4331
  var SKILLS_DIR = findSkillsDir();
3959
4332
  function getSkillPath(name) {
@@ -3961,25 +4334,25 @@ function getSkillPath(name) {
3961
4334
  const portable = findPortableSkill(skillName);
3962
4335
  if (portable)
3963
4336
  return portable.path;
3964
- const legacyCustomPath = join11(getDataDir(), "custom", skillName);
3965
- if (existsSync11(legacyCustomPath))
4337
+ const legacyCustomPath = join12(getDataDir(), "custom", skillName);
4338
+ if (existsSync12(legacyCustomPath))
3966
4339
  return legacyCustomPath;
3967
4340
  const extensionPath = findExtensionSkillPath(skillName);
3968
4341
  if (extensionPath)
3969
4342
  return extensionPath;
3970
- return join11(SKILLS_DIR, skillName);
4343
+ return join12(SKILLS_DIR, skillName);
3971
4344
  }
3972
4345
  function getCanonicalSkillName(name) {
3973
4346
  return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
3974
4347
  }
3975
4348
  function skillExists(name) {
3976
- return existsSync11(getSkillPath(name));
4349
+ return existsSync12(getSkillPath(name));
3977
4350
  }
3978
4351
  function installSkill(name, options = {}) {
3979
4352
  const { targetDir = process.cwd(), overwrite = false } = options;
3980
4353
  const canonicalName = getCanonicalSkillName(name);
3981
4354
  const skillName = normalizeSkillName(canonicalName);
3982
- if (!existsSync11(getSkillPath(name))) {
4355
+ if (!existsSync12(getSkillPath(name))) {
3983
4356
  const knownOfficial = Boolean(getSkill(name));
3984
4357
  return {
3985
4358
  skill: canonicalName,
@@ -4006,7 +4379,7 @@ function installSkill(name, options = {}) {
4006
4379
  }
4007
4380
  function installSkillSource(name, _options = {}) {
4008
4381
  const canonicalName = getCanonicalSkillName(name);
4009
- if (!existsSync11(getSkillPath(name))) {
4382
+ if (!existsSync12(getSkillPath(name))) {
4010
4383
  return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "source" };
4011
4384
  }
4012
4385
  return {
@@ -4027,12 +4400,12 @@ function installSkillManifest(manifest, _options = {}) {
4027
4400
  }
4028
4401
  function createLocalSkillManifest(name, generateSkillMd) {
4029
4402
  const sourcePath = getSkillPath(name);
4030
- if (!existsSync11(sourcePath))
4403
+ if (!existsSync12(sourcePath))
4031
4404
  return null;
4032
4405
  let skillMd = "";
4033
- const skillMdPath = join11(sourcePath, "SKILL.md");
4034
- if (existsSync11(skillMdPath)) {
4035
- skillMd = readFileSync9(skillMdPath, "utf-8");
4406
+ const skillMdPath = join12(sourcePath, "SKILL.md");
4407
+ if (existsSync12(skillMdPath)) {
4408
+ skillMd = readFileSync10(skillMdPath, "utf-8");
4036
4409
  } else if (generateSkillMd) {
4037
4410
  skillMd = generateSkillMd(name) ?? "";
4038
4411
  } else {
@@ -4104,20 +4477,21 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
4104
4477
  const base = projectDir || process.cwd();
4105
4478
  switch (agent) {
4106
4479
  case "pi":
4107
- return scope === "project" ? join11(base, ".pi", "skills") : join11(homedir3(), ".pi", "agent", "skills");
4480
+ return scope === "project" ? join12(base, ".pi", "skills") : join12(homedir4(), ".pi", "agent", "skills");
4108
4481
  case "opencode":
4109
- return scope === "project" ? join11(base, ".opencode", "skills") : join11(homedir3(), ".config", "opencode", "skills");
4482
+ return scope === "project" ? join12(base, ".opencode", "skills") : join12(homedir4(), ".config", "opencode", "skills");
4110
4483
  default:
4111
- return scope === "project" ? join11(base, `.${agent}`, "skills") : join11(homedir3(), `.${agent}`, "skills");
4484
+ return scope === "project" ? join12(base, `.${agent}`, "skills") : join12(homedir4(), `.${agent}`, "skills");
4112
4485
  }
4113
4486
  }
4114
4487
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
4115
4488
  const skillName = normalizeSkillName(getCanonicalSkillName(name));
4116
- return join11(getAgentSkillsDir(agent, scope, projectDir), skillName);
4489
+ return join12(getAgentSkillsDir(agent, scope, projectDir), skillName);
4117
4490
  }
4118
4491
  function installSkillForAgent(name, options, generateSkillMd) {
4492
+ assertNativeExportAllowed();
4119
4493
  const canonicalName = getCanonicalSkillName(name);
4120
- if (!existsSync11(getSkillPath(name))) {
4494
+ if (!existsSync12(getSkillPath(name))) {
4121
4495
  return { skill: canonicalName, success: false, error: `Skill '${name}' not found` };
4122
4496
  }
4123
4497
  const scope = options.scope ?? "global";
@@ -4148,9 +4522,9 @@ function removeSkillForAgent(name, options) {
4148
4522
  }
4149
4523
  function resolveAgentSkillMd(name, generateSkillMd) {
4150
4524
  const sourcePath = getSkillPath(name);
4151
- const skillMdPath = join11(sourcePath, "SKILL.md");
4152
- if (existsSync11(skillMdPath))
4153
- return readFileSync9(skillMdPath, "utf-8");
4525
+ const skillMdPath = join12(sourcePath, "SKILL.md");
4526
+ if (existsSync12(skillMdPath))
4527
+ return readFileSync10(skillMdPath, "utf-8");
4154
4528
  if (generateSkillMd)
4155
4529
  return generateSkillMd(name);
4156
4530
  return generateMinimalSkillMd(name);
@@ -4168,7 +4542,7 @@ function warnMissingDependencies(name, targetDir) {
4168
4542
  }
4169
4543
  function generateMinimalSkillMd(name) {
4170
4544
  const sourcePath = getSkillPath(name);
4171
- if (!existsSync11(sourcePath))
4545
+ if (!existsSync12(sourcePath))
4172
4546
  return null;
4173
4547
  const canonicalName = getCanonicalSkillName(name);
4174
4548
  const meta = getSkill(canonicalName);
@@ -4183,7 +4557,7 @@ function generateMinimalSkillMd(name) {
4183
4557
  "---",
4184
4558
  ""
4185
4559
  ].filter(Boolean);
4186
- const fallbackDoc = readFileIfExists(join11(sourcePath, "README.md")) || readFileIfExists(join11(sourcePath, "CLAUDE.md"));
4560
+ const fallbackDoc = readFileIfExists(join12(sourcePath, "README.md")) || readFileIfExists(join12(sourcePath, "CLAUDE.md"));
4187
4561
  if (fallbackDoc)
4188
4562
  return `${frontmatter.join(`
4189
4563
  `)}${fallbackDoc.trim()}
@@ -4202,38 +4576,38 @@ skills run ${canonicalName}
4202
4576
  `;
4203
4577
  }
4204
4578
  function readBundledSkillVersion(name) {
4205
- const pkgPath = join11(getSkillPath(name), "package.json");
4206
- if (!existsSync11(pkgPath))
4579
+ const pkgPath = join12(getSkillPath(name), "package.json");
4580
+ if (!existsSync12(pkgPath))
4207
4581
  return "unknown";
4208
4582
  try {
4209
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
4583
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
4210
4584
  return pkg.version || "unknown";
4211
4585
  } catch {
4212
4586
  return "unknown";
4213
4587
  }
4214
4588
  }
4215
4589
  function readFileIfExists(path) {
4216
- return existsSync11(path) ? readFileSync9(path, "utf-8") : null;
4590
+ return existsSync12(path) ? readFileSync10(path, "utf-8") : null;
4217
4591
  }
4218
4592
  function loadProjectConfigCompat(targetDir) {
4219
4593
  return loadProjectConfig(targetDir);
4220
4594
  }
4221
4595
  // src/lib/run-state.ts
4222
- import { createHash as createHash2, randomBytes } from "crypto";
4223
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync10, readdirSync as readdirSync8, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
4224
- import { extname, join as join12, relative as relative2 } from "path";
4596
+ import { createHash as createHash3, randomBytes } from "crypto";
4597
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync9, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
4598
+ import { extname, join as join13, relative as relative4 } from "path";
4225
4599
  function createSkillRun(params, targetDir = process.cwd()) {
4226
4600
  const now = new Date;
4227
4601
  const id = createRunId(now);
4228
4602
  const day = now.toISOString().slice(0, 10);
4229
4603
  const skillName = normalizeSkillName(params.skill);
4230
4604
  const root = getProjectStateDir(targetDir);
4231
- const runDir = join12(root, "runs", day, id);
4232
- const logsDir = join12(runDir, "logs");
4233
- const exportDir = join12(root, "exports", skillName, id);
4234
- mkdirSync6(logsDir, { recursive: true });
4235
- mkdirSync6(exportDir, { recursive: true });
4236
- mkdirSync6(join12(root, "tmp"), { recursive: true });
4605
+ const runDir = join13(root, "runs", day, id);
4606
+ const logsDir = join13(runDir, "logs");
4607
+ const exportDir = join13(root, "exports", skillName, id);
4608
+ mkdirSync7(logsDir, { recursive: true });
4609
+ mkdirSync7(exportDir, { recursive: true });
4610
+ mkdirSync7(join13(root, "tmp"), { recursive: true });
4237
4611
  const record = {
4238
4612
  id,
4239
4613
  skill: skillName,
@@ -4285,27 +4659,27 @@ function updateSkillRun(context, patch) {
4285
4659
  return context.record;
4286
4660
  }
4287
4661
  function writeRunLogs(context, stdout = "", stderr = "") {
4288
- writeFileSync6(join12(context.logsDir, "stdout.log"), stdout);
4289
- writeFileSync6(join12(context.logsDir, "stderr.log"), stderr);
4662
+ writeFileSync7(join13(context.logsDir, "stdout.log"), stdout);
4663
+ writeFileSync7(join13(context.logsDir, "stderr.log"), stderr);
4290
4664
  }
4291
4665
  function appendRunEvent(context, event, data = {}) {
4292
4666
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
4293
4667
  `;
4294
- const path = join12(context.runDir, "events.ndjson");
4295
- const previous = existsSync12(path) ? readFileSync10(path, "utf-8") : "";
4296
- writeFileSync6(path, previous + line);
4668
+ const path = join13(context.runDir, "events.ndjson");
4669
+ const previous = existsSync13(path) ? readFileSync11(path, "utf-8") : "";
4670
+ writeFileSync7(path, previous + line);
4297
4671
  }
4298
4672
  function listSkillRuns(targetDir = process.cwd(), limit = 50) {
4299
- const runsRoot = join12(getProjectStateDir(targetDir), "runs");
4300
- if (!existsSync12(runsRoot))
4673
+ const runsRoot = join13(getProjectStateDir(targetDir), "runs");
4674
+ if (!existsSync13(runsRoot))
4301
4675
  return [];
4302
4676
  const records = [];
4303
- for (const day of readdirSync8(runsRoot).sort().reverse()) {
4304
- const dayDir = join12(runsRoot, day);
4677
+ for (const day of readdirSync9(runsRoot).sort().reverse()) {
4678
+ const dayDir = join13(runsRoot, day);
4305
4679
  if (!statSync8(dayDir).isDirectory())
4306
4680
  continue;
4307
- for (const runId of readdirSync8(dayDir).sort().reverse()) {
4308
- const record = readRunRecord(join12(dayDir, runId));
4681
+ for (const runId of readdirSync9(dayDir).sort().reverse()) {
4682
+ const record = readRunRecord(join13(dayDir, runId));
4309
4683
  if (record)
4310
4684
  records.push(record);
4311
4685
  if (records.length >= limit)
@@ -4315,57 +4689,57 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
4315
4689
  return records;
4316
4690
  }
4317
4691
  function findSkillRun(runId, targetDir = process.cwd()) {
4318
- const runsRoot = join12(getProjectStateDir(targetDir), "runs");
4319
- if (!existsSync12(runsRoot))
4692
+ const runsRoot = join13(getProjectStateDir(targetDir), "runs");
4693
+ if (!existsSync13(runsRoot))
4320
4694
  return null;
4321
- for (const day of readdirSync8(runsRoot)) {
4322
- const record = readRunRecord(join12(runsRoot, day, runId));
4695
+ for (const day of readdirSync9(runsRoot)) {
4696
+ const record = readRunRecord(join13(runsRoot, day, runId));
4323
4697
  if (record)
4324
4698
  return record;
4325
4699
  }
4326
4700
  return null;
4327
4701
  }
4328
4702
  function getRunExportDir(runId, skill, targetDir = process.cwd()) {
4329
- return join12(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
4703
+ return join13(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
4330
4704
  }
4331
4705
  function writeRunRecord(context) {
4332
- writeFileSync6(join12(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
4706
+ writeFileSync7(join13(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
4333
4707
  `);
4334
4708
  }
4335
4709
  function writeArtifactsManifest(context, artifacts) {
4336
- writeFileSync6(join12(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
4710
+ writeFileSync7(join13(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
4337
4711
  `);
4338
4712
  }
4339
4713
  function collectRunArtifacts(context) {
4340
- if (!existsSync12(context.exportDir))
4714
+ if (!existsSync13(context.exportDir))
4341
4715
  return [];
4342
4716
  const artifacts = [];
4343
4717
  for (const path of walkFiles(context.exportDir)) {
4344
4718
  const stat = statSync8(path);
4345
- const bytes = readFileSync10(path);
4719
+ const bytes = readFileSync11(path);
4346
4720
  artifacts.push({
4347
4721
  path: toProjectRelative(context.targetDir, path),
4348
4722
  mime: mimeForPath(path),
4349
- sha256: createHash2("sha256").update(bytes).digest("hex"),
4723
+ sha256: createHash3("sha256").update(bytes).digest("hex"),
4350
4724
  sizeBytes: stat.size
4351
4725
  });
4352
4726
  }
4353
4727
  return artifacts.sort((a, b) => a.path.localeCompare(b.path));
4354
4728
  }
4355
4729
  function readRunRecord(runDir) {
4356
- const path = join12(runDir, "run.json");
4357
- if (!existsSync12(path))
4730
+ const path = join13(runDir, "run.json");
4731
+ if (!existsSync13(path))
4358
4732
  return null;
4359
4733
  try {
4360
- return JSON.parse(readFileSync10(path, "utf-8"));
4734
+ return JSON.parse(readFileSync11(path, "utf-8"));
4361
4735
  } catch {
4362
4736
  return null;
4363
4737
  }
4364
4738
  }
4365
4739
  function walkFiles(dir) {
4366
4740
  const files = [];
4367
- for (const entry of readdirSync8(dir)) {
4368
- const full = join12(dir, entry);
4741
+ for (const entry of readdirSync9(dir)) {
4742
+ const full = join13(dir, entry);
4369
4743
  if (statSync8(full).isDirectory())
4370
4744
  files.push(...walkFiles(full));
4371
4745
  else
@@ -4377,7 +4751,7 @@ function createRunId(now) {
4377
4751
  return `run_${now.getTime().toString(36)}_${randomBytes(4).toString("hex")}`;
4378
4752
  }
4379
4753
  function toProjectRelative(targetDir, path) {
4380
- const rel = relative2(targetDir, path).split(/[\\/]/).join("/");
4754
+ const rel = relative4(targetDir, path).split(/[\\/]/).join("/");
4381
4755
  return rel.startsWith(".") ? rel : `./${rel}`;
4382
4756
  }
4383
4757
  function mimeForPath(path) {
@@ -4411,16 +4785,16 @@ function mimeForPath(path) {
4411
4785
  }
4412
4786
  }
4413
4787
  // src/lib/skillinfo.ts
4414
- import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
4415
- import { join as join13 } from "path";
4788
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
4789
+ import { join as join14 } from "path";
4416
4790
  function isInstructionSkillDir(skillPath, meta) {
4417
4791
  if (meta?.kind === "instruction")
4418
4792
  return true;
4419
- const skillMdPath = join13(skillPath, "SKILL.md");
4420
- if (!existsSync13(skillMdPath))
4793
+ const skillMdPath = join14(skillPath, "SKILL.md");
4794
+ if (!existsSync14(skillMdPath))
4421
4795
  return false;
4422
4796
  try {
4423
- return parseSkillFrontmatter(readFileSync11(skillMdPath, "utf-8"))?.kind === "instruction";
4797
+ return parseSkillFrontmatter(readFileSync12(skillMdPath, "utf-8"))?.kind === "instruction";
4424
4798
  } catch {
4425
4799
  return false;
4426
4800
  }
@@ -4443,12 +4817,12 @@ var HOSTED_PROVIDER_ENV_PREFIXES = [
4443
4817
  ];
4444
4818
  function getSkillDocs(name) {
4445
4819
  const skillPath = getSkillPath(name);
4446
- if (!existsSync13(skillPath))
4820
+ if (!existsSync14(skillPath))
4447
4821
  return null;
4448
4822
  return {
4449
- skillMd: readIfExists(join13(skillPath, "SKILL.md")),
4450
- readme: readIfExists(join13(skillPath, "README.md")),
4451
- claudeMd: readIfExists(join13(skillPath, "CLAUDE.md"))
4823
+ skillMd: readIfExists(join14(skillPath, "SKILL.md")),
4824
+ readme: readIfExists(join14(skillPath, "README.md")),
4825
+ claudeMd: readIfExists(join14(skillPath, "CLAUDE.md"))
4452
4826
  };
4453
4827
  }
4454
4828
  function getSkillBestDoc(name) {
@@ -4459,17 +4833,24 @@ function getSkillBestDoc(name) {
4459
4833
  }
4460
4834
  function getSkillRequirements(name) {
4461
4835
  const skillPath = getSkillPath(name);
4462
- if (!existsSync13(skillPath))
4836
+ if (!existsSync14(skillPath))
4463
4837
  return null;
4464
4838
  const texts = [];
4465
4839
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
4466
- const content = readIfExists(join13(skillPath, file));
4840
+ const content = readIfExists(join14(skillPath, file));
4467
4841
  if (content)
4468
4842
  texts.push(content);
4469
4843
  }
4844
+ const meta = getSkill(name);
4845
+ let dependencies = {};
4846
+ try {
4847
+ dependencies = JSON.parse(readFileSync12(join14(skillPath, "package.json"), "utf8")).dependencies || {};
4848
+ } catch {}
4849
+ return getSkillRequirementsFromContent(name, texts, dependencies, meta);
4850
+ }
4851
+ function getSkillRequirementsFromContent(name, texts, dependencies = {}, meta) {
4470
4852
  const allText = texts.join(`
4471
4853
  `);
4472
- const meta = getSkill(name);
4473
4854
  const canonicalName = meta?.name ?? normalizeSkillName(name);
4474
4855
  const envVars = extractEnvVars(allText);
4475
4856
  if (isHostedPremiumSkill(canonicalName, meta)) {
@@ -4502,14 +4883,6 @@ function getSkillRequirements(name) {
4502
4883
  }
4503
4884
  const skillName = normalizeSkillName(name);
4504
4885
  let cliCommand = `skills run ${skillName}`;
4505
- let dependencies = {};
4506
- const pkgPath = join13(skillPath, "package.json");
4507
- if (existsSync13(pkgPath)) {
4508
- try {
4509
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
4510
- dependencies = pkg.dependencies || {};
4511
- } catch {}
4512
- }
4513
4886
  return {
4514
4887
  envVars: Array.from(envVars).sort(),
4515
4888
  systemDeps: Array.from(systemDeps).sort(),
@@ -4524,7 +4897,7 @@ async function runSkill(name, args, options = {}) {
4524
4897
  const meta = getSkill(name);
4525
4898
  const canonicalName = meta?.name ?? name;
4526
4899
  const skillPath = getSkillPath(canonicalName);
4527
- if (!existsSync13(skillPath)) {
4900
+ if (!existsSync14(skillPath)) {
4528
4901
  return { exitCode: 1, error: `Skill '${name}' not found` };
4529
4902
  }
4530
4903
  if (isInstructionSkillDir(skillPath, meta)) {
@@ -4533,13 +4906,13 @@ async function runSkill(name, args, options = {}) {
4533
4906
  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'.`
4534
4907
  };
4535
4908
  }
4536
- const pkgPath = join13(skillPath, "package.json");
4537
- if (!existsSync13(pkgPath)) {
4909
+ const pkgPath = join14(skillPath, "package.json");
4910
+ if (!existsSync14(pkgPath)) {
4538
4911
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
4539
4912
  }
4540
4913
  let entryPoint;
4541
4914
  try {
4542
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
4915
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
4543
4916
  if (pkg.bin) {
4544
4917
  const binValues = Object.values(pkg.bin);
4545
4918
  entryPoint = binValues[0];
@@ -4553,12 +4926,12 @@ async function runSkill(name, args, options = {}) {
4553
4926
  } catch {
4554
4927
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
4555
4928
  }
4556
- const entryPath = join13(skillPath, entryPoint);
4557
- if (!existsSync13(entryPath)) {
4929
+ const entryPath = join14(skillPath, entryPoint);
4930
+ if (!existsSync14(entryPath)) {
4558
4931
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
4559
4932
  }
4560
- const nodeModules = join13(skillPath, "node_modules");
4561
- if (!existsSync13(nodeModules)) {
4933
+ const nodeModules = join14(skillPath, "node_modules");
4934
+ if (!existsSync14(nodeModules)) {
4562
4935
  const install = Bun.spawn(["bun", "install", "--no-save"], {
4563
4936
  cwd: skillPath,
4564
4937
  stdout: "pipe",
@@ -4630,7 +5003,7 @@ function generateSkillMd(name) {
4630
5003
  if (!meta)
4631
5004
  return null;
4632
5005
  const skillPath = getSkillPath(name);
4633
- if (!existsSync13(skillPath))
5006
+ if (!existsSync14(skillPath))
4634
5007
  return null;
4635
5008
  const frontmatter = [
4636
5009
  "---",
@@ -4639,13 +5012,13 @@ function generateSkillMd(name) {
4639
5012
  "---"
4640
5013
  ].join(`
4641
5014
  `);
4642
- const readme = readIfExists(join13(skillPath, "README.md"));
4643
- const claudeMd = readIfExists(join13(skillPath, "CLAUDE.md"));
5015
+ const readme = readIfExists(join14(skillPath, "README.md"));
5016
+ const claudeMd = readIfExists(join14(skillPath, "CLAUDE.md"));
4644
5017
  let cliCommand = null;
4645
- const pkgPath = join13(skillPath, "package.json");
4646
- if (existsSync13(pkgPath)) {
5018
+ const pkgPath = join14(skillPath, "package.json");
5019
+ if (existsSync14(pkgPath)) {
4647
5020
  try {
4648
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
5021
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
4649
5022
  if (pkg.bin) {
4650
5023
  const binKeys = Object.keys(pkg.bin);
4651
5024
  if (binKeys.length > 0)
@@ -4720,8 +5093,8 @@ function extractEnvVars(text) {
4720
5093
  }
4721
5094
  function readIfExists(path) {
4722
5095
  try {
4723
- if (existsSync13(path)) {
4724
- return readFileSync11(path, "utf-8");
5096
+ if (existsSync14(path)) {
5097
+ return readFileSync12(path, "utf-8");
4725
5098
  }
4726
5099
  } catch {}
4727
5100
  return null;
@@ -8702,11 +9075,11 @@ var NEVER = INVALID;
8702
9075
  // ../contracts/dist/client/transport.js
8703
9076
  import { isIP } from "net";
8704
9077
  import { spawnSync } from "child_process";
8705
- import { closeSync, fstatSync, openSync, readFileSync as readFileSync12 } from "fs";
9078
+ import { closeSync, fstatSync, openSync, readFileSync as readFileSync13 } from "fs";
8706
9079
  import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
8707
9080
  import { createRequire } from "module";
8708
9081
  import { hostname as osHostname } from "os";
8709
- import { isAbsolute as isAbsolute3, join as join14 } from "path";
9082
+ import { isAbsolute as isAbsolute4, join as join15 } from "path";
8710
9083
  function envToken(name) {
8711
9084
  return name.toUpperCase().replace(/-/g, "_");
8712
9085
  }
@@ -8765,21 +9138,21 @@ function homeDir(env) {
8765
9138
  }
8766
9139
  function absoluteOverride(env, key) {
8767
9140
  const value = env[key]?.trim();
8768
- return value && isAbsolute3(value) ? value : null;
9141
+ return value && isAbsolute4(value) ? value : null;
8769
9142
  }
8770
9143
  function hasnaHomeDir(env) {
8771
9144
  const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
8772
9145
  if (override)
8773
9146
  return override;
8774
9147
  const home = homeDir(env);
8775
- return home ? join14(home, HASNA_HOME_DIR) : null;
9148
+ return home ? join15(home, HASNA_HOME_DIR) : null;
8776
9149
  }
8777
9150
  function appConfigDir(name, env) {
8778
9151
  const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
8779
9152
  if (configRoot)
8780
- return join14(configRoot, name);
9153
+ return join15(configRoot, name);
8781
9154
  const root = hasnaHomeDir(env);
8782
- return root ? join14(root, name, CONFIG_SUBDIR) : null;
9155
+ return root ? join15(root, name, CONFIG_SUBDIR) : null;
8783
9156
  }
8784
9157
  function credentialDiskSourceList(name, env, profile = null) {
8785
9158
  if (!SAFE_APP_SLUG.test(name))
@@ -8788,7 +9161,7 @@ function credentialDiskSourceList(name, env, profile = null) {
8788
9161
  if (!directory)
8789
9162
  return [];
8790
9163
  const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
8791
- return [{ path: join14(directory, file), tier: "disk" }];
9164
+ return [{ path: join15(directory, file), tier: "disk" }];
8792
9165
  }
8793
9166
  function credentialDiskSources(name, env) {
8794
9167
  return credentialDiskSourceList(name, env, null).map((s) => s.path);
@@ -8863,7 +9236,7 @@ function readAppConfigFile(path) {
8863
9236
  unsafe("the file is not owned by the current user");
8864
9237
  if (before.size > MAX_CREDENTIAL_FILE_BYTES)
8865
9238
  unsafe("the file exceeds the size limit");
8866
- const bytes = readFileSync12(fd);
9239
+ const bytes = readFileSync13(fd);
8867
9240
  const after = fstatSync(fd);
8868
9241
  if (!configFileReadsCoherent(before, after)) {
8869
9242
  unsafe("the file changed while being read");
@@ -9386,7 +9759,7 @@ var AUTHORITY_OVERRIDE_HEADERS = new Set([
9386
9759
  ]);
9387
9760
 
9388
9761
  // src/lib/instance-credentials.ts
9389
- import { closeSync as closeSync2, constants, fstatSync as fstatSync2, lstatSync as lstatSync4, openSync as openSync2, readSync } from "fs";
9762
+ import { closeSync as closeSync2, constants, fstatSync as fstatSync2, lstatSync as lstatSync5, openSync as openSync2, readSync } from "fs";
9390
9763
  var SKILLS_BOUND_API_URL = "HASNA_SKILLS_BOUND_API_URL";
9391
9764
  function selectedSkillsProfile(env, explicit) {
9392
9765
  const selected = explicit ?? env.HASNA_PROFILE;
@@ -9402,7 +9775,7 @@ function skillsProfileCredentialFiles(env, explicit) {
9402
9775
  }
9403
9776
  function fileIdentity(file) {
9404
9777
  try {
9405
- return lstatSync4(file);
9778
+ return lstatSync5(file);
9406
9779
  } catch (error) {
9407
9780
  if (["ENOENT", "ENOTDIR"].includes(error.code ?? ""))
9408
9781
  return null;
@@ -11082,7 +11455,7 @@ function object(value) {
11082
11455
  }
11083
11456
 
11084
11457
  // src/lib/remote-files.ts
11085
- import { createHash as createHash3 } from "crypto";
11458
+ import { createHash as createHash4 } from "crypto";
11086
11459
  var MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
11087
11460
  function describeRemoteFiles(files) {
11088
11461
  if (files.length > 10)
@@ -11100,7 +11473,7 @@ function describeRemoteFiles(files) {
11100
11473
  });
11101
11474
  }
11102
11475
  function sha256(bytes) {
11103
- return createHash3("sha256").update(bytes).digest("hex");
11476
+ return createHash4("sha256").update(bytes).digest("hex");
11104
11477
  }
11105
11478
  async function readBoundedResponse(response, maximum) {
11106
11479
  if (!Number.isSafeInteger(maximum) || maximum < 0 || maximum > MAX_REMOTE_FILE_BYTES)
@@ -11193,10 +11566,10 @@ async function readQuoteUnavailableCode(response) {
11193
11566
  let timer;
11194
11567
  let deadlineExceeded = false;
11195
11568
  const expired = Symbol("quote body deadline");
11196
- const deadline = new Promise((resolve2) => {
11569
+ const deadline = new Promise((resolve4) => {
11197
11570
  timer = setTimeout(() => {
11198
11571
  deadlineExceeded = true;
11199
- resolve2(expired);
11572
+ resolve4(expired);
11200
11573
  reader.cancel().catch(() => {});
11201
11574
  }, 1500);
11202
11575
  });
@@ -11315,6 +11688,7 @@ class RemoteSkillsClient {
11315
11688
  credentials: "omit",
11316
11689
  signal: options?.signal ?? AbortSignal.timeout(15000),
11317
11690
  headers: {
11691
+ "User-Agent": "hasna-skills",
11318
11692
  Authorization: `Bearer ${this.apiKey}`,
11319
11693
  "Content-Type": "application/json",
11320
11694
  ...options?.headers
@@ -12009,26 +12383,26 @@ async function createRemoteSkillsClient(env = process.env) {
12009
12383
  return connection ? new RemoteSkillsClient(connection.apiKey, connection.apiOrigin) : null;
12010
12384
  }
12011
12385
  // src/lib/scheduler.ts
12012
- import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
12013
- import { join as join15 } from "path";
12386
+ import { existsSync as existsSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
12387
+ import { join as join16 } from "path";
12014
12388
  function getSchedulesPath(targetDir = process.cwd()) {
12015
- return join15(targetDir, ".skills", "schedules.json");
12389
+ return join16(targetDir, ".skills", "schedules.json");
12016
12390
  }
12017
12391
  function loadSchedules(targetDir = process.cwd()) {
12018
12392
  const path = getSchedulesPath(targetDir);
12019
- if (existsSync14(path)) {
12393
+ if (existsSync15(path)) {
12020
12394
  try {
12021
- return JSON.parse(readFileSync13(path, "utf-8"));
12395
+ return JSON.parse(readFileSync14(path, "utf-8"));
12022
12396
  } catch {}
12023
12397
  }
12024
12398
  return { version: 1, schedules: [] };
12025
12399
  }
12026
12400
  function saveSchedules(data, targetDir = process.cwd()) {
12027
12401
  const path = getSchedulesPath(targetDir);
12028
- const dir = join15(targetDir, ".skills");
12029
- if (!existsSync14(dir))
12030
- mkdirSync7(dir, { recursive: true });
12031
- writeFileSync7(path, JSON.stringify(data, null, 2));
12402
+ const dir = join16(targetDir, ".skills");
12403
+ if (!existsSync15(dir))
12404
+ mkdirSync8(dir, { recursive: true });
12405
+ writeFileSync8(path, JSON.stringify(data, null, 2));
12032
12406
  }
12033
12407
  function validateCronField(expr, min, max, label) {
12034
12408
  for (const part of expr.split(",")) {
@@ -12213,7 +12587,7 @@ function recordScheduleRun(id, status, targetDir) {
12213
12587
  saveSchedules(data, targetDir);
12214
12588
  }
12215
12589
  // src/lib/revision.ts
12216
- import { createHash as createHash4 } from "crypto";
12590
+ import { createHash as createHash5 } from "crypto";
12217
12591
  var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
12218
12592
  function revisionIdOf(content) {
12219
12593
  const canonical = JSON.stringify({
@@ -12229,19 +12603,19 @@ function revisionIdOf(content) {
12229
12603
  bundleSha256: content.bundleSha256 ?? null,
12230
12604
  bundleByteSize: content.bundleByteSize ?? null
12231
12605
  });
12232
- return createHash4("sha256").update(canonical).digest("hex");
12606
+ return createHash5("sha256").update(canonical).digest("hex");
12233
12607
  }
12234
12608
  function revisionIdOfRecord(record4) {
12235
12609
  return revisionIdOf(record4);
12236
12610
  }
12237
12611
  // src/lib/pull.ts
12238
- import { existsSync as existsSync15, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, readFileSync as readFileSync15, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "fs";
12239
- import { dirname as dirname6, join as join17 } from "path";
12612
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
12613
+ import { dirname as dirname7, join as join18 } from "path";
12240
12614
 
12241
12615
  // src/lib/skill-bundle.ts
12242
- import { createHash as createHash5 } from "crypto";
12243
- import { readFileSync as readFileSync14, readdirSync as readdirSync9, statSync as statSync9 } from "fs";
12244
- import { join as join16, relative as relative3 } from "path";
12616
+ import { createHash as createHash6 } from "crypto";
12617
+ import { readFileSync as readFileSync15, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
12618
+ import { join as join17, relative as relative5 } from "path";
12245
12619
  import { createGunzip } from "zlib";
12246
12620
  var BLOCK = 512;
12247
12621
  var ANY_SEGMENT_EXCLUDES = new Set([
@@ -12394,7 +12768,7 @@ function ownBytes(view) {
12394
12768
  return out;
12395
12769
  }
12396
12770
  function sha256Hex(bytes) {
12397
- return createHash5("sha256").update(bytes).digest("hex");
12771
+ return createHash6("sha256").update(bytes).digest("hex");
12398
12772
  }
12399
12773
  function collectSkillBundleEntries(dir) {
12400
12774
  const entries = [];
@@ -12402,9 +12776,9 @@ function collectSkillBundleEntries(dir) {
12402
12776
  return entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
12403
12777
  }
12404
12778
  function walk(root, current, out) {
12405
- for (const entry of readdirSync9(current, { withFileTypes: true })) {
12406
- const absolute = join16(current, entry.name);
12407
- const rel = relative3(root, absolute).split("\\").join("/");
12779
+ for (const entry of readdirSync10(current, { withFileTypes: true })) {
12780
+ const absolute = join17(current, entry.name);
12781
+ const rel = relative5(root, absolute).split("\\").join("/");
12408
12782
  const isRootLevel = !rel.includes("/");
12409
12783
  if (ANY_SEGMENT_EXCLUDES.has(entry.name.toLowerCase()))
12410
12784
  continue;
@@ -12427,7 +12801,7 @@ function walk(root, current, out) {
12427
12801
  const stats = statSync9(absolute);
12428
12802
  out.push({
12429
12803
  path: rel,
12430
- bytes: ownBytes(readFileSync14(absolute)),
12804
+ bytes: ownBytes(readFileSync15(absolute)),
12431
12805
  mode: stats.mode & 64 ? 493 : 420
12432
12806
  });
12433
12807
  }
@@ -12640,7 +13014,7 @@ async function inspectSkillBundle(bundle, options = {}) {
12640
13014
  parser.push(chunk);
12641
13015
  bytesSinceYield += chunk.byteLength;
12642
13016
  if (bytesSinceYield >= 256 * 1024) {
12643
- await new Promise((resolve2) => setTimeout(resolve2, 0));
13017
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
12644
13018
  bytesSinceYield = 0;
12645
13019
  check();
12646
13020
  }
@@ -12886,7 +13260,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
12886
13260
  return reconcileTombstone(slug, corpusOptions);
12887
13261
  }
12888
13262
  if (bundleResponse.status === 404) {
12889
- const marker = readPullMarker(join17(getPortableSkillsRoot(corpusOptions), slug));
13263
+ const marker = readPullMarker(join18(getPortableSkillsRoot(corpusOptions), slug));
12890
13264
  if (isPublishedInstallMarker(marker)) {
12891
13265
  return { name: slug, success: true, purged: true, removed: false };
12892
13266
  }
@@ -12925,7 +13299,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
12925
13299
  return { name: slug, success: false, error: `Skill '${slug}' was not found on the configured Skills instance.` };
12926
13300
  }
12927
13301
  if (!meta?.revisionId) {
12928
- const marker = readPullMarker(join17(getPortableSkillsRoot(corpusOptions), slug));
13302
+ const marker = readPullMarker(join18(getPortableSkillsRoot(corpusOptions), slug));
12929
13303
  if (isPublishedInstallMarker(marker)) {
12930
13304
  return { name: slug, success: true, purged: true, removed: false };
12931
13305
  }
@@ -12982,7 +13356,7 @@ function provenRevision(meta, slug, bundle) {
12982
13356
  return declared;
12983
13357
  }
12984
13358
  function reconcileTombstone(slug, corpusOptions) {
12985
- const target = join17(getPortableSkillsRoot(corpusOptions), slug);
13359
+ const target = join18(getPortableSkillsRoot(corpusOptions), slug);
12986
13360
  if (!hasSkillsOwnershipMarker(target)) {
12987
13361
  return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
12988
13362
  }
@@ -12991,7 +13365,7 @@ function reconcileTombstone(slug, corpusOptions) {
12991
13365
  }
12992
13366
  function readPullMarker(dir) {
12993
13367
  try {
12994
- return JSON.parse(readFileSync15(join17(dir, PULL_MARKER_FILE), "utf-8"));
13368
+ return JSON.parse(readFileSync16(join18(dir, PULL_MARKER_FILE), "utf-8"));
12995
13369
  } catch {
12996
13370
  return null;
12997
13371
  }
@@ -13125,17 +13499,17 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
13125
13499
  }
13126
13500
  function installBundleAtomically(name, entries, options = {}, marker = {}) {
13127
13501
  const root = getPortableSkillsRoot(options);
13128
- mkdirSync8(root, { recursive: true });
13129
- const target = join17(root, name);
13130
- const created = !existsSync15(target);
13131
- const staging = mkdtempSync3(join17(root, `.pull-${name}-`));
13502
+ mkdirSync9(root, { recursive: true });
13503
+ const target = join18(root, name);
13504
+ const created = !existsSync16(target);
13505
+ const staging = mkdtempSync3(join18(root, `.pull-${name}-`));
13132
13506
  let moved = false;
13133
13507
  let backup = null;
13134
13508
  try {
13135
13509
  for (const entry of entries) {
13136
- const destination = join17(staging, entry.path);
13137
- mkdirSync8(dirname6(destination), { recursive: true });
13138
- writeFileSync8(destination, entry.bytes, { mode: entry.mode });
13510
+ const destination = join18(staging, entry.path);
13511
+ mkdirSync9(dirname7(destination), { recursive: true });
13512
+ writeFileSync9(destination, entry.bytes, { mode: entry.mode });
13139
13513
  }
13140
13514
  writePullMarker(staging, {
13141
13515
  skill: name,
@@ -13145,19 +13519,19 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
13145
13519
  ...marker.signature ? { signature: marker.signature } : {},
13146
13520
  ...marker.revisionId ? { revisionId: marker.revisionId } : {}
13147
13521
  });
13148
- if (existsSync15(target)) {
13149
- backup = mkdtempSync3(join17(root, `.pull-backup-${name}-`));
13150
- renameSync3(target, join17(backup, name));
13522
+ if (existsSync16(target)) {
13523
+ backup = mkdtempSync3(join18(root, `.pull-backup-${name}-`));
13524
+ renameSync4(target, join18(backup, name));
13151
13525
  moved = true;
13152
13526
  }
13153
- renameSync3(staging, target);
13527
+ renameSync4(staging, target);
13154
13528
  if (moved && backup)
13155
13529
  rmSync4(backup, { recursive: true, force: true });
13156
13530
  } catch (error) {
13157
13531
  rmSync4(staging, { recursive: true, force: true });
13158
- if (moved && backup && existsSync15(join17(backup, name))) {
13532
+ if (moved && backup && existsSync16(join18(backup, name))) {
13159
13533
  try {
13160
- renameSync3(join17(backup, name), target);
13534
+ renameSync4(join18(backup, name), target);
13161
13535
  } catch {}
13162
13536
  }
13163
13537
  throw error;
@@ -13176,7 +13550,7 @@ function writePullMarker(dir, record4) {
13176
13550
  ...record4.revisionId ? { revisionId: record4.revisionId } : {},
13177
13551
  syncedAt: new Date().toISOString()
13178
13552
  };
13179
- writeFileSync8(join17(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
13553
+ writeFileSync9(join18(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
13180
13554
  `);
13181
13555
  }
13182
13556
  async function safeMeta(client, slug) {
@@ -13331,12 +13705,12 @@ function findSkillsParityForMcpTool(tool) {
13331
13705
  return SKILLS_CLI_MCP_PARITY.find((entry) => entry.mcpTools.includes(tool));
13332
13706
  }
13333
13707
  // src/lib/registry-sync.ts
13334
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
13335
- import { dirname as dirname7, relative as relative4 } from "path";
13708
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
13709
+ import { dirname as dirname8, relative as relative6 } from "path";
13336
13710
  // package.json
13337
13711
  var package_default = {
13338
13712
  name: "@hasna/skills",
13339
- version: "0.5.10",
13713
+ version: "0.6.0",
13340
13714
  description: "Skills library for AI coding agents",
13341
13715
  type: "module",
13342
13716
  bin: {
@@ -13452,7 +13826,8 @@ var package_default = {
13452
13826
  },
13453
13827
  repository: {
13454
13828
  type: "git",
13455
- url: "git+https://github.com/hasna/apps.git"
13829
+ url: "https://github.com/hasna/apps.git",
13830
+ directory: "apps/skills"
13456
13831
  },
13457
13832
  homepage: "https://github.com/hasna/skills",
13458
13833
  bugs: {
@@ -13472,7 +13847,7 @@ function createRegistrySyncArtifact(options = {}) {
13472
13847
  const registry = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
13473
13848
  const skills = registry.map((skill) => {
13474
13849
  const skillPath = getSkillPath(skill.name);
13475
- const directory = relative4(process.cwd(), skillPath) || skillPath;
13850
+ const directory = relative6(process.cwd(), skillPath) || skillPath;
13476
13851
  const validation = includeValidation ? validateSkillDirectory(skill.name, skillPath, skill) : undefined;
13477
13852
  const docs = includeDocs ? buildDocs(skill.name) : undefined;
13478
13853
  return {
@@ -13517,8 +13892,8 @@ function createRegistrySyncArtifact(options = {}) {
13517
13892
  };
13518
13893
  }
13519
13894
  function writeRegistrySyncArtifact(path, artifact) {
13520
- mkdirSync9(dirname7(path), { recursive: true });
13521
- writeFileSync9(path, `${JSON.stringify(artifact, null, 2)}
13895
+ mkdirSync10(dirname8(path), { recursive: true });
13896
+ writeFileSync10(path, `${JSON.stringify(artifact, null, 2)}
13522
13897
  `);
13523
13898
  }
13524
13899
  function buildDocs(name) {
@@ -14590,17 +14965,17 @@ function clone2(value) {
14590
14965
  return JSON.parse(JSON.stringify(value));
14591
14966
  }
14592
14967
  // src/lib/feedback.ts
14593
- import { appendFileSync, existsSync as existsSync16, mkdirSync as mkdirSync10 } from "fs";
14594
- import { dirname as dirname8, join as join18 } from "path";
14968
+ import { appendFileSync, existsSync as existsSync17, mkdirSync as mkdirSync11 } from "fs";
14969
+ import { dirname as dirname9, join as join19 } from "path";
14595
14970
  import { Database } from "bun:sqlite";
14596
14971
  function getFeedbackDbPath() {
14597
- return join18(getDataDir(), "skills.db");
14972
+ return join19(getDataDir(), "skills.db");
14598
14973
  }
14599
14974
  function getFeedbackDb() {
14600
14975
  const dbPath = getFeedbackDbPath();
14601
- const dir = dirname8(dbPath);
14602
- if (!existsSync16(dir))
14603
- mkdirSync10(dir, { recursive: true });
14976
+ const dir = dirname9(dbPath);
14977
+ if (!existsSync17(dir))
14978
+ mkdirSync11(dir, { recursive: true });
14604
14979
  const db = new Database(dbPath);
14605
14980
  db.exec("PRAGMA journal_mode = WAL");
14606
14981
  db.exec([
@@ -14626,10 +15001,10 @@ function saveFeedback(input) {
14626
15001
  throw new Error("Feedback message is required");
14627
15002
  const category = input.category ?? "general";
14628
15003
  if (isApiMode()) {
14629
- const path = join18(getDataDir(), "feedback.jsonl");
14630
- const dir = dirname8(path);
14631
- if (!existsSync16(dir))
14632
- mkdirSync10(dir, { recursive: true });
15004
+ const path = join19(getDataDir(), "feedback.jsonl");
15005
+ const dir = dirname9(path);
15006
+ if (!existsSync17(dir))
15007
+ mkdirSync11(dir, { recursive: true });
14633
15008
  appendFileSync(path, JSON.stringify({ message, category, email: input.email ?? null, agent: input.agent ?? null, version: input.version ?? null, createdAt: new Date().toISOString() }) + `
14634
15009
  `);
14635
15010
  return { saved: true, category, path };
@@ -14650,16 +15025,16 @@ function isApiMode(env = process.env) {
14650
15025
  }
14651
15026
  }
14652
15027
  // src/lib/native-storage.ts
14653
- import { createHash as createHash6, createHmac as createHmac2 } from "crypto";
15028
+ import { createHash as createHash7, createHmac as createHmac2 } from "crypto";
14654
15029
  import {
14655
- existsSync as existsSync17,
14656
- mkdirSync as mkdirSync11,
14657
- readFileSync as readFileSync16,
14658
- readdirSync as readdirSync10,
15030
+ existsSync as existsSync18,
15031
+ mkdirSync as mkdirSync12,
15032
+ readFileSync as readFileSync17,
15033
+ readdirSync as readdirSync11,
14659
15034
  statSync as statSync10,
14660
- writeFileSync as writeFileSync10
15035
+ writeFileSync as writeFileSync11
14661
15036
  } from "fs";
14662
- import { dirname as dirname9, join as join19, normalize as normalize3, relative as relative5, sep as sep2 } from "path";
15037
+ import { dirname as dirname10, join as join20, normalize as normalize3, relative as relative7, sep as sep4 } from "path";
14663
15038
  var SKILLS_STORAGE_TABLES = [
14664
15039
  "skills_sync_records",
14665
15040
  "skills_sync_cursors"
@@ -14796,7 +15171,7 @@ function getSkillsNativeStorageStatus(options = {}) {
14796
15171
  local: {
14797
15172
  dataDir: getDataDir(),
14798
15173
  projectStateDir: getProjectStateDir(targetDir),
14799
- feedbackDbPath: join19(getDataDir(), "skills.db")
15174
+ feedbackDbPath: join20(getDataDir(), "skills.db")
14800
15175
  },
14801
15176
  remote: {
14802
15177
  databaseConfigured: Boolean(config.databaseUrl),
@@ -14819,14 +15194,14 @@ function getStorageStatus(options = {}) {
14819
15194
  function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
14820
15195
  const projectStateDir = getProjectStateDir(targetDir);
14821
15196
  const files = [];
14822
- if (existsSync17(projectStateDir)) {
15197
+ if (existsSync18(projectStateDir)) {
14823
15198
  for (const filePath of walkFiles2(projectStateDir)) {
14824
- const bytes = readFileSync16(filePath);
14825
- const relativePath = toPosix(relative5(targetDir, filePath));
15199
+ const bytes = readFileSync17(filePath);
15200
+ const relativePath = toPosix(relative7(targetDir, filePath));
14826
15201
  files.push({
14827
15202
  path: relativePath,
14828
15203
  sizeBytes: bytes.byteLength,
14829
- sha256: createHash6("sha256").update(bytes).digest("hex"),
15204
+ sha256: createHash7("sha256").update(bytes).digest("hex"),
14830
15205
  ...options.includeFileContents ? { contentBase64: Buffer.from(bytes).toString("base64") } : {}
14831
15206
  });
14832
15207
  }
@@ -14846,17 +15221,17 @@ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options
14846
15221
  continue;
14847
15222
  }
14848
15223
  const absolutePath = resolveSnapshotPath(targetDir, file.path);
14849
- if (existsSync17(absolutePath) && !options.overwrite) {
15224
+ if (existsSync18(absolutePath) && !options.overwrite) {
14850
15225
  skipped += 1;
14851
15226
  continue;
14852
15227
  }
14853
15228
  const bytes = Buffer.from(file.contentBase64, "base64");
14854
- const hash = createHash6("sha256").update(bytes).digest("hex");
15229
+ const hash = createHash7("sha256").update(bytes).digest("hex");
14855
15230
  if (hash !== file.sha256) {
14856
15231
  throw new Error(`Snapshot file checksum mismatch: ${file.path}`);
14857
15232
  }
14858
- mkdirSync11(dirname9(absolutePath), { recursive: true });
14859
- writeFileSync10(absolutePath, bytes);
15233
+ mkdirSync12(dirname10(absolutePath), { recursive: true });
15234
+ writeFileSync11(absolutePath, bytes);
14860
15235
  written += 1;
14861
15236
  }
14862
15237
  return { written, skipped };
@@ -15152,8 +15527,8 @@ function parsePositiveInteger(value) {
15152
15527
  }
15153
15528
  function walkFiles2(dir) {
15154
15529
  const files = [];
15155
- for (const entry of readdirSync10(dir)) {
15156
- const full = join19(dir, entry);
15530
+ for (const entry of readdirSync11(dir)) {
15531
+ const full = join20(dir, entry);
15157
15532
  const stats = statSync10(full);
15158
15533
  if (stats.isDirectory())
15159
15534
  files.push(...walkFiles2(full));
@@ -15164,13 +15539,13 @@ function walkFiles2(dir) {
15164
15539
  }
15165
15540
  function resolveSnapshotPath(targetDir, snapshotPath) {
15166
15541
  const normalizedPath = normalize3(snapshotPath);
15167
- if (normalizedPath.startsWith("..") || normalizedPath.includes(`${sep2}..${sep2}`) || normalizedPath.startsWith(sep2)) {
15542
+ if (normalizedPath.startsWith("..") || normalizedPath.includes(`${sep4}..${sep4}`) || normalizedPath.startsWith(sep4)) {
15168
15543
  throw new Error(`Unsafe snapshot path: ${snapshotPath}`);
15169
15544
  }
15170
15545
  if (!toPosix(normalizedPath).startsWith(".skills/")) {
15171
15546
  throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
15172
15547
  }
15173
- return join19(targetDir, normalizedPath);
15548
+ return join20(targetDir, normalizedPath);
15174
15549
  }
15175
15550
  function normalizeS3Prefix(prefix) {
15176
15551
  return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
@@ -15183,7 +15558,7 @@ function toIsoString(value) {
15183
15558
  return Number.isNaN(date.getTime()) ? value : date.toISOString();
15184
15559
  }
15185
15560
  function sha256Hex2(value) {
15186
- return createHash6("sha256").update(value).digest("hex");
15561
+ return createHash7("sha256").update(value).digest("hex");
15187
15562
  }
15188
15563
  function normalizeHeaders(headers) {
15189
15564
  const result = {};
@@ -15235,20 +15610,20 @@ function toArrayBuffer(bytes) {
15235
15610
  return buffer;
15236
15611
  }
15237
15612
  // src/lib/station-snapshot.ts
15238
- import { createHash as createHash7 } from "crypto";
15613
+ import { createHash as createHash8 } from "crypto";
15239
15614
  import {
15240
15615
  copyFileSync as copyFileSync2,
15241
- mkdirSync as mkdirSync12,
15242
- readFileSync as readFileSync17,
15616
+ mkdirSync as mkdirSync13,
15617
+ readFileSync as readFileSync18,
15243
15618
  statSync as statSync12,
15244
- writeFileSync as writeFileSync11
15619
+ writeFileSync as writeFileSync12
15245
15620
  } from "fs";
15246
- import { dirname as dirname10, isAbsolute as isAbsolute4, relative as relative6, resolve as resolve2, sep as sep4 } from "path";
15621
+ import { dirname as dirname11, isAbsolute as isAbsolute5, relative as relative8, resolve as resolve4, sep as sep6 } from "path";
15247
15622
 
15248
15623
  // src/lib/portable-snapshot-filter.ts
15249
- import { readdirSync as readdirSync11, statSync as statSync11 } from "fs";
15250
- import { homedir as homedir4 } from "os";
15251
- import { join as join20, sep as sep3 } from "path";
15624
+ import { readdirSync as readdirSync12, statSync as statSync11 } from "fs";
15625
+ import { homedir as homedir5 } from "os";
15626
+ import { join as join21, sep as sep5 } from "path";
15252
15627
  var SYNC_HOMES = [
15253
15628
  { name: "skills", subClass: "skills", agent: null },
15254
15629
  { name: "custom", subClass: "custom", agent: null },
@@ -15339,29 +15714,29 @@ function isPortableWithinSkill(relativeParts) {
15339
15714
  return PORTABLE_SUBDIRS.has(second);
15340
15715
  }
15341
15716
  function homePathFor(definition, homesRoot) {
15342
- const home = homesRoot ?? homedir4();
15717
+ const home = homesRoot ?? homedir5();
15343
15718
  if (definition.subClass === "skills" || definition.subClass === "custom") {
15344
- return join20(skillsDataRootForHome(home), definition.name);
15719
+ return join21(skillsDataRootForHome(home), definition.name);
15345
15720
  }
15346
15721
  if (definition.agent === "opencode") {
15347
- return join20(home, ".config", "opencode", "skills");
15722
+ return join21(home, ".config", "opencode", "skills");
15348
15723
  }
15349
- return join20(home, `.${definition.agent}`, "skills");
15724
+ return join21(home, `.${definition.agent}`, "skills");
15350
15725
  }
15351
15726
  function destinationFor(definition, stationId, relativePath) {
15352
- const category = definition.subClass === "agent-homes" ? join20("agent-homes", definition.agent ?? "") : definition.name;
15353
- return join20("resources", stationId, "skills", category, ...relativePath.split(sep3));
15727
+ const category = definition.subClass === "agent-homes" ? join21("agent-homes", definition.agent ?? "") : definition.name;
15728
+ return join21("resources", stationId, "skills", category, ...relativePath.split(sep5));
15354
15729
  }
15355
15730
  function walkEntries(absoluteRoot) {
15356
15731
  let entries;
15357
15732
  try {
15358
- entries = readdirSync11(absoluteRoot, { withFileTypes: true });
15733
+ entries = readdirSync12(absoluteRoot, { withFileTypes: true });
15359
15734
  } catch {
15360
15735
  return [];
15361
15736
  }
15362
15737
  const output = [];
15363
15738
  for (const entry of entries) {
15364
- const childFull = join20(absoluteRoot, entry.name);
15739
+ const childFull = join21(absoluteRoot, entry.name);
15365
15740
  if (entry.isSymbolicLink()) {
15366
15741
  output.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
15367
15742
  continue;
@@ -15372,7 +15747,7 @@ function walkEntries(absoluteRoot) {
15372
15747
  }
15373
15748
  const nested = walkEntries(childFull);
15374
15749
  for (const item of nested) {
15375
- output.push({ ...item, relativePath: join20(entry.name, item.relativePath) });
15750
+ output.push({ ...item, relativePath: join21(entry.name, item.relativePath) });
15376
15751
  }
15377
15752
  continue;
15378
15753
  }
@@ -15410,7 +15785,7 @@ function validateStationId(stationId) {
15410
15785
  }
15411
15786
  }
15412
15787
  function sha256File(filePath) {
15413
- return createHash7("sha256").update(readFileSync17(filePath)).digest("hex");
15788
+ return createHash8("sha256").update(readFileSync18(filePath)).digest("hex");
15414
15789
  }
15415
15790
  function scanHome(definition, homesRoot) {
15416
15791
  const homePath = homePathFor(definition, homesRoot);
@@ -15418,7 +15793,7 @@ function scanHome(definition, homesRoot) {
15418
15793
  const portable = [];
15419
15794
  const skipped = [];
15420
15795
  for (const entry of entries) {
15421
- const relativeParts = entry.relativePath.split(sep4);
15796
+ const relativeParts = entry.relativePath.split(sep6);
15422
15797
  const fileName = relativeParts[relativeParts.length - 1];
15423
15798
  if (entry.kind === "symlink") {
15424
15799
  skipped.push({ relativePath: entry.relativePath, reason: "symlink" });
@@ -15481,7 +15856,7 @@ function humanHomes(scanned) {
15481
15856
  }));
15482
15857
  }
15483
15858
  function writeStationSnapshot(options) {
15484
- const repoRoot = resolve2(options.repoRoot ?? process.cwd());
15859
+ const repoRoot = resolve4(options.repoRoot ?? process.cwd());
15485
15860
  const { scanned, plans, totalBytes } = planStationSnapshot(options);
15486
15861
  const manifestFiles = plans.map((plan) => ({
15487
15862
  relativePath: plan.source.relativePath,
@@ -15507,9 +15882,9 @@ function writeStationSnapshot(options) {
15507
15882
  const conflicts = [];
15508
15883
  const untouched = [];
15509
15884
  for (const plan of plans) {
15510
- const destination = resolve2(repoRoot, plan.destination);
15511
- const destinationRelative = relative6(repoRoot, destination);
15512
- if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep4) || isAbsolute4(destinationRelative)) {
15885
+ const destination = resolve4(repoRoot, plan.destination);
15886
+ const destinationRelative = relative8(repoRoot, destination);
15887
+ if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep6) || isAbsolute5(destinationRelative)) {
15513
15888
  throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
15514
15889
  }
15515
15890
  let existingDigest = null;
@@ -15530,8 +15905,8 @@ function writeStationSnapshot(options) {
15530
15905
  }
15531
15906
  let written = 0;
15532
15907
  for (const plan of untouched) {
15533
- const destination = resolve2(repoRoot, plan.destination);
15534
- mkdirSync12(dirname10(destination), { recursive: true });
15908
+ const destination = resolve4(repoRoot, plan.destination);
15909
+ mkdirSync13(dirname11(destination), { recursive: true });
15535
15910
  copyFileSync2(plan.source.fullPath, destination);
15536
15911
  written += 1;
15537
15912
  }
@@ -15549,9 +15924,9 @@ function writeStationSnapshot(options) {
15549
15924
  },
15550
15925
  files: manifestFiles
15551
15926
  };
15552
- const manifestPath = resolve2(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
15553
- mkdirSync12(dirname10(manifestPath), { recursive: true });
15554
- writeFileSync11(manifestPath, `${JSON.stringify(manifest, null, 2)}
15927
+ const manifestPath = resolve4(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
15928
+ mkdirSync13(dirname11(manifestPath), { recursive: true });
15929
+ writeFileSync12(manifestPath, `${JSON.stringify(manifest, null, 2)}
15555
15930
  `);
15556
15931
  return {
15557
15932
  ...base,
@@ -15561,16 +15936,16 @@ function writeStationSnapshot(options) {
15561
15936
  };
15562
15937
  }
15563
15938
  // src/lib/station-hydrate.ts
15564
- import { createHash as createHash8 } from "crypto";
15939
+ import { createHash as createHash9 } from "crypto";
15565
15940
  import {
15566
15941
  copyFileSync as copyFileSync3,
15567
- mkdirSync as mkdirSync13,
15568
- readdirSync as readdirSync12,
15569
- readFileSync as readFileSync18,
15942
+ mkdirSync as mkdirSync14,
15943
+ readdirSync as readdirSync13,
15944
+ readFileSync as readFileSync19,
15570
15945
  statSync as statSync13,
15571
- writeFileSync as writeFileSync12
15946
+ writeFileSync as writeFileSync13
15572
15947
  } from "fs";
15573
- import { dirname as dirname11, join as join21, resolve as resolve3, sep as sep5 } from "path";
15948
+ import { dirname as dirname12, join as join22, resolve as resolve5, sep as sep7 } from "path";
15574
15949
  var STATION_HYDRATION_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-hydration-manifest/v1";
15575
15950
  var STATION_HYDRATION_PRODUCER = { name: "@hasna/skills", version: package_default.version };
15576
15951
  var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
@@ -15578,14 +15953,14 @@ function fail(code, message, detail = []) {
15578
15953
  throw new StationSnapshotError(code, message, detail);
15579
15954
  }
15580
15955
  function snapshotRootFor(repoRoot, stationId) {
15581
- return join21(repoRoot, "resources", stationId, "skills");
15956
+ return join22(repoRoot, "resources", stationId, "skills");
15582
15957
  }
15583
15958
  function readSnapshotManifest(repoRoot, stationId) {
15584
15959
  const snapshotRoot = snapshotRootFor(repoRoot, stationId);
15585
- const manifestPath = join21(snapshotRoot, "sync-manifest.json");
15960
+ const manifestPath = join22(snapshotRoot, "sync-manifest.json");
15586
15961
  let manifest;
15587
15962
  try {
15588
- manifest = JSON.parse(readFileSync18(manifestPath, "utf8"));
15963
+ manifest = JSON.parse(readFileSync19(manifestPath, "utf8"));
15589
15964
  } catch (error) {
15590
15965
  fail("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error.message}`);
15591
15966
  }
@@ -15609,10 +15984,10 @@ function planStationHydration(stationId, repoRoot) {
15609
15984
  const hashMismatches = [];
15610
15985
  const skippedByRule = [];
15611
15986
  for (const agent of SYNC_AGENTS) {
15612
- const agentRoot = join21(snapshotRoot, "agent-homes", agent);
15987
+ const agentRoot = join22(snapshotRoot, "agent-homes", agent);
15613
15988
  let identEntries;
15614
15989
  try {
15615
- identEntries = readdirSync12(agentRoot, { withFileTypes: true });
15990
+ identEntries = readdirSync13(agentRoot, { withFileTypes: true });
15616
15991
  } catch {
15617
15992
  continue;
15618
15993
  }
@@ -15620,10 +15995,10 @@ function planStationHydration(stationId, repoRoot) {
15620
15995
  if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
15621
15996
  continue;
15622
15997
  }
15623
- const identRoot = join21(agentRoot, identEntry.name);
15998
+ const identRoot = join22(agentRoot, identEntry.name);
15624
15999
  const entries = walkEntries(identRoot);
15625
16000
  for (const entry of entries) {
15626
- const relativeParts = [identEntry.name, ...entry.relativePath.split(sep5)];
16001
+ const relativeParts = [identEntry.name, ...entry.relativePath.split(sep7)];
15627
16002
  if (entry.kind === "symlink") {
15628
16003
  symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
15629
16004
  continue;
@@ -15647,8 +16022,8 @@ function planStationHydration(stationId, repoRoot) {
15647
16022
  });
15648
16023
  continue;
15649
16024
  }
15650
- const withinIdent = relativeParts.slice(1).join(sep5);
15651
- const homeRelative = relativeParts.join(sep5);
16025
+ const withinIdent = relativeParts.slice(1).join(sep7);
16026
+ const homeRelative = relativeParts.join(sep7);
15652
16027
  if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
15653
16028
  skippedByRule.push({
15654
16029
  ident: identEntry.name,
@@ -15721,7 +16096,7 @@ function planStationHydration(stationId, repoRoot) {
15721
16096
  for (const copy of copies) {
15722
16097
  let isStub = false;
15723
16098
  try {
15724
- isStub = isPointerSkillMd(readFileSync18(copy.fullPath, "utf8"));
16099
+ isStub = isPointerSkillMd(readFileSync19(copy.fullPath, "utf8"));
15725
16100
  } catch {
15726
16101
  isStub = false;
15727
16102
  }
@@ -15767,12 +16142,12 @@ function skillSha256(skill) {
15767
16142
  return sha256File(skill.files[0].winner.fullPath);
15768
16143
  }
15769
16144
  const joined = skill.files.map((file) => sha256File(file.winner.fullPath));
15770
- return createHash8("sha256").update(joined.sort().join(`
16145
+ return createHash9("sha256").update(joined.sort().join(`
15771
16146
  `)).digest("hex");
15772
16147
  }
15773
16148
  function writeStationHydration(options) {
15774
- const repoRoot = resolve3(options.repoRoot ?? process.cwd());
15775
- const cacheRoot = resolve3(options.cacheRoot ?? resolveCorpusRoot());
16149
+ const repoRoot = resolve5(options.repoRoot ?? process.cwd());
16150
+ const cacheRoot = resolve5(options.cacheRoot ?? resolveCorpusRoot());
15776
16151
  const plan = planStationHydration(options.stationId, repoRoot);
15777
16152
  const resultSkills = plan.winners.map((skill) => ({
15778
16153
  ident: skill.ident,
@@ -15805,7 +16180,7 @@ function writeStationHydration(options) {
15805
16180
  const toWrite = [];
15806
16181
  for (const skill of plan.winners) {
15807
16182
  for (const file of skill.files) {
15808
- const destination = join21(cacheRoot, skill.ident, file.withinIdent);
16183
+ const destination = join22(cacheRoot, skill.ident, file.withinIdent);
15809
16184
  const digest = sha256File(file.winner.fullPath);
15810
16185
  let existingDigest = null;
15811
16186
  try {
@@ -15826,7 +16201,7 @@ function writeStationHydration(options) {
15826
16201
  }
15827
16202
  let written = 0;
15828
16203
  for (const entry of toWrite) {
15829
- mkdirSync13(dirname11(entry.destination), { recursive: true });
16204
+ mkdirSync14(dirname12(entry.destination), { recursive: true });
15830
16205
  copyFileSync3(entry.fullPath, entry.destination);
15831
16206
  written += 1;
15832
16207
  }
@@ -15847,9 +16222,9 @@ function writeStationHydration(options) {
15847
16222
  },
15848
16223
  skills: resultSkills
15849
16224
  };
15850
- const hydrationManifestPath = join21(dirname11(cacheRoot), `hydration-${options.stationId}.json`);
15851
- mkdirSync13(dirname11(hydrationManifestPath), { recursive: true });
15852
- writeFileSync12(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
16225
+ const hydrationManifestPath = join22(dirname12(cacheRoot), `hydration-${options.stationId}.json`);
16226
+ mkdirSync14(dirname12(hydrationManifestPath), { recursive: true });
16227
+ writeFileSync13(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
15853
16228
  `);
15854
16229
  return {
15855
16230
  ...base,
@@ -15954,7 +16329,7 @@ async function requestInvitationEmail(origin, action, input) {
15954
16329
  }
15955
16330
 
15956
16331
  // src/lib/remote-private-publications.ts
15957
- import { createHash as createHash9 } from "crypto";
16332
+ import { createHash as createHash10 } from "crypto";
15958
16333
  var PRIVATE_PUBLICATION_MAX_BYTES = 16 * 1024 * 1024;
15959
16334
  var failures = {
15960
16335
  INVALID_REQUEST: [400, "The publication request is invalid."],
@@ -16001,7 +16376,7 @@ var hash = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v);
16001
16376
  var record5 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
16002
16377
  var exact = (v, keys) => record5(v) && Object.keys(v).sort().join(",") === keys.sort().join(",");
16003
16378
  var date = (v) => typeof v === "string" && /^\d{4}-\d\d-\d\dT[0-9:.]+(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(v) && Number.isFinite(Date.parse(v));
16004
- var publicationSha256 = (bytes) => createHash9("sha256").update(bytes).digest("hex");
16379
+ var publicationSha256 = (bytes) => createHash10("sha256").update(bytes).digest("hex");
16005
16380
  function checkedPublicationDeclaration(value) {
16006
16381
  if (!exact(value, ["idempotencyKey", "version", "expectedCurrentVersionId", "manifestText", "archiveSha256", "archiveByteSize"]) || !publicationUuid(value.idempotencyKey) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || typeof value.manifestText !== "string" || Buffer.byteLength(value.manifestText) > 16384 || !hash(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES)
16007
16382
  return bad();
@@ -16246,12 +16621,12 @@ class RemotePrivatePublicationsClient {
16246
16621
  previous = view;
16247
16622
  if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
16248
16623
  return view;
16249
- await new Promise((resolve4) => {
16624
+ await new Promise((resolve6) => {
16250
16625
  const timer = setTimeout(done, Math.min(1000, until - Date.now()));
16251
16626
  function done() {
16252
16627
  clearTimeout(timer);
16253
16628
  options.signal?.removeEventListener("abort", done);
16254
- resolve4();
16629
+ resolve6();
16255
16630
  }
16256
16631
  options.signal?.addEventListener("abort", done, { once: true });
16257
16632
  });
@@ -16483,23 +16858,23 @@ class RemoteSkillsAuthClient {
16483
16858
  }
16484
16859
  }
16485
16860
  // src/lib/private-publication-recovery.ts
16486
- import { constants as constants2, closeSync as closeSync3, fsyncSync, fstatSync as fstatSync3, lstatSync as lstatSync5, mkdirSync as mkdirSync14, openSync as openSync3, readSync as readSync2, realpathSync as realpathSync2, renameSync as renameSync4, unlinkSync, writeFileSync as writeFileSync13 } from "fs";
16487
- import { dirname as dirname12, isAbsolute as isAbsolute5, join as join22, resolve as resolve4 } from "path";
16488
- import { randomUUID } from "crypto";
16861
+ import { constants as constants2, closeSync as closeSync3, fsyncSync, fstatSync as fstatSync3, lstatSync as lstatSync6, mkdirSync as mkdirSync15, openSync as openSync3, readSync as readSync2, realpathSync as realpathSync4, renameSync as renameSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync14 } from "fs";
16862
+ import { dirname as dirname13, isAbsolute as isAbsolute6, join as join23, resolve as resolve6 } from "path";
16863
+ import { randomUUID as randomUUID2 } from "crypto";
16489
16864
  var fail2 = () => {
16490
16865
  throw new PrivatePublicationError("PUBLICATION_RECOVERY_INVALID", "The recovery directory is invalid or changed. Preserve it and inspect the existing intent; do not start a replacement automatically.");
16491
16866
  };
16492
16867
  function safeDirectory(directory) {
16493
- if (!isAbsolute5(directory) || directory !== resolve4(directory) || realpathSync2(directory) !== directory)
16868
+ if (!isAbsolute6(directory) || directory !== resolve6(directory) || realpathSync4(directory) !== directory)
16494
16869
  return fail2();
16495
- for (let path = directory;; path = dirname12(path)) {
16496
- const stat = lstatSync5(path);
16870
+ for (let path = directory;; path = dirname13(path)) {
16871
+ const stat = lstatSync6(path);
16497
16872
  if (!stat.isDirectory() || stat.isSymbolicLink())
16498
16873
  return fail2();
16499
- if (path === dirname12(path))
16874
+ if (path === dirname13(path))
16500
16875
  break;
16501
16876
  }
16502
- const own = lstatSync5(directory);
16877
+ const own = lstatSync6(directory);
16503
16878
  if ((own.mode & 63) !== 0 || process.getuid && own.uid !== process.getuid())
16504
16879
  return fail2();
16505
16880
  return { dev: own.dev, ino: own.ino };
@@ -16510,7 +16885,7 @@ function unchangedDirectory(directory, identity) {
16510
16885
  return fail2();
16511
16886
  }
16512
16887
  function readOwned(directory, name, max) {
16513
- const identity = safeDirectory(directory), file = join22(directory, name);
16888
+ const identity = safeDirectory(directory), file = join23(directory, name);
16514
16889
  const fd = openSync3(file, constants2.O_RDONLY | constants2.O_NOFOLLOW);
16515
16890
  try {
16516
16891
  const stat = fstatSync3(fd);
@@ -16534,20 +16909,20 @@ function readOwned(directory, name, max) {
16534
16909
  }
16535
16910
  }
16536
16911
  function writeOwned(directory, name, bytes) {
16537
- const fd = openSync3(join22(directory, name), constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
16912
+ const fd = openSync3(join23(directory, name), constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
16538
16913
  try {
16539
- writeFileSync13(fd, bytes);
16914
+ writeFileSync14(fd, bytes);
16540
16915
  fsyncSync(fd);
16541
16916
  } finally {
16542
16917
  closeSync3(fd);
16543
16918
  }
16544
16919
  }
16545
16920
  function save(directory, value) {
16546
- const identity = safeDirectory(directory), temporary = `.receipt-${randomUUID()}.json`;
16921
+ const identity = safeDirectory(directory), temporary = `.receipt-${randomUUID2()}.json`;
16547
16922
  writeOwned(directory, temporary, JSON.stringify(value) + `
16548
16923
  `);
16549
16924
  unchangedDirectory(directory, identity);
16550
- renameSync4(join22(directory, temporary), join22(directory, "receipt.json"));
16925
+ renameSync5(join23(directory, temporary), join23(directory, "receipt.json"));
16551
16926
  const fd = openSync3(directory, constants2.O_RDONLY | constants2.O_NOFOLLOW);
16552
16927
  try {
16553
16928
  fsyncSync(fd);
@@ -16560,7 +16935,7 @@ function bind(client, receipt) {
16560
16935
  throw new PrivatePublicationError("PUBLICATION_IDENTITY_CHANGED", "The fresh session does not match the recovery directory's server, account and membership.");
16561
16936
  }
16562
16937
  async function locked(directory, action) {
16563
- const identity = safeDirectory(directory), file = join22(directory, "operation.lock");
16938
+ const identity = safeDirectory(directory), file = join23(directory, "operation.lock");
16564
16939
  let fd;
16565
16940
  try {
16566
16941
  fd = openSync3(file, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
@@ -16569,17 +16944,17 @@ async function locked(directory, action) {
16569
16944
  }
16570
16945
  const lock = fstatSync3(fd);
16571
16946
  try {
16572
- writeFileSync13(fd, JSON.stringify({ pid: process.pid }) + `
16947
+ writeFileSync14(fd, JSON.stringify({ pid: process.pid }) + `
16573
16948
  `);
16574
16949
  fsyncSync(fd);
16575
16950
  return await action();
16576
16951
  } finally {
16577
16952
  closeSync3(fd);
16578
16953
  unchangedDirectory(directory, identity);
16579
- const now = lstatSync5(file);
16954
+ const now = lstatSync6(file);
16580
16955
  if (now.dev !== lock.dev || now.ino !== lock.ino || !now.isFile() || now.isSymbolicLink())
16581
16956
  fail2();
16582
- unlinkSync(file);
16957
+ unlinkSync2(file);
16583
16958
  }
16584
16959
  }
16585
16960
  function readPrivatePublicationRecovery(directory) {
@@ -16610,7 +16985,7 @@ async function preparePrivatePublication(client, sourceDirectory, recoveryDirect
16610
16985
  throw new PrivatePublicationError("PUBLICATION_BUNDLE_INVALID", "The skill must have a valid skill.json with its current content hash. Validate the skill before publishing.");
16611
16986
  const manifestText = new TextDecoder("utf-8", { fatal: true }).decode(manifest.bytes);
16612
16987
  const declaration = checkedPublicationDeclaration({
16613
- idempotencyKey: input.idempotencyKey ?? randomUUID(),
16988
+ idempotencyKey: input.idempotencyKey ?? randomUUID2(),
16614
16989
  version: JSON.parse(manifestText).version,
16615
16990
  expectedCurrentVersionId: input.expectedCurrentVersionId,
16616
16991
  manifestText,
@@ -16628,11 +17003,11 @@ async function preparePrivatePublication(client, sourceDirectory, recoveryDirect
16628
17003
  phase: "prepared",
16629
17004
  intent: null
16630
17005
  };
16631
- if (!isAbsolute5(recoveryDirectory) || resolve4(recoveryDirectory) !== recoveryDirectory || realpathSync2(dirname12(recoveryDirectory)) !== dirname12(recoveryDirectory))
17006
+ if (!isAbsolute6(recoveryDirectory) || resolve6(recoveryDirectory) !== recoveryDirectory || realpathSync4(dirname13(recoveryDirectory)) !== dirname13(recoveryDirectory))
16632
17007
  return fail2();
16633
- mkdirSync14(recoveryDirectory, { mode: 448 });
17008
+ mkdirSync15(recoveryDirectory, { mode: 448 });
16634
17009
  safeDirectory(recoveryDirectory);
16635
- const parent = openSync3(dirname12(recoveryDirectory), constants2.O_RDONLY | constants2.O_NOFOLLOW);
17010
+ const parent = openSync3(dirname13(recoveryDirectory), constants2.O_RDONLY | constants2.O_NOFOLLOW);
16636
17011
  try {
16637
17012
  fsyncSync(parent);
16638
17013
  } finally {
@@ -16718,6 +17093,565 @@ async function inspectLocked(client, directory, cancel) {
16718
17093
  throw new PrivatePublicationError("PUBLICATION_INTENT_UNKNOWN", "Reconcile the saved begin request with publication resume before cancelling its intent.");
16719
17094
  return privatePublicationResult(directory, receipt);
16720
17095
  }
17096
+ // src/lib/profile-client.ts
17097
+ function object2(value) {
17098
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
17099
+ }
17100
+ function identifier(value) {
17101
+ return typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(value) && !value.includes("..");
17102
+ }
17103
+ function invalid4() {
17104
+ throw new Error("The Skills API returned an invalid profile response");
17105
+ }
17106
+ function validateSelection(value) {
17107
+ if (!object2(value) || !identifier(value.slug) || typeof value.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/.test(value.version) || value.version.includes("..") || typeof value.bundleDigest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value.bundleDigest))
17108
+ invalid4();
17109
+ if (value.triggers !== undefined) {
17110
+ if (!object2(value.triggers) || Object.keys(value.triggers).some((key) => !["keywords", "paths", "always"].includes(key)))
17111
+ invalid4();
17112
+ if (value.triggers.always !== undefined && typeof value.triggers.always !== "boolean")
17113
+ invalid4();
17114
+ for (const key of ["keywords", "paths"]) {
17115
+ const values = value.triggers[key];
17116
+ if (values !== undefined && (!Array.isArray(values) || values.length > 32 || values.some((term) => typeof term !== "string" || !term.trim() || term.length > 256)))
17117
+ invalid4();
17118
+ }
17119
+ }
17120
+ }
17121
+
17122
+ class HttpProfileClient {
17123
+ key;
17124
+ authority;
17125
+ origin;
17126
+ constructor(key, origin) {
17127
+ this.key = key;
17128
+ if (!key.trim())
17129
+ throw new Error("A Skills credential is required");
17130
+ this.origin = normalizeSkillsApiOrigin(origin);
17131
+ this.authority = skillsApiRequestUrl(this.origin, "/api/v1/").replace(/\/+$/, "");
17132
+ }
17133
+ async request(path, init = {}) {
17134
+ let response;
17135
+ try {
17136
+ response = await fetch(skillsApiRequestUrl(this.origin, `/api/v1${path}`), {
17137
+ ...init,
17138
+ redirect: "error",
17139
+ credentials: "omit",
17140
+ signal: AbortSignal.timeout(15000),
17141
+ headers: {
17142
+ "Content-Type": "application/json",
17143
+ "User-Agent": `hasna-skills/${package_default.version}`,
17144
+ Authorization: `Bearer ${this.key}`,
17145
+ ...init.headers
17146
+ }
17147
+ });
17148
+ } catch {
17149
+ throw new Error("Unable to reach the configured Skills API");
17150
+ }
17151
+ if (!response.ok) {
17152
+ response.body?.cancel().catch(() => {});
17153
+ throw new Error(`Skills API request failed (HTTP ${response.status})`);
17154
+ }
17155
+ return response;
17156
+ }
17157
+ async read(response) {
17158
+ try {
17159
+ return JSON.parse(new TextDecoder().decode(await readBoundedResponse(response, 1e6)));
17160
+ } catch {
17161
+ invalid4();
17162
+ }
17163
+ }
17164
+ async resolveProfile(id) {
17165
+ if (!identifier(id))
17166
+ throw new Error("Invalid selection profile id");
17167
+ const result = await this.read(await this.request(`/profiles/${encodeURIComponent(id)}/resolve`));
17168
+ if (!object2(result) || result.profileId !== id || result.authority !== this.authority || !identifier(result.workspaceId) || !identifier(result.profileRevision) || !Array.isArray(result.selections) || result.selections.length > 256)
17169
+ invalid4();
17170
+ const seen = new Set;
17171
+ for (const entry of result.selections) {
17172
+ validateSelection(entry);
17173
+ const item = entry;
17174
+ if (seen.has(item.slug) || item.authority !== this.authority || item.workspaceId !== result.workspaceId || item.profileRevision !== result.profileRevision)
17175
+ invalid4();
17176
+ seen.add(item.slug);
17177
+ }
17178
+ return result;
17179
+ }
17180
+ async recordStation(id, input) {
17181
+ if (!identifier(id) || !identifier(input.profileId) || !identifier(input.profileRevision) || !Array.isArray(input.selections) || input.selections.length > 256)
17182
+ throw new Error("Invalid station state");
17183
+ input.selections.forEach(validateSelection);
17184
+ const result = await this.read(await this.request(`/stations/${encodeURIComponent(id)}/state`, {
17185
+ method: "PUT",
17186
+ body: JSON.stringify(input)
17187
+ }));
17188
+ if (!object2(result) || result.stationId !== id || result.profileId !== input.profileId || result.profileRevision !== input.profileRevision || !identifier(result.workspaceId) || !identifier(result.actorId) || typeof result.appliedAt !== "string" || !Array.isArray(result.selections))
17189
+ invalid4();
17190
+ result.selections.forEach(validateSelection);
17191
+ return result;
17192
+ }
17193
+ async getBundle(slug, version) {
17194
+ if (!identifier(slug) || !/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/.test(version) || version.includes(".."))
17195
+ throw new Error("Invalid immutable skill reference");
17196
+ return this.request(`/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/bundle`);
17197
+ }
17198
+ }
17199
+ async function createProfileClient() {
17200
+ const connection = await resolveSkillsConnection();
17201
+ if (!connection)
17202
+ throw new Error("Selection profiles require a configured Skills API");
17203
+ return new HttpProfileClient(connection.apiKey, connection.apiOrigin);
17204
+ }
17205
+ // src/lib/skill-context.ts
17206
+ import { createHash as createHash12 } from "crypto";
17207
+ import { resolve as resolve8 } from "path";
17208
+
17209
+ // src/lib/selection-cache.ts
17210
+ import { createHash as createHash11, randomUUID as randomUUID3 } from "crypto";
17211
+ import { closeSync as closeSync4, constants as constants3, fstatSync as fstatSync4, lstatSync as lstatSync7, mkdirSync as mkdirSync16, openSync as openSync4, readFileSync as readFileSync20, renameSync as renameSync6, unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "fs";
17212
+ import { dirname as dirname14, join as join24, parse, resolve as resolve7 } from "path";
17213
+ class SkillSelectionError extends Error {
17214
+ code;
17215
+ constructor(code, message) {
17216
+ super(message);
17217
+ this.code = code;
17218
+ this.name = "SkillSelectionError";
17219
+ }
17220
+ }
17221
+ var MAX_CACHED_PROFILE_AGE_MS = 24 * 60 * 60 * 1000;
17222
+ var SELECTION_LOCK_FILE = "selection.lock.json";
17223
+ var MAX_RECEIPT_BYTES = 1024 * 1024;
17224
+ function selectionCacheRoot(options = {}) {
17225
+ return resolve7(options.cacheDir ?? join24(getDataDirReadOnly(), "selection-cache"));
17226
+ }
17227
+ function selectionKey(selection) {
17228
+ validateSelection2(selection);
17229
+ return hash2([selection.authority, selection.workspaceId, selection.slug, selection.version, selection.bundleDigest]);
17230
+ }
17231
+ function hash2(value) {
17232
+ return createHash11("sha256").update(JSON.stringify(value)).digest("hex");
17233
+ }
17234
+ function validateSelection2(selection) {
17235
+ if (!selection || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(selection.slug) || !isValidSkillVersion(selection.version) || !/^sha256:[a-f0-9]{64}$/.test(selection.bundleDigest) || typeof selection.workspaceId !== "string" || !selection.workspaceId.trim() || typeof selection.profileRevision !== "string" || !selection.profileRevision.trim()) {
17236
+ throw new SkillSelectionError("INVALID_SELECTION", "A skill selection must carry an exact version, digest, workspace and profile revision.");
17237
+ }
17238
+ try {
17239
+ const url = new URL(selection.authority);
17240
+ if (url.username || url.password || url.search || url.hash || !["https:", "http:"].includes(url.protocol) || url.protocol === "http:" && !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))
17241
+ throw new Error;
17242
+ } catch {
17243
+ throw new SkillSelectionError("INVALID_SELECTION", "The skill selection authority is invalid.");
17244
+ }
17245
+ }
17246
+ function validateResolvedProfile(profile, authority) {
17247
+ if (!profile || typeof profile.profileId !== "string" || !profile.profileId.trim() || !Array.isArray(profile.selections) || profile.selections.length > 1000 || typeof profile.workspaceId !== "string" || !profile.workspaceId.trim() || typeof profile.profileRevision !== "string" || !profile.profileRevision.trim() || typeof profile.authority !== "string" || authority !== undefined && profile.authority !== authority) {
17248
+ throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The resolved profile does not match the configured Skills authority.");
17249
+ }
17250
+ const slugs = new Set;
17251
+ for (const selection of profile.selections) {
17252
+ validateSelection2(selection);
17253
+ if (selection.authority !== profile.authority || selection.workspaceId !== profile.workspaceId || selection.profileRevision !== profile.profileRevision || slugs.has(selection.slug)) {
17254
+ throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The resolved profile contains conflicting selection identities.");
17255
+ }
17256
+ slugs.add(selection.slug);
17257
+ }
17258
+ }
17259
+ function selectionBundlePath(selection, options = {}) {
17260
+ validateSelection2(selection);
17261
+ return join24(selectionCacheRoot(options), "objects", hash2(selection.authority), hash2(selection.workspaceId), `${selection.bundleDigest.slice(7)}.tar.gz`);
17262
+ }
17263
+ function assertRegularPath(path, createParents = false) {
17264
+ const absolute = resolve7(path);
17265
+ const root = parse(absolute).root;
17266
+ let cursor2 = root;
17267
+ const segments = absolute.slice(root.length).split("/");
17268
+ for (let index = 0;index < segments.length; index++) {
17269
+ cursor2 = join24(cursor2, segments[index]);
17270
+ try {
17271
+ const stat = lstatSync7(cursor2);
17272
+ if (stat.isSymbolicLink() || index < segments.length - 1 && !stat.isDirectory()) {
17273
+ throw new SkillSelectionError("UNSAFE_CACHE_PATH", "Skills cache paths must not traverse symlinks or non-directories.");
17274
+ }
17275
+ } catch (error) {
17276
+ if (error.code !== "ENOENT")
17277
+ throw error;
17278
+ if (createParents && index < segments.length - 1)
17279
+ mkdirSync16(cursor2, { mode: 448 });
17280
+ }
17281
+ }
17282
+ }
17283
+ function readRegularFile(path, limit) {
17284
+ assertRegularPath(path);
17285
+ let fd;
17286
+ try {
17287
+ fd = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
17288
+ } catch (error) {
17289
+ if (error.code === "ENOENT")
17290
+ return null;
17291
+ throw error;
17292
+ }
17293
+ try {
17294
+ const stat = fstatSync4(fd);
17295
+ if (!stat.isFile() || stat.size > limit)
17296
+ throw new SkillSelectionError("INVALID_CACHE_FILE", "The Skills cache contains an invalid or oversized file.");
17297
+ return readFileSync20(fd);
17298
+ } finally {
17299
+ closeSync4(fd);
17300
+ }
17301
+ }
17302
+ function writeSelectionJson(path, value) {
17303
+ const bytes = Buffer.from(`${JSON.stringify(value, null, 2)}
17304
+ `);
17305
+ if (bytes.length > MAX_RECEIPT_BYTES)
17306
+ throw new SkillSelectionError("RECEIPT_TOO_LARGE", "The Skills selection receipt exceeds its size limit.");
17307
+ atomicWrite2(path, bytes, 384);
17308
+ }
17309
+ function atomicWrite2(path, bytes, mode) {
17310
+ assertRegularPath(path, true);
17311
+ const temporary = join24(dirname14(path), `.selection-${randomUUID3()}.tmp`);
17312
+ try {
17313
+ writeFileSync15(temporary, bytes, { flag: "wx", mode });
17314
+ renameSync6(temporary, path);
17315
+ } finally {
17316
+ try {
17317
+ unlinkSync3(temporary);
17318
+ } catch (error) {
17319
+ if (error.code !== "ENOENT")
17320
+ throw error;
17321
+ }
17322
+ }
17323
+ }
17324
+ function readSelectionJson(path) {
17325
+ const bytes = readRegularFile(path, MAX_RECEIPT_BYTES);
17326
+ if (!bytes)
17327
+ return null;
17328
+ try {
17329
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
17330
+ } catch {
17331
+ throw new SkillSelectionError("INVALID_RECEIPT", "The Skills selection receipt is unreadable; sync the profile again.");
17332
+ }
17333
+ }
17334
+ async function verifiedEntries(selection, bytes) {
17335
+ if (`sha256:${sha256Hex(bytes)}` !== selection.bundleDigest)
17336
+ throw new SkillSelectionError("BUNDLE_DIGEST_MISMATCH", "The skill bundle does not match its selected digest.");
17337
+ const bundle = await inspectSkillBundle(bytes);
17338
+ return bundle.entries;
17339
+ }
17340
+ async function readCachedSelection(selection, options = {}) {
17341
+ const bytes = readRegularFile(selectionBundlePath(selection, options), SKILL_BUNDLE_INSPECTION_LIMITS.compressedBytes);
17342
+ return bytes ? verifiedEntries(selection, bytes) : null;
17343
+ }
17344
+ async function cacheSelectionBundle(selection, response, options = {}) {
17345
+ validateSelection2(selection);
17346
+ if (!response?.ok)
17347
+ throw new SkillSelectionError("BUNDLE_UNAVAILABLE", `The selected skill bundle is unavailable${response ? ` (HTTP ${response.status})` : ""}.`);
17348
+ const declared = response.headers.get("X-Skill-Bundle-Sha256");
17349
+ const version = response.headers.get("X-Skill-Version");
17350
+ if (declared && `sha256:${declared}` !== selection.bundleDigest || version && version !== selection.version) {
17351
+ await response.body?.cancel();
17352
+ throw new SkillSelectionError("BUNDLE_IDENTITY_MISMATCH", "The returned bundle does not match the selected version and digest.");
17353
+ }
17354
+ const maximum = SKILL_BUNDLE_INSPECTION_LIMITS.compressedBytes;
17355
+ const reader = response.body?.getReader();
17356
+ if (!reader)
17357
+ throw new SkillSelectionError("BUNDLE_UNAVAILABLE", "The selected bundle response has no body.");
17358
+ const chunks = [];
17359
+ let length = 0;
17360
+ try {
17361
+ while (true) {
17362
+ const next = await reader.read();
17363
+ if (next.done)
17364
+ break;
17365
+ length += next.value.byteLength;
17366
+ if (length > maximum)
17367
+ throw new SkillSelectionError("BUNDLE_TOO_LARGE", "The selected bundle exceeds the cache size limit.");
17368
+ chunks.push(next.value);
17369
+ }
17370
+ } catch (error) {
17371
+ await reader.cancel().catch(() => {});
17372
+ throw error;
17373
+ } finally {
17374
+ reader.releaseLock();
17375
+ }
17376
+ const bytes = new Uint8Array(length);
17377
+ let offset = 0;
17378
+ for (const chunk of chunks) {
17379
+ bytes.set(chunk, offset);
17380
+ offset += chunk.byteLength;
17381
+ }
17382
+ const entries = await verifiedEntries(selection, bytes);
17383
+ const path = selectionBundlePath(selection, options);
17384
+ const existing = readRegularFile(path, maximum);
17385
+ if (existing)
17386
+ await verifiedEntries(selection, existing);
17387
+ else
17388
+ atomicWrite2(path, bytes, 256);
17389
+ return entries;
17390
+ }
17391
+ function profilePath(profileId, options) {
17392
+ return join24(selectionCacheRoot(options), "profiles", `${hash2(profileId)}.json`);
17393
+ }
17394
+ function activateSelectionProfile(profile, options = {}) {
17395
+ validateResolvedProfile(profile);
17396
+ const receipt = { schemaVersion: 1, verifiedAt: new Date((options.now ?? Date.now)()).toISOString(), profile };
17397
+ writeSelectionJson(profilePath(profile.profileId, options), receipt);
17398
+ return receipt;
17399
+ }
17400
+ function readSelectionProfile(profileId, options = {}) {
17401
+ const receipt = readSelectionJson(profilePath(profileId, options));
17402
+ if (receipt)
17403
+ validateSelectionReceipt(receipt);
17404
+ return receipt;
17405
+ }
17406
+ function validateSelectionReceipt(receipt) {
17407
+ if (!receipt || receipt.schemaVersion !== 1 || !Number.isFinite(Date.parse(receipt.verifiedAt)))
17408
+ throw new SkillSelectionError("INVALID_RECEIPT", "The Skills selection receipt is invalid.");
17409
+ validateResolvedProfile(receipt.profile);
17410
+ }
17411
+ function assertFreshCachedProfile(receipt, options = {}) {
17412
+ validateSelectionReceipt(receipt);
17413
+ const age = (options.now ?? Date.now)() - Date.parse(receipt.verifiedAt);
17414
+ const maximum = options.maxAgeMs ?? MAX_CACHED_PROFILE_AGE_MS;
17415
+ if (!Number.isFinite(maximum) || maximum <= 0 || maximum > MAX_CACHED_PROFILE_AGE_MS || age < 0 || age > maximum) {
17416
+ throw new SkillSelectionError("CACHED_PROFILE_EXPIRED", "The cached Skills profile has expired; authenticate and sync it again.");
17417
+ }
17418
+ }
17419
+ function projectSelectionLockPath(projectDir) {
17420
+ return join24(resolve7(projectDir), ".skills", SELECTION_LOCK_FILE);
17421
+ }
17422
+ function readProjectSelection(projectDir) {
17423
+ const receipt = readSelectionJson(projectSelectionLockPath(projectDir));
17424
+ if (receipt)
17425
+ validateSelectionReceipt(receipt);
17426
+ return receipt;
17427
+ }
17428
+ function sessionReceiptPath(sessionId, options = {}) {
17429
+ if (!sessionId.trim() || sessionId.length > 256)
17430
+ throw new SkillSelectionError("INVALID_SESSION", "A Skills session id must contain 1\u2013256 characters.");
17431
+ return join24(selectionCacheRoot(options), "sessions", `${hash2(sessionId)}.json`);
17432
+ }
17433
+ function readSkillSession(sessionId, options = {}) {
17434
+ const receipt = readSelectionJson(sessionReceiptPath(sessionId, options));
17435
+ if (receipt) {
17436
+ validateSelectionReceipt(receipt);
17437
+ if (receipt.sessionId !== sessionId || !Array.isArray(receipt.loaded) || !receipt.loaded.every((key) => /^[a-f0-9]{64}$/.test(key))) {
17438
+ throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt is invalid.");
17439
+ }
17440
+ }
17441
+ return receipt;
17442
+ }
17443
+
17444
+ // src/lib/selected-document.ts
17445
+ var DOCUMENT_PATHS = ["SKILL.md", "README.md", "CLAUDE.md"];
17446
+ function readSelectedDocument(entries, file) {
17447
+ const entry = file !== undefined ? entries.find((candidate) => candidate.path === file) : DOCUMENT_PATHS.map((path) => entries.find((candidate) => candidate.path === path && candidate.bytes.length > 0)).find(Boolean);
17448
+ if (!entry) {
17449
+ if (file !== undefined)
17450
+ throw new SkillSelectionError("SKILL_FILE_MISSING", "The selected skill bundle does not contain that file.");
17451
+ throw new SkillSelectionError("SKILL_DOCS_MISSING", "The selected bundle has no nonempty SKILL.md, README.md, or CLAUDE.md. Use skills load <slug>@<version> --file <path> to read an exact bundle file; loading context never runs the skill.");
17452
+ }
17453
+ try {
17454
+ return { file: entry.path, content: new TextDecoder("utf-8", { fatal: true }).decode(entry.bytes) };
17455
+ } catch {
17456
+ throw new SkillSelectionError("SKILL_FILE_NOT_TEXT", "The selected bundle file is not valid UTF-8 text; choose a documentation file with --file.");
17457
+ }
17458
+ }
17459
+
17460
+ // src/lib/selection-resolver.ts
17461
+ async function syncSelectionProfile(profileId, options = {}) {
17462
+ const client = options.client ?? await createProfileClient();
17463
+ const profile = await client.resolveProfile(profileId);
17464
+ validateResolvedProfile(profile, client.authority);
17465
+ if (profile.profileId !== profileId)
17466
+ throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The API returned a different selection profile.");
17467
+ const previous = readSelectionProfile(profileId, options);
17468
+ const changed = !previous || JSON.stringify(previous.profile) !== JSON.stringify(profile);
17469
+ let downloaded = 0;
17470
+ for (const selection of profile.selections) {
17471
+ const entries = await readCachedSelection(selection, options);
17472
+ if (entries)
17473
+ continue;
17474
+ if (options.check) {
17475
+ downloaded++;
17476
+ continue;
17477
+ }
17478
+ await cacheSelectionBundle(selection, await client.getBundle(selection.slug, selection.version), options);
17479
+ downloaded++;
17480
+ }
17481
+ if (options.check)
17482
+ return { profile, receipt: previous, changed: changed || downloaded > 0, downloaded, stationReported: false };
17483
+ const receipt = activateSelectionProfile(profile, options);
17484
+ if (options.projectDir)
17485
+ writeSelectionJson(projectSelectionLockPath(options.projectDir), receipt);
17486
+ let stationReported = false;
17487
+ if (options.stationId) {
17488
+ try {
17489
+ await client.recordStation(options.stationId, {
17490
+ profileId: profile.profileId,
17491
+ profileRevision: profile.profileRevision,
17492
+ selections: profile.selections
17493
+ });
17494
+ } catch {
17495
+ throw new SkillSelectionError("PROFILE_APPLIED_REPORT_FAILED", "The verified Skills profile is active, but the station receipt could not be reported. Retry sync to confirm the station state.");
17496
+ }
17497
+ stationReported = true;
17498
+ }
17499
+ return { profile, receipt, changed, downloaded, stationReported };
17500
+ }
17501
+ async function resolveSelectionContext(profileId, options = {}) {
17502
+ const session = options.sessionId ? readSkillSession(options.sessionId, options) : null;
17503
+ const parentSession = !session && options.parentSessionId ? readSkillSession(options.parentSessionId, options) : null;
17504
+ const project = options.projectDir ? readProjectSelection(options.projectDir) : null;
17505
+ if (session && session.profile.profileId !== profileId || parentSession && parentSession.profile.profileId !== profileId || project && project.profile.profileId !== profileId) {
17506
+ throw new SkillSelectionError("PROFILE_LOCK_MISMATCH", "The session or project is pinned to a different Skills profile. Select that profile or explicitly sync the new selection.");
17507
+ }
17508
+ if (options.cached) {
17509
+ const receipt2 = session ?? parentSession ?? project ?? readSelectionProfile(profileId, options);
17510
+ if (!receipt2)
17511
+ throw new SkillSelectionError("CACHED_PROFILE_MISSING", "No verified Skills profile is cached; authenticate and sync it first.");
17512
+ assertFreshCachedProfile(receipt2, options);
17513
+ assertBinding(receipt2.profile, options);
17514
+ return { receipt: receipt2, session, inheritedLoaded: parentSession?.loaded };
17515
+ }
17516
+ const client = options.client ?? await createProfileClient();
17517
+ const current = await client.resolveProfile(profileId);
17518
+ validateResolvedProfile(current, client.authority);
17519
+ if (current.profileId !== profileId)
17520
+ throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The API returned a different selection profile.");
17521
+ assertBinding(current, options);
17522
+ const locked2 = session ?? parentSession ?? project;
17523
+ if (locked2 && (locked2.profile.authority !== current.authority || locked2.profile.workspaceId !== current.workspaceId)) {
17524
+ throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The pinned selection belongs to another Skills authority or workspace.");
17525
+ }
17526
+ const receipt = locked2 ?? { schemaVersion: 1, verifiedAt: new Date((options.now ?? Date.now)()).toISOString(), profile: current };
17527
+ return { receipt, session, client, inheritedLoaded: parentSession?.loaded };
17528
+ }
17529
+ function assertBinding(profile, options) {
17530
+ if (options.authority !== undefined && profile.authority !== options.authority || options.workspaceId !== undefined && profile.workspaceId !== options.workspaceId) {
17531
+ throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The cached selection belongs to another Skills authority or workspace.");
17532
+ }
17533
+ }
17534
+ async function readSelectedEntries(selection, context, options = {}) {
17535
+ if (options.cached) {
17536
+ const entries = await readCachedSelection(selection, options);
17537
+ if (!entries)
17538
+ throw new SkillSelectionError("CACHED_BUNDLE_MISSING", "The exact selected bundle is not cached. Cached mode cannot fetch or use an authoring draft.");
17539
+ return entries;
17540
+ }
17541
+ const client = context.client ?? options.client ?? await createProfileClient();
17542
+ return cacheSelectionBundle(selection, await client.getBundle(selection.slug, selection.version), options);
17543
+ }
17544
+ function exactProfileSelection(spec, profile) {
17545
+ const at = spec.lastIndexOf("@");
17546
+ const slug = at > 0 ? spec.slice(0, at) : spec;
17547
+ const version = at > 0 ? spec.slice(at + 1) : undefined;
17548
+ const selection = profile.selections.find((entry) => entry.slug === slug && (version === undefined || version === entry.version));
17549
+ if (!selection || version === "")
17550
+ throw new SkillSelectionError("SKILL_NOT_SELECTED", "The requested exact skill version is not selected by this profile or project lock.");
17551
+ return selection;
17552
+ }
17553
+
17554
+ // src/lib/skill-context.ts
17555
+ function words(value) {
17556
+ return new Set(value.toLowerCase().match(/[a-z0-9]+/g) ?? []);
17557
+ }
17558
+ function pathMatch(pattern, path) {
17559
+ if (!pattern || pattern.length > 512)
17560
+ return false;
17561
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\x00").replace(/\*/g, "[^/]*").replace(/\u0000/g, ".*");
17562
+ return new RegExp(`^(?:${escaped})$`).test(path.replace(/\\/g, "/"));
17563
+ }
17564
+ async function buildSkillContext(input, options = {}) {
17565
+ const prompt = input.prompt ?? "";
17566
+ if (prompt.length > 128 * 1024 || (input.paths?.length ?? 0) > 100 || (input.skills?.length ?? 0) > 100) {
17567
+ throw new SkillSelectionError("CONTEXT_INPUT_TOO_LARGE", "The Skills context input exceeds its size limit.");
17568
+ }
17569
+ const maxChars = options.maxChars ?? 8000;
17570
+ const maxSkills = options.maxSkills ?? 3;
17571
+ if (!Number.isInteger(maxChars) || maxChars < 512 || maxChars > 64000 || !Number.isInteger(maxSkills) || maxSkills < 1 || maxSkills > 20) {
17572
+ throw new SkillSelectionError("INVALID_CONTEXT_BUDGET", "Context limits must be 512\u201364000 characters and 1\u201320 skills.");
17573
+ }
17574
+ const profileId = input.profileId ?? "default";
17575
+ const sessionId = input.sessionId ? input.agentId ? `${input.sessionId}:${input.agentId}` : input.sessionId : undefined;
17576
+ const resolverOptions = { ...options, projectDir: input.cwd ?? options.projectDir, sessionId, parentSessionId: input.agentId ? input.sessionId : undefined };
17577
+ const resolved = await resolveSelectionContext(profileId, resolverOptions);
17578
+ const profile = resolved.receipt.profile;
17579
+ const explicit = new Set((input.skills ?? []).map((spec) => selectionKey(exactProfileSelection(spec, profile))));
17580
+ for (const match of prompt.matchAll(/\$([a-z0-9]+(?:-[a-z0-9]+)*(?:@[a-zA-Z0-9._-]+)?)/g)) {
17581
+ const spec = match[1];
17582
+ const slug = spec.split("@")[0];
17583
+ if (profile.selections.some((entry) => entry.slug === slug))
17584
+ explicit.add(selectionKey(exactProfileSelection(spec, profile)));
17585
+ }
17586
+ const promptWords = words(prompt);
17587
+ const loaded = new Set(resolved.session?.loaded ?? []);
17588
+ const paths = [...input.paths ?? [], ...input.cwd ? [input.cwd, resolve8(input.cwd)] : []];
17589
+ const candidates = profile.selections.map((selection) => {
17590
+ const key = selectionKey(selection);
17591
+ if (input.restore && loaded.has(key))
17592
+ return { selection, reason: "session-restore", score: 2000 };
17593
+ if (resolved.inheritedLoaded?.includes(key))
17594
+ return { selection, reason: "subagent-inherit", score: 2000 };
17595
+ if (explicit.has(key))
17596
+ return { selection, reason: "explicit", score: 1000 };
17597
+ if (selection.triggers?.always)
17598
+ return { selection, reason: "profile-required", score: 900 };
17599
+ const matchedPath = selection.triggers?.paths?.some((pattern) => paths.some((path) => pathMatch(pattern, path)));
17600
+ if (matchedPath)
17601
+ return { selection, reason: "path", score: 500 };
17602
+ const keywords = selection.triggers?.keywords ?? selection.slug.split("-").filter((word) => word.length >= 4);
17603
+ const matches = keywords.filter((keyword) => {
17604
+ const tokens = [...words(keyword)];
17605
+ return tokens.length > 0 && tokens.every((token) => promptWords.has(token));
17606
+ }).length;
17607
+ return { selection, reason: "prompt", score: matches * 100 };
17608
+ }).filter((candidate) => candidate.score > 0).sort((a, b) => b.score - a.score || a.selection.slug.localeCompare(b.selection.slug));
17609
+ const sections = [];
17610
+ const selections = [];
17611
+ const omitted = [];
17612
+ let chars = 0;
17613
+ for (const candidate of candidates) {
17614
+ const { selection, reason } = candidate;
17615
+ if (!input.restore && !explicit.has(selectionKey(selection)) && loaded.has(selectionKey(selection)))
17616
+ continue;
17617
+ const loadCommand = `skills load ${selection.slug}@${selection.version} --selection-profile ${profile.profileId}`;
17618
+ if (selections.length >= maxSkills) {
17619
+ omitted.push({ slug: selection.slug, version: selection.version, reason: "skill-limit", loadCommand });
17620
+ continue;
17621
+ }
17622
+ const entries = await readSelectedEntries(selection, resolved, resolverOptions);
17623
+ const { content } = readSelectedDocument(entries);
17624
+ const section = `Skill ${selection.slug}@${selection.version} (${selection.bundleDigest}; ${reason})
17625
+ ${content}`;
17626
+ if (chars + section.length + (sections.length ? 2 : 0) > maxChars) {
17627
+ omitted.push({ slug: selection.slug, version: selection.version, reason: "context-budget", loadCommand });
17628
+ continue;
17629
+ }
17630
+ chars += section.length + (sections.length ? 2 : 0);
17631
+ sections.push(section);
17632
+ selections.push({ ...selection, reason });
17633
+ loaded.add(selectionKey(selection));
17634
+ }
17635
+ const context = sections.join(`
17636
+
17637
+ `);
17638
+ const receipt = {
17639
+ schemaVersion: 1,
17640
+ id: createHash12("sha256").update(JSON.stringify({ profile: profile.profileRevision, sessionId, selections, omitted })).digest("hex"),
17641
+ profileId: profile.profileId,
17642
+ profileRevision: profile.profileRevision,
17643
+ authority: profile.authority,
17644
+ workspaceId: profile.workspaceId,
17645
+ source: options.cached ? "verified-cache" : "api",
17646
+ ...sessionId ? { sessionId } : {},
17647
+ restored: Boolean(input.restore),
17648
+ chars: context.length,
17649
+ selections
17650
+ };
17651
+ if (sessionId)
17652
+ writeSelectionJson(sessionReceiptPath(sessionId, options), { ...resolved.receipt, sessionId, loaded: [...loaded] });
17653
+ return { context, selections, omitted, receipt };
17654
+ }
16721
17655
  export {
16722
17656
  writeStationSnapshot,
16723
17657
  writeStationHydration,
@@ -16744,6 +17678,7 @@ export {
16744
17678
  unpinSkill,
16745
17679
  unpinProjectSkill,
16746
17680
  syncSkillsToAgents,
17681
+ syncSelectionProfile,
16747
17682
  summarizeMcpToolContract,
16748
17683
  storageCapabilities,
16749
17684
  skillsPostgresSyncSchemaSql,
@@ -16793,6 +17728,7 @@ export {
16793
17728
  planStationSnapshot,
16794
17729
  planStationHydration,
16795
17730
  planSkillsS3SnapshotUpload,
17731
+ planAgentIntegration,
16796
17732
  pinSkill,
16797
17733
  pinProjectSkill,
16798
17734
  parseSkillFrontmatter,
@@ -16825,6 +17761,7 @@ export {
16825
17761
  isGatewayBackedSkill,
16826
17762
  isExcludedSkillFileName,
16827
17763
  isBasicSkillName,
17764
+ inventoryNativeSkills,
16828
17765
  installSkills,
16829
17766
  installSkillSource,
16830
17767
  installSkillManifest,
@@ -16892,6 +17829,7 @@ export {
16892
17829
  createSkillMcpMetadata,
16893
17830
  createRemoteSkillsClient,
16894
17831
  createRegistrySyncArtifact,
17832
+ createProfileClient,
16895
17833
  createMcpContractManifest,
16896
17834
  createLocalSkillManifest,
16897
17835
  continuePrivatePublication,
@@ -16903,6 +17841,9 @@ export {
16903
17841
  canonicalizeManifest,
16904
17842
  buildSkillsS3ObjectUrl,
16905
17843
  buildSkillsApiUrl,
17844
+ buildSkillContext,
17845
+ archiveNativeSkills,
17846
+ applyAgentIntegration,
16906
17847
  appendRunEvent,
16907
17848
  agentGlobalSkillsDir,
16908
17849
  addSchedule,
@@ -16971,6 +17912,7 @@ export {
16971
17912
  MissingSkillsFleetError,
16972
17913
  MCP_CONTRACT_SCHEMA_VERSION,
16973
17914
  InvitationEmailInputError,
17915
+ HttpProfileClient,
16974
17916
  HostedApiError,
16975
17917
  DEFAULT_EXPORT_DIR,
16976
17918
  ContentHashInputError,