@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/dist/index.js CHANGED
@@ -8323,6 +8323,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
8323
8323
  const version = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
8324
8324
  const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
8325
8325
  const commands = parseManifestCommands(jsonManifest) ?? (kind === "instruction" ? [] : inferPackageCommands(pkg, name)) ?? [];
8326
+ const runtime = parseManifestRuntime(jsonManifest) ?? inferRuntimeFromCommands(commands);
8326
8327
  return {
8327
8328
  $schema: stringField(jsonManifest, "$schema") ?? PORTABLE_SKILL_SCHEMA,
8328
8329
  standard: stringField(jsonManifest, "standard") ?? PORTABLE_SKILL_STANDARD,
@@ -8335,7 +8336,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
8335
8336
  ...kind ? { kind } : {},
8336
8337
  inputs: kind === "instruction" ? parseManifestInputs(jsonManifest) ?? [] : parseManifestInputs(jsonManifest) ?? DEFAULT_INPUTS,
8337
8338
  commands,
8338
- ...parseManifestRuntime(jsonManifest) ? { runtime: parseManifestRuntime(jsonManifest) } : {},
8339
+ ...runtime ? { runtime } : {},
8339
8340
  ...parseManifestProvenance(jsonManifest) ? { provenance: parseManifestProvenance(jsonManifest) } : {}
8340
8341
  };
8341
8342
  }
@@ -8854,6 +8855,12 @@ function inferPackageCommands(pkg, fallbackName) {
8854
8855
  }
8855
8856
  return;
8856
8857
  }
8858
+ function inferRuntimeFromCommands(commands) {
8859
+ const entrypoint = commands.find((command) => command.entry)?.entry;
8860
+ if (!entrypoint || !entrypoint.toLowerCase().endsWith(".py"))
8861
+ return;
8862
+ return { ...defaultRuntimeContract(entrypoint), runtime: "python3" };
8863
+ }
8857
8864
  function readJsonObject(path) {
8858
8865
  const parsed = JSON.parse(readFileSync5(path, "utf-8"));
8859
8866
  if (!isRecord2(parsed))
@@ -11441,7 +11448,7 @@ import { isAbsolute as isAbsolute10, join as join20, resolve as resolve10 } from
11441
11448
  // package.json
11442
11449
  var package_default = {
11443
11450
  name: "@hasna/skills",
11444
- version: "0.8.12",
11451
+ version: "0.9.1",
11445
11452
  description: "Skills library for AI coding agents",
11446
11453
  type: "module",
11447
11454
  bin: {
@@ -35954,6 +35961,8 @@ var remoteSkillSchema = exports_external.object({
35954
35961
  tags: exports_external.array(exports_external.string()).optional(),
35955
35962
  dependencies: exports_external.array(exports_external.string()).optional(),
35956
35963
  version: exports_external.string().optional(),
35964
+ revisionId: exports_external.union([exports_external.string().regex(/^[\x21-\x7e]+$/), exports_external.null()]).optional(),
35965
+ lifecycle: exports_external.enum(["active", "archived"]).optional(),
35957
35966
  availability: remoteAvailabilitySchema.optional()
35958
35967
  }).passthrough().refine((skill) => skill.name || skill.slug, {
35959
35968
  message: "Remote skill requires name or slug"
@@ -36016,6 +36025,8 @@ function normalizeRemoteSkill(skill) {
36016
36025
  tags: skill.tags || ["remote"],
36017
36026
  dependencies: skill.dependencies,
36018
36027
  ...skill.version ? { version: skill.version } : {},
36028
+ ...skill.revisionId !== undefined ? { revisionId: skill.revisionId } : {},
36029
+ ...skill.lifecycle ? { lifecycle: skill.lifecycle } : {},
36019
36030
  availability: normalizeRemoteAvailability(skill.availability),
36020
36031
  source: "remote"
36021
36032
  };
@@ -37732,6 +37743,18 @@ class RemoteRequestError extends Error {
37732
37743
  this.name = "RemoteRequestError";
37733
37744
  }
37734
37745
  }
37746
+ var KNOWN_SKILL_LIFECYCLE_CODES = new Set(["SKILL_ARCHIVE_PROFILE_CONFLICT", "LIFECYCLE_ROLE_REQUIRED"]);
37747
+
37748
+ class RemoteSkillLifecycleError extends RemoteRequestError {
37749
+ code;
37750
+ constructor(path, status, code) {
37751
+ super(path, status);
37752
+ this.name = "RemoteSkillLifecycleError";
37753
+ const safeCode = code !== undefined && KNOWN_SKILL_LIFECYCLE_CODES.has(code) ? code : undefined;
37754
+ this.code = safeCode;
37755
+ this.message = `Skill lifecycle update was refused (HTTP ${status}${safeCode ? `, code ${safeCode}` : ""})`;
37756
+ }
37757
+ }
37735
37758
 
37736
37759
  class RemoteCreditCheckoutError extends RemoteRequestError {
37737
37760
  code;
@@ -37912,6 +37935,39 @@ class RemoteSkillsClient {
37912
37935
  })();
37913
37936
  return this.capabilities;
37914
37937
  }
37938
+ async addSkillPublishScope(keyId, expectedScopes, expectedOrgId) {
37939
+ if (!/^[A-Za-z0-9_-]+$/.test(keyId))
37940
+ throw new Error("Invalid API key id");
37941
+ if (!/^[A-Za-z0-9_-]{1,256}$/.test(expectedOrgId))
37942
+ throw new Error("Invalid expected organization id");
37943
+ 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)
37944
+ throw new Error("Invalid expected API key scopes");
37945
+ const path = `/api/v1/admin/keys/${encodeURIComponent(keyId)}/scopes`;
37946
+ const response = await this.request(path, { method: "PATCH", body: JSON.stringify({ expected_scopes: expectedScopes, add_scopes: ["skills:publish"] }) });
37947
+ if (!response.ok)
37948
+ throw new RemoteRequestError(path, response.status);
37949
+ let value;
37950
+ try {
37951
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 64 * 1024)));
37952
+ } catch {
37953
+ throw new Error("Invalid API key scope update response");
37954
+ }
37955
+ if (!value || typeof value !== "object" || Array.isArray(value))
37956
+ throw new Error("Invalid API key scope update response");
37957
+ const body = value;
37958
+ const returnedId = body.keyId;
37959
+ const orgId = body.orgId;
37960
+ const scopes = body.scopes;
37961
+ if (returnedId !== keyId || typeof orgId !== "string" || !orgId || expectedOrgId !== undefined && orgId !== expectedOrgId || !Array.isArray(scopes) || scopes.some((scope) => typeof scope !== "string") || body.updated !== true) {
37962
+ throw new Error("Invalid API key scope update response");
37963
+ }
37964
+ const returnedScopes = scopes;
37965
+ const expectedResult = expectedScopes.includes("skills:publish") ? expectedScopes : [...expectedScopes, "skills:publish"];
37966
+ if (returnedScopes.length !== expectedResult.length || expectedResult.some((scope, index) => returnedScopes[index] !== scope)) {
37967
+ throw new Error("API key scope update response did not preserve the expected scopes");
37968
+ }
37969
+ return { keyId, orgId, scopes: [...returnedScopes], updated: true };
37970
+ }
37915
37971
  async submitQuotedRun(slug2, input2 = {}, args = [], approval = {}) {
37916
37972
  runQuoteReceipt(approval.quoteReceipt);
37917
37973
  ({ input: input2, args, approval } = JSON.parse(JSON.stringify({ input: input2, args, approval })));
@@ -38429,6 +38485,19 @@ class RemoteSkillsClient {
38429
38485
  async deleteSkill(slug2) {
38430
38486
  return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}`, { method: "DELETE" });
38431
38487
  }
38488
+ async setSkillLifecycle(slug2, lifecycle, options) {
38489
+ const path = `/api/v1/skills/${encodeURIComponent(slug2)}/lifecycle`;
38490
+ const response = await this.request(path, {
38491
+ method: "PATCH",
38492
+ headers: { "If-Match": options.expectedRevisionId },
38493
+ body: JSON.stringify({ lifecycle, ...options.reason ? { reason: options.reason } : {}, ...options.replacementSlug ? { replacementSlug: options.replacementSlug } : {} })
38494
+ });
38495
+ if (!response.ok) {
38496
+ const code = await readResponseCode(response);
38497
+ throw new RemoteSkillLifecycleError(path, response.status, code);
38498
+ }
38499
+ return response.json();
38500
+ }
38432
38501
  async downloadSkillBundle(slug2) {
38433
38502
  return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}/bundle`, { method: "GET" });
38434
38503
  }
@@ -38581,9 +38650,13 @@ function normalizeSkillSummaryList(payload) {
38581
38650
  return payload.map(normalizeSkillSummary);
38582
38651
  }
38583
38652
  async function responseBodyCarriesCode(response, codes) {
38653
+ const code = await readResponseCode(response);
38654
+ return code !== undefined && codes.includes(code);
38655
+ }
38656
+ async function readResponseCode(response) {
38584
38657
  const reader = response.body?.getReader();
38585
38658
  if (!reader)
38586
- return false;
38659
+ return;
38587
38660
  const maximum = 8 * 1024;
38588
38661
  let deadline;
38589
38662
  const expired = new Promise((_, reject) => {
@@ -38598,7 +38671,7 @@ async function responseBodyCarriesCode(response, codes) {
38598
38671
  break;
38599
38672
  size += next.value.byteLength;
38600
38673
  if (size > maximum)
38601
- return false;
38674
+ return;
38602
38675
  chunks.push(next.value);
38603
38676
  }
38604
38677
  const bytes = new Uint8Array(size);
@@ -38609,11 +38682,11 @@ async function responseBodyCarriesCode(response, codes) {
38609
38682
  }
38610
38683
  const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
38611
38684
  if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Object.hasOwn(payload, "code"))
38612
- return false;
38685
+ return;
38613
38686
  const code = payload.code;
38614
- return typeof code === "string" && codes.includes(code);
38687
+ return typeof code === "string" && /^[A-Z][A-Z0-9_:-]{0,127}$/.test(code) ? code : undefined;
38615
38688
  } catch {
38616
- return false;
38689
+ return;
38617
38690
  } finally {
38618
38691
  clearTimeout(deadline);
38619
38692
  reader.cancel().catch(() => {});
@@ -38847,7 +38920,7 @@ function recordScheduleRun(id, status, targetDir) {
38847
38920
  import { createHash as createHash17 } from "crypto";
38848
38921
  var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
38849
38922
  function revisionIdOf(content) {
38850
- const canonical4 = JSON.stringify({
38923
+ const canonicalContent = {
38851
38924
  slug: content.slug,
38852
38925
  displayName: content.displayName,
38853
38926
  description: content.description,
@@ -38859,7 +38932,13 @@ function revisionIdOf(content) {
38859
38932
  skillMd: content.skillMd ?? null,
38860
38933
  bundleSha256: content.bundleSha256 ?? null,
38861
38934
  bundleByteSize: content.bundleByteSize ?? null
38862
- });
38935
+ };
38936
+ if (content.lifecycle === "archived" || content.archiveReason !== undefined || content.replacementSlug !== undefined) {
38937
+ canonicalContent.lifecycle = content.lifecycle ?? "active";
38938
+ canonicalContent.archiveReason = content.archiveReason ?? null;
38939
+ canonicalContent.replacementSlug = content.replacementSlug ?? null;
38940
+ }
38941
+ const canonical4 = JSON.stringify(canonicalContent);
38863
38942
  return createHash17("sha256").update(canonical4).digest("hex");
38864
38943
  }
38865
38944
  function revisionIdOfRecord(record7) {
@@ -1,3 +1,18 @@
1
+ import type { PureExecutionContract } from "../sdk/execution/types.js";
2
+ export interface CloudEligibility {
3
+ contractVersion: 1;
4
+ eligible: boolean;
5
+ skill: string;
6
+ version: string;
7
+ bundleDigest: string;
8
+ reason?: string;
9
+ runtimeImageDigest?: string;
10
+ executionContract?: PureExecutionContract | {
11
+ id: "pdf.v1";
12
+ };
13
+ secrets?: "none";
14
+ egress?: "deny";
15
+ }
1
16
  export interface CloudExecution {
2
17
  contractVersion: 1;
3
18
  id: string;
@@ -7,6 +22,9 @@ export interface CloudExecution {
7
22
  bundleDigest: string;
8
23
  inputDigest: string;
9
24
  runtimeImageDigest: string;
25
+ executionContract?: PureExecutionContract | {
26
+ id: "pdf.v1";
27
+ };
10
28
  status: "admitted" | "leased" | "running" | "succeeded" | "failed" | "cancelled";
11
29
  exitCode?: number;
12
30
  artifacts: {
@@ -22,6 +40,8 @@ export declare class CloudExecutionClient {
22
40
  constructor(apiOrigin: string, apiKey: string);
23
41
  static configured(): Promise<CloudExecutionClient>;
24
42
  private request;
43
+ /** Metadata only: never creates, starts or reconciles a task. */
44
+ eligibility(slug: string, version: string, bundleDigest?: string): Promise<CloudEligibility>;
25
45
  submit(slug: string, version: string, input: unknown, key: string, selection?: {
26
46
  bundleDigest: string;
27
47
  workspaceId?: string;
@@ -12,6 +12,9 @@ export interface SkillMeta {
12
12
  tags: string[];
13
13
  dependencies?: string[];
14
14
  version?: string;
15
+ /** Server registry revision and lifecycle, when supplied by a remote read. */
16
+ revisionId?: string | null;
17
+ lifecycle?: "active" | "archived";
15
18
  /**
16
19
  * Artifact class of the skill. "executable" skills carry a runnable
17
20
  * package.json/bin/src; "instruction" skills are SKILL.md-primary prose for
@@ -30,6 +30,10 @@ export declare class RemoteRequestError extends Error {
30
30
  readonly status: number;
31
31
  constructor(path: string, status: number, _statusText?: string);
32
32
  }
33
+ export declare class RemoteSkillLifecycleError extends RemoteRequestError {
34
+ readonly code?: string;
35
+ constructor(path: string, status: number, code?: string);
36
+ }
33
37
  /** Bounded checkout outcome; the key is caller-owned, never copied from a server error. */
34
38
  export declare class RemoteCreditCheckoutError extends RemoteRequestError {
35
39
  readonly code: RemoteCreditCheckoutErrorCode;
@@ -144,6 +148,8 @@ export declare class RemoteSkillsClient {
144
148
  };
145
149
  recurringConsents?: RecurringCapability;
146
150
  }>;
151
+ /** Add the single supported publication scope to an existing key, metadata only. */
152
+ addSkillPublishScope(keyId: string, expectedScopes: string[], expectedOrgId: string): Promise<Record<string, unknown>>;
147
153
  /** Quote first and fail closed when the caller has not approved the required credits. */
148
154
  submitQuotedRun(slug: string, input?: Record<string, unknown>, args?: string[], approval?: RemoteRunApproval): Promise<RemoteSkillRunContract>;
149
155
  getIdentity(): Promise<Record<string, unknown>>;
@@ -252,6 +258,11 @@ export declare class RemoteSkillsClient {
252
258
  */
253
259
  publishSkill(manifest: Record<string, unknown>, bundle?: Uint8Array, ifMatch?: string): Promise<Response>;
254
260
  deleteSkill(slug: string): Promise<Response>;
261
+ setSkillLifecycle(slug: string, lifecycle: "active" | "archived", options: {
262
+ reason?: string;
263
+ replacementSlug?: string;
264
+ expectedRevisionId: string;
265
+ }): Promise<any>;
255
266
  downloadSkillBundle(slug: string): Promise<Response>;
256
267
  /**
257
268
  * Bundle fetch for the verified-pull path. Returns the raw Response so the caller can
@@ -12,6 +12,9 @@ export interface RevisionContent {
12
12
  skillMd?: string;
13
13
  bundleSha256?: string;
14
14
  bundleByteSize?: number;
15
+ lifecycle?: "active" | "archived";
16
+ archiveReason?: string;
17
+ replacementSlug?: string;
15
18
  }
16
19
  export declare function revisionIdOf(content: RevisionContent): string;
17
20
  export declare function revisionIdOfRecord(record: RevisionContent): string;
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import type { ImageProfileRegistry } from "./image-profile.js";
12
12
  import type { RunExecutionStore } from "./storage.js";
13
- import { type FrozenAdmission, type RunLimits, type RunPolicy, type RuntimeName } from "./types.js";
13
+ import { type FrozenAdmission, type RunLimits, type RunPolicy, type RuntimeName, type PureExecutionContract } from "./types.js";
14
14
  export interface SubmitRunInput {
15
15
  tenantId: string;
16
16
  skillId: string;
@@ -24,6 +24,7 @@ export interface SubmitRunInput {
24
24
  systemDeps?: string[];
25
25
  policy?: Partial<RunPolicy>;
26
26
  limits?: Partial<RunLimits>;
27
+ executionContract?: PureExecutionContract;
27
28
  }
28
29
  export interface SubmitRunResult {
29
30
  run: FrozenAdmission;
@@ -33,6 +33,13 @@ export interface RunLimits {
33
33
  maxArtifactsBytes: number;
34
34
  maxConcurrency: number;
35
35
  }
36
+ /** Trusted, versioned adapter identity. Absent only for legacy PDF admissions. */
37
+ export interface PureExecutionContract {
38
+ id: "regex-test.v1";
39
+ descriptorDigest: string;
40
+ entrypoint: string;
41
+ entrypointDigest: string;
42
+ }
36
43
  /** Everything frozen at admission under one stable run_id. */
37
44
  export interface FrozenAdmission {
38
45
  contractVersion: number;
@@ -46,6 +53,7 @@ export interface FrozenAdmission {
46
53
  runtimeImageDigest: string;
47
54
  /** Prebuilt dependency layer tag, when the manifest's system_deps are allowlisted. */
48
55
  dependencyLayerTag: string | null;
56
+ executionContract?: PureExecutionContract;
49
57
  /** sha256 of the canonical serialization of the run input. */
50
58
  inputDigest: string;
51
59
  runtime: RuntimeName;
@@ -109,6 +117,7 @@ export interface AttemptReceipt {
109
117
  runtimeImageDigest: string;
110
118
  bundleDigest: string;
111
119
  dependencyLayerTag: string | null;
120
+ executionContract?: PureExecutionContract;
112
121
  policy: RunPolicy;
113
122
  limits: RunLimits;
114
123
  exitCode: number | null;
@@ -15,6 +15,7 @@
15
15
  * — the shared @hasna/contracts ladder — instead of reading an environment
16
16
  * variable of its own. See lib/fleet-credentials.ts.
17
17
  */
18
+ export { CloudExecutionClient, type CloudExecution, type CloudEligibility } from "../lib/cloud-executions.js";
18
19
  export { readExecutionGrantPolicy, saveExecutionGrantPolicy, resolveExecutionGrant } from "../lib/execution-grant-client.js";
19
20
  export type { ExecutionGrant, ExecutionGrantPolicy, ExecutionGrantRequest, ResolvedExecutionGrant } from "../lib/execution-grants.js";
20
21
  export { HttpProfileClient, createProfileClient, type ProfileClient, type AuthenticatedProfilePrincipal, type AuthenticatedProfileRole } from "../lib/profile-client.js";