@hasna/skills 0.10.76 → 0.10.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/mcp.js CHANGED
@@ -23,7 +23,7 @@ var package_default;
23
23
  var init_package = __esm(() => {
24
24
  package_default = {
25
25
  name: "@hasna/skills",
26
- version: "0.10.76",
26
+ version: "0.10.78",
27
27
  description: "Skills library for AI coding agents",
28
28
  type: "module",
29
29
  bin: {
@@ -1087,6 +1087,10 @@ function skillsFileHasCredential(file) {
1087
1087
  return false;
1088
1088
  return /^\s*(?:export\s+)?(?:HASNA_SKILLS_API_KEY|SKILLS_API_KEY|HASNA_SKILLS_API_KEY_REF)\s*=\s*\S/m.test(text);
1089
1089
  }
1090
+ function skillsFileHasVaultReference(file) {
1091
+ const text = readMetadataText(file);
1092
+ return text !== null && /^\s*(?:export\s+)?HASNA_SKILLS_API_KEY_REF\s*=\s*\S/m.test(text);
1093
+ }
1090
1094
  var SKILLS_BOUND_API_URL = "HASNA_SKILLS_BOUND_API_URL";
1091
1095
  var init_instance_credentials = __esm(() => {
1092
1096
  init_transport();
@@ -1121,6 +1125,20 @@ var init_local_opt_in = __esm(() => {
1121
1125
  // src/lib/product-default.ts
1122
1126
  var SKILLS_PRODUCT_DEFAULT_ORIGIN = "https://skills.md";
1123
1127
 
1128
+ // src/lib/credential-profile-command.ts
1129
+ function skillsShellArgument(value) {
1130
+ return /^[A-Za-z0-9._:/-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
1131
+ }
1132
+ function skillsApiUrlEnvironment(origin) {
1133
+ return urlKeys.map((name) => `${name}=${skillsShellArgument(origin)}`).join(" ");
1134
+ }
1135
+ var urlKeys;
1136
+ var init_credential_profile_command = __esm(() => {
1137
+ init_instance_credentials();
1138
+ init_transport();
1139
+ urlKeys = clientTransportEnvKeys("skills").apiUrlKeys;
1140
+ });
1141
+
1124
1142
  // src/lib/fleet-credentials.ts
1125
1143
  var exports_fleet_credentials = {};
1126
1144
  __export(exports_fleet_credentials, {
@@ -1145,6 +1163,8 @@ __export(exports_fleet_credentials, {
1145
1163
  gatewaySignInRefusal: () => gatewaySignInRefusal,
1146
1164
  credentialPairing: () => credentialPairing,
1147
1165
  configuredSkillsApiUrl: () => configuredSkillsApiUrl,
1166
+ captureSkillsSignInTarget: () => captureSkillsSignInTarget,
1167
+ assertSkillsSignInCredentialReplaceable: () => assertSkillsSignInCredentialReplaceable,
1148
1168
  SkillsFleetCredentialError: () => SkillsFleetCredentialError,
1149
1169
  SKILLS_LOCAL_OPT_IN_ENV_KEYS: () => SKILLS_LOCAL_OPT_IN_ENV_KEYS,
1150
1170
  SKILLS_CREDENTIAL_TIERS: () => SKILLS_CREDENTIAL_TIERS,
@@ -1450,8 +1470,24 @@ function gatewaySignInRefusal(target) {
1450
1470
  if (normalizeSkillsApiOrigin(target.origin) !== normalizeSkillsApiOrigin(defaultFleetGatewayBaseUrl(SKILLS_APP)))
1451
1471
  return null;
1452
1472
  const cause = target.credentialSource ? `${target.credentialSource} holds a Skills credential for the internal gateway` : target.source === "--url" ? "--url names the internal gateway" : `${target.source} selects the internal gateway`;
1453
- const unset = target.credentialSource ?? (target.source === "--url" ? null : target.source);
1454
- return new SkillsFleetCredentialError(`${cause}, which has no sign-in service. Nothing was sent.` + (unset ? ` To sign in to the default server instead, first unset ${unset} (or remove it from where it is stored), then run: skills login.` : ""), "GATEWAY_AUTH_UNAVAILABLE", unset ? [`unset ${unset}`, "skills login"] : undefined);
1473
+ const command = (SKILLS_API_URL_ENV_KEYS.includes(target.source) ? `${skillsApiUrlEnvironment(SKILLS_PRODUCT_DEFAULT_ORIGIN)} ` : "") + "skills --profile <new-profile> login";
1474
+ return new SkillsFleetCredentialError(`${cause}, which has no sign-in service. Nothing was sent. ` + `Keep the fleet configuration and use a separate profile for product sign-in: ${command}.`, "GATEWAY_AUTH_UNAVAILABLE", [command]);
1475
+ }
1476
+ function assertSkillsSignInCredentialReplaceable(env = process.env, profile) {
1477
+ const files = skillsProfileCredentialFiles(env, profile);
1478
+ const verifyUnchanged = captureSkillsCredentialFiles(files);
1479
+ const gateway = normalizeSkillsApiOrigin(defaultFleetGatewayBaseUrl(SKILLS_APP));
1480
+ const command = (SKILLS_API_URL_ENV_KEYS.some((name) => env[name]?.trim()) ? `${skillsApiUrlEnvironment(SKILLS_PRODUCT_DEFAULT_ORIGIN)} ` : "") + "skills --profile <new-profile> login";
1481
+ for (const file of files) {
1482
+ if (!skillsFileHasCredential(file))
1483
+ continue;
1484
+ const metadata = readSkillsInstanceMetadata(file);
1485
+ const instance = metadata.binding ?? metadata.apiUrl;
1486
+ if (skillsFileHasVaultReference(file) || !instance || normalizeSkillsApiOrigin(instance) === gateway) {
1487
+ throw new SkillsFleetCredentialError("The selected profile contains a protected fleet credential or vault reference. Nothing was sent or changed. " + `Use a separate profile for product sign-in: ${command}.`, "STORED_CREDENTIAL_PROTECTED", [command]);
1488
+ }
1489
+ }
1490
+ verifyUnchanged();
1455
1491
  }
1456
1492
  function explicitSignInOrigin(url) {
1457
1493
  if (!url.trim() || /[\x00-\x1f\x7f]/.test(url)) {
@@ -1479,13 +1515,15 @@ function resolveSkillsSignInOrigin(env = process.env, options = {}, explicitUrl)
1479
1515
  const snapshot = snapshotSkillsEnvironment(env);
1480
1516
  const resolvedOptions = snapshotSkillsOptions(env, options);
1481
1517
  const profile = resolvedOptions.credentials?.profile;
1518
+ assertSkillsSignInCredentialReplaceable(snapshot, profile);
1482
1519
  const configured = configuredSkillsApiUrl(snapshot, resolvedOptions.credentials?.keychain, profile);
1483
1520
  const declared = configured && configured.source !== "default" ? configured : null;
1484
1521
  if (explicitUrl !== undefined) {
1485
1522
  const origin = explicitSignInOrigin(explicitUrl);
1486
1523
  const storedHere = declared ? skillsProfileCredentialFiles(snapshot, profile).includes(declared.source) : false;
1487
1524
  if (declared && !storedHere && normalizeSkillsApiOrigin(declared.value) !== origin) {
1488
- throw new SkillsFleetCredentialError(`--url ${origin} conflicts with ${declared.source}, which outranks the saved configuration and would ` + `shadow the key this sign-in stores. Unset ${declared.source} or pass the same URL.`, "INVALID_API_URL");
1525
+ const command = `${skillsApiUrlEnvironment(origin)} skills --profile <new-profile> login`;
1526
+ throw new SkillsFleetCredentialError(`--url ${origin} conflicts with ${declared.source}, which outranks the saved configuration and would ` + `shadow the key this sign-in stores. Keep the existing configuration and use a separate process and profile: ${command}.`, "INVALID_API_URL", [command]);
1489
1527
  }
1490
1528
  return { origin, source: "--url" };
1491
1529
  }
@@ -1517,6 +1555,14 @@ function resolveSkillsSignInOrigin(env = process.env, options = {}, explicitUrl)
1517
1555
  throw error;
1518
1556
  }
1519
1557
  }
1558
+ function captureSkillsSignInTarget(env = process.env, options = {}, explicitUrl) {
1559
+ const snapshot = snapshotSkillsEnvironment(env);
1560
+ const profile = options.credentials?.profile ?? selectedCliSkillsCredentialProfile();
1561
+ const unchanged2 = captureSkillsCredentialFiles(skillsProfileCredentialFiles(snapshot, profile));
1562
+ const target = resolveSkillsSignInOrigin(env, options, explicitUrl);
1563
+ unchanged2();
1564
+ return { ...target, unchanged: unchanged2 };
1565
+ }
1520
1566
  function requireSkillsFleet(action = "This command", env = process.env, options = {}) {
1521
1567
  const fleet = resolveSkillsFleet(env, options);
1522
1568
  if (fleet.mode === "hosted")
@@ -1528,6 +1574,7 @@ var init_fleet_credentials = __esm(() => {
1528
1574
  init_transport();
1529
1575
  init_instance_credentials();
1530
1576
  init_local_opt_in();
1577
+ init_credential_profile_command();
1531
1578
  init_local_opt_in();
1532
1579
  ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
1533
1580
  SKILLS_API_URL_ENV_KEYS = ENV_KEYS.apiUrlKeys;
@@ -22758,7 +22805,8 @@ var init_codex_native_compatibility = __esm(() => {
22758
22805
  "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
22759
22806
  "codex-cli 0.161.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
22760
22807
  "codex-cli 0.162.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
22761
- "codex-cli 0.162.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
22808
+ "codex-cli 0.162.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
22809
+ "codex-cli 0.163.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
22762
22810
  };
22763
22811
  SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version2) => supportsCodexNativeCapability(version2, "hooks")));
22764
22812
  });
@@ -32251,6 +32299,40 @@ var init_codex_settings_witness_v1 = __esm(() => {
32251
32299
  VERBOSITY = new Set(["low", "medium", "high"]);
32252
32300
  });
32253
32301
 
32302
+ // src/lib/codex-plugin-capability-paths.ts
32303
+ function foldCodexPluginEntryName(name) {
32304
+ return name.toUpperCase().toLowerCase();
32305
+ }
32306
+ function codexPluginRootCapabilityEntry(name) {
32307
+ const folded = foldCodexPluginEntryName(name);
32308
+ return CODEX_PLUGIN_ROOT_CAPABILITY_ENTRIES.includes(folded) ? folded : null;
32309
+ }
32310
+ function isCodexSkillDocumentName(name) {
32311
+ return foldCodexPluginEntryName(name) === "skill.md";
32312
+ }
32313
+ var CODEX_PLUGIN_MANIFEST_LOCATIONS, CODEX_PLUGIN_MANIFEST_DIRECTORIES, CODEX_PLUGIN_ROOT_CAPABILITY_ENTRIES;
32314
+ var init_codex_plugin_capability_paths = __esm(() => {
32315
+ CODEX_PLUGIN_MANIFEST_LOCATIONS = Object.freeze([
32316
+ "plugin.json",
32317
+ ".codex-plugin/plugin.json",
32318
+ ".claude-plugin/plugin.json",
32319
+ ".cursor-plugin/plugin.json"
32320
+ ]);
32321
+ CODEX_PLUGIN_MANIFEST_DIRECTORIES = Object.freeze([".codex-plugin", ".claude-plugin", ".cursor-plugin"]);
32322
+ CODEX_PLUGIN_ROOT_CAPABILITY_ENTRIES = Object.freeze([
32323
+ "plugin.json",
32324
+ ".codex-plugin",
32325
+ ".claude-plugin",
32326
+ ".cursor-plugin",
32327
+ "hooks",
32328
+ "hooks.json",
32329
+ ".mcp.json",
32330
+ ".app.json",
32331
+ "commands",
32332
+ "agents"
32333
+ ]);
32334
+ });
32335
+
32254
32336
  // src/lib/codex-hook-mcp-dependencies.ts
32255
32337
  import { createHash as createHash12 } from "crypto";
32256
32338
  import { basename as basename7, dirname as dirname12, isAbsolute as isAbsolute12, join as join16, resolve as resolve11, sep as sep5 } from "path";
@@ -32395,15 +32477,13 @@ function captureCodexHookMcpDependencies(configPath, reviewed, configText, chang
32395
32477
  }
32396
32478
  };
32397
32479
  const root = join16(cacheRoot, identity2, selectCodexPluginVersion(versions2, selectionBudget));
32398
- const agent = read(join16(root, "plugin.json"));
32480
+ const [agentPluginManifest, ...legacyManifests] = CODEX_PLUGIN_MANIFEST_LOCATIONS;
32481
+ const agent = read(join16(root, agentPluginManifest));
32399
32482
  if (agent !== null) {
32400
32483
  const schema = JSON.parse(agent).$schema;
32401
32484
  need5(typeof schema !== "string" || !schema.startsWith("https://agent-plugins.org/schemas/"));
32402
32485
  }
32403
- const codex = read(join16(root, ".codex-plugin/plugin.json"));
32404
- const claude = read(join16(root, ".claude-plugin/plugin.json"));
32405
- const cursor = read(join16(root, ".cursor-plugin/plugin.json"));
32406
- const text2 = codex ?? claude ?? cursor;
32486
+ const text2 = legacyManifests.map((location) => read(join16(root, location))).find((value) => value !== null) ?? null;
32407
32487
  if (text2 === null)
32408
32488
  continue;
32409
32489
  const manifest = JSON.parse(text2);
@@ -32425,6 +32505,7 @@ var init_codex_hook_mcp_dependencies = __esm(() => {
32425
32505
  init_claude_settings_witness();
32426
32506
  init_agent_discovery_directories();
32427
32507
  init_agent_discovery_bytes();
32508
+ init_codex_plugin_capability_paths();
32428
32509
  events = new Set(["PreToolUse", "PermissionRequest", "PostToolUse", "PreCompact", "PostCompact", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart", "SubagentStop", "Stop", "Interrupt"]);
32429
32510
  });
32430
32511
 
@@ -34007,9 +34088,10 @@ var CODEX_VENDOR_MARKETPLACES, CODEX_REMOTE_VENDOR_MARKETPLACES, OTHER_PLUGIN_CO
34007
34088
  var init_codex_vendor_refresh = __esm(() => {
34008
34089
  init_claude_settings_witness();
34009
34090
  init_codex_plugin_skill_controls();
34091
+ init_codex_plugin_capability_paths();
34010
34092
  CODEX_VENDOR_MARKETPLACES = Object.freeze(["openai-bundled", "openai-curated", "openai-curated-remote"]);
34011
34093
  CODEX_REMOTE_VENDOR_MARKETPLACES = Object.freeze(["openai-curated-remote"]);
34012
- OTHER_PLUGIN_CONTROLS = Object.freeze(["plugin.json", ".claude-plugin", ".cursor-plugin", "hooks", "hooks.json", ".mcp.json", "commands", "agents"]);
34094
+ OTHER_PLUGIN_CONTROLS = Object.freeze(CODEX_PLUGIN_ROOT_CAPABILITY_ENTRIES.filter((entry) => entry !== ".codex-plugin" && entry !== ".app.json"));
34013
34095
  VENDOR_MANIFEST_KEYS = Object.freeze(["name", "version", "description", "author", "homepage", "repository", "license", "keywords", "interface", "skills", "apps"]);
34014
34096
  DEFAULT_SKILLS_PATHS = Object.freeze(["./skills", "./skills/"]);
34015
34097
  });
@@ -35114,14 +35196,23 @@ function assertParents(cacheRoot, controls, appOnlyParents, hookParents, files,
35114
35196
  continue;
35115
35197
  const parent = join21(cacheRoot, parts[0], parts[1]);
35116
35198
  const root = join21(parent, parts[2]);
35117
- const base = parts[parts.length - 1];
35118
- if (parts.includes(".codex-plugin") && (parts.length < 4 || parts[3] !== ".codex-plugin" || (parts.length === 4 ? base !== ".codex-plugin" : parts.length !== 5 || base !== "plugin.json")))
35199
+ const base = parts[parts.length - 1], folded = foldCodexPluginEntryName(base);
35200
+ if (parts.length === 3 && (codexPluginRootCapabilityEntry(base) !== null || isCodexSkillDocumentName(base)))
35201
+ refuse3();
35202
+ const rootEntry = parts.length >= 4 ? codexPluginRootCapabilityEntry(parts[3]) : null;
35203
+ if (rootEntry !== null && rootEntry !== parts[3])
35204
+ refuse3();
35205
+ if (rootEntry !== null && !SEMANTIC_BOUND_ROOT_ENTRIES.includes(rootEntry))
35119
35206
  refuse3();
35120
- if ([".app.json", ".mcp.json"].includes(base) && (parts.length !== 4 || dirname16(member) !== root))
35207
+ if (isCodexSkillDocumentName(base) && base !== "SKILL.md")
35121
35208
  refuse3();
35122
- if ([".app.json", ".mcp.json"].includes(base) && hookParents.some((reviewed) => reviewed.pluginParent === parent))
35209
+ if (parts.some((part) => CODEX_PLUGIN_MANIFEST_DIRECTORIES.includes(foldCodexPluginEntryName(part))) && (parts.length < 4 || parts[3] !== ".codex-plugin" || (parts.length === 4 ? base !== ".codex-plugin" : parts.length !== 5 || base !== "plugin.json")))
35123
35210
  refuse3();
35124
- if ((parts[3] === "hooks" || base === "hooks.json") && (![4, 5].includes(parts.length) || parts[3] !== "hooks" || (parts.length === 4 ? base !== "hooks" : base !== "hooks.json") || !hookParents.some((reviewed) => reviewed.pluginParent === join21(cacheRoot, parts[0], parts[1]))))
35211
+ if ([".app.json", ".mcp.json"].includes(folded) && (parts.length !== 4 || dirname16(member) !== root))
35212
+ refuse3();
35213
+ if ([".app.json", ".mcp.json"].includes(folded) && hookParents.some((reviewed) => reviewed.pluginParent === parent))
35214
+ refuse3();
35215
+ if ((rootEntry === "hooks" || folded === "hooks.json") && (![4, 5].includes(parts.length) || parts[3] !== "hooks" || (parts.length === 4 ? base !== "hooks" : base !== "hooks.json") || !hookParents.some((reviewed) => reviewed.pluginParent === join21(cacheRoot, parts[0], parts[1]))))
35125
35216
  refuse3();
35126
35217
  }
35127
35218
  }
@@ -35343,13 +35434,15 @@ function assertCodexSemanticCacheWitnessUnchanged(previous, current) {
35343
35434
  if (hash3(priorProjection) !== previous.sha256 || hash3(currentProjection) !== current.sha256 || canonical8(comparable(priorProjection)) !== canonical8(comparable(currentProjection)))
35344
35435
  refuse3();
35345
35436
  }
35346
- var CODEX_SEMANTIC_CACHE_WITNESS_SCHEMA = "hasna.codex-semantic-cache-witness.v1", identifier4 = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(value), digest = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value), refuse3 = () => {
35437
+ var CODEX_SEMANTIC_CACHE_WITNESS_SCHEMA = "hasna.codex-semantic-cache-witness.v1", SEMANTIC_BOUND_ROOT_ENTRIES, identifier4 = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(value), digest = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value), refuse3 = () => {
35347
35438
  throw new Error("CODEX_SEMANTIC_CACHE_WITNESS_UNSUPPORTED: review Codex plugin cache identity and controls");
35348
35439
  }, MAX_DOCUMENT_BYTES, MAX_RECEIPT_BYTES, codexHookEvents;
35349
35440
  var init_codex_semantic_cache_witness = __esm(() => {
35350
35441
  init_claude_settings_witness();
35351
35442
  init_codex_plugin_skill_controls();
35352
35443
  init_codex_native_skill_catalog();
35444
+ init_codex_plugin_capability_paths();
35445
+ SEMANTIC_BOUND_ROOT_ENTRIES = Object.freeze([".codex-plugin", ".app.json", ".mcp.json", "hooks"]);
35353
35446
  MAX_DOCUMENT_BYTES = 1024 * 1024;
35354
35447
  MAX_RECEIPT_BYTES = 16 * 1024;
35355
35448
  codexHookEvents = new Set([
@@ -39028,6 +39121,7 @@ function normalizeRemoteSkill(skill) {
39028
39121
  if (!name)
39029
39122
  throw new Error("Remote skill requires name or slug");
39030
39123
  return {
39124
+ ...skill.id !== undefined ? { id: skill.id } : {},
39031
39125
  name,
39032
39126
  displayName: skill.displayName || titleize(name),
39033
39127
  description: skill.description || "",
@@ -39132,11 +39226,12 @@ var init_remote_registry = __esm(() => {
39132
39226
  details: exports_external.array(exports_external.string()).optional()
39133
39227
  }).passthrough();
39134
39228
  remoteSkillSchema = exports_external.object({
39229
+ id: exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/).optional(),
39135
39230
  name: exports_external.string().min(1).optional(),
39136
39231
  slug: exports_external.string().min(1).optional(),
39137
39232
  displayName: exports_external.string().optional(),
39138
39233
  description: exports_external.string().optional(),
39139
- category: exports_external.string().optional(),
39234
+ category: exports_external.string().nullable().optional(),
39140
39235
  tags: exports_external.array(exports_external.string()).optional(),
39141
39236
  dependencies: exports_external.array(exports_external.string()).optional(),
39142
39237
  version: exports_external.string().optional(),
@@ -41525,6 +41620,24 @@ var init_remote_invitation_recovery = __esm(() => {
41525
41620
 
41526
41621
  // src/lib/remote-private-publications.ts
41527
41622
  import { createHash as createHash26 } from "crypto";
41623
+ function checkedPrivateSkillCreate(value) {
41624
+ const refuse5 = (field3, accepted) => {
41625
+ throw new PrivatePublicationError("INVALID_SKILL_CREATE_INPUT", `Invalid ${field3}. ${accepted}`);
41626
+ };
41627
+ if (!record10(value) || !Object.keys(value).every((key2) => ["slug", "displayName", "description", "visibility", "confirm"].includes(key2)))
41628
+ return refuse5("create input", "Provide only slug, displayName, description, visibility and confirm.");
41629
+ if (value.confirm !== true)
41630
+ throw new PrivatePublicationError("SKILL_CREATE_CONFIRM_REQUIRED", "Explicit confirmation is required to create this skill.");
41631
+ if (typeof value.slug !== "string" || value.slug.length > 128 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value.slug))
41632
+ return refuse5("slug", "Use lowercase kebab-case, at most 128 characters.");
41633
+ if (typeof value.displayName !== "string" || !value.displayName.trim() || value.displayName.length > 256 || /[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value.displayName))
41634
+ return refuse5("displayName", "Use nonempty text, at most 256 characters, without control characters.");
41635
+ if (value.description !== undefined && (typeof value.description !== "string" || Buffer.byteLength(value.description) > 8192 || /[\p{Cc}\p{Cs}]/u.test(value.description.replace(/[\r\n\t]/g, ""))))
41636
+ return refuse5("description", "Use text of at most 8192 UTF-8 bytes, without control characters other than tabs and newlines.");
41637
+ if (value.visibility !== undefined && value.visibility !== "private" && value.visibility !== "team")
41638
+ return refuse5("visibility", "Use private or team.");
41639
+ return Object.freeze({ slug: value.slug, displayName: value.displayName.trim(), description: value.description ?? "", visibility: value.visibility ?? "private" });
41640
+ }
41528
41641
  function checkedPublicationDeclaration(value) {
41529
41642
  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 || !hash4(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES)
41530
41643
  return bad();
@@ -41544,11 +41657,11 @@ function checkedPublicationView(value, skillId, intentId, declaration) {
41544
41657
  return invalid4();
41545
41658
  return Object.freeze({ ...value });
41546
41659
  }
41547
- async function boundedJson(url2, init, token, mutation, budget3 = 15000, signal) {
41660
+ async function boundedJson(url2, init, token, mutation, budget3 = 15000, signal, create = false) {
41548
41661
  const controller = new AbortController;
41549
41662
  let reader;
41550
41663
  let response;
41551
- const timeout2 = new PrivatePublicationError("PUBLICATION_UNCONFIRMED", "No confirmed publication result. Inspect the saved intent before retrying.", mutation);
41664
+ const timeout2 = new PrivatePublicationError(create ? "SKILL_CREATE_UNCONFIRMED" : "PUBLICATION_UNCONFIRMED", create ? "Skill creation could not be confirmed. Inspect the workspace for this slug before another create; do not retry automatically." : "No confirmed publication result. Inspect the saved intent before retrying.", mutation);
41552
41665
  let timer;
41553
41666
  let abort;
41554
41667
  try {
@@ -41588,11 +41701,19 @@ async function boundedJson(url2, init, token, mutation, budget3 = 15000, signal)
41588
41701
  return invalid4();
41589
41702
  }
41590
41703
  if (!response.ok) {
41591
- const code = record10(body) && typeof body.code === "string" && Object.hasOwn(failures2, body.code) ? body.code : null;
41592
- if (code && failures2[code][0] === response.status)
41593
- throw new PrivatePublicationError(code, failures2[code][1], mutation && code === "PUBLICATION_UNCERTAIN", response.status);
41704
+ const known = create ? skillCreateFailures : failures2;
41705
+ const code = record10(body) && typeof body.code === "string" && Object.hasOwn(known, body.code) ? body.code : null;
41706
+ if (code && known[code][0] === response.status)
41707
+ throw new PrivatePublicationError(code, known[code][1], mutation && code === "PUBLICATION_UNCERTAIN", response.status);
41708
+ if (create) {
41709
+ if (response.status >= 500)
41710
+ throw timeout2;
41711
+ throw new PrivatePublicationError("SKILL_CREATE_REQUEST_FAILED", "The server refused skill creation.", false, response.status);
41712
+ }
41594
41713
  throw new PrivatePublicationError("PUBLICATION_REQUEST_FAILED", "The publication request was refused. Inspect its status before retrying.", mutation && response.status >= 500, response.status);
41595
41714
  }
41715
+ if (create && response.status !== 201)
41716
+ return invalid4();
41596
41717
  return body;
41597
41718
  })(), new Promise((_, reject) => {
41598
41719
  abort = () => {
@@ -41639,6 +41760,15 @@ class RemotePrivatePublicationsClient {
41639
41760
  this.membershipId = checked.user.membershipId;
41640
41761
  Object.freeze(this);
41641
41762
  }
41763
+ async createSkill(input2, options = {}) {
41764
+ const body = checkedPrivateSkillCreate(input2);
41765
+ if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 15000))
41766
+ throw new PrivatePublicationError("INVALID_SKILL_CREATE_INPUT", "Invalid timeoutMs. Use an integer from 1 to 15000.");
41767
+ const value = await boundedJson(skillsApiRequestUrl(this.apiOrigin, "/api/v1/skills"), { method: "POST", body: JSON.stringify(body) }, this.#token, true, options.timeoutMs, options.signal, true);
41768
+ if (!record10(value) || !publicationUuid(value.id) || value.slug !== body.slug || value.displayName !== body.displayName || value.description !== body.description || value.visibility !== body.visibility || value.sourceType !== "private_hosted" || !record10(value.metadata) || value.metadata.ownerOrganizationId !== this.organizationId)
41769
+ throw new PrivatePublicationError("SKILL_CREATE_UNCONFIRMED", "Skill creation returned an unbound result. Inspect the workspace for this slug before another create; do not retry automatically.", true);
41770
+ return Object.freeze({ id: value.id, slug: body.slug, visibility: body.visibility, sourceType: "private_hosted", ownerOrganizationId: this.organizationId });
41771
+ }
41642
41772
  async getCapability(options = {}) {
41643
41773
  if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 15000))
41644
41774
  return bad();
@@ -41781,7 +41911,7 @@ class RemotePrivatePublicationsClient {
41781
41911
  }
41782
41912
  }
41783
41913
  }
41784
- var PRIVATE_PUBLICATION_MAX_BYTES, failures2, PrivatePublicationError, bad = () => {
41914
+ var PRIVATE_PUBLICATION_MAX_BYTES, skillCreateFailures, failures2, PrivatePublicationError, bad = () => {
41785
41915
  throw new PrivatePublicationError("INVALID_PUBLICATION_INPUT", "Invalid publication input or recovery data.");
41786
41916
  }, invalid4 = () => {
41787
41917
  throw new PrivatePublicationError("INVALID_PUBLICATION_RESPONSE", "The server returned an invalid publication result.");
@@ -41791,6 +41921,16 @@ var init_remote_private_publications = __esm(() => {
41791
41921
  init_remote_workspace_selection();
41792
41922
  init_skill_contract();
41793
41923
  PRIVATE_PUBLICATION_MAX_BYTES = 16 * 1024 * 1024;
41924
+ skillCreateFailures = {
41925
+ INVALID_SLUG: [400, "Provide a lowercase kebab-case skill slug."],
41926
+ INVALID_DISPLAY_NAME: [400, "Provide a nonempty skill display name."],
41927
+ INVALID_VISIBILITY: [400, "Use private or team visibility for source publication."],
41928
+ SESSION_EXPIRED: [401, "Sign in again before creating a private skill."],
41929
+ INTERACTIVE_SESSION_REQUIRED: [403, "An interactive account session is required to create a private skill."],
41930
+ ACCOUNT_UNAVAILABLE: [403, "The current account membership cannot create a private skill."],
41931
+ PLAN_REQUIRED: [403, "Private and team skills require the Pro plan."],
41932
+ SLUG_CONFLICT: [409, "This skill slug already exists. Inspect the existing skill before publishing."]
41933
+ };
41794
41934
  failures2 = {
41795
41935
  INVALID_REQUEST: [400, "The publication request is invalid."],
41796
41936
  SESSION_EXPIRED: [401, "Sign in again to manage this publication."],
@@ -48220,15 +48360,42 @@ var init_remote_invitation_tools = __esm(() => {
48220
48360
  });
48221
48361
 
48222
48362
  // src/lib/private-publication-customer.ts
48363
+ function preparePrivatePublicationSession(requested) {
48364
+ const selectors = [
48365
+ "HASNA_PROFILE",
48366
+ "HASNA_HOME",
48367
+ "HASNA_CONFIG_HOME",
48368
+ "HASNA_STATION",
48369
+ "HASNA_SKILLS_API_URL",
48370
+ "SKILLS_API_URL",
48371
+ "HASNA_SKILLS_API_KEY_OVERRIDE",
48372
+ "HASNA_SKILLS_API_KEY_REF",
48373
+ "HASNA_SKILLS_API_KEY",
48374
+ "SKILLS_API_KEY"
48375
+ ];
48376
+ const before = new Map(selectors.map((key2) => [key2, process.env[key2]]));
48377
+ const profile = selectedSkillsProfile(process.env), pending = prepareProfileWorkspace("Manage private publications");
48378
+ const captured = requested ? { ...requested } : undefined;
48379
+ const unchanged3 = () => {
48380
+ if (selectors.some((key2) => process.env[key2] !== before.get(key2)) || selectedSkillsProfile(process.env) !== profile)
48381
+ throw new PrivatePublicationError("PUBLICATION_TARGET_CHANGED", "The selected profile or server changed during verification. Start again with a stable target; no skill action was sent.");
48382
+ };
48383
+ return { origin: pending.origin, async verify(email5, code2) {
48384
+ unchanged3();
48385
+ const target = await pending.resolve();
48386
+ const context2 = captured ?? target.context;
48387
+ if (!context2 || !publicationUuid(context2.userId) || !publicationUuid(context2.membershipId) || target.context && (context2.userId !== target.context.userId || context2.membershipId !== target.context.membershipId))
48388
+ throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide the observed user and membership IDs, or select an enrolled workspace profile.");
48389
+ unchanged3();
48390
+ target.unchanged();
48391
+ const client = await new RemoteSkillsAuthClient(target.origin).openPrivatePublications(email5, code2, context2);
48392
+ unchanged3();
48393
+ target.unchanged();
48394
+ return client;
48395
+ } };
48396
+ }
48223
48397
  async function privatePublicationSession(email5, code2, requested) {
48224
- const target = await captureProfileWorkspace("Manage private publications");
48225
- const context2 = requested ?? target.context;
48226
- if (!context2 || !publicationUuid(context2.userId) || !publicationUuid(context2.membershipId) || target.context && (context2.userId !== target.context.userId || context2.membershipId !== target.context.membershipId))
48227
- throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide the observed user and membership IDs, or select an enrolled workspace profile.");
48228
- target.unchanged();
48229
- const client = await new RemoteSkillsAuthClient(target.origin).openPrivatePublications(email5, code2, context2);
48230
- target.unchanged();
48231
- return client;
48398
+ return preparePrivatePublicationSession(requested).verify(email5, code2);
48232
48399
  }
48233
48400
  function privatePublicationCustomerError(error61) {
48234
48401
  return error61 instanceof PrivatePublicationError ? { error: error61.message, code: error61.code, uncertain: error61.uncertain } : { error: "The publication action could not be confirmed. Preserve the recovery directory and inspect the same intent; credentials were not changed.", code: "PUBLICATION_ACTION_UNCONFIRMED", uncertain: true };
@@ -48237,6 +48404,7 @@ var init_private_publication_customer = __esm(() => {
48237
48404
  init_remote_auth();
48238
48405
  init_remote_private_publications();
48239
48406
  init_workspace_profile();
48407
+ init_instance_credentials();
48240
48408
  });
48241
48409
 
48242
48410
  // src/lib/private-publication-recovery.ts
package/bin/migrate.js CHANGED
@@ -25,7 +25,7 @@ import { join as join5 } from "path";
25
25
  // package.json
26
26
  var package_default = {
27
27
  name: "@hasna/skills",
28
- version: "0.10.76",
28
+ version: "0.10.78",
29
29
  description: "Skills library for AI coding agents",
30
30
  type: "module",
31
31
  bin: {