@hasna/skills 0.8.12 → 0.9.1

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/README.md CHANGED
@@ -592,13 +592,20 @@ before submitting again. The selected profile must include this exact version,
592
592
  and the consumer needs `runs:write` as well as `skills:read`.
593
593
 
594
594
  Cloud execution is enabled only when the deployment configures a reviewed image
595
- and exact bundle allowlist. The first supported lane is `pdf-generate`; arbitrary
596
- uploaded code is not admitted. Runs capture version, bundle digest, input digest,
595
+ and exact bundle allowlist; arbitrary
596
+ uploaded code is not admitted. The PDF lane accepts reviewed `pdf-generate` versions.
597
+ The optional `regex-test.v1` pure contract accepts only a tenant-specific, reviewed
598
+ self-contained Bun bundle, with no secrets, dependencies or network. Uploading or
599
+ selecting a bundle never admits it to cloud execution. Check an exact version with
600
+ `skills executions eligibility <skill> --skill-version <version> --json`; this
601
+ reads metadata without creating a run. Runs capture version, bundle digest, input digest,
597
602
  runtime image digest, limits and policy. The cloud worker runs in a separate
598
603
  Fargate task; the skill process has no API/provider credentials, no network, a
599
604
  read-only root and bounded temporary storage, execution time and output.
600
605
  `GET /skills/v1/capabilities` reports whether this deployment has cloud execution
601
- configured. Authorization and runtime availability are checked separately.
606
+ configured and lists supported `cloudExecutionContracts`. Each exact bundle needs
607
+ its own review and admission; the flag does not promise arbitrary executable
608
+ support. Authorization and runtime availability are checked separately.
602
609
 
603
610
  On a managed station, local execution also resolves the selected immutable
604
611
  bundle. Self-contained local executables run with explicit environment references
@@ -1433,7 +1440,7 @@ src/
1433
1440
  |---|---|---|
1434
1441
  | Catalog skills | 0 | `SKILLS.length` (`src/lib/registry-data/`) |
1435
1442
  | Categories | 17 | `CATEGORIES` (`src/lib/registry-types.ts`) |
1436
- | MCP tools | 81 | `tools/list` against a live `buildServer()` |
1443
+ | MCP tools | 82 | `tools/list` against a live `buildServer()` |
1437
1444
 
1438
1445
  Every number in this table is re-derived from the source tree on each test run by
1439
1446
  `src/lib/readme-derived-counts.test.ts`, so a drifted figure fails a test rather
package/bin/index.js CHANGED
@@ -36868,7 +36868,7 @@ var package_default;
36868
36868
  var init_package = __esm(() => {
36869
36869
  package_default = {
36870
36870
  name: "@hasna/skills",
36871
- version: "0.8.12",
36871
+ version: "0.9.1",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -39186,6 +39186,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
39186
39186
  const version = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
39187
39187
  const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
39188
39188
  const commands = parseManifestCommands(jsonManifest) ?? (kind === "instruction" ? [] : inferPackageCommands(pkg, name)) ?? [];
39189
+ const runtime = parseManifestRuntime(jsonManifest) ?? inferRuntimeFromCommands(commands);
39189
39190
  return {
39190
39191
  $schema: stringField(jsonManifest, "$schema") ?? PORTABLE_SKILL_SCHEMA,
39191
39192
  standard: stringField(jsonManifest, "standard") ?? PORTABLE_SKILL_STANDARD,
@@ -39198,7 +39199,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
39198
39199
  ...kind ? { kind } : {},
39199
39200
  inputs: kind === "instruction" ? parseManifestInputs(jsonManifest) ?? [] : parseManifestInputs(jsonManifest) ?? DEFAULT_INPUTS,
39200
39201
  commands,
39201
- ...parseManifestRuntime(jsonManifest) ? { runtime: parseManifestRuntime(jsonManifest) } : {},
39202
+ ...runtime ? { runtime } : {},
39202
39203
  ...parseManifestProvenance(jsonManifest) ? { provenance: parseManifestProvenance(jsonManifest) } : {}
39203
39204
  };
39204
39205
  }
@@ -39717,6 +39718,12 @@ function inferPackageCommands(pkg, fallbackName) {
39717
39718
  }
39718
39719
  return;
39719
39720
  }
39721
+ function inferRuntimeFromCommands(commands) {
39722
+ const entrypoint = commands.find((command) => command.entry)?.entry;
39723
+ if (!entrypoint || !entrypoint.toLowerCase().endsWith(".py"))
39724
+ return;
39725
+ return { ...defaultRuntimeContract(entrypoint), runtime: "python3" };
39726
+ }
39720
39727
  function readJsonObject(path) {
39721
39728
  const parsed = JSON.parse(readFileSync6(path, "utf-8"));
39722
39729
  if (!isRecord2(parsed))
@@ -76206,6 +76213,8 @@ function normalizeRemoteSkill(skill) {
76206
76213
  tags: skill.tags || ["remote"],
76207
76214
  dependencies: skill.dependencies,
76208
76215
  ...skill.version ? { version: skill.version } : {},
76216
+ ...skill.revisionId !== undefined ? { revisionId: skill.revisionId } : {},
76217
+ ...skill.lifecycle ? { lifecycle: skill.lifecycle } : {},
76209
76218
  availability: normalizeRemoteAvailability(skill.availability),
76210
76219
  source: "remote"
76211
76220
  };
@@ -76323,6 +76332,8 @@ var init_remote_registry = __esm(() => {
76323
76332
  tags: exports_external.array(exports_external.string()).optional(),
76324
76333
  dependencies: exports_external.array(exports_external.string()).optional(),
76325
76334
  version: exports_external.string().optional(),
76335
+ revisionId: exports_external.union([exports_external.string().regex(/^[\x21-\x7e]+$/), exports_external.null()]).optional(),
76336
+ lifecycle: exports_external.enum(["active", "archived"]).optional(),
76326
76337
  availability: remoteAvailabilitySchema.optional()
76327
76338
  }).passthrough().refine((skill) => skill.name || skill.slug, {
76328
76339
  message: "Remote skill requires name or slug"
@@ -77711,6 +77722,7 @@ __export(exports_remote_client, {
77711
77722
  RemoteWorkspaceSelectionError: () => RemoteWorkspaceSelectionError,
77712
77723
  RemoteWorkspaceMemberError: () => RemoteWorkspaceMemberError,
77713
77724
  RemoteSkillsClient: () => RemoteSkillsClient,
77725
+ RemoteSkillLifecycleError: () => RemoteSkillLifecycleError,
77714
77726
  RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
77715
77727
  RemoteRequestError: () => RemoteRequestError,
77716
77728
  RemoteQuoteUnavailableError: () => RemoteQuoteUnavailableError,
@@ -77842,6 +77854,39 @@ class RemoteSkillsClient {
77842
77854
  })();
77843
77855
  return this.capabilities;
77844
77856
  }
77857
+ async addSkillPublishScope(keyId, expectedScopes, expectedOrgId) {
77858
+ if (!/^[A-Za-z0-9_-]+$/.test(keyId))
77859
+ throw new Error("Invalid API key id");
77860
+ if (!/^[A-Za-z0-9_-]{1,256}$/.test(expectedOrgId))
77861
+ throw new Error("Invalid expected organization id");
77862
+ if (!Array.isArray(expectedScopes) || expectedScopes.length > 32 || expectedScopes.some((scope) => typeof scope !== "string" || scope.length > 128 || !/^(?:\*|[a-z][a-z0-9_-]*:(?:\*|[a-z][a-z0-9_-]*))$/.test(scope)) || new Set(expectedScopes).size !== expectedScopes.length)
77863
+ throw new Error("Invalid expected API key scopes");
77864
+ const path = `/api/v1/admin/keys/${encodeURIComponent(keyId)}/scopes`;
77865
+ const response = await this.request(path, { method: "PATCH", body: JSON.stringify({ expected_scopes: expectedScopes, add_scopes: ["skills:publish"] }) });
77866
+ if (!response.ok)
77867
+ throw new RemoteRequestError(path, response.status);
77868
+ let value;
77869
+ try {
77870
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 64 * 1024)));
77871
+ } catch {
77872
+ throw new Error("Invalid API key scope update response");
77873
+ }
77874
+ if (!value || typeof value !== "object" || Array.isArray(value))
77875
+ throw new Error("Invalid API key scope update response");
77876
+ const body = value;
77877
+ const returnedId = body.keyId;
77878
+ const orgId = body.orgId;
77879
+ const scopes = body.scopes;
77880
+ if (returnedId !== keyId || typeof orgId !== "string" || !orgId || expectedOrgId !== undefined && orgId !== expectedOrgId || !Array.isArray(scopes) || scopes.some((scope) => typeof scope !== "string") || body.updated !== true) {
77881
+ throw new Error("Invalid API key scope update response");
77882
+ }
77883
+ const returnedScopes = scopes;
77884
+ const expectedResult = expectedScopes.includes("skills:publish") ? expectedScopes : [...expectedScopes, "skills:publish"];
77885
+ if (returnedScopes.length !== expectedResult.length || expectedResult.some((scope, index) => returnedScopes[index] !== scope)) {
77886
+ throw new Error("API key scope update response did not preserve the expected scopes");
77887
+ }
77888
+ return { keyId, orgId, scopes: [...returnedScopes], updated: true };
77889
+ }
77845
77890
  async submitQuotedRun(slug2, input2 = {}, args = [], approval = {}) {
77846
77891
  runQuoteReceipt(approval.quoteReceipt);
77847
77892
  ({ input: input2, args, approval } = JSON.parse(JSON.stringify({ input: input2, args, approval })));
@@ -78359,6 +78404,19 @@ class RemoteSkillsClient {
78359
78404
  async deleteSkill(slug2) {
78360
78405
  return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}`, { method: "DELETE" });
78361
78406
  }
78407
+ async setSkillLifecycle(slug2, lifecycle, options) {
78408
+ const path = `/api/v1/skills/${encodeURIComponent(slug2)}/lifecycle`;
78409
+ const response = await this.request(path, {
78410
+ method: "PATCH",
78411
+ headers: { "If-Match": options.expectedRevisionId },
78412
+ body: JSON.stringify({ lifecycle, ...options.reason ? { reason: options.reason } : {}, ...options.replacementSlug ? { replacementSlug: options.replacementSlug } : {} })
78413
+ });
78414
+ if (!response.ok) {
78415
+ const code = await readResponseCode(response);
78416
+ throw new RemoteSkillLifecycleError(path, response.status, code);
78417
+ }
78418
+ return response.json();
78419
+ }
78362
78420
  async downloadSkillBundle(slug2) {
78363
78421
  return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}/bundle`, { method: "GET" });
78364
78422
  }
@@ -78510,9 +78568,13 @@ function normalizeSkillSummaryList(payload) {
78510
78568
  return payload.map(normalizeSkillSummary);
78511
78569
  }
78512
78570
  async function responseBodyCarriesCode(response, codes) {
78571
+ const code = await readResponseCode(response);
78572
+ return code !== undefined && codes.includes(code);
78573
+ }
78574
+ async function readResponseCode(response) {
78513
78575
  const reader = response.body?.getReader();
78514
78576
  if (!reader)
78515
- return false;
78577
+ return;
78516
78578
  const maximum = 8 * 1024;
78517
78579
  let deadline;
78518
78580
  const expired = new Promise((_, reject2) => {
@@ -78527,7 +78589,7 @@ async function responseBodyCarriesCode(response, codes) {
78527
78589
  break;
78528
78590
  size2 += next.value.byteLength;
78529
78591
  if (size2 > maximum)
78530
- return false;
78592
+ return;
78531
78593
  chunks.push(next.value);
78532
78594
  }
78533
78595
  const bytes = new Uint8Array(size2);
@@ -78538,11 +78600,11 @@ async function responseBodyCarriesCode(response, codes) {
78538
78600
  }
78539
78601
  const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
78540
78602
  if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Object.hasOwn(payload, "code"))
78541
- return false;
78603
+ return;
78542
78604
  const code = payload.code;
78543
- return typeof code === "string" && codes.includes(code);
78605
+ return typeof code === "string" && /^[A-Z][A-Z0-9_:-]{0,127}$/.test(code) ? code : undefined;
78544
78606
  } catch {
78545
- return false;
78607
+ return;
78546
78608
  } finally {
78547
78609
  clearTimeout(deadline);
78548
78610
  reader.cancel().catch(() => {});
@@ -78571,7 +78633,7 @@ async function createRemoteSkillsClient(env3 = process.env) {
78571
78633
  function createRemoteSkillsClientReadOnly(env3 = process.env) {
78572
78634
  return createRemoteSkillsClient(env3);
78573
78635
  }
78574
- var RemoteRouteUnsupportedError, RemoteRequestError, RemoteCreditCheckoutError, RemoteQuoteUnavailableError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
78636
+ var RemoteRouteUnsupportedError, RemoteRequestError, KNOWN_SKILL_LIFECYCLE_CODES, RemoteSkillLifecycleError, RemoteCreditCheckoutError, RemoteQuoteUnavailableError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
78575
78637
  var init_remote_client = __esm(() => {
78576
78638
  init_remote_recurring();
78577
78639
  init_remote_invitations();
@@ -78608,6 +78670,17 @@ var init_remote_client = __esm(() => {
78608
78670
  this.name = "RemoteRequestError";
78609
78671
  }
78610
78672
  };
78673
+ KNOWN_SKILL_LIFECYCLE_CODES = new Set(["SKILL_ARCHIVE_PROFILE_CONFLICT", "LIFECYCLE_ROLE_REQUIRED"]);
78674
+ RemoteSkillLifecycleError = class RemoteSkillLifecycleError extends RemoteRequestError {
78675
+ code;
78676
+ constructor(path, status, code) {
78677
+ super(path, status);
78678
+ this.name = "RemoteSkillLifecycleError";
78679
+ const safeCode = code !== undefined && KNOWN_SKILL_LIFECYCLE_CODES.has(code) ? code : undefined;
78680
+ this.code = safeCode;
78681
+ this.message = `Skill lifecycle update was refused (HTTP ${status}${safeCode ? `, code ${safeCode}` : ""})`;
78682
+ }
78683
+ };
78611
78684
  RemoteCreditCheckoutError = class RemoteCreditCheckoutError extends RemoteRequestError {
78612
78685
  code;
78613
78686
  requestIdempotencyKey;
@@ -78663,7 +78736,7 @@ var init_remote_client = __esm(() => {
78663
78736
  // src/lib/revision.ts
78664
78737
  import { createHash as createHash16 } from "crypto";
78665
78738
  function revisionIdOf(content) {
78666
- const canonical4 = JSON.stringify({
78739
+ const canonicalContent = {
78667
78740
  slug: content.slug,
78668
78741
  displayName: content.displayName,
78669
78742
  description: content.description,
@@ -78675,7 +78748,13 @@ function revisionIdOf(content) {
78675
78748
  skillMd: content.skillMd ?? null,
78676
78749
  bundleSha256: content.bundleSha256 ?? null,
78677
78750
  bundleByteSize: content.bundleByteSize ?? null
78678
- });
78751
+ };
78752
+ if (content.lifecycle === "archived" || content.archiveReason !== undefined || content.replacementSlug !== undefined) {
78753
+ canonicalContent.lifecycle = content.lifecycle ?? "active";
78754
+ canonicalContent.archiveReason = content.archiveReason ?? null;
78755
+ canonicalContent.replacementSlug = content.replacementSlug ?? null;
78756
+ }
78757
+ const canonical4 = JSON.stringify(canonicalContent);
78679
78758
  return createHash16("sha256").update(canonical4).digest("hex");
78680
78759
  }
78681
78760
  var REVISION_ID_PATTERN;
@@ -82131,6 +82210,15 @@ class CloudExecutionClient {
82131
82210
  }
82132
82211
  return response;
82133
82212
  }
82213
+ async eligibility(slug2, version2, bundleDigest) {
82214
+ if (!/^[a-z0-9-]+$/.test(slug2) || !/^\d+\.\d+\.\d+$/.test(version2) || bundleDigest !== undefined && !/^(sha256:)?[a-f0-9]{64}$/.test(bundleDigest))
82215
+ throw Error("Cloud eligibility requires an exact skill version and valid digest");
82216
+ const query = new URLSearchParams({ version: version2, ...bundleDigest ? { bundleDigest } : {} });
82217
+ const v = await jsonBody(await this.request(`${slug2}/eligibility?${query}`));
82218
+ if (!v || v.contractVersion !== 1 || typeof v.eligible !== "boolean" || v.skill !== slug2 || v.version !== version2 || !/^[a-f0-9]{64}$/.test(v.bundleDigest) || bundleDigest && bundleDigest.replace(/^sha256:/, "") !== v.bundleDigest || v.eligible && (!v.executionContract || !["pdf.v1", "regex-test.v1"].includes(v.executionContract.id)))
82219
+ throw Error("Invalid cloud eligibility receipt");
82220
+ return v;
82221
+ }
82134
82222
  async submit(slug2, version2, input2, key, selection) {
82135
82223
  if (!/^[a-z0-9-]+$/.test(slug2) || !/^\d+\.\d+\.\d+$/.test(version2))
82136
82224
  throw Error("Cloud execution requires skill@exact-version");
@@ -103202,6 +103290,18 @@ var init_run_routing = __esm(() => {
103202
103290
  import { existsSync as existsSync28, readdirSync as readdirSync17, statSync as statSync16 } from "fs";
103203
103291
  import { join as join46 } from "path";
103204
103292
  function registerOperationTools(server) {
103293
+ server.registerTool("cloud_skill_eligibility", {
103294
+ title: "Cloud Skill Eligibility",
103295
+ description: "Inspect the authenticated workspace's exact reviewed cloud contract. Does not execute or admit a skill.",
103296
+ inputSchema: { name: exports_external.string(), version: exports_external.string(), bundleDigest: exports_external.string().optional() }
103297
+ }, async ({ name, version: version2, bundleDigest }) => {
103298
+ try {
103299
+ const { CloudExecutionClient: CloudExecutionClient2 } = await Promise.resolve().then(() => (init_cloud_executions(), exports_cloud_executions));
103300
+ return mcpJson(await (await CloudExecutionClient2.configured()).eligibility(name, version2, bundleDigest));
103301
+ } catch (error61) {
103302
+ return mcpError("CLOUD_ELIGIBILITY_FAILED", error61.message);
103303
+ }
103304
+ });
103205
103305
  server.registerTool("scaffold_skill", {
103206
103306
  title: "Scaffold Skill",
103207
103307
  description: "Create a portable skill folder under ~/.hasna/skills/installed/<name> with SKILL.md, skill.json, AGENTS.md, package.json, and src/index.ts.",
@@ -105518,6 +105618,15 @@ function registerRuntime(parent) {
105518
105618
  await handleRun(name, args2, options, separatorBeforeSkill);
105519
105619
  });
105520
105620
  const executions = parent.command("executions").description("Inspect isolated cloud executions");
105621
+ executions.command("eligibility").argument("<skill>").requiredOption("--skill-version <version>", "Exact published version").option("--bundle-digest <digest>", "Expected published bundle SHA256").option("--json", "Output as JSON", false).description("Inspect reviewed cloud eligibility without executing a skill").action(async (slug2, options) => {
105622
+ try {
105623
+ const client2 = await CloudExecutionClient.configured();
105624
+ await writeCliOutput(JSON.stringify(await client2.eligibility(slug2, options.skillVersion, options.bundleDigest), null, 2));
105625
+ } catch (error61) {
105626
+ console.error(JSON.stringify({ error: error61.message }));
105627
+ process.exitCode = 1;
105628
+ }
105629
+ });
105521
105630
  for (const operation of ["show", "logs", "artifacts", "cancel"]) {
105522
105631
  executions.command(operation).argument("<execution-id>").option("--json", "Output as JSON", false).action(async (id2) => {
105523
105632
  try {
@@ -109219,6 +109328,7 @@ function validateRawCandidate(candidate2) {
109219
109328
  const issues = validatePortableManifestContract(candidate2, { strict: true });
109220
109329
  if (issues.length)
109221
109330
  throw new Error(`Invalid skill.json: ${issues.map((issue2) => issue2.code).join(", ")}`);
109331
+ validateRuntimeEntrypointCompatibility(candidate2);
109222
109332
  for (const field2 of ["inputs", "commands"]) {
109223
109333
  const items = candidate2[field2];
109224
109334
  if (items === undefined)
@@ -109244,6 +109354,18 @@ function validateRawCandidate(candidate2) {
109244
109354
  }
109245
109355
  }
109246
109356
  }
109357
+ function validateRuntimeEntrypointCompatibility(candidate2) {
109358
+ if (candidate2.kind !== "executable" || !isRecord6(candidate2.runtime))
109359
+ return;
109360
+ const runtime = candidate2.runtime.runtime;
109361
+ const entrypoint = candidate2.runtime.entrypoint;
109362
+ if (typeof runtime !== "string" || typeof entrypoint !== "string")
109363
+ return;
109364
+ const extension = entrypoint.toLowerCase().split(".").pop();
109365
+ if (extension === "py" && runtime !== "python3") {
109366
+ throw new Error("Invalid skill.json: runtime.entrypoint with .py extension requires runtime python3.");
109367
+ }
109368
+ }
109247
109369
  function compareVersions(left, right) {
109248
109370
  const parts = (value) => value.split("+")[0].split(/-(.*)/s).slice(0, 2);
109249
109371
  const [a, ap] = parts(left), [b, bp] = parts(right);
@@ -109779,7 +109901,8 @@ var exports_registry2 = {};
109779
109901
  __export(exports_registry2, {
109780
109902
  registerVersions: () => registerVersions,
109781
109903
  registerRegistry: () => registerRegistry,
109782
- registerPull: () => registerPull
109904
+ registerPull: () => registerPull,
109905
+ registerLifecycle: () => registerLifecycle
109783
109906
  });
109784
109907
  function registerRegistry(parent) {
109785
109908
  const registry2 = parent.command("registry").description("Generate registry artifacts for hosted skills services");
@@ -109935,6 +110058,25 @@ Versions of ${name}
109935
110058
  }
109936
110059
  });
109937
110060
  }
110061
+ function registerLifecycle(parent) {
110062
+ parent.command("lifecycle").argument("<name>", "Skill name on the configured instance").requiredOption("--state <state>", "Lifecycle state: active or archived").requiredOption("--revision <revision>", "Exact revision id from skills info/get").option("--reason <reason>", "Bounded archive reason").option("--replacement <slug>", "Optional successor slug").option("--json", "Output as JSON", false).description("Change a hosted skill's active/archive lifecycle with an exact revision guard").action(async (name, options) => {
110063
+ if (options.state !== "active" && options.state !== "archived")
110064
+ throw new Error("--state must be active or archived");
110065
+ const client2 = await createRemoteSkillsClient();
110066
+ if (!client2)
110067
+ throw new Error("No API key configured, so there is no instance to update.");
110068
+ try {
110069
+ const result2 = await client2.setSkillLifecycle(name.trim(), options.state, { expectedRevisionId: options.revision, ...options.reason ? { reason: options.reason } : {}, ...options.replacement ? { replacementSlug: options.replacement } : {} });
110070
+ console.log(options.json ? JSON.stringify(result2, null, 2) : `Updated ${name} to ${options.state}.`);
110071
+ } catch (error61) {
110072
+ if (options.json)
110073
+ console.log(JSON.stringify({ error: error61.message }, null, 2));
110074
+ else
110075
+ console.error(source_default.red(error61.message));
110076
+ process.exitCode = 1;
110077
+ }
110078
+ });
110079
+ }
109938
110080
  var init_registry2 = __esm(() => {
109939
110081
  init_source();
109940
110082
  init_registry_sync();
@@ -110482,6 +110624,11 @@ function authIdentityPayload(authSource, live, cached2, offline = false) {
110482
110624
  const userId = stringField2(user?.id) ?? cached2?.userId;
110483
110625
  const orgId = stringField2(organization2?.id) ?? cached2?.orgId;
110484
110626
  const role3 = stringField2(user?.role);
110627
+ const apiKey = recordField(root2.apiKey) ?? recordField(data?.apiKey);
110628
+ const apiKeyId = stringField2(apiKey?.id);
110629
+ const apiKeyScopes = apiKey?.scopes;
110630
+ const safeApiKeyId = apiKeyId && /^[A-Za-z0-9_-]{1,256}$/.test(apiKeyId) ? apiKeyId : undefined;
110631
+ const safeApiKeyScopes = Array.isArray(apiKeyScopes) && apiKeyScopes.length <= 32 && apiKeyScopes.every((scope) => typeof scope === "string" && scope.length <= 128 && /^(?:\*|[a-z][a-z0-9_-]*:(?:\*|[a-z][a-z0-9_-]*))$/.test(scope)) ? [...new Set(apiKeyScopes)] : undefined;
110485
110632
  return {
110486
110633
  status: "authenticated",
110487
110634
  authSource,
@@ -110491,7 +110638,8 @@ function authIdentityPayload(authSource, live, cached2, offline = false) {
110491
110638
  ...orgName ? { organizationName: orgName } : {},
110492
110639
  ...userId ? { userId } : {},
110493
110640
  ...orgId ? { orgId } : {},
110494
- ...role3 ? { role: role3 } : {}
110641
+ ...role3 ? { role: role3 } : {},
110642
+ ...safeApiKeyId && safeApiKeyScopes ? { apiKeyId: safeApiKeyId, scopes: safeApiKeyScopes } : {}
110495
110643
  };
110496
110644
  }
110497
110645
  function printWhoami(payload) {
@@ -110501,6 +110649,8 @@ function printWhoami(payload) {
110501
110649
  console.log(source_default.bold("Org: ") + payload.organization);
110502
110650
  if (payload.role)
110503
110651
  console.log(source_default.bold("Role: ") + payload.role);
110652
+ if (payload.apiKeyId)
110653
+ console.log(source_default.bold("Key: ") + payload.apiKeyId);
110504
110654
  if (payload.organizationName)
110505
110655
  console.log(source_default.bold("Name: ") + payload.organizationName);
110506
110656
  if (payload.authSource)
@@ -110822,6 +110972,25 @@ function registerAuth(parent) {
110822
110972
  writeCommandError(error61, "Failed to revoke API key", options.json);
110823
110973
  }
110824
110974
  });
110975
+ keys3.command("add-publish-scope").argument("<key-id>").requiredOption("--expected-scopes <csv>", "Current comma-separated scopes; stale values are refused").option("--json", "Output as JSON", false).description("Add skills:publish to one existing key without minting or rotating it").action(async (id3, options) => {
110976
+ try {
110977
+ const connection = await resolveSkillsConnection();
110978
+ if (!connection)
110979
+ throw new Error("A hosted Skills credential is required for key scope administration");
110980
+ const identity4 = await apiRequest("/api/auth/whoami", { headers: { Authorization: `Bearer ${connection.apiKey}` } }, connection.apiOrigin);
110981
+ const identityOrg = recordField(identity4.organization);
110982
+ const orgId = stringField2(identityOrg?.id);
110983
+ if (!orgId)
110984
+ throw new Error("The Skills authority did not return a tenant identity; refusing key scope administration");
110985
+ const result2 = await new RemoteSkillsClient(connection.apiKey, connection.apiOrigin).addSkillPublishScope(id3, options.expectedScopes.split(",").map((scope) => scope.trim()).filter(Boolean), orgId);
110986
+ if (options.json || !isTTY)
110987
+ console.log(JSON.stringify(result2, null, 2));
110988
+ else
110989
+ console.log(source_default.green(`Added skills:publish to ${id3}; scopes read back from the server.`));
110990
+ } catch (error61) {
110991
+ writeCommandError(error61, "Failed to add Skills publication scope", options.json);
110992
+ }
110993
+ });
110825
110994
  auth.command("login").description("Sign in with browser/device code or email code").option("--email <email>", "Email address (non-interactive)").option("--code <code>", "Verification code (non-interactive)").option("--membership-id <id>", "Enroll an exact workspace membership into an explicit HASNA_PROFILE").option("--code-stdin", "Read a fresh six-digit code for workspace enrollment from stdin").option("--api-key <key>", "Verify and store an API key").option("--device", "Use browser/device-code login", false).option("--no-open", "Do not open a browser for device-code login").option("--poll", "Poll until browser authentication completes in non-interactive mode", false).option("--poll-timeout-ms <ms>", "Maximum time to wait for device-code login").option("--json", "Output result as JSON", false).action(async (options) => {
110826
110995
  if (options.membershipId !== undefined) {
110827
110996
  if (options.apiKey || options.device || options.code || options.poll) {
@@ -119885,10 +120054,11 @@ var { registerPortableSkillCommands: registerPortableSkillCommands2 } = await Pr
119885
120054
  registerPortableSkillCommands2(program2);
119886
120055
  var { registerSchedule: registerSchedule2 } = await Promise.resolve().then(() => (init_schedule(), exports_schedule));
119887
120056
  registerSchedule2(program2);
119888
- var { registerRegistry: registerRegistry2, registerPull: registerPull2, registerVersions: registerVersions2 } = await Promise.resolve().then(() => (init_registry2(), exports_registry2));
120057
+ var { registerRegistry: registerRegistry2, registerPull: registerPull2, registerVersions: registerVersions2, registerLifecycle: registerLifecycle2 } = await Promise.resolve().then(() => (init_registry2(), exports_registry2));
119889
120058
  registerRegistry2(program2);
119890
120059
  registerPull2(program2);
119891
120060
  registerVersions2(program2);
120061
+ registerLifecycle2(program2);
119892
120062
  var { registerPublish: registerPublish2 } = await Promise.resolve().then(() => (init_publish(), exports_publish));
119893
120063
  registerPublish2(program2);
119894
120064
  var { registerPrivatePublications: registerPrivatePublications2 } = await Promise.resolve().then(() => (init_private_publications(), exports_private_publications));