@hasna/skills 0.1.63 → 0.1.64

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 (38) hide show
  1. package/README.md +28 -6
  2. package/bin/index.js +1593 -228
  3. package/bin/mcp.js +290 -24
  4. package/bin/migrate.js +229 -33
  5. package/bin/server.js +774 -116
  6. package/bin/worker.js +558 -79
  7. package/dist/cli/commands/registry-reconcile.d.ts +2 -0
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +559 -251
  10. package/dist/lib/agent-sync.d.ts +26 -6
  11. package/dist/lib/auth-store.d.ts +37 -0
  12. package/dist/lib/config.d.ts +51 -0
  13. package/dist/lib/home-census.d.ts +4 -0
  14. package/dist/lib/home-migration.d.ts +8 -9
  15. package/dist/lib/native-storage.d.ts +1 -1
  16. package/dist/lib/portable-skills.d.ts +35 -6
  17. package/dist/lib/pull.d.ts +31 -0
  18. package/dist/lib/registry-reconcile.d.ts +114 -0
  19. package/dist/lib/registry.d.ts +7 -4
  20. package/dist/lib/remote-client.d.ts +118 -1
  21. package/dist/lib/remote-registry.d.ts +26 -0
  22. package/dist/lib/revision.d.ts +29 -0
  23. package/dist/sdk/index.js +1351 -353
  24. package/dist/server/app.d.ts +1 -1
  25. package/dist/server/config.d.ts +12 -2
  26. package/dist/server/rows.d.ts +2 -1
  27. package/dist/server/skills-api.d.ts +108 -6
  28. package/dist/server/sqlite-store.d.ts +20 -3
  29. package/dist/server/store.d.ts +31 -5
  30. package/dist/server/types.d.ts +98 -3
  31. package/dist/storage.js +7 -2
  32. package/migrations/postgres/0004_hosted_pins.sql +28 -0
  33. package/migrations/postgres/0005_revision_tombstone_registry.sql +37 -0
  34. package/migrations/postgres/0005_tag_projection.sql +36 -0
  35. package/migrations/sqlite/0004_hosted_pins.sql +21 -0
  36. package/migrations/sqlite/0005_revision_tombstone_registry.sql +18 -0
  37. package/migrations/sqlite/0005_tag_projection.sql +25 -0
  38. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -154,6 +154,11 @@ function normalizeConfigValue(key, value) {
154
154
  }
155
155
  var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
156
156
  var INSTALLED_SKILLS_DIRNAME = "installed";
157
+ var SKILLS_CACHE_DIRNAME = "skills";
158
+ var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
159
+ function isOwnerLayoutMigrated(appDir) {
160
+ return existsSync(join(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
161
+ }
157
162
  function getDataDir() {
158
163
  const override = process.env[DATA_DIR_ENV];
159
164
  if (override) {
@@ -435,7 +440,7 @@ var DEVELOPMENT_TOOLS_SKILLS = [
435
440
  {
436
441
  name: "monitor",
437
442
  displayName: "Monitor",
438
- description: "Operate the open-monitor MCP for machine health, processes, cron jobs, and cleanup workflows",
443
+ description: "Operate the monitor MCP for machine health, processes, cron jobs, and cleanup workflows",
439
444
  category: "Development Tools",
440
445
  tags: ["monitoring", "mcp", "processes", "operations"]
441
446
  },
@@ -481,6 +486,14 @@ var DEVELOPMENT_TOOLS_SKILLS = [
481
486
  description: "Validate configuration files for syntax and schema compliance",
482
487
  category: "Development Tools",
483
488
  tags: ["config", "validation", "schema", "linting"]
489
+ },
490
+ {
491
+ name: "session-inject-monitor",
492
+ displayName: "Session Inject Monitor",
493
+ description: "Set up a declarative monitor that injects a prompt into a live coding-agent session when a watched source (conversations, email, todos, knowledge, command output) has new content",
494
+ category: "Development Tools",
495
+ tags: ["monitor", "session", "injection", "automation", "wake"],
496
+ kind: "instruction"
484
497
  }
485
498
  ];
486
499
 
@@ -868,7 +881,7 @@ var DESIGN_BRANDING_SKILLS = [
868
881
  displayName: "Site Analyze",
869
882
  description: "Analyze any website's design system \u2014 detects shadcn/ui, Tailwind, extracts colors, typography, and components via Playwright + Claude Vision.",
870
883
  category: "Design & Branding",
871
- tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "open-styles"]
884
+ tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "styles"]
872
885
  }
873
886
  ];
874
887
 
@@ -2234,6 +2247,9 @@ function getPortableSkillsRoot(options = {}) {
2234
2247
  if (options.rootDir)
2235
2248
  return options.rootDir;
2236
2249
  const appDir = options.homeDir ? join5(options.homeDir, ".hasna", "skills") : getDataDir();
2250
+ const cache = join5(appDir, SKILLS_CACHE_DIRNAME);
2251
+ if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
2252
+ return cache;
2237
2253
  const installed = join5(appDir, INSTALLED_SKILLS_DIRNAME);
2238
2254
  migrateLegacySkillLayout(appDir, installed);
2239
2255
  return installed;
@@ -2454,10 +2470,7 @@ function writeCorpusSkill(input, options = {}) {
2454
2470
  const skillPath = join5(root, name);
2455
2471
  const created = !existsSync5(skillPath);
2456
2472
  mkdirSync3(skillPath, { recursive: true });
2457
- const skillMd = input.skillMd.endsWith(`
2458
- `) ? input.skillMd : `${input.skillMd}
2459
- `;
2460
- writeFileSync3(join5(skillPath, "SKILL.md"), skillMd);
2473
+ writeFileSync3(join5(skillPath, "SKILL.md"), input.skillMd);
2461
2474
  const frontmatter = parseSkillFrontmatter(input.skillMd) ?? undefined;
2462
2475
  const kind = input.meta?.kind ?? parseSkillKind(frontmatter?.kind) ?? "instruction";
2463
2476
  const manifest = {
@@ -2991,45 +3004,28 @@ function getAllTags() {
2991
3004
  return Array.from(tagSet).sort();
2992
3005
  }
2993
3006
  // src/lib/installer.ts
2994
- import { existsSync as existsSync10, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
2995
- import { dirname as dirname5, join as join10 } from "path";
3007
+ import { existsSync as existsSync9, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
3008
+ import { dirname as dirname5, join as join9 } from "path";
2996
3009
  import { homedir as homedir3 } from "os";
2997
3010
  import { fileURLToPath } from "url";
2998
3011
 
2999
3012
  // src/lib/agent-sync.ts
3000
3013
  import {
3001
3014
  cpSync as cpSync3,
3002
- existsSync as existsSync8,
3003
- mkdirSync as mkdirSync5,
3015
+ existsSync as existsSync7,
3016
+ mkdirSync as mkdirSync4,
3004
3017
  mkdtempSync,
3005
3018
  readFileSync as readFileSync6,
3006
- readdirSync as readdirSync7,
3007
- renameSync as renameSync3,
3008
- rmSync as rmSync3,
3009
- statSync as statSync7,
3010
- writeFileSync as writeFileSync5
3019
+ readdirSync as readdirSync6,
3020
+ renameSync as renameSync2,
3021
+ rmSync as rmSync2,
3022
+ statSync as statSync6,
3023
+ writeFileSync as writeFileSync4
3011
3024
  } from "fs";
3012
3025
  import { homedir as homedir2 } from "os";
3013
- import { basename as basename3, dirname as dirname4, join as join8 } from "path";
3014
-
3026
+ import { basename as basename3, dirname as dirname4, join as join7 } from "path";
3015
3027
  // src/lib/home-migration.ts
3016
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync6, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync6, writeFileSync as writeFileSync4 } from "fs";
3017
- import { join as join7 } from "path";
3018
- var SKILLS_CACHE_DIRNAME = "skills";
3019
- var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
3020
- function layoutMigrationRecordPath(appDir) {
3021
- return join7(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD);
3022
- }
3023
- function isOwnerLayoutMigrated(appDir) {
3024
- return existsSync7(layoutMigrationRecordPath(appDir));
3025
- }
3026
3028
  function resolveCorpusRoot(options = {}) {
3027
- if (options.rootDir)
3028
- return options.rootDir;
3029
- const appDir = options.homeDir ? join7(options.homeDir, ".hasna", "skills") : getDataDir();
3030
- const cache = join7(appDir, SKILLS_CACHE_DIRNAME);
3031
- if (isOwnerLayoutMigrated(appDir) && existsSync7(cache))
3032
- return cache;
3033
3029
  return getPortableSkillsRoot(options);
3034
3030
  }
3035
3031
 
@@ -3052,9 +3048,9 @@ function resolveSyncAgents(arg) {
3052
3048
  function agentGlobalSkillsDir(agent, homeDir = homedir2()) {
3053
3049
  switch (agent) {
3054
3050
  case "opencode":
3055
- return join8(homeDir, ".config", "opencode", "skills");
3051
+ return join7(homeDir, ".config", "opencode", "skills");
3056
3052
  default:
3057
- return join8(homeDir, `.${agent}`, "skills");
3053
+ return join7(homeDir, `.${agent}`, "skills");
3058
3054
  }
3059
3055
  }
3060
3056
  function adaptSkillMdForAgent(skillMd, agent) {
@@ -3075,6 +3071,10 @@ ${lines.join(`
3075
3071
  ---
3076
3072
  ${body}`;
3077
3073
  }
3074
+ var POINTER_MARKER_PHRASE = "This is an executable skill from the @hasna/skills catalog";
3075
+ function isPointerSkillMd(markdown) {
3076
+ return markdown.includes(POINTER_MARKER_PHRASE) && /^kind:\s*executable\b/m.test(markdown);
3077
+ }
3078
3078
  function pointerSkillMd(name, description) {
3079
3079
  const display = name.replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
3080
3080
  return [
@@ -3100,7 +3100,7 @@ function resolveSyncCorpus(options = {}) {
3100
3100
  if (explicit) {
3101
3101
  const roots = packageSourceRoots(explicit);
3102
3102
  if (roots.length === 0) {
3103
- throw new Error(`SKILLS_SOURCE '${explicit}' contains no skills: expected a corpus directory or a package root with skills/ and/or agent-skills/`);
3103
+ throw new Error(`SKILLS_SOURCE '${explicit}' contains no skills: expected a corpus directory or a package root with skills/`);
3104
3104
  }
3105
3105
  return { roots, source: "source" };
3106
3106
  }
@@ -3108,9 +3108,9 @@ function resolveSyncCorpus(options = {}) {
3108
3108
  }
3109
3109
  function packageSourceRoots(source) {
3110
3110
  const roots = [];
3111
- for (const sub of ["skills", "agent-skills"]) {
3112
- const candidate = join8(source, sub);
3113
- if (existsSync8(candidate) && isDirectory(candidate))
3111
+ for (const sub of ["skills"]) {
3112
+ const candidate = join7(source, sub);
3113
+ if (existsSync7(candidate) && isDirectory(candidate))
3114
3114
  roots.push(candidate);
3115
3115
  }
3116
3116
  if (roots.length > 0)
@@ -3120,20 +3120,20 @@ function packageSourceRoots(source) {
3120
3120
  function containsSkillDirectories(path) {
3121
3121
  let entries;
3122
3122
  try {
3123
- entries = readdirSync7(path);
3123
+ entries = readdirSync6(path);
3124
3124
  } catch {
3125
3125
  return false;
3126
3126
  }
3127
3127
  return entries.some((entry) => {
3128
- const candidate = join8(path, entry);
3128
+ const candidate = join7(path, entry);
3129
3129
  if (!isDirectory(candidate))
3130
3130
  return false;
3131
- return existsSync8(join8(candidate, "SKILL.md")) || existsSync8(join8(candidate, "skill.json")) || existsSync8(join8(candidate, "package.json"));
3131
+ return existsSync7(join7(candidate, "SKILL.md")) || existsSync7(join7(candidate, "skill.json")) || existsSync7(join7(candidate, "package.json"));
3132
3132
  });
3133
3133
  }
3134
3134
  function isDirectory(path) {
3135
3135
  try {
3136
- return statSync7(path).isDirectory();
3136
+ return statSync6(path).isDirectory();
3137
3137
  } catch {
3138
3138
  return false;
3139
3139
  }
@@ -3167,7 +3167,7 @@ function syncSkillsToAgents(options = {}) {
3167
3167
  actions.push({
3168
3168
  skill: name,
3169
3169
  agent,
3170
- path: join8(agentGlobalSkillsDir(agent, homeDir), name, "SKILL.md"),
3170
+ path: join7(agentGlobalSkillsDir(agent, homeDir), name, "SKILL.md"),
3171
3171
  action: "skip",
3172
3172
  reason: "not found in this machine's corpus"
3173
3173
  });
@@ -3197,7 +3197,7 @@ function syncSkillsToAgents(options = {}) {
3197
3197
  }
3198
3198
  function writeManagedAgentSkill(params) {
3199
3199
  const homeDir = params.homeDir ?? homedir2();
3200
- const dir = join8(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
3200
+ const dir = join7(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
3201
3201
  const result = writeManagedSkillDir(dir, params.skillMd, {
3202
3202
  skill: params.skill,
3203
3203
  source: params.source,
@@ -3214,11 +3214,11 @@ function writeManagedAgentSkill(params) {
3214
3214
  };
3215
3215
  }
3216
3216
  function writeManagedSkillDir(dir, skillMd, options) {
3217
- const skillMdPath = join8(dir, "SKILL.md");
3218
- const markerPath = join8(dir, SYNC_MARKER_FILE);
3219
- const dirExists = existsSync8(dir);
3220
- const managed = existsSync8(markerPath);
3221
- const hasSkillMd = existsSync8(skillMdPath);
3217
+ const skillMdPath = join7(dir, "SKILL.md");
3218
+ const markerPath = join7(dir, SYNC_MARKER_FILE);
3219
+ const dirExists = existsSync7(dir);
3220
+ const managed = existsSync7(markerPath);
3221
+ const hasSkillMd = existsSync7(skillMdPath);
3222
3222
  if (dirExists && !managed && !hasSkillMd) {
3223
3223
  return {
3224
3224
  action: "skip",
@@ -3233,35 +3233,50 @@ function writeManagedSkillDir(dir, skillMd, options) {
3233
3233
  reason: "an unmanaged SKILL.md already exists here (hand-authored); pass --force to overwrite"
3234
3234
  };
3235
3235
  }
3236
+ if (dirExists && managed && hasSkillMd && !options.force && isPointerSkillMd(skillMd)) {
3237
+ let existingIsStub = false;
3238
+ try {
3239
+ existingIsStub = isPointerSkillMd(readFileSync6(skillMdPath, "utf-8"));
3240
+ } catch {
3241
+ existingIsStub = false;
3242
+ }
3243
+ if (!existingIsStub) {
3244
+ return {
3245
+ action: "skip",
3246
+ path: skillMdPath,
3247
+ reason: "refusing to replace a content-bearing managed home with an executable pointer stub (the corpus entry lacks kind: instruction); pass --force to overwrite"
3248
+ };
3249
+ }
3250
+ }
3236
3251
  const action = dirExists ? "update" : "create";
3237
3252
  if (options.dryRun)
3238
3253
  return { action, path: skillMdPath };
3239
3254
  const parentDir = dirname4(dir);
3240
- mkdirSync5(parentDir, { recursive: true });
3241
- const transactionDir = mkdtempSync(join8(parentDir, `.hasna-skills-write-${basename3(dir)}-`));
3242
- const candidateDir = join8(transactionDir, "candidate");
3243
- const backupDir = join8(transactionDir, "backup");
3244
- const candidateSkillMdPath = join8(candidateDir, "SKILL.md");
3245
- const candidateMarkerPath = join8(candidateDir, SYNC_MARKER_FILE);
3255
+ mkdirSync4(parentDir, { recursive: true });
3256
+ const transactionDir = mkdtempSync(join7(parentDir, `.hasna-skills-write-${basename3(dir)}-`));
3257
+ const candidateDir = join7(transactionDir, "candidate");
3258
+ const backupDir = join7(transactionDir, "backup");
3259
+ const candidateSkillMdPath = join7(candidateDir, "SKILL.md");
3260
+ const candidateMarkerPath = join7(candidateDir, SYNC_MARKER_FILE);
3246
3261
  const marker = {
3247
3262
  managedBy: SYNC_MARKER_MANAGED_BY,
3248
3263
  skill: options.skill,
3249
3264
  source: options.source ?? "corpus",
3250
3265
  syncedAt: new Date().toISOString()
3251
3266
  };
3252
- const renameDirectory = options.renameDirectory ?? renameSync3;
3267
+ const renameDirectory = options.renameDirectory ?? renameSync2;
3253
3268
  let originalMoved = false;
3254
3269
  let preserveTransaction = false;
3255
3270
  try {
3256
3271
  if (options.resourceDir) {
3257
3272
  cpSync3(options.resourceDir, candidateDir, { recursive: true, force: true });
3258
3273
  } else {
3259
- mkdirSync5(candidateDir, { recursive: true });
3274
+ mkdirSync4(candidateDir, { recursive: true });
3260
3275
  }
3261
- writeFileSync5(candidateSkillMdPath, skillMd.endsWith(`
3276
+ writeFileSync4(candidateSkillMdPath, skillMd.endsWith(`
3262
3277
  `) ? skillMd : `${skillMd}
3263
3278
  `);
3264
- writeFileSync5(candidateMarkerPath, `${JSON.stringify(marker, null, 2)}
3279
+ writeFileSync4(candidateMarkerPath, `${JSON.stringify(marker, null, 2)}
3265
3280
  `);
3266
3281
  if (dirExists) {
3267
3282
  originalMoved = true;
@@ -3269,10 +3284,10 @@ function writeManagedSkillDir(dir, skillMd, options) {
3269
3284
  }
3270
3285
  renameDirectory(candidateDir, dir);
3271
3286
  } catch (error) {
3272
- if (originalMoved && existsSync8(backupDir)) {
3287
+ if (originalMoved && existsSync7(backupDir)) {
3273
3288
  try {
3274
- if (existsSync8(dir))
3275
- rmSync3(dir, { recursive: true, force: true });
3289
+ if (existsSync7(dir))
3290
+ rmSync2(dir, { recursive: true, force: true });
3276
3291
  renameDirectory(backupDir, dir);
3277
3292
  originalMoved = false;
3278
3293
  } catch (rollbackError) {
@@ -3284,23 +3299,23 @@ function writeManagedSkillDir(dir, skillMd, options) {
3284
3299
  } finally {
3285
3300
  if (!preserveTransaction) {
3286
3301
  try {
3287
- rmSync3(transactionDir, { recursive: true, force: true });
3302
+ rmSync2(transactionDir, { recursive: true, force: true });
3288
3303
  } catch {}
3289
3304
  }
3290
3305
  }
3291
3306
  return { action, path: skillMdPath };
3292
3307
  }
3293
3308
  function removeManagedAgentSkill(skill, agent, homeDir = homedir2()) {
3294
- const dir = join8(agentGlobalSkillsDir(agent, homeDir), skill);
3295
- if (!existsSync8(join8(dir, SYNC_MARKER_FILE)))
3309
+ const dir = join7(agentGlobalSkillsDir(agent, homeDir), skill);
3310
+ if (!existsSync7(join7(dir, SYNC_MARKER_FILE)))
3296
3311
  return false;
3297
- rmSync3(dir, { recursive: true, force: true });
3312
+ rmSync2(dir, { recursive: true, force: true });
3298
3313
  return true;
3299
3314
  }
3300
3315
  function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
3301
3316
  if (kind === "instruction" || preferBundledDocs) {
3302
- const skillMdPath = join8(skillPath, "SKILL.md");
3303
- if (existsSync8(skillMdPath))
3317
+ const skillMdPath = join7(skillPath, "SKILL.md");
3318
+ if (existsSync7(skillMdPath))
3304
3319
  return readFileSync6(skillMdPath, "utf-8");
3305
3320
  }
3306
3321
  return pointerSkillMd(name, description);
@@ -3331,8 +3346,8 @@ function normalizeSkillName(name) {
3331
3346
  }
3332
3347
 
3333
3348
  // src/lib/project-state.ts
3334
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
3335
- import { join as join9 } from "path";
3349
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
3350
+ import { join as join8 } from "path";
3336
3351
  var VALID_PIN_SOURCES = [
3337
3352
  "official",
3338
3353
  "custom",
@@ -3347,14 +3362,14 @@ var SKILLS_PROJECT_DIR = ".skills";
3347
3362
  var PROJECT_CONFIG_FILE = "project.json";
3348
3363
  var DEFAULT_EXPORT_DIR = ".skills/exports";
3349
3364
  function getProjectStateDir(targetDir = process.cwd()) {
3350
- return join9(targetDir, SKILLS_PROJECT_DIR);
3365
+ return join8(targetDir, SKILLS_PROJECT_DIR);
3351
3366
  }
3352
3367
  function getProjectConfigPath(targetDir = process.cwd()) {
3353
- return join9(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
3368
+ return join8(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
3354
3369
  }
3355
3370
  function loadProjectConfig(targetDir = process.cwd()) {
3356
3371
  const path = getProjectConfigPath(targetDir);
3357
- if (!existsSync9(path))
3372
+ if (!existsSync8(path))
3358
3373
  return null;
3359
3374
  try {
3360
3375
  return normalizeProjectConfig(JSON.parse(readFileSync7(path, "utf-8")));
@@ -3378,9 +3393,9 @@ function ensureProjectConfig(targetDir = process.cwd()) {
3378
3393
  }
3379
3394
  function saveProjectConfig(config, targetDir = process.cwd()) {
3380
3395
  const dir = getProjectStateDir(targetDir);
3381
- mkdirSync6(dir, { recursive: true });
3396
+ mkdirSync5(dir, { recursive: true });
3382
3397
  const normalized = normalizeProjectConfig({ ...config, updatedAt: new Date().toISOString() });
3383
- writeFileSync6(getProjectConfigPath(targetDir), JSON.stringify(normalized, null, 2) + `
3398
+ writeFileSync5(getProjectConfigPath(targetDir), JSON.stringify(normalized, null, 2) + `
3384
3399
  `);
3385
3400
  }
3386
3401
  function pinProjectSkill(name, details = {}, targetDir = process.cwd()) {
@@ -3475,12 +3490,12 @@ var __dirname2 = dirname5(fileURLToPath(import.meta.url));
3475
3490
  function findSkillsDir() {
3476
3491
  let dir = __dirname2;
3477
3492
  for (let i = 0;i < 5; i++) {
3478
- const candidate = join10(dir, "skills");
3479
- if (existsSync10(candidate) && !dir.includes(".skills"))
3493
+ const candidate = join9(dir, "skills");
3494
+ if (existsSync9(candidate) && !dir.includes(".skills"))
3480
3495
  return candidate;
3481
3496
  dir = dirname5(dir);
3482
3497
  }
3483
- return join10(__dirname2, "..", "skills");
3498
+ return join9(__dirname2, "..", "skills");
3484
3499
  }
3485
3500
  var SKILLS_DIR = findSkillsDir();
3486
3501
  function getSkillPath(name) {
@@ -3488,25 +3503,25 @@ function getSkillPath(name) {
3488
3503
  const portable = findPortableSkill(skillName);
3489
3504
  if (portable)
3490
3505
  return portable.path;
3491
- const legacyCustomPath = join10(getDataDir(), "custom", skillName);
3492
- if (existsSync10(legacyCustomPath))
3506
+ const legacyCustomPath = join9(getDataDir(), "custom", skillName);
3507
+ if (existsSync9(legacyCustomPath))
3493
3508
  return legacyCustomPath;
3494
3509
  const extensionPath = findExtensionSkillPath(skillName);
3495
3510
  if (extensionPath)
3496
3511
  return extensionPath;
3497
- return join10(SKILLS_DIR, skillName);
3512
+ return join9(SKILLS_DIR, skillName);
3498
3513
  }
3499
3514
  function getCanonicalSkillName(name) {
3500
3515
  return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
3501
3516
  }
3502
3517
  function skillExists(name) {
3503
- return existsSync10(getSkillPath(name));
3518
+ return existsSync9(getSkillPath(name));
3504
3519
  }
3505
3520
  function installSkill(name, options = {}) {
3506
3521
  const { targetDir = process.cwd(), overwrite = false } = options;
3507
3522
  const canonicalName = getCanonicalSkillName(name);
3508
3523
  const skillName = normalizeSkillName(canonicalName);
3509
- if (!existsSync10(getSkillPath(name))) {
3524
+ if (!existsSync9(getSkillPath(name))) {
3510
3525
  const knownOfficial = Boolean(getSkill(name));
3511
3526
  return {
3512
3527
  skill: canonicalName,
@@ -3533,7 +3548,7 @@ function installSkill(name, options = {}) {
3533
3548
  }
3534
3549
  function installSkillSource(name, _options = {}) {
3535
3550
  const canonicalName = getCanonicalSkillName(name);
3536
- if (!existsSync10(getSkillPath(name))) {
3551
+ if (!existsSync9(getSkillPath(name))) {
3537
3552
  return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "source" };
3538
3553
  }
3539
3554
  return {
@@ -3554,11 +3569,11 @@ function installSkillManifest(manifest, _options = {}) {
3554
3569
  }
3555
3570
  function createLocalSkillManifest(name, generateSkillMd) {
3556
3571
  const sourcePath = getSkillPath(name);
3557
- if (!existsSync10(sourcePath))
3572
+ if (!existsSync9(sourcePath))
3558
3573
  return null;
3559
3574
  let skillMd = "";
3560
- const skillMdPath = join10(sourcePath, "SKILL.md");
3561
- if (existsSync10(skillMdPath)) {
3575
+ const skillMdPath = join9(sourcePath, "SKILL.md");
3576
+ if (existsSync9(skillMdPath)) {
3562
3577
  skillMd = readFileSync8(skillMdPath, "utf-8");
3563
3578
  } else if (generateSkillMd) {
3564
3579
  skillMd = generateSkillMd(name) ?? "";
@@ -3631,20 +3646,20 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
3631
3646
  const base = projectDir || process.cwd();
3632
3647
  switch (agent) {
3633
3648
  case "pi":
3634
- return scope === "project" ? join10(base, ".pi", "skills") : join10(homedir3(), ".pi", "agent", "skills");
3649
+ return scope === "project" ? join9(base, ".pi", "skills") : join9(homedir3(), ".pi", "agent", "skills");
3635
3650
  case "opencode":
3636
- return scope === "project" ? join10(base, ".opencode", "skills") : join10(homedir3(), ".config", "opencode", "skills");
3651
+ return scope === "project" ? join9(base, ".opencode", "skills") : join9(homedir3(), ".config", "opencode", "skills");
3637
3652
  default:
3638
- return scope === "project" ? join10(base, `.${agent}`, "skills") : join10(homedir3(), `.${agent}`, "skills");
3653
+ return scope === "project" ? join9(base, `.${agent}`, "skills") : join9(homedir3(), `.${agent}`, "skills");
3639
3654
  }
3640
3655
  }
3641
3656
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
3642
3657
  const skillName = normalizeSkillName(getCanonicalSkillName(name));
3643
- return join10(getAgentSkillsDir(agent, scope, projectDir), skillName);
3658
+ return join9(getAgentSkillsDir(agent, scope, projectDir), skillName);
3644
3659
  }
3645
3660
  function installSkillForAgent(name, options, generateSkillMd) {
3646
3661
  const canonicalName = getCanonicalSkillName(name);
3647
- if (!existsSync10(getSkillPath(name))) {
3662
+ if (!existsSync9(getSkillPath(name))) {
3648
3663
  return { skill: canonicalName, success: false, error: `Skill '${name}' not found` };
3649
3664
  }
3650
3665
  const scope = options.scope ?? "global";
@@ -3668,15 +3683,15 @@ function removeSkillForAgent(name, options) {
3668
3683
  const canonicalName = getCanonicalSkillName(name);
3669
3684
  const scope = options.scope ?? "global";
3670
3685
  const dir = getAgentSkillPath(canonicalName, options.agent, scope, options.projectDir);
3671
- if (!existsSync10(join10(dir, SYNC_MARKER_FILE)))
3686
+ if (!existsSync9(join9(dir, SYNC_MARKER_FILE)))
3672
3687
  return false;
3673
- rmSync4(dir, { recursive: true, force: true });
3688
+ rmSync3(dir, { recursive: true, force: true });
3674
3689
  return true;
3675
3690
  }
3676
3691
  function resolveAgentSkillMd(name, generateSkillMd) {
3677
3692
  const sourcePath = getSkillPath(name);
3678
- const skillMdPath = join10(sourcePath, "SKILL.md");
3679
- if (existsSync10(skillMdPath))
3693
+ const skillMdPath = join9(sourcePath, "SKILL.md");
3694
+ if (existsSync9(skillMdPath))
3680
3695
  return readFileSync8(skillMdPath, "utf-8");
3681
3696
  if (generateSkillMd)
3682
3697
  return generateSkillMd(name);
@@ -3695,7 +3710,7 @@ function warnMissingDependencies(name, targetDir) {
3695
3710
  }
3696
3711
  function generateMinimalSkillMd(name) {
3697
3712
  const sourcePath = getSkillPath(name);
3698
- if (!existsSync10(sourcePath))
3713
+ if (!existsSync9(sourcePath))
3699
3714
  return null;
3700
3715
  const canonicalName = getCanonicalSkillName(name);
3701
3716
  const meta = getSkill(canonicalName);
@@ -3710,7 +3725,7 @@ function generateMinimalSkillMd(name) {
3710
3725
  "---",
3711
3726
  ""
3712
3727
  ].filter(Boolean);
3713
- const fallbackDoc = readFileIfExists(join10(sourcePath, "README.md")) || readFileIfExists(join10(sourcePath, "CLAUDE.md"));
3728
+ const fallbackDoc = readFileIfExists(join9(sourcePath, "README.md")) || readFileIfExists(join9(sourcePath, "CLAUDE.md"));
3714
3729
  if (fallbackDoc)
3715
3730
  return `${frontmatter.join(`
3716
3731
  `)}${fallbackDoc.trim()}
@@ -3729,8 +3744,8 @@ skills run ${canonicalName}
3729
3744
  `;
3730
3745
  }
3731
3746
  function readBundledSkillVersion(name) {
3732
- const pkgPath = join10(getSkillPath(name), "package.json");
3733
- if (!existsSync10(pkgPath))
3747
+ const pkgPath = join9(getSkillPath(name), "package.json");
3748
+ if (!existsSync9(pkgPath))
3734
3749
  return "unknown";
3735
3750
  try {
3736
3751
  const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
@@ -3740,27 +3755,27 @@ function readBundledSkillVersion(name) {
3740
3755
  }
3741
3756
  }
3742
3757
  function readFileIfExists(path) {
3743
- return existsSync10(path) ? readFileSync8(path, "utf-8") : null;
3758
+ return existsSync9(path) ? readFileSync8(path, "utf-8") : null;
3744
3759
  }
3745
3760
  function loadProjectConfigCompat(targetDir) {
3746
3761
  return loadProjectConfig(targetDir);
3747
3762
  }
3748
3763
  // src/lib/run-state.ts
3749
3764
  import { createHash as createHash2, randomBytes } from "crypto";
3750
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync9, readdirSync as readdirSync8, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
3751
- import { extname, join as join11, relative as relative2 } from "path";
3765
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync7, statSync as statSync7, writeFileSync as writeFileSync6 } from "fs";
3766
+ import { extname, join as join10, relative as relative2 } from "path";
3752
3767
  function createSkillRun(params, targetDir = process.cwd()) {
3753
3768
  const now = new Date;
3754
3769
  const id = createRunId(now);
3755
3770
  const day = now.toISOString().slice(0, 10);
3756
3771
  const skillName = normalizeSkillName(params.skill);
3757
3772
  const root = getProjectStateDir(targetDir);
3758
- const runDir = join11(root, "runs", day, id);
3759
- const logsDir = join11(runDir, "logs");
3760
- const exportDir = join11(root, "exports", skillName, id);
3761
- mkdirSync7(logsDir, { recursive: true });
3762
- mkdirSync7(exportDir, { recursive: true });
3763
- mkdirSync7(join11(root, "tmp"), { recursive: true });
3773
+ const runDir = join10(root, "runs", day, id);
3774
+ const logsDir = join10(runDir, "logs");
3775
+ const exportDir = join10(root, "exports", skillName, id);
3776
+ mkdirSync6(logsDir, { recursive: true });
3777
+ mkdirSync6(exportDir, { recursive: true });
3778
+ mkdirSync6(join10(root, "tmp"), { recursive: true });
3764
3779
  const record = {
3765
3780
  id,
3766
3781
  skill: skillName,
@@ -3811,27 +3826,27 @@ function updateSkillRun(context, patch) {
3811
3826
  return context.record;
3812
3827
  }
3813
3828
  function writeRunLogs(context, stdout = "", stderr = "") {
3814
- writeFileSync7(join11(context.logsDir, "stdout.log"), stdout);
3815
- writeFileSync7(join11(context.logsDir, "stderr.log"), stderr);
3829
+ writeFileSync6(join10(context.logsDir, "stdout.log"), stdout);
3830
+ writeFileSync6(join10(context.logsDir, "stderr.log"), stderr);
3816
3831
  }
3817
3832
  function appendRunEvent(context, event, data = {}) {
3818
3833
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
3819
3834
  `;
3820
- const path = join11(context.runDir, "events.ndjson");
3821
- const previous = existsSync11(path) ? readFileSync9(path, "utf-8") : "";
3822
- writeFileSync7(path, previous + line);
3835
+ const path = join10(context.runDir, "events.ndjson");
3836
+ const previous = existsSync10(path) ? readFileSync9(path, "utf-8") : "";
3837
+ writeFileSync6(path, previous + line);
3823
3838
  }
3824
3839
  function listSkillRuns(targetDir = process.cwd(), limit = 50) {
3825
- const runsRoot = join11(getProjectStateDir(targetDir), "runs");
3826
- if (!existsSync11(runsRoot))
3840
+ const runsRoot = join10(getProjectStateDir(targetDir), "runs");
3841
+ if (!existsSync10(runsRoot))
3827
3842
  return [];
3828
3843
  const records = [];
3829
- for (const day of readdirSync8(runsRoot).sort().reverse()) {
3830
- const dayDir = join11(runsRoot, day);
3831
- if (!statSync8(dayDir).isDirectory())
3844
+ for (const day of readdirSync7(runsRoot).sort().reverse()) {
3845
+ const dayDir = join10(runsRoot, day);
3846
+ if (!statSync7(dayDir).isDirectory())
3832
3847
  continue;
3833
- for (const runId of readdirSync8(dayDir).sort().reverse()) {
3834
- const record = readRunRecord(join11(dayDir, runId));
3848
+ for (const runId of readdirSync7(dayDir).sort().reverse()) {
3849
+ const record = readRunRecord(join10(dayDir, runId));
3835
3850
  if (record)
3836
3851
  records.push(record);
3837
3852
  if (records.length >= limit)
@@ -3841,33 +3856,33 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
3841
3856
  return records;
3842
3857
  }
3843
3858
  function findSkillRun(runId, targetDir = process.cwd()) {
3844
- const runsRoot = join11(getProjectStateDir(targetDir), "runs");
3845
- if (!existsSync11(runsRoot))
3859
+ const runsRoot = join10(getProjectStateDir(targetDir), "runs");
3860
+ if (!existsSync10(runsRoot))
3846
3861
  return null;
3847
- for (const day of readdirSync8(runsRoot)) {
3848
- const record = readRunRecord(join11(runsRoot, day, runId));
3862
+ for (const day of readdirSync7(runsRoot)) {
3863
+ const record = readRunRecord(join10(runsRoot, day, runId));
3849
3864
  if (record)
3850
3865
  return record;
3851
3866
  }
3852
3867
  return null;
3853
3868
  }
3854
3869
  function getRunExportDir(runId, skill, targetDir = process.cwd()) {
3855
- return join11(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
3870
+ return join10(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
3856
3871
  }
3857
3872
  function writeRunRecord(context) {
3858
- writeFileSync7(join11(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
3873
+ writeFileSync6(join10(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
3859
3874
  `);
3860
3875
  }
3861
3876
  function writeArtifactsManifest(context, artifacts) {
3862
- writeFileSync7(join11(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
3877
+ writeFileSync6(join10(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
3863
3878
  `);
3864
3879
  }
3865
3880
  function collectRunArtifacts(context) {
3866
- if (!existsSync11(context.exportDir))
3881
+ if (!existsSync10(context.exportDir))
3867
3882
  return [];
3868
3883
  const artifacts = [];
3869
3884
  for (const path of walkFiles(context.exportDir)) {
3870
- const stat = statSync8(path);
3885
+ const stat = statSync7(path);
3871
3886
  const bytes = readFileSync9(path);
3872
3887
  artifacts.push({
3873
3888
  path: toProjectRelative(context.targetDir, path),
@@ -3879,8 +3894,8 @@ function collectRunArtifacts(context) {
3879
3894
  return artifacts.sort((a, b) => a.path.localeCompare(b.path));
3880
3895
  }
3881
3896
  function readRunRecord(runDir) {
3882
- const path = join11(runDir, "run.json");
3883
- if (!existsSync11(path))
3897
+ const path = join10(runDir, "run.json");
3898
+ if (!existsSync10(path))
3884
3899
  return null;
3885
3900
  try {
3886
3901
  return JSON.parse(readFileSync9(path, "utf-8"));
@@ -3890,9 +3905,9 @@ function readRunRecord(runDir) {
3890
3905
  }
3891
3906
  function walkFiles(dir) {
3892
3907
  const files = [];
3893
- for (const entry of readdirSync8(dir)) {
3894
- const full = join11(dir, entry);
3895
- if (statSync8(full).isDirectory())
3908
+ for (const entry of readdirSync7(dir)) {
3909
+ const full = join10(dir, entry);
3910
+ if (statSync7(full).isDirectory())
3896
3911
  files.push(...walkFiles(full));
3897
3912
  else
3898
3913
  files.push(full);
@@ -3937,13 +3952,13 @@ function mimeForPath(path) {
3937
3952
  }
3938
3953
  }
3939
3954
  // src/lib/skillinfo.ts
3940
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
3941
- import { join as join12 } from "path";
3955
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
3956
+ import { join as join11 } from "path";
3942
3957
  function isInstructionSkillDir(skillPath, meta) {
3943
3958
  if (meta?.kind === "instruction")
3944
3959
  return true;
3945
- const skillMdPath = join12(skillPath, "SKILL.md");
3946
- if (!existsSync12(skillMdPath))
3960
+ const skillMdPath = join11(skillPath, "SKILL.md");
3961
+ if (!existsSync11(skillMdPath))
3947
3962
  return false;
3948
3963
  try {
3949
3964
  return parseSkillFrontmatter(readFileSync10(skillMdPath, "utf-8"))?.kind === "instruction";
@@ -3969,12 +3984,12 @@ var HOSTED_PROVIDER_ENV_PREFIXES = [
3969
3984
  ];
3970
3985
  function getSkillDocs(name) {
3971
3986
  const skillPath = getSkillPath(name);
3972
- if (!existsSync12(skillPath))
3987
+ if (!existsSync11(skillPath))
3973
3988
  return null;
3974
3989
  return {
3975
- skillMd: readIfExists(join12(skillPath, "SKILL.md")),
3976
- readme: readIfExists(join12(skillPath, "README.md")),
3977
- claudeMd: readIfExists(join12(skillPath, "CLAUDE.md"))
3990
+ skillMd: readIfExists(join11(skillPath, "SKILL.md")),
3991
+ readme: readIfExists(join11(skillPath, "README.md")),
3992
+ claudeMd: readIfExists(join11(skillPath, "CLAUDE.md"))
3978
3993
  };
3979
3994
  }
3980
3995
  function getSkillBestDoc(name) {
@@ -3985,11 +4000,11 @@ function getSkillBestDoc(name) {
3985
4000
  }
3986
4001
  function getSkillRequirements(name) {
3987
4002
  const skillPath = getSkillPath(name);
3988
- if (!existsSync12(skillPath))
4003
+ if (!existsSync11(skillPath))
3989
4004
  return null;
3990
4005
  const texts = [];
3991
4006
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
3992
- const content = readIfExists(join12(skillPath, file));
4007
+ const content = readIfExists(join11(skillPath, file));
3993
4008
  if (content)
3994
4009
  texts.push(content);
3995
4010
  }
@@ -4028,8 +4043,8 @@ function getSkillRequirements(name) {
4028
4043
  const skillName = normalizeSkillName(name);
4029
4044
  let cliCommand = `skills run ${skillName}`;
4030
4045
  let dependencies = {};
4031
- const pkgPath = join12(skillPath, "package.json");
4032
- if (existsSync12(pkgPath)) {
4046
+ const pkgPath = join11(skillPath, "package.json");
4047
+ if (existsSync11(pkgPath)) {
4033
4048
  try {
4034
4049
  const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
4035
4050
  dependencies = pkg.dependencies || {};
@@ -4049,7 +4064,7 @@ async function runSkill(name, args, options = {}) {
4049
4064
  const meta = getSkill(name);
4050
4065
  const canonicalName = meta?.name ?? name;
4051
4066
  const skillPath = getSkillPath(canonicalName);
4052
- if (!existsSync12(skillPath)) {
4067
+ if (!existsSync11(skillPath)) {
4053
4068
  return { exitCode: 1, error: `Skill '${name}' not found` };
4054
4069
  }
4055
4070
  if (isInstructionSkillDir(skillPath, meta)) {
@@ -4058,8 +4073,8 @@ async function runSkill(name, args, options = {}) {
4058
4073
  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'.`
4059
4074
  };
4060
4075
  }
4061
- const pkgPath = join12(skillPath, "package.json");
4062
- if (!existsSync12(pkgPath)) {
4076
+ const pkgPath = join11(skillPath, "package.json");
4077
+ if (!existsSync11(pkgPath)) {
4063
4078
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
4064
4079
  }
4065
4080
  let entryPoint;
@@ -4078,12 +4093,12 @@ async function runSkill(name, args, options = {}) {
4078
4093
  } catch {
4079
4094
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
4080
4095
  }
4081
- const entryPath = join12(skillPath, entryPoint);
4082
- if (!existsSync12(entryPath)) {
4096
+ const entryPath = join11(skillPath, entryPoint);
4097
+ if (!existsSync11(entryPath)) {
4083
4098
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
4084
4099
  }
4085
- const nodeModules = join12(skillPath, "node_modules");
4086
- if (!existsSync12(nodeModules)) {
4100
+ const nodeModules = join11(skillPath, "node_modules");
4101
+ if (!existsSync11(nodeModules)) {
4087
4102
  const install = Bun.spawn(["bun", "install", "--no-save"], {
4088
4103
  cwd: skillPath,
4089
4104
  stdout: "pipe",
@@ -4155,7 +4170,7 @@ function generateSkillMd(name) {
4155
4170
  if (!meta)
4156
4171
  return null;
4157
4172
  const skillPath = getSkillPath(name);
4158
- if (!existsSync12(skillPath))
4173
+ if (!existsSync11(skillPath))
4159
4174
  return null;
4160
4175
  const frontmatter = [
4161
4176
  "---",
@@ -4164,11 +4179,11 @@ function generateSkillMd(name) {
4164
4179
  "---"
4165
4180
  ].join(`
4166
4181
  `);
4167
- const readme = readIfExists(join12(skillPath, "README.md"));
4168
- const claudeMd = readIfExists(join12(skillPath, "CLAUDE.md"));
4182
+ const readme = readIfExists(join11(skillPath, "README.md"));
4183
+ const claudeMd = readIfExists(join11(skillPath, "CLAUDE.md"));
4169
4184
  let cliCommand = null;
4170
- const pkgPath = join12(skillPath, "package.json");
4171
- if (existsSync12(pkgPath)) {
4185
+ const pkgPath = join11(skillPath, "package.json");
4186
+ if (existsSync11(pkgPath)) {
4172
4187
  try {
4173
4188
  const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
4174
4189
  if (pkg.bin) {
@@ -4245,7 +4260,7 @@ function extractEnvVars(text) {
4245
4260
  }
4246
4261
  function readIfExists(path) {
4247
4262
  try {
4248
- if (existsSync12(path)) {
4263
+ if (existsSync11(path)) {
4249
4264
  return readFileSync10(path, "utf-8");
4250
4265
  }
4251
4266
  } catch {}
@@ -8249,18 +8264,22 @@ function requireApiUrl(action = "This command", config, env) {
8249
8264
  }
8250
8265
 
8251
8266
  // src/lib/auth-store.ts
8252
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8, unlinkSync } from "fs";
8253
- import { join as join13 } from "path";
8267
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7, unlinkSync } from "fs";
8268
+ import { dirname as dirname6, join as join12 } from "path";
8254
8269
  import { homedir as homedir4 } from "os";
8255
- var AUTH_DIR = join13(homedir4(), ".hasna", "skills");
8256
- var AUTH_FILE = join13(AUTH_DIR, "auth.json");
8257
- var LEGACY_AUTH_FILE = join13(homedir4(), ".skills", "auth.json");
8270
+ function getAuthFilePath() {
8271
+ return join12(getDataDir(), "auth.json");
8272
+ }
8273
+ function legacyAuthFilePath() {
8274
+ return join12(process.env["HOME"] || process.env["USERPROFILE"] || homedir4(), ".skills", "auth.json");
8275
+ }
8258
8276
  var cachedConfig;
8259
8277
  function getAuthConfig() {
8260
8278
  if (cachedConfig !== undefined)
8261
8279
  return cachedConfig;
8262
8280
  try {
8263
- const raw = readFileSync11(existsSync13(AUTH_FILE) ? AUTH_FILE : LEGACY_AUTH_FILE, "utf-8");
8281
+ const file = existsSync12(getAuthFilePath()) ? getAuthFilePath() : legacyAuthFilePath();
8282
+ const raw = readFileSync11(file, "utf-8");
8264
8283
  const config = JSON.parse(raw);
8265
8284
  if (!config.apiKey) {
8266
8285
  cachedConfig = null;
@@ -9084,6 +9103,30 @@ function pickNumber(record, key) {
9084
9103
  }
9085
9104
 
9086
9105
  // src/lib/remote-client.ts
9106
+ class RemoteRouteUnsupportedError extends Error {
9107
+ path;
9108
+ status;
9109
+ instance;
9110
+ constructor(path, status, instance) {
9111
+ super(`The configured Skills instance does not support ${path} (HTTP ${status}). ` + `The instance at ${instance} predates this client feature \u2014 upgrade the server, or ` + `use a client version that matches it.`);
9112
+ this.path = path;
9113
+ this.status = status;
9114
+ this.instance = instance;
9115
+ this.name = "RemoteRouteUnsupportedError";
9116
+ }
9117
+ }
9118
+
9119
+ class RemoteRequestError extends Error {
9120
+ path;
9121
+ status;
9122
+ constructor(path, status, statusText) {
9123
+ super(`Remote request to ${path} failed: HTTP ${status}${statusText ? ` ${statusText}` : ""}`);
9124
+ this.path = path;
9125
+ this.status = status;
9126
+ this.name = "RemoteRequestError";
9127
+ }
9128
+ }
9129
+
9087
9130
  class RemoteSkillsClient {
9088
9131
  apiUrl;
9089
9132
  apiKey;
@@ -9101,6 +9144,20 @@ class RemoteSkillsClient {
9101
9144
  }
9102
9145
  });
9103
9146
  }
9147
+ async requestNewRoute(path, options, opts = {}) {
9148
+ const response = await this.request(path, options);
9149
+ const routePath = path.split("?")[0];
9150
+ if (response.status === 404 || response.status === 405) {
9151
+ if (response.status === 404 && opts.domainNotFoundCodes?.length && await responseBodyCarriesCode(response, opts.domainNotFoundCodes)) {
9152
+ return response;
9153
+ }
9154
+ throw new RemoteRouteUnsupportedError(routePath, response.status, this.apiUrl);
9155
+ }
9156
+ if (!response.ok) {
9157
+ throw new RemoteRequestError(routePath, response.status, response.statusText);
9158
+ }
9159
+ return response;
9160
+ }
9104
9161
  async listSkills() {
9105
9162
  const res = await this.request("/api/v1/skills");
9106
9163
  return res.json();
@@ -9117,6 +9174,14 @@ class RemoteSkillsClient {
9117
9174
  return null;
9118
9175
  return res.json();
9119
9176
  }
9177
+ async getSkillStatus(slug) {
9178
+ const res = await this.request(`/api/v1/skills/${encodeURIComponent(slug)}`, { method: "GET" });
9179
+ let body = null;
9180
+ try {
9181
+ body = await res.json();
9182
+ } catch {}
9183
+ return { status: res.status, body };
9184
+ }
9120
9185
  async submitRun(slug, input, args) {
9121
9186
  const res = await this.request(`/api/v1/runs/${slug}`, {
9122
9187
  method: "POST",
@@ -9150,15 +9215,18 @@ class RemoteSkillsClient {
9150
9215
  method: "GET"
9151
9216
  });
9152
9217
  }
9153
- async publishSkill(manifest, bundle) {
9218
+ async publishSkill(manifest, bundle, ifMatch) {
9154
9219
  const form = new FormData;
9155
9220
  form.set("manifest", JSON.stringify(manifest));
9156
9221
  if (bundle) {
9157
9222
  form.set("bundle", new Blob([bundle], { type: "application/gzip" }), `${String(manifest.slug ?? "skill")}.tar.gz`);
9158
9223
  }
9224
+ const headers = { Authorization: `Bearer ${this.apiKey}` };
9225
+ if (ifMatch)
9226
+ headers["If-Match"] = ifMatch;
9159
9227
  return fetch(`${this.apiUrl}/api/v1/skills`, {
9160
9228
  method: "POST",
9161
- headers: { Authorization: `Bearer ${this.apiKey}` },
9229
+ headers,
9162
9230
  body: form
9163
9231
  });
9164
9232
  }
@@ -9174,6 +9242,135 @@ class RemoteSkillsClient {
9174
9242
  return null;
9175
9243
  return response;
9176
9244
  }
9245
+ async listPins() {
9246
+ const response = await this.requestNewRoute("/api/v1/pins");
9247
+ return normalizePinList(await response.json());
9248
+ }
9249
+ async pin(slug, metadata) {
9250
+ const path = `/api/v1/pins/${encodeURIComponent(slug)}`;
9251
+ const response = await this.requestNewRoute(path, {
9252
+ method: "PUT",
9253
+ body: JSON.stringify({ ...metadata ? { metadata } : {} })
9254
+ });
9255
+ return normalizePin(await response.json());
9256
+ }
9257
+ async unpin(slug) {
9258
+ const path = `/api/v1/pins/${encodeURIComponent(slug)}`;
9259
+ const response = await this.requestNewRoute(path, { method: "DELETE" }, { domainNotFoundCodes: ["PIN_NOT_FOUND"] });
9260
+ return response.status !== 404;
9261
+ }
9262
+ async listTags() {
9263
+ const response = await this.requestNewRoute("/api/v1/tags");
9264
+ const payload = await response.json();
9265
+ if (!Array.isArray(payload)) {
9266
+ throw new Error("Remote tags payload did not match the expected contract (expected an array of tag names)");
9267
+ }
9268
+ for (const tag of payload) {
9269
+ if (typeof tag !== "string" || tag.trim().length === 0) {
9270
+ throw new Error("Remote tags payload did not match the expected contract (every element must be a non-empty tag name)");
9271
+ }
9272
+ }
9273
+ return payload;
9274
+ }
9275
+ async skillsByTag(tag) {
9276
+ const path = `/api/v1/tags/${encodeURIComponent(tag)}/skills`;
9277
+ const response = await this.requestNewRoute(path);
9278
+ return normalizeSkillSummaryList(await response.json());
9279
+ }
9280
+ async listUpdatedSince(since, options = {}) {
9281
+ const params = new URLSearchParams({ since });
9282
+ if (options.cursor)
9283
+ params.set("cursor", options.cursor);
9284
+ if (options.limit !== undefined)
9285
+ params.set("limit", String(options.limit));
9286
+ const response = await this.requestNewRoute(`/api/v1/skills/updated?${params.toString()}`);
9287
+ return normalizeUpdatedSincePage(await response.json());
9288
+ }
9289
+ }
9290
+ function requireOptionalString(record, field) {
9291
+ if (record[field] === undefined)
9292
+ return;
9293
+ if (typeof record[field] !== "string") {
9294
+ throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
9295
+ }
9296
+ return record[field];
9297
+ }
9298
+ function normalizePin(entry) {
9299
+ if (!entry || typeof entry !== "object") {
9300
+ throw new Error("Remote pin payload did not match the expected contract (expected an object)");
9301
+ }
9302
+ const record = entry;
9303
+ const slug = typeof record.slug === "string" && record.slug.trim() ? record.slug.trim() : undefined;
9304
+ if (!slug) {
9305
+ throw new Error("Remote pin payload did not match the expected contract (missing slug)");
9306
+ }
9307
+ let metadata;
9308
+ if (record.metadata !== undefined) {
9309
+ if (!record.metadata || typeof record.metadata !== "object" || Array.isArray(record.metadata)) {
9310
+ throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
9311
+ }
9312
+ metadata = record.metadata;
9313
+ }
9314
+ const pinnedAt = requireOptionalString(record, "pinnedAt");
9315
+ return {
9316
+ slug,
9317
+ ...pinnedAt !== undefined ? { pinnedAt } : {},
9318
+ ...metadata ? { metadata } : {}
9319
+ };
9320
+ }
9321
+ function normalizePinList(payload) {
9322
+ if (!Array.isArray(payload)) {
9323
+ throw new Error("Remote pins payload did not match the expected contract (expected an array of pins)");
9324
+ }
9325
+ return payload.map(normalizePin);
9326
+ }
9327
+ function normalizeSkillSummary(entry) {
9328
+ if (!entry || typeof entry !== "object") {
9329
+ throw new Error("Remote skill payload did not match the expected contract (expected an object)");
9330
+ }
9331
+ const record = entry;
9332
+ const slug = typeof record.slug === "string" && record.slug.trim() ? record.slug.trim() : undefined;
9333
+ if (!slug) {
9334
+ throw new Error("Remote skill payload did not match the expected contract (missing slug)");
9335
+ }
9336
+ return {
9337
+ slug,
9338
+ ...requireOptionalString(record, "name") !== undefined ? { name: requireOptionalString(record, "name") } : {},
9339
+ ...requireOptionalString(record, "version") !== undefined ? { version: requireOptionalString(record, "version") } : {},
9340
+ ...requireOptionalString(record, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record, "updatedAt") } : {}
9341
+ };
9342
+ }
9343
+ function normalizeSkillSummaryList(payload) {
9344
+ if (!Array.isArray(payload)) {
9345
+ throw new Error("Remote skills payload did not match the expected contract (expected an array of skills)");
9346
+ }
9347
+ return payload.map(normalizeSkillSummary);
9348
+ }
9349
+ async function responseBodyCarriesCode(response, codes) {
9350
+ try {
9351
+ const payload = await response.clone().json();
9352
+ if (!payload || typeof payload !== "object")
9353
+ return false;
9354
+ const code = payload.code;
9355
+ return typeof code === "string" && codes.includes(code);
9356
+ } catch {
9357
+ return false;
9358
+ }
9359
+ }
9360
+ function normalizeUpdatedSincePage(payload) {
9361
+ if (!payload || typeof payload !== "object") {
9362
+ throw new Error("Updated-since payload did not match the expected contract (expected an object)");
9363
+ }
9364
+ const record = payload;
9365
+ if (!Array.isArray(record.skills)) {
9366
+ throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
9367
+ }
9368
+ const skills = record.skills.map(normalizeSkillSummary);
9369
+ const nextCursor = record.nextCursor === undefined || record.nextCursor === null ? null : record.nextCursor;
9370
+ if (nextCursor !== null && typeof nextCursor !== "string") {
9371
+ throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
9372
+ }
9373
+ return { skills, nextCursor };
9177
9374
  }
9178
9375
  function createRemoteSkillsClient() {
9179
9376
  const apiKey = getApiKey();
@@ -9182,14 +9379,14 @@ function createRemoteSkillsClient() {
9182
9379
  return new RemoteSkillsClient(apiKey);
9183
9380
  }
9184
9381
  // src/lib/scheduler.ts
9185
- import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9 } from "fs";
9186
- import { join as join14 } from "path";
9382
+ import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
9383
+ import { join as join13 } from "path";
9187
9384
  function getSchedulesPath(targetDir = process.cwd()) {
9188
- return join14(targetDir, ".skills", "schedules.json");
9385
+ return join13(targetDir, ".skills", "schedules.json");
9189
9386
  }
9190
9387
  function loadSchedules(targetDir = process.cwd()) {
9191
9388
  const path = getSchedulesPath(targetDir);
9192
- if (existsSync14(path)) {
9389
+ if (existsSync13(path)) {
9193
9390
  try {
9194
9391
  return JSON.parse(readFileSync12(path, "utf-8"));
9195
9392
  } catch {}
@@ -9198,10 +9395,10 @@ function loadSchedules(targetDir = process.cwd()) {
9198
9395
  }
9199
9396
  function saveSchedules(data, targetDir = process.cwd()) {
9200
9397
  const path = getSchedulesPath(targetDir);
9201
- const dir = join14(targetDir, ".skills");
9202
- if (!existsSync14(dir))
9203
- mkdirSync9(dir, { recursive: true });
9204
- writeFileSync9(path, JSON.stringify(data, null, 2));
9398
+ const dir = join13(targetDir, ".skills");
9399
+ if (!existsSync13(dir))
9400
+ mkdirSync8(dir, { recursive: true });
9401
+ writeFileSync8(path, JSON.stringify(data, null, 2));
9205
9402
  }
9206
9403
  function validateCronField(expr, min, max, label) {
9207
9404
  for (const part of expr.split(",")) {
@@ -9386,11 +9583,34 @@ function recordScheduleRun(id, status, targetDir) {
9386
9583
  saveSchedules(data, targetDir);
9387
9584
  }
9388
9585
  // src/lib/pull.ts
9389
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync2, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync10 } from "fs";
9390
- import { dirname as dirname6, join as join15 } from "path";
9586
+ import { existsSync as existsSync14, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync2, readFileSync as readFileSync13, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
9587
+ import { dirname as dirname7, join as join14 } from "path";
9391
9588
 
9392
- // src/lib/skill-bundle.ts
9589
+ // src/lib/revision.ts
9393
9590
  import { createHash as createHash3 } from "crypto";
9591
+ var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
9592
+ function revisionIdOf(content) {
9593
+ const canonical = JSON.stringify({
9594
+ slug: content.slug,
9595
+ displayName: content.displayName,
9596
+ description: content.description,
9597
+ category: content.category,
9598
+ tags: content.tags,
9599
+ source: content.source,
9600
+ kind: content.kind,
9601
+ version: content.version ?? null,
9602
+ skillMd: content.skillMd ?? null,
9603
+ bundleSha256: content.bundleSha256 ?? null,
9604
+ bundleByteSize: content.bundleByteSize ?? null
9605
+ });
9606
+ return createHash3("sha256").update(canonical).digest("hex");
9607
+ }
9608
+ function revisionIdOfRecord(record) {
9609
+ return revisionIdOf(record);
9610
+ }
9611
+
9612
+ // src/lib/skill-bundle.ts
9613
+ import { createHash as createHash4 } from "crypto";
9394
9614
  var BLOCK = 512;
9395
9615
  var ANY_SEGMENT_EXCLUDES = new Set([
9396
9616
  ".git",
@@ -9403,6 +9623,7 @@ var ANY_SEGMENT_EXCLUDES = new Set([
9403
9623
  ".docker"
9404
9624
  ]);
9405
9625
  var ROOT_EXCLUDES = new Set(["dist", "build", ".turbo"]);
9626
+ var TOOL_SIDECAR_FILENAMES = new Set([".hasna-skills.json"]);
9406
9627
  var CREDENTIAL_FILENAMES = new Set([
9407
9628
  ".npmrc",
9408
9629
  ".pypirc",
@@ -9519,7 +9740,7 @@ function ownBytes(view) {
9519
9740
  return out;
9520
9741
  }
9521
9742
  function sha256Hex(bytes) {
9522
- return createHash3("sha256").update(bytes).digest("hex");
9743
+ return createHash4("sha256").update(bytes).digest("hex");
9523
9744
  }
9524
9745
  function unpackSkillBundle(bundle) {
9525
9746
  return readTar(ownBytes(Bun.gunzipSync(ownBytes(bundle))));
@@ -9598,9 +9819,9 @@ function verifyBundleSignature(bytes, signature, key) {
9598
9819
  // src/lib/pull.ts
9599
9820
  var BUNDLE_DIGEST_HEADER = "X-Skill-Bundle-Sha256";
9600
9821
  var BUNDLE_SIGNATURE_HEADER = "X-Skill-Bundle-Signature";
9822
+ var BUNDLE_REVISION_ID_HEADER = "X-Skill-Revision-Id";
9823
+ var BUNDLE_REVISION_NUMBER_HEADER = "X-Skill-Revision-Number";
9601
9824
  var PULL_MARKER_FILE = ".hasna-skills.json";
9602
- var SKILLS_CACHE_DIRNAME2 = "skills";
9603
- var LAYOUT_MIGRATION_RECORD2 = ".layout-migration.json";
9604
9825
 
9605
9826
  class PullSkillError extends Error {
9606
9827
  detail;
@@ -9650,6 +9871,15 @@ async function pullOne(client, rawName, corpusOptions, verify) {
9650
9871
  return { name: slug, success: false, error: `Failed to fetch '${slug}': ${error.message}` };
9651
9872
  }
9652
9873
  if (bundleResponse && !bundleResponse.ok) {
9874
+ if (bundleResponse.status === 410) {
9875
+ return reconcileTombstone(slug, corpusOptions);
9876
+ }
9877
+ if (bundleResponse.status === 404) {
9878
+ const marker = readPullMarker(join14(getPortableSkillsRoot(corpusOptions), slug));
9879
+ if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
9880
+ return { name: slug, success: true, purged: true, removed: false };
9881
+ }
9882
+ }
9653
9883
  return {
9654
9884
  name: slug,
9655
9885
  success: false,
@@ -9675,17 +9905,78 @@ async function pullOne(client, rawName, corpusOptions, verify) {
9675
9905
  if (skillMd === null) {
9676
9906
  return { name: slug, success: false, error: `Skill '${slug}' was not found on the configured Skills instance.` };
9677
9907
  }
9908
+ if (!meta?.revisionId) {
9909
+ const marker = readPullMarker(join14(getPortableSkillsRoot(corpusOptions), slug));
9910
+ if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
9911
+ return { name: slug, success: true, purged: true, removed: false };
9912
+ }
9913
+ }
9914
+ let provenRevisionId;
9915
+ try {
9916
+ provenRevisionId = meta?.revisionId ? provenRevision({ ...meta, skillMd }, slug, {}) : undefined;
9917
+ } catch (error) {
9918
+ if (error instanceof PullSkillError) {
9919
+ return { name: slug, success: false, error: error.message };
9920
+ }
9921
+ throw error;
9922
+ }
9678
9923
  const written = writeCorpusSkill({ name: slug, skillMd, meta }, corpusOptions);
9679
- writePullMarker(written.path, { skill: slug, version: written.manifest.version });
9924
+ writePullMarker(written.path, {
9925
+ skill: slug,
9926
+ version: written.manifest.version,
9927
+ ...provenRevisionId ? { revisionId: provenRevisionId } : {}
9928
+ });
9680
9929
  return {
9681
9930
  name: slug,
9682
9931
  success: true,
9683
9932
  path: written.path,
9684
9933
  kind: written.manifest.kind,
9685
9934
  version: written.manifest.version,
9686
- created: written.created
9935
+ created: written.created,
9936
+ ...provenRevisionId ? { revisionId: provenRevisionId } : {}
9687
9937
  };
9688
9938
  }
9939
+ function provenRevision(meta, slug, bundle) {
9940
+ const declared = meta.revisionId;
9941
+ if (!declared)
9942
+ return "";
9943
+ const source = meta.publishedSource;
9944
+ if (typeof source !== "string" || source.length === 0) {
9945
+ throw new PullSkillError(`Revision proof failed for '${slug}': the instance declared revision '${declared.slice(0, 12)}\u2026' but did not serve the content fields needed to recompute it (publishedSource). Nothing was installed.`);
9946
+ }
9947
+ const recomputed = revisionIdOf({
9948
+ slug,
9949
+ displayName: meta.displayName ?? "",
9950
+ description: meta.description ?? "",
9951
+ category: meta.category ?? "",
9952
+ tags: meta.tags ?? [],
9953
+ source,
9954
+ kind: meta.kind ?? "instruction",
9955
+ ...meta.version ? { version: meta.version } : {},
9956
+ ...typeof meta.skillMd === "string" && meta.skillMd.length > 0 ? { skillMd: meta.skillMd } : {},
9957
+ ...bundle.sha256 ? { bundleSha256: bundle.sha256 } : {},
9958
+ ...bundle.byteSize !== undefined && bundle.byteSize !== null ? { bundleByteSize: bundle.byteSize } : {}
9959
+ });
9960
+ if (recomputed !== declared) {
9961
+ throw new PullSkillError(`Revision proof failed for '${slug}': the instance declared revision '${declared.slice(0, 12)}\u2026' but the served content recomputes to '${recomputed.slice(0, 12)}\u2026'. The declared revision does not identify the content that was received. Nothing was installed.`);
9962
+ }
9963
+ return declared;
9964
+ }
9965
+ function reconcileTombstone(slug, corpusOptions) {
9966
+ const target = join14(getPortableSkillsRoot(corpusOptions), slug);
9967
+ if (!existsSync14(join14(target, PULL_MARKER_FILE))) {
9968
+ return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
9969
+ }
9970
+ rmSync4(target, { recursive: true, force: true });
9971
+ return { name: slug, success: true, tombstoned: true, removed: true };
9972
+ }
9973
+ function readPullMarker(dir) {
9974
+ try {
9975
+ return JSON.parse(readFileSync13(join14(dir, PULL_MARKER_FILE), "utf-8"));
9976
+ } catch {
9977
+ return null;
9978
+ }
9979
+ }
9689
9980
  function installVerifiedBundle(slug, response, meta, corpusOptions, verify) {
9690
9981
  return response.arrayBuffer().then((buffer) => {
9691
9982
  const verified = verifyBundleResponseBytes(buffer, response, verify);
@@ -9697,11 +9988,17 @@ function installVerifiedBundle(slug, response, meta, corpusOptions, verify) {
9697
9988
  }
9698
9989
  const version = str(meta?.version) ?? versionFromEntries(entries) ?? "unknown";
9699
9990
  const sourceCommit = sourceCommitFromEntries(entries);
9991
+ const declaredRevision = verified.revisionId ?? meta?.revisionId;
9992
+ if (declaredRevision && meta?.revisionId && verified.revisionId && declaredRevision !== meta.revisionId) {
9993
+ throw new PullSkillError(`Revision proof failed for '${slug}': the bundle header declares '${verified.revisionId.slice(0, 12)}\u2026' but the metadata declares '${meta.revisionId.slice(0, 12)}\u2026'. The instance is inconsistent. Nothing was installed.`);
9994
+ }
9995
+ const provenRevisionId = declaredRevision ? provenRevision(meta ?? {}, slug, { sha256: verified.contentHash, byteSize: verified.bytes.byteLength }) : undefined;
9700
9996
  const installed = installBundleAtomically(slug, entries, corpusOptions, {
9701
9997
  version,
9702
9998
  contentHash: verified.contentHash,
9703
9999
  ...sourceCommit ? { sourceCommit } : {},
9704
- ...verified.signature ? { signature: verified.signature } : {}
10000
+ ...verified.signature ? { signature: verified.signature } : {},
10001
+ ...provenRevisionId ? { revisionId: provenRevisionId } : {}
9705
10002
  });
9706
10003
  return {
9707
10004
  name: slug,
@@ -9711,6 +10008,7 @@ function installVerifiedBundle(slug, response, meta, corpusOptions, verify) {
9711
10008
  version,
9712
10009
  contentHash: verified.contentHash,
9713
10010
  ...sourceCommit ? { sourceCommit } : {},
10011
+ ...provenRevisionId ? { revisionId: provenRevisionId } : {},
9714
10012
  created: installed.created
9715
10013
  };
9716
10014
  });
@@ -9761,11 +10059,20 @@ function sourceCommitFromEntries(entries) {
9761
10059
  function verifyBundleResponseBytes(buffer, response, verify = {}) {
9762
10060
  const serverHash = response.headers.get(BUNDLE_DIGEST_HEADER);
9763
10061
  const signature = response.headers.get(BUNDLE_SIGNATURE_HEADER);
10062
+ const revisionId = response.headers.get(BUNDLE_REVISION_ID_HEADER);
10063
+ const revisionNumberRaw = response.headers.get(BUNDLE_REVISION_NUMBER_HEADER);
9764
10064
  const bytes = new Uint8Array(buffer.slice(0));
9765
10065
  const contentHash = sha256Hex(bytes);
9766
10066
  if (serverHash && serverHash.toLowerCase() !== contentHash) {
9767
10067
  throw new PullSkillError(`Bundle digest mismatch: the instance declared ${serverHash} but the received bundle hashes to ${contentHash}.`, ["The bundle was tampered with or truncated in transit. Nothing was installed."]);
9768
10068
  }
10069
+ if (revisionId && !REVISION_ID_PATTERN.test(revisionId)) {
10070
+ throw new PullSkillError(`Malformed revision id: the instance declared '${revisionId}', which is not a 64-character lowercase hex sha-256.`, ["The revision headers were tampered with or the instance is broken. Nothing was installed."]);
10071
+ }
10072
+ const revisionNumber = revisionNumberRaw === null ? undefined : Number(revisionNumberRaw);
10073
+ if (revisionNumberRaw !== null && (!Number.isInteger(revisionNumber) || revisionNumber < 0)) {
10074
+ throw new PullSkillError(`Malformed revision number: the instance declared '${revisionNumberRaw}', which is not a non-negative integer.`, ["The revision headers were tampered with or the instance is broken. Nothing was installed."]);
10075
+ }
9769
10076
  if (signature) {
9770
10077
  const key = verify.signingKey;
9771
10078
  if (!key) {
@@ -9778,43 +10085,46 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
9778
10085
  bytes,
9779
10086
  contentHash,
9780
10087
  ...serverHash ? { serverHash } : {},
9781
- ...signature ? { signature } : {}
10088
+ ...signature ? { signature } : {},
10089
+ ...revisionId ? { revisionId } : {},
10090
+ ...revisionNumberRaw === null || revisionNumber === undefined ? {} : { revisionNumber }
9782
10091
  };
9783
10092
  }
9784
10093
  function installBundleAtomically(name, entries, options = {}, marker = {}) {
9785
10094
  const root = getPortableSkillsRoot(options);
9786
- mkdirSync10(root, { recursive: true });
9787
- const target = join15(root, name);
9788
- const created = !existsSync15(target);
9789
- const staging = mkdtempSync2(join15(root, `.pull-${name}-`));
10095
+ mkdirSync9(root, { recursive: true });
10096
+ const target = join14(root, name);
10097
+ const created = !existsSync14(target);
10098
+ const staging = mkdtempSync2(join14(root, `.pull-${name}-`));
9790
10099
  let moved = false;
9791
10100
  let backup = null;
9792
10101
  try {
9793
10102
  for (const entry of entries) {
9794
- const destination = join15(staging, entry.path);
9795
- mkdirSync10(dirname6(destination), { recursive: true });
9796
- writeFileSync10(destination, entry.bytes, { mode: entry.mode });
10103
+ const destination = join14(staging, entry.path);
10104
+ mkdirSync9(dirname7(destination), { recursive: true });
10105
+ writeFileSync9(destination, entry.bytes, { mode: entry.mode });
9797
10106
  }
9798
10107
  writePullMarker(staging, {
9799
10108
  skill: name,
9800
10109
  ...marker.version ? { version: marker.version } : {},
9801
10110
  ...marker.contentHash ? { contentHash: marker.contentHash } : {},
9802
10111
  ...marker.sourceCommit ? { sourceCommit: marker.sourceCommit } : {},
9803
- ...marker.signature ? { signature: marker.signature } : {}
10112
+ ...marker.signature ? { signature: marker.signature } : {},
10113
+ ...marker.revisionId ? { revisionId: marker.revisionId } : {}
9804
10114
  });
9805
- if (existsSync15(target)) {
9806
- backup = mkdtempSync2(join15(root, `.pull-backup-${name}-`));
9807
- renameSync4(target, join15(backup, name));
10115
+ if (existsSync14(target)) {
10116
+ backup = mkdtempSync2(join14(root, `.pull-backup-${name}-`));
10117
+ renameSync3(target, join14(backup, name));
9808
10118
  moved = true;
9809
10119
  }
9810
- renameSync4(staging, target);
10120
+ renameSync3(staging, target);
9811
10121
  if (moved && backup)
9812
- rmSync5(backup, { recursive: true, force: true });
10122
+ rmSync4(backup, { recursive: true, force: true });
9813
10123
  } catch (error) {
9814
- rmSync5(staging, { recursive: true, force: true });
9815
- if (moved && backup && existsSync15(join15(backup, name))) {
10124
+ rmSync4(staging, { recursive: true, force: true });
10125
+ if (moved && backup && existsSync14(join14(backup, name))) {
9816
10126
  try {
9817
- renameSync4(join15(backup, name), target);
10127
+ renameSync3(join14(backup, name), target);
9818
10128
  } catch {}
9819
10129
  }
9820
10130
  throw error;
@@ -9825,14 +10135,15 @@ function writePullMarker(dir, record) {
9825
10135
  const marker = {
9826
10136
  managedBy: "@hasna/skills",
9827
10137
  skill: record.skill,
9828
- source: "pull",
10138
+ source: record.source ?? "pull",
9829
10139
  ...record.version ? { version: record.version } : {},
9830
10140
  ...record.contentHash ? { contentHash: record.contentHash } : {},
9831
10141
  ...record.sourceCommit ? { sourceCommit: record.sourceCommit } : {},
9832
10142
  ...record.signature ? { signature: record.signature } : {},
10143
+ ...record.revisionId ? { revisionId: record.revisionId } : {},
9833
10144
  syncedAt: new Date().toISOString()
9834
10145
  };
9835
- writeFileSync10(join15(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
10146
+ writeFileSync9(join14(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
9836
10147
  `);
9837
10148
  }
9838
10149
  async function safeMeta(client, slug) {
@@ -9853,20 +10164,14 @@ async function safeMeta(client, slug) {
9853
10164
  ...str(record.category) ? { category: str(record.category) } : {},
9854
10165
  ...tags && tags.length ? { tags } : {},
9855
10166
  ...str(record.version) ? { version: str(record.version) } : {},
9856
- ...kind ? { kind } : {}
10167
+ ...kind ? { kind } : {},
10168
+ ...REVISION_ID_PATTERN.test(str(record.revisionId) ?? "") ? { revisionId: str(record.revisionId) } : {},
10169
+ ...typeof record.skillMd === "string" && record.skillMd.length > 0 ? { skillMd: record.skillMd } : {},
10170
+ ...str(record.publishedSource) ? { publishedSource: str(record.publishedSource) } : {}
9857
10171
  };
9858
10172
  }
9859
10173
  function pickCorpusOptions(options) {
9860
- return { rootDir: resolvePullCorpusRoot(options) };
9861
- }
9862
- function resolvePullCorpusRoot(options) {
9863
- if (options.rootDir)
9864
- return options.rootDir;
9865
- const appDir = options.homeDir ? join15(options.homeDir, ".hasna", "skills") : getDataDir();
9866
- const cache = join15(appDir, SKILLS_CACHE_DIRNAME2);
9867
- if (existsSync15(join15(cache, LAYOUT_MIGRATION_RECORD2)) && existsSync15(cache))
9868
- return cache;
9869
- return getPortableSkillsRoot(options);
10174
+ return { rootDir: resolveCorpusRoot(options) };
9870
10175
  }
9871
10176
  function extractSlug(entry) {
9872
10177
  if (!entry || typeof entry !== "object")
@@ -9984,12 +10289,12 @@ function findSkillsParityForMcpTool(tool) {
9984
10289
  return SKILLS_CLI_MCP_PARITY.find((entry) => entry.mcpTools.includes(tool));
9985
10290
  }
9986
10291
  // src/lib/registry-sync.ts
9987
- import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
9988
- import { dirname as dirname7, relative as relative3 } from "path";
10292
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
10293
+ import { dirname as dirname8, relative as relative3 } from "path";
9989
10294
  // package.json
9990
10295
  var package_default = {
9991
10296
  name: "@hasna/skills",
9992
- version: "0.1.63",
10297
+ version: "0.1.64",
9993
10298
  description: "Skills library for AI coding agents",
9994
10299
  type: "module",
9995
10300
  bin: {
@@ -10063,6 +10368,7 @@ var package_default = {
10063
10368
  license: "Apache-2.0",
10064
10369
  devDependencies: {
10065
10370
  "@types/bun": "latest",
10371
+ "@types/node": "25.2.3",
10066
10372
  "@types/react": "^18.2.0",
10067
10373
  "bun-types": "1.3.14",
10068
10374
  "react-devtools-core": "^7.0.1",
@@ -10071,7 +10377,7 @@ var package_default = {
10071
10377
  dependencies: {
10072
10378
  "@aws-sdk/client-ecs": "^3.1079.0",
10073
10379
  "@aws-sdk/client-s3": "^3.1079.0",
10074
- "@hasna/events": "0.1.7",
10380
+ "@hasna/events": "0.1.16",
10075
10381
  "@modelcontextprotocol/sdk": "^1.26.0",
10076
10382
  chalk: "^5.3.0",
10077
10383
  commander: "^12.1.0",
@@ -10156,8 +10462,8 @@ function createRegistrySyncArtifact(options = {}) {
10156
10462
  };
10157
10463
  }
10158
10464
  function writeRegistrySyncArtifact(path, artifact) {
10159
- mkdirSync11(dirname7(path), { recursive: true });
10160
- writeFileSync11(path, `${JSON.stringify(artifact, null, 2)}
10465
+ mkdirSync10(dirname8(path), { recursive: true });
10466
+ writeFileSync10(path, `${JSON.stringify(artifact, null, 2)}
10161
10467
  `);
10162
10468
  }
10163
10469
  function buildDocs(name) {
@@ -10996,17 +11302,17 @@ function clone2(value) {
10996
11302
  return JSON.parse(JSON.stringify(value));
10997
11303
  }
10998
11304
  // src/lib/feedback.ts
10999
- import { existsSync as existsSync16, mkdirSync as mkdirSync12 } from "fs";
11000
- import { dirname as dirname8, join as join16 } from "path";
11305
+ import { existsSync as existsSync15, mkdirSync as mkdirSync11 } from "fs";
11306
+ import { dirname as dirname9, join as join15 } from "path";
11001
11307
  import { Database } from "bun:sqlite";
11002
11308
  function getFeedbackDbPath() {
11003
- return join16(getDataDir(), "skills.db");
11309
+ return join15(getDataDir(), "skills.db");
11004
11310
  }
11005
11311
  function getFeedbackDb() {
11006
11312
  const dbPath = getFeedbackDbPath();
11007
- const dir = dirname8(dbPath);
11008
- if (!existsSync16(dir))
11009
- mkdirSync12(dir, { recursive: true });
11313
+ const dir = dirname9(dbPath);
11314
+ if (!existsSync15(dir))
11315
+ mkdirSync11(dir, { recursive: true });
11010
11316
  const db = new Database(dbPath);
11011
11317
  db.exec("PRAGMA journal_mode = WAL");
11012
11318
  db.exec([
@@ -11040,16 +11346,16 @@ function saveFeedback(input) {
11040
11346
  return { saved: true, category, path: getFeedbackDbPath() };
11041
11347
  }
11042
11348
  // src/lib/native-storage.ts
11043
- import { createHash as createHash4, createHmac as createHmac2 } from "crypto";
11349
+ import { createHash as createHash5, createHmac as createHmac2 } from "crypto";
11044
11350
  import {
11045
- existsSync as existsSync17,
11046
- mkdirSync as mkdirSync13,
11047
- readFileSync as readFileSync13,
11048
- readdirSync as readdirSync9,
11049
- statSync as statSync9,
11050
- writeFileSync as writeFileSync12
11351
+ existsSync as existsSync16,
11352
+ mkdirSync as mkdirSync12,
11353
+ readFileSync as readFileSync14,
11354
+ readdirSync as readdirSync8,
11355
+ statSync as statSync8,
11356
+ writeFileSync as writeFileSync11
11051
11357
  } from "fs";
11052
- import { dirname as dirname9, join as join17, normalize as normalize3, relative as relative4, sep as sep2 } from "path";
11358
+ import { dirname as dirname10, join as join16, normalize as normalize3, relative as relative4, sep as sep2 } from "path";
11053
11359
  var SKILLS_STORAGE_TABLES = [
11054
11360
  "skills_sync_records",
11055
11361
  "skills_sync_cursors"
@@ -11128,7 +11434,7 @@ function getSkillsNativeStorageStatus(options = {}) {
11128
11434
  const s3BucketEnv = readStorageEnv(env, "s3Bucket");
11129
11435
  const targetDir = options.targetDir ?? process.cwd();
11130
11436
  return {
11131
- package: "open-skills",
11437
+ package: "skills",
11132
11438
  tables: [...SKILLS_STORAGE_TABLES],
11133
11439
  env: {
11134
11440
  databaseUrl: SKILLS_NATIVE_STORAGE_ENV.databaseUrl,
@@ -11137,7 +11443,7 @@ function getSkillsNativeStorageStatus(options = {}) {
11137
11443
  local: {
11138
11444
  dataDir: getDataDir(),
11139
11445
  projectStateDir: getProjectStateDir(targetDir),
11140
- feedbackDbPath: join17(getDataDir(), "skills.db")
11446
+ feedbackDbPath: join16(getDataDir(), "skills.db")
11141
11447
  },
11142
11448
  remote: {
11143
11449
  databaseConfigured: Boolean(config.databaseUrl),
@@ -11160,14 +11466,14 @@ function getStorageStatus(options = {}) {
11160
11466
  function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
11161
11467
  const projectStateDir = getProjectStateDir(targetDir);
11162
11468
  const files = [];
11163
- if (existsSync17(projectStateDir)) {
11469
+ if (existsSync16(projectStateDir)) {
11164
11470
  for (const filePath of walkFiles2(projectStateDir)) {
11165
- const bytes = readFileSync13(filePath);
11471
+ const bytes = readFileSync14(filePath);
11166
11472
  const relativePath = toPosix(relative4(targetDir, filePath));
11167
11473
  files.push({
11168
11474
  path: relativePath,
11169
11475
  sizeBytes: bytes.byteLength,
11170
- sha256: createHash4("sha256").update(bytes).digest("hex"),
11476
+ sha256: createHash5("sha256").update(bytes).digest("hex"),
11171
11477
  ...options.includeFileContents ? { contentBase64: Buffer.from(bytes).toString("base64") } : {}
11172
11478
  });
11173
11479
  }
@@ -11187,17 +11493,17 @@ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options
11187
11493
  continue;
11188
11494
  }
11189
11495
  const absolutePath = resolveSnapshotPath(targetDir, file.path);
11190
- if (existsSync17(absolutePath) && !options.overwrite) {
11496
+ if (existsSync16(absolutePath) && !options.overwrite) {
11191
11497
  skipped += 1;
11192
11498
  continue;
11193
11499
  }
11194
11500
  const bytes = Buffer.from(file.contentBase64, "base64");
11195
- const hash = createHash4("sha256").update(bytes).digest("hex");
11501
+ const hash = createHash5("sha256").update(bytes).digest("hex");
11196
11502
  if (hash !== file.sha256) {
11197
11503
  throw new Error(`Snapshot file checksum mismatch: ${file.path}`);
11198
11504
  }
11199
- mkdirSync13(dirname9(absolutePath), { recursive: true });
11200
- writeFileSync12(absolutePath, bytes);
11505
+ mkdirSync12(dirname10(absolutePath), { recursive: true });
11506
+ writeFileSync11(absolutePath, bytes);
11201
11507
  written += 1;
11202
11508
  }
11203
11509
  return { written, skipped };
@@ -11300,7 +11606,7 @@ function createSkillsSnapshotSyncRecord(snapshot, options = {}) {
11300
11606
  kind: "local-snapshot",
11301
11607
  id: options.id ?? "project-state",
11302
11608
  updatedAt: snapshot.exportedAt,
11303
- source: options.source ?? "open-skills",
11609
+ source: options.source ?? "skills",
11304
11610
  payload: snapshot
11305
11611
  };
11306
11612
  }
@@ -11493,9 +11799,9 @@ function parsePositiveInteger(value) {
11493
11799
  }
11494
11800
  function walkFiles2(dir) {
11495
11801
  const files = [];
11496
- for (const entry of readdirSync9(dir)) {
11497
- const full = join17(dir, entry);
11498
- const stats = statSync9(full);
11802
+ for (const entry of readdirSync8(dir)) {
11803
+ const full = join16(dir, entry);
11804
+ const stats = statSync8(full);
11499
11805
  if (stats.isDirectory())
11500
11806
  files.push(...walkFiles2(full));
11501
11807
  else
@@ -11511,7 +11817,7 @@ function resolveSnapshotPath(targetDir, snapshotPath) {
11511
11817
  if (!toPosix(normalizedPath).startsWith(".skills/")) {
11512
11818
  throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
11513
11819
  }
11514
- return join17(targetDir, normalizedPath);
11820
+ return join16(targetDir, normalizedPath);
11515
11821
  }
11516
11822
  function normalizeS3Prefix(prefix) {
11517
11823
  return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
@@ -11524,7 +11830,7 @@ function toIsoString(value) {
11524
11830
  return Number.isNaN(date.getTime()) ? value : date.toISOString();
11525
11831
  }
11526
11832
  function sha256Hex2(value) {
11527
- return createHash4("sha256").update(value).digest("hex");
11833
+ return createHash5("sha256").update(value).digest("hex");
11528
11834
  }
11529
11835
  function normalizeHeaders(headers) {
11530
11836
  const result = {};
@@ -11747,6 +12053,8 @@ export {
11747
12053
  SKILLS_CLI_MCP_PARITY,
11748
12054
  SKILLS,
11749
12055
  RemoteSkillsClient,
12056
+ RemoteRouteUnsupportedError,
12057
+ RemoteRequestError,
11750
12058
  REMOTE_SKILL_RUN_CONTRACT_VERSION,
11751
12059
  PullSkillError,
11752
12060
  PROJECT_CONFIG_FILE,