@hasna/skills 0.10.81 → 0.10.82

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.81",
26
+ version: "0.10.82",
27
27
  description: "Skills library for AI coding agents",
28
28
  type: "module",
29
29
  bin: {
@@ -23031,7 +23031,7 @@ function hashClaudePluginCacheOrphanMarker(path, budget2, changes) {
23031
23031
  if (!before)
23032
23032
  return null;
23033
23033
  const uid = process.getuid?.();
23034
- if (!before.isFile() || uid === undefined || before.uid !== uid || before.size > 32)
23034
+ if (!before.isFile() || uid === undefined || before.uid !== uid || before.size > 32 || (before.mode & 73) !== 0)
23035
23035
  throw new Error("Invalid Claude orphan marker file or owner");
23036
23036
  const fd = openSync7(path, constants4.O_RDONLY | constants4.O_NOFOLLOW | constants4.O_NONBLOCK);
23037
23037
  try {
@@ -30444,7 +30444,7 @@ function safe2(path) {
30444
30444
  function same2(before, after) {
30445
30445
  return Boolean(after && after.isDirectory() && before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs);
30446
30446
  }
30447
- function capture(path, budget2, excludedRoots, omittedRoots = [], rows) {
30447
+ function capture(path, budget2, excludedRoots, omittedRoots = [], rows, claudeMarkers) {
30448
30448
  safe2(path);
30449
30449
  if (omittedRoots.some((root2) => path === root2 || path.startsWith(root2 + "/")))
30450
30450
  return { path, sha256: null };
@@ -30472,6 +30472,11 @@ function capture(path, budget2, excludedRoots, omittedRoots = [], rows) {
30472
30472
  need3(child2.length <= AGENT_POLICY_LIMITS.pathCharacters, "Native discovery directory path limit exceeded");
30473
30473
  const stat = lstatSync8(child2);
30474
30474
  need3(stat.isFile() || stat.isDirectory(), "Refusing symlink or special native discovery directory member");
30475
+ if (claudeMarkers && isClaudePluginCacheOrphanMarker(child2)) {
30476
+ need3(hashClaudePluginCacheOrphanMarker(child2) !== null, "Claude orphan marker changed while reading directory");
30477
+ if (claudeMarkers === "omit")
30478
+ continue;
30479
+ }
30475
30480
  const row = JSON.stringify([childRelative, stat.isDirectory() ? "directory" : "file"]) + `
30476
30481
  `;
30477
30482
  budget2.bytes += Buffer.byteLength(row);
@@ -30528,25 +30533,33 @@ function verifyDiscoveryDirectoryProjection(directories, omittedRoots, tolerance
30528
30533
  witnessChanged(new NativeDiscoveryWitnessChange("membership", directories[i].path, "Native discovery directory membership changed outside retired Codex roots"), tolerance, tolerance?.acceptsMembership(directories[i].path) === true);
30529
30534
  }
30530
30535
  }
30531
- function captureDiscoveryDirectories(paths, excludedRoots = []) {
30536
+ function captureOrdinaryDirectories(paths, excludedRoots, claudeMarkers) {
30532
30537
  need3(Array.isArray(paths) && paths.length <= AGENT_POLICY_LIMITS.discoveryDirectories && new Set(paths).size === paths.length, "Invalid native discovery directory collection");
30533
30538
  const budget2 = { entries: 0, bytes: 0 };
30534
30539
  for (const root of excludedRoots)
30535
30540
  safe2(root);
30536
- return paths.map((path) => capture(path, budget2, excludedRoots));
30541
+ return paths.map((path) => capture(path, budget2, excludedRoots, [], undefined, claudeMarkers));
30542
+ }
30543
+ function captureDiscoveryDirectories(paths, excludedRoots = []) {
30544
+ return captureOrdinaryDirectories(paths, excludedRoots, "omit");
30537
30545
  }
30538
30546
  function verifyDiscoveryDirectories(directories, excludedRoots = [], tolerance) {
30539
30547
  need3(Array.isArray(directories) && directories.length <= AGENT_POLICY_LIMITS.discoveryDirectories, "Invalid native discovery directory collection");
30540
30548
  for (const directory of directories)
30541
30549
  need3(directory && typeof directory === "object" && (directory.sha256 === null || typeof directory.sha256 === "string" && /^[a-f0-9]{64}$/.test(directory.sha256)), "Invalid native discovery directory digest");
30542
30550
  const current = captureDiscoveryDirectories(directories.map((directory) => directory.path), excludedRoots);
30551
+ let legacy;
30543
30552
  for (let i = 0;i < directories.length; i++)
30544
30553
  if (current[i].sha256 !== directories[i].sha256) {
30554
+ legacy ??= captureOrdinaryDirectories(directories.map((directory) => directory.path), excludedRoots, "retain");
30555
+ if (legacy[i].sha256 === directories[i].sha256)
30556
+ continue;
30545
30557
  witnessChanged(new NativeDiscoveryWitnessChange("membership", directories[i].path, `Native discovery directory membership changed; run skills hook install with a fresh discovery review: ${directories[i].path}`), tolerance, tolerance?.acceptsMembership(directories[i].path) === true);
30546
30558
  }
30547
30559
  }
30548
30560
  var init_agent_discovery_directories = __esm(() => {
30549
30561
  init_agent_policy_limits();
30562
+ init_claude_plugin_cache_marker();
30550
30563
  init_native_discovery_witness_change();
30551
30564
  });
30552
30565
 
@@ -41871,7 +41884,7 @@ function checkedPublicationView(value, skillId, intentId, declaration) {
41871
41884
  return invalid4();
41872
41885
  return Object.freeze({ ...value });
41873
41886
  }
41874
- async function boundedJson(url2, init, token, mutation, budget3 = 15000, signal, create = false) {
41887
+ async function boundedJson(url2, init, token, mutation, budget3 = 15000, signal, create = false, readBinding) {
41875
41888
  const controller = new AbortController;
41876
41889
  let reader;
41877
41890
  let response;
@@ -41887,8 +41900,18 @@ async function boundedJson(url2, init, token, mutation, budget3 = 15000, signal,
41887
41900
  redirect: "error",
41888
41901
  credentials: "omit",
41889
41902
  signal: controller.signal,
41890
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }
41903
+ headers: {
41904
+ "Content-Type": "application/json",
41905
+ Authorization: `Bearer ${token}`,
41906
+ ...readBinding ? {
41907
+ "x-skills-user-id": readBinding.userId,
41908
+ "x-skills-workspace-id": readBinding.organizationId,
41909
+ "x-skills-membership-id": readBinding.membershipId
41910
+ } : {}
41911
+ }
41891
41912
  });
41913
+ if (readBinding && response.ok && response.status !== 200)
41914
+ return invalid4();
41892
41915
  const length = response.headers.get("content-length");
41893
41916
  if (length !== null && (!/^\d+$/.test(length) || Number(length) > 65536))
41894
41917
  return invalid4();
@@ -41956,6 +41979,81 @@ async function boundedJson(url2, init, token, mutation, budget3 = 15000, signal,
41956
41979
  response?.body?.cancel().catch(() => {});
41957
41980
  }
41958
41981
  }
41982
+ function historyDate(value) {
41983
+ if (typeof value !== "string")
41984
+ return false;
41985
+ const parts = /^(\d{4})-(\d{2})-(\d{2})(T| )(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|[+-](\d{2})(?::(\d{2}))?)$/.exec(value);
41986
+ if (!parts)
41987
+ return false;
41988
+ const [, y, m, d, separator, h, minute, second, fraction, zone, offsetHour, offsetMinute] = parts;
41989
+ if (separator === " " ? zone === "Z" || (fraction?.length ?? 0) > 6 : zone !== "Z" && offsetMinute === undefined)
41990
+ return false;
41991
+ const year = Number(y), month = Number(m), day = Number(d);
41992
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
41993
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
41994
+ return year > 0 && month >= 1 && month <= 12 && day >= 1 && day <= days[month - 1] && Number(h) <= 23 && Number(minute) <= 59 && Number(second) <= 59 && (zone === "Z" || Number(offsetHour) <= 23 && Number(offsetMinute ?? 0) <= 59);
41995
+ }
41996
+ function privateHistory(value, organizationId, skillId, limit, cursor2) {
41997
+ if (!record10(value) || value.organizationId !== organizationId || value.skillId !== skillId || typeof value.slug !== "string" || !/^[a-z0-9-]+$/.test(value.slug) || !(value.currentVersion === null || historyText(value.currentVersion)) || !(value.currentVersionId === null || publicationUuid(value.currentVersionId)) || value.currentVersion === null !== (value.currentVersionId === null) || !(value.nextCursor === null || publicationUuid(value.nextCursor)) || !Array.isArray(value.versions) || value.versions.length > limit)
41998
+ return historyResponse();
41999
+ const seen = new Set;
42000
+ let previous = cursor2;
42001
+ const versions2 = value.versions.map((row) => {
42002
+ if (!record10(row) || !publicationUuid(row.id) || seen.has(row.id) || previous !== undefined && row.id >= previous || !historyText(row.version) || !(row.releasedAt === null || historyDate(row.releasedAt)) || !historyDate(row.createdAt) || row.id === value.currentVersionId !== (row.version === value.currentVersion))
42003
+ return historyResponse();
42004
+ seen.add(row.id);
42005
+ previous = row.id;
42006
+ return Object.freeze({ id: row.id, version: row.version, releasedAt: row.releasedAt, createdAt: row.createdAt });
42007
+ });
42008
+ if (value.nextCursor !== null && (!versions2.length || value.nextCursor !== versions2.at(-1).id))
42009
+ return historyResponse();
42010
+ return Object.freeze({
42011
+ organizationId,
42012
+ skillId,
42013
+ slug: value.slug,
42014
+ currentVersion: value.currentVersion,
42015
+ currentVersionId: value.currentVersionId,
42016
+ versions: Object.freeze(versions2),
42017
+ nextCursor: value.nextCursor
42018
+ });
42019
+ }
42020
+
42021
+ class RemotePrivateSkillHistoryClient {
42022
+ apiOrigin;
42023
+ organizationId;
42024
+ userId;
42025
+ membershipId;
42026
+ #token;
42027
+ constructor(apiUrl, session) {
42028
+ const checked = parseWorkspaceSession(session, { userId: session?.user?.id, membershipId: session?.user?.membershipId });
42029
+ this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
42030
+ this.#token = checked.token;
42031
+ this.organizationId = checked.organization.id;
42032
+ this.userId = checked.user.id;
42033
+ this.membershipId = checked.user.membershipId;
42034
+ Object.freeze(this);
42035
+ }
42036
+ async listVersions(skillId, options = {}) {
42037
+ if (!publicationUuid(skillId) || options.limit !== undefined && (!Number.isSafeInteger(options.limit) || options.limit < 1 || options.limit > 50) || options.cursor !== undefined && !publicationUuid(options.cursor) || options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 15000))
42038
+ return historyInput();
42039
+ const limit = options.limit ?? 20, cursor2 = options.cursor;
42040
+ const query = new URLSearchParams;
42041
+ if (options.limit !== undefined)
42042
+ query.set("limit", String(limit));
42043
+ if (cursor2 !== undefined)
42044
+ query.set("cursor", cursor2);
42045
+ const path = `/api/v1/skills/${skillId}/versions${query.size ? `?${query}` : ""}`;
42046
+ let value;
42047
+ try {
42048
+ value = await boundedJson(skillsApiRequestUrl(this.apiOrigin, path), { method: "GET" }, this.#token, false, options.timeoutMs, options.signal, false, this);
42049
+ } catch (error61) {
42050
+ if (error61 instanceof PrivatePublicationError && error61.code === "INVALID_PUBLICATION_RESPONSE")
42051
+ return historyResponse();
42052
+ throw new PrivatePublicationError("PRIVATE_HISTORY_UNAVAILABLE", "Unable to read private version history. Check the selected server, current membership and fresh sign-in; no skill or credential was changed.", false, error61 instanceof PrivatePublicationError ? error61.status : undefined);
42053
+ }
42054
+ return privateHistory(value, this.organizationId, skillId, limit, cursor2);
42055
+ }
42056
+ }
41959
42057
 
41960
42058
  class RemotePrivatePublicationsClient {
41961
42059
  apiOrigin;
@@ -42129,7 +42227,11 @@ var PRIVATE_PUBLICATION_MAX_BYTES, skillCreateFailures, failures2, PrivatePublic
42129
42227
  throw new PrivatePublicationError("INVALID_PUBLICATION_INPUT", "Invalid publication input or recovery data.");
42130
42228
  }, invalid4 = () => {
42131
42229
  throw new PrivatePublicationError("INVALID_PUBLICATION_RESPONSE", "The server returned an invalid publication result.");
42132
- }, publicationUuid = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v), hash4 = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v), record10 = (v) => !!v && typeof v === "object" && !Array.isArray(v), exact = (v, keys2) => record10(v) && Object.keys(v).sort().join(",") === keys2.sort().join(","), date5 = (v) => typeof v === "string" && /^\d{4}-\d\d-\d\dT[0-9:.]+(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(v) && Number.isFinite(Date.parse(v)), publicationSha256 = (bytes) => createHash27("sha256").update(bytes).digest("hex");
42230
+ }, publicationUuid = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v), hash4 = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v), record10 = (v) => !!v && typeof v === "object" && !Array.isArray(v), exact = (v, keys2) => record10(v) && Object.keys(v).sort().join(",") === keys2.sort().join(","), date5 = (v) => typeof v === "string" && /^\d{4}-\d\d-\d\dT[0-9:.]+(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(v) && Number.isFinite(Date.parse(v)), publicationSha256 = (bytes) => createHash27("sha256").update(bytes).digest("hex"), historyInput = () => {
42231
+ throw new PrivatePublicationError("INVALID_PRIVATE_HISTORY_INPUT", "Provide a private skill UUID, limit from 1 to 50, and an optional UUID cursor; timeoutMs must be from 1 to 15000.");
42232
+ }, historyResponse = () => {
42233
+ throw new PrivatePublicationError("INVALID_PRIVATE_HISTORY_RESPONSE", "The server returned invalid private version history.");
42234
+ }, historyText = (value) => typeof value === "string" && value.length > 0 && value.length <= 128 && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value);
42133
42235
  var init_remote_private_publications = __esm(() => {
42134
42236
  init_fleet_credentials();
42135
42237
  init_remote_workspace_selection();
@@ -42244,6 +42346,10 @@ class RemoteSkillsAuthClient {
42244
42346
  const origin = this.apiOrigin, captured = workspaceContext(context);
42245
42347
  return new RemotePrivatePublicationsClient(origin, await this.switchWorkspace(email4, code, captured));
42246
42348
  }
42349
+ async openPrivateSkillHistory(email4, code, context) {
42350
+ const origin = this.apiOrigin, captured = workspaceContext(context);
42351
+ return new RemotePrivateSkillHistoryClient(origin, await this.switchWorkspace(email4, code, captured));
42352
+ }
42247
42353
  requestInvitationEmailChallenge(input2) {
42248
42354
  return requestInvitationEmail(this.apiOrigin, "challenge", input2);
42249
42355
  }
@@ -48574,7 +48680,7 @@ var init_remote_invitation_tools = __esm(() => {
48574
48680
  });
48575
48681
 
48576
48682
  // src/lib/private-publication-customer.ts
48577
- function preparePrivatePublicationSession(requested) {
48683
+ function preparePrivateSession(requested, action, open2) {
48578
48684
  const selectors = [
48579
48685
  "HASNA_PROFILE",
48580
48686
  "HASNA_HOME",
@@ -48588,7 +48694,7 @@ function preparePrivatePublicationSession(requested) {
48588
48694
  "SKILLS_API_KEY"
48589
48695
  ];
48590
48696
  const before = new Map(selectors.map((key2) => [key2, process.env[key2]]));
48591
- const profile = selectedSkillsProfile(process.env), pending = prepareProfileWorkspace("Manage private publications");
48697
+ const profile = selectedSkillsProfile(process.env), pending = prepareProfileWorkspace(action);
48592
48698
  const captured = requested ? { ...requested } : undefined;
48593
48699
  const unchanged3 = () => {
48594
48700
  if (selectors.some((key2) => process.env[key2] !== before.get(key2)) || selectedSkillsProfile(process.env) !== profile)
@@ -48602,12 +48708,15 @@ function preparePrivatePublicationSession(requested) {
48602
48708
  throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide the observed user and membership IDs, or select an enrolled workspace profile.");
48603
48709
  unchanged3();
48604
48710
  target.unchanged();
48605
- const client = await new RemoteSkillsAuthClient(target.origin).openPrivatePublications(email5, code2, context2);
48711
+ const client = await open2(new RemoteSkillsAuthClient(target.origin), email5, code2, context2);
48606
48712
  unchanged3();
48607
48713
  target.unchanged();
48608
48714
  return client;
48609
48715
  } };
48610
48716
  }
48717
+ function preparePrivatePublicationSession(requested) {
48718
+ return preparePrivateSession(requested, "Manage private publications", (auth, email5, code2, context2) => auth.openPrivatePublications(email5, code2, context2));
48719
+ }
48611
48720
  async function privatePublicationSession(email5, code2, requested) {
48612
48721
  return preparePrivatePublicationSession(requested).verify(email5, code2);
48613
48722
  }
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.81",
28
+ version: "0.10.82",
29
29
  description: "Skills library for AI coding agents",
30
30
  type: "module",
31
31
  bin: {
package/bin/server.js CHANGED
@@ -24140,7 +24140,7 @@ restoreLaunchCwd();
24140
24140
  // package.json
24141
24141
  var package_default = {
24142
24142
  name: "@hasna/skills",
24143
- version: "0.10.81",
24143
+ version: "0.10.82",
24144
24144
  description: "Skills library for AI coding agents",
24145
24145
  type: "module",
24146
24146
  bin: {
package/bin/worker.js CHANGED
@@ -24143,7 +24143,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24143
24143
  // package.json
24144
24144
  var package_default = {
24145
24145
  name: "@hasna/skills",
24146
- version: "0.10.81",
24146
+ version: "0.10.82",
24147
24147
  description: "Skills library for AI coding agents",
24148
24148
  type: "module",
24149
24149
  bin: {
package/dist/index.d.ts CHANGED
@@ -53,7 +53,7 @@ export { WorkspaceLeaveInputError, RemoteWorkspaceLeaveError, RemoteWorkspaceLea
53
53
  export type { RemoteWorkspaceInvitation, RemoteWorkspaceInvitationsPage, RemoteWorkspaceInvitationResult, RemoteWorkspaceInvitationAcceptance, ListRemoteWorkspaceInvitations, IssueRemoteWorkspaceInvitation, ResendRemoteWorkspaceInvitation, RevokeRemoteWorkspaceInvitation, AcceptRemoteWorkspaceInvitation, RemoteWorkspaceInvitationErrorCode } from "./lib/remote-invitations.js";
54
54
  export { WorkspaceInvitationInputError, RemoteWorkspaceInvitationError, RemoteWorkspaceInvitationUnconfirmedError, RemoteWorkspaceInvitationReadError } from "./lib/remote-invitations.js";
55
55
  export { InvitationEmailInputError, RemoteInvitationEmailError, RemoteInvitationEmailUnconfirmedError, type RequestInvitationEmailChallenge, type AcceptInvitationEmailChallenge, type RemoteInvitationEmailChallenge, type RemoteInvitationEmailAcceptance, type RemoteInvitationEmailErrorCode } from "./lib/remote-invitation-recovery.js";
56
- export { RemotePrivatePublicationsClient, PrivatePublicationError, PRIVATE_PUBLICATION_MAX_BYTES, type CreatePrivateSkillInput, type CreatedPrivateSkill, type PrivatePublicationDeclaration, type PrivatePublicationView, type PrivatePublicationState, type PrivatePublishingCapability } from "./lib/remote-private-publications.js";
56
+ export { RemotePrivatePublicationsClient, RemotePrivateSkillHistoryClient, PrivatePublicationError, PRIVATE_PUBLICATION_MAX_BYTES, type PrivateSkillVersion, type PrivateSkillVersionHistory, type PrivateSkillVersionHistoryOptions, type CreatePrivateSkillInput, type CreatedPrivateSkill, type PrivatePublicationDeclaration, type PrivatePublicationView, type PrivatePublicationState, type PrivatePublishingCapability } from "./lib/remote-private-publications.js";
57
57
  export { preparePrivatePublication, readPrivatePublicationRecovery, continuePrivatePublication, inspectPrivatePublication, type PrivatePublicationRecovery, type PrivatePublicationResult } from "./lib/private-publication-recovery.js";
58
58
  export { HttpProfileClient, createProfileClient, type ProfileClient, type AuthenticatedProfilePrincipal, type AuthenticatedProfileRole } from "./lib/profile-client.js";
59
59
  export type { SkillSelection, SkillProfile, ResolvedSkillSelection, ResolvedSkillProfile, StationSkillStateInput, StationSkillState } from "./types/skill-selection.js";
package/dist/index.js CHANGED
@@ -9643,7 +9643,7 @@ function hashClaudePluginCacheOrphanMarker(path, budget2, changes) {
9643
9643
  if (!before)
9644
9644
  return null;
9645
9645
  const uid = process.getuid?.();
9646
- if (!before.isFile() || uid === undefined || before.uid !== uid || before.size > 32)
9646
+ if (!before.isFile() || uid === undefined || before.uid !== uid || before.size > 32 || (before.mode & 73) !== 0)
9647
9647
  throw new Error("Invalid Claude orphan marker file or owner");
9648
9648
  const fd = openSync3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
9649
9649
  try {
@@ -10269,7 +10269,7 @@ function safe2(path) {
10269
10269
  function same2(before, after) {
10270
10270
  return Boolean(after && after.isDirectory() && before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs);
10271
10271
  }
10272
- function capture(path, budget2, excludedRoots, omittedRoots = [], rows) {
10272
+ function capture(path, budget2, excludedRoots, omittedRoots = [], rows, claudeMarkers) {
10273
10273
  safe2(path);
10274
10274
  if (omittedRoots.some((root2) => path === root2 || path.startsWith(root2 + "/")))
10275
10275
  return { path, sha256: null };
@@ -10297,6 +10297,11 @@ function capture(path, budget2, excludedRoots, omittedRoots = [], rows) {
10297
10297
  need3(child2.length <= AGENT_POLICY_LIMITS.pathCharacters, "Native discovery directory path limit exceeded");
10298
10298
  const stat = lstatSync7(child2);
10299
10299
  need3(stat.isFile() || stat.isDirectory(), "Refusing symlink or special native discovery directory member");
10300
+ if (claudeMarkers && isClaudePluginCacheOrphanMarker(child2)) {
10301
+ need3(hashClaudePluginCacheOrphanMarker(child2) !== null, "Claude orphan marker changed while reading directory");
10302
+ if (claudeMarkers === "omit")
10303
+ continue;
10304
+ }
10300
10305
  const row = JSON.stringify([childRelative, stat.isDirectory() ? "directory" : "file"]) + `
10301
10306
  `;
10302
10307
  budget2.bytes += Buffer.byteLength(row);
@@ -10353,20 +10358,27 @@ function verifyDiscoveryDirectoryProjection(directories, omittedRoots, tolerance
10353
10358
  witnessChanged(new NativeDiscoveryWitnessChange("membership", directories[i].path, "Native discovery directory membership changed outside retired Codex roots"), tolerance, tolerance?.acceptsMembership(directories[i].path) === true);
10354
10359
  }
10355
10360
  }
10356
- function captureDiscoveryDirectories(paths, excludedRoots = []) {
10361
+ function captureOrdinaryDirectories(paths, excludedRoots, claudeMarkers) {
10357
10362
  need3(Array.isArray(paths) && paths.length <= AGENT_POLICY_LIMITS.discoveryDirectories && new Set(paths).size === paths.length, "Invalid native discovery directory collection");
10358
10363
  const budget2 = { entries: 0, bytes: 0 };
10359
10364
  for (const root of excludedRoots)
10360
10365
  safe2(root);
10361
- return paths.map((path) => capture(path, budget2, excludedRoots));
10366
+ return paths.map((path) => capture(path, budget2, excludedRoots, [], undefined, claudeMarkers));
10367
+ }
10368
+ function captureDiscoveryDirectories(paths, excludedRoots = []) {
10369
+ return captureOrdinaryDirectories(paths, excludedRoots, "omit");
10362
10370
  }
10363
10371
  function verifyDiscoveryDirectories(directories, excludedRoots = [], tolerance) {
10364
10372
  need3(Array.isArray(directories) && directories.length <= AGENT_POLICY_LIMITS.discoveryDirectories, "Invalid native discovery directory collection");
10365
10373
  for (const directory of directories)
10366
10374
  need3(directory && typeof directory === "object" && (directory.sha256 === null || typeof directory.sha256 === "string" && /^[a-f0-9]{64}$/.test(directory.sha256)), "Invalid native discovery directory digest");
10367
10375
  const current = captureDiscoveryDirectories(directories.map((directory) => directory.path), excludedRoots);
10376
+ let legacy;
10368
10377
  for (let i = 0;i < directories.length; i++)
10369
10378
  if (current[i].sha256 !== directories[i].sha256) {
10379
+ legacy ??= captureOrdinaryDirectories(directories.map((directory) => directory.path), excludedRoots, "retain");
10380
+ if (legacy[i].sha256 === directories[i].sha256)
10381
+ continue;
10370
10382
  witnessChanged(new NativeDiscoveryWitnessChange("membership", directories[i].path, `Native discovery directory membership changed; run skills hook install with a fresh discovery review: ${directories[i].path}`), tolerance, tolerance?.acceptsMembership(directories[i].path) === true);
10371
10383
  }
10372
10384
  }
@@ -15852,7 +15864,7 @@ import { basename as basename14, dirname as dirname25, isAbsolute as isAbsolute2
15852
15864
  import { homedir as homedir5 } from "os";
15853
15865
  import { isAbsolute as isAbsolute25, join as join37, resolve as resolve24 } from "path";
15854
15866
  // package.json
15855
- var version = "0.10.81";
15867
+ var version = "0.10.82";
15856
15868
  var package_default = {
15857
15869
  name: "@hasna/skills",
15858
15870
  version,
@@ -49531,7 +49543,7 @@ function checkedPublicationView(value, skillId, intentId, declaration) {
49531
49543
  return invalid4();
49532
49544
  return Object.freeze({ ...value });
49533
49545
  }
49534
- async function boundedJson(url2, init, token, mutation, budget3 = 15000, signal, create = false) {
49546
+ async function boundedJson(url2, init, token, mutation, budget3 = 15000, signal, create = false, readBinding) {
49535
49547
  const controller = new AbortController;
49536
49548
  let reader;
49537
49549
  let response;
@@ -49547,8 +49559,18 @@ async function boundedJson(url2, init, token, mutation, budget3 = 15000, signal,
49547
49559
  redirect: "error",
49548
49560
  credentials: "omit",
49549
49561
  signal: controller.signal,
49550
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }
49562
+ headers: {
49563
+ "Content-Type": "application/json",
49564
+ Authorization: `Bearer ${token}`,
49565
+ ...readBinding ? {
49566
+ "x-skills-user-id": readBinding.userId,
49567
+ "x-skills-workspace-id": readBinding.organizationId,
49568
+ "x-skills-membership-id": readBinding.membershipId
49569
+ } : {}
49570
+ }
49551
49571
  });
49572
+ if (readBinding && response.ok && response.status !== 200)
49573
+ return invalid4();
49552
49574
  const length = response.headers.get("content-length");
49553
49575
  if (length !== null && (!/^\d+$/.test(length) || Number(length) > 65536))
49554
49576
  return invalid4();
@@ -49616,6 +49638,88 @@ async function boundedJson(url2, init, token, mutation, budget3 = 15000, signal,
49616
49638
  response?.body?.cancel().catch(() => {});
49617
49639
  }
49618
49640
  }
49641
+ var historyInput = () => {
49642
+ throw new PrivatePublicationError("INVALID_PRIVATE_HISTORY_INPUT", "Provide a private skill UUID, limit from 1 to 50, and an optional UUID cursor; timeoutMs must be from 1 to 15000.");
49643
+ };
49644
+ var historyResponse = () => {
49645
+ throw new PrivatePublicationError("INVALID_PRIVATE_HISTORY_RESPONSE", "The server returned invalid private version history.");
49646
+ };
49647
+ var historyText = (value) => typeof value === "string" && value.length > 0 && value.length <= 128 && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value);
49648
+ function historyDate(value) {
49649
+ if (typeof value !== "string")
49650
+ return false;
49651
+ const parts = /^(\d{4})-(\d{2})-(\d{2})(T| )(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|[+-](\d{2})(?::(\d{2}))?)$/.exec(value);
49652
+ if (!parts)
49653
+ return false;
49654
+ const [, y, m, d, separator, h, minute, second, fraction, zone, offsetHour, offsetMinute] = parts;
49655
+ if (separator === " " ? zone === "Z" || (fraction?.length ?? 0) > 6 : zone !== "Z" && offsetMinute === undefined)
49656
+ return false;
49657
+ const year = Number(y), month = Number(m), day = Number(d);
49658
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
49659
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
49660
+ return year > 0 && month >= 1 && month <= 12 && day >= 1 && day <= days[month - 1] && Number(h) <= 23 && Number(minute) <= 59 && Number(second) <= 59 && (zone === "Z" || Number(offsetHour) <= 23 && Number(offsetMinute ?? 0) <= 59);
49661
+ }
49662
+ function privateHistory(value, organizationId, skillId, limit, cursor2) {
49663
+ if (!record11(value) || value.organizationId !== organizationId || value.skillId !== skillId || typeof value.slug !== "string" || !/^[a-z0-9-]+$/.test(value.slug) || !(value.currentVersion === null || historyText(value.currentVersion)) || !(value.currentVersionId === null || publicationUuid(value.currentVersionId)) || value.currentVersion === null !== (value.currentVersionId === null) || !(value.nextCursor === null || publicationUuid(value.nextCursor)) || !Array.isArray(value.versions) || value.versions.length > limit)
49664
+ return historyResponse();
49665
+ const seen = new Set;
49666
+ let previous = cursor2;
49667
+ const versions2 = value.versions.map((row) => {
49668
+ if (!record11(row) || !publicationUuid(row.id) || seen.has(row.id) || previous !== undefined && row.id >= previous || !historyText(row.version) || !(row.releasedAt === null || historyDate(row.releasedAt)) || !historyDate(row.createdAt) || row.id === value.currentVersionId !== (row.version === value.currentVersion))
49669
+ return historyResponse();
49670
+ seen.add(row.id);
49671
+ previous = row.id;
49672
+ return Object.freeze({ id: row.id, version: row.version, releasedAt: row.releasedAt, createdAt: row.createdAt });
49673
+ });
49674
+ if (value.nextCursor !== null && (!versions2.length || value.nextCursor !== versions2.at(-1).id))
49675
+ return historyResponse();
49676
+ return Object.freeze({
49677
+ organizationId,
49678
+ skillId,
49679
+ slug: value.slug,
49680
+ currentVersion: value.currentVersion,
49681
+ currentVersionId: value.currentVersionId,
49682
+ versions: Object.freeze(versions2),
49683
+ nextCursor: value.nextCursor
49684
+ });
49685
+ }
49686
+
49687
+ class RemotePrivateSkillHistoryClient {
49688
+ apiOrigin;
49689
+ organizationId;
49690
+ userId;
49691
+ membershipId;
49692
+ #token;
49693
+ constructor(apiUrl, session) {
49694
+ const checked = parseWorkspaceSession(session, { userId: session?.user?.id, membershipId: session?.user?.membershipId });
49695
+ this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
49696
+ this.#token = checked.token;
49697
+ this.organizationId = checked.organization.id;
49698
+ this.userId = checked.user.id;
49699
+ this.membershipId = checked.user.membershipId;
49700
+ Object.freeze(this);
49701
+ }
49702
+ async listVersions(skillId, options = {}) {
49703
+ if (!publicationUuid(skillId) || options.limit !== undefined && (!Number.isSafeInteger(options.limit) || options.limit < 1 || options.limit > 50) || options.cursor !== undefined && !publicationUuid(options.cursor) || options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 15000))
49704
+ return historyInput();
49705
+ const limit = options.limit ?? 20, cursor2 = options.cursor;
49706
+ const query = new URLSearchParams;
49707
+ if (options.limit !== undefined)
49708
+ query.set("limit", String(limit));
49709
+ if (cursor2 !== undefined)
49710
+ query.set("cursor", cursor2);
49711
+ const path = `/api/v1/skills/${skillId}/versions${query.size ? `?${query}` : ""}`;
49712
+ let value;
49713
+ try {
49714
+ value = await boundedJson(skillsApiRequestUrl(this.apiOrigin, path), { method: "GET" }, this.#token, false, options.timeoutMs, options.signal, false, this);
49715
+ } catch (error61) {
49716
+ if (error61 instanceof PrivatePublicationError && error61.code === "INVALID_PUBLICATION_RESPONSE")
49717
+ return historyResponse();
49718
+ throw new PrivatePublicationError("PRIVATE_HISTORY_UNAVAILABLE", "Unable to read private version history. Check the selected server, current membership and fresh sign-in; no skill or credential was changed.", false, error61 instanceof PrivatePublicationError ? error61.status : undefined);
49719
+ }
49720
+ return privateHistory(value, this.organizationId, skillId, limit, cursor2);
49721
+ }
49722
+ }
49619
49723
 
49620
49724
  class RemotePrivatePublicationsClient {
49621
49725
  apiOrigin;
@@ -49868,6 +49972,10 @@ class RemoteSkillsAuthClient {
49868
49972
  const origin = this.apiOrigin, captured = workspaceContext(context);
49869
49973
  return new RemotePrivatePublicationsClient(origin, await this.switchWorkspace(email4, code, captured));
49870
49974
  }
49975
+ async openPrivateSkillHistory(email4, code, context) {
49976
+ const origin = this.apiOrigin, captured = workspaceContext(context);
49977
+ return new RemotePrivateSkillHistoryClient(origin, await this.switchWorkspace(email4, code, captured));
49978
+ }
49871
49979
  requestInvitationEmailChallenge(input2) {
49872
49980
  return requestInvitationEmail(this.apiOrigin, "challenge", input2);
49873
49981
  }
@@ -53463,6 +53571,7 @@ export {
53463
53571
  RemoteRouteUnsupportedError,
53464
53572
  RemoteRequestError,
53465
53573
  RemoteQuoteUnavailableError,
53574
+ RemotePrivateSkillHistoryClient,
53466
53575
  RemotePrivatePublicationsClient,
53467
53576
  RemoteInvitationEmailUnconfirmedError,
53468
53577
  RemoteInvitationEmailError,
@@ -15,7 +15,8 @@ export declare function captureDiscoveryDirectoryProjection(paths: string[], omi
15
15
  * membership change is a NativeDiscoveryWitnessChange: thrown, unless the live
16
16
  * Codex prompt hook's tolerance accepts it. Invalid inventories always throw. */
17
17
  export declare function verifyDiscoveryDirectoryProjection(directories: DiscoveryDirectoryProjection[], omittedRoots: readonly string[], tolerance?: NativeDiscoveryWitnessTolerance): void;
18
- /** Read-only capture; callers must review both membership and source bytes. */
18
+ /** Read-only membership capture, excluding validated Claude cleanup markers.
19
+ * Callers must still review source bytes; projected inventories remain exact. */
19
20
  export declare function captureDiscoveryDirectories(paths: string[], excludedRoots?: readonly string[]): DiscoveryDirectory[];
20
21
  /** A membership change is a NativeDiscoveryWitnessChange: thrown, unless the
21
22
  * live Codex prompt hook's tolerance accepts it. Invalid collections always throw. */
@@ -1,10 +1,13 @@
1
1
  import { RemotePrivatePublicationsClient } from "./remote-private-publications.js";
2
2
  import type { RemoteWorkspaceContext } from "./remote-workspace-selection.js";
3
- /** Capture before code input, so a later profile/origin change cannot retarget consent. */
4
3
  export declare function preparePrivatePublicationSession(requested?: RemoteWorkspaceContext): {
5
4
  origin: string;
6
5
  verify(email: string, code: string): Promise<RemotePrivatePublicationsClient>;
7
6
  };
7
+ export declare function preparePrivateSkillHistorySession(requested?: RemoteWorkspaceContext): {
8
+ origin: string;
9
+ verify(email: string, code: string): Promise<import("./remote-private-publications.js").RemotePrivateSkillHistoryClient>;
10
+ };
8
11
  /** Neither the session nor a signed upload capability is saved to host credentials. */
9
12
  export declare function privatePublicationSession(email: string, code: string, requested?: RemoteWorkspaceContext): Promise<RemotePrivatePublicationsClient>;
10
13
  export declare function privatePublicationCustomerError(error: unknown): {
@@ -1,5 +1,5 @@
1
1
  import { type RequestInvitationEmailChallenge, type AcceptInvitationEmailChallenge } from "./remote-invitation-recovery.js";
2
- import { RemotePrivatePublicationsClient } from "./remote-private-publications.js";
2
+ import { RemotePrivatePublicationsClient, RemotePrivateSkillHistoryClient } from "./remote-private-publications.js";
3
3
  import { type ListRemoteWorkspaceInvitations, type IssueRemoteWorkspaceInvitation, type ResendRemoteWorkspaceInvitation, type RevokeRemoteWorkspaceInvitation, type AcceptRemoteWorkspaceInvitation } from "./remote-invitations.js";
4
4
  import { type LeaveRemoteWorkspace } from "./remote-workspace-leave.js";
5
5
  import { type RemoteWorkspaceContext, type RemoteWorkspaceSession, type RemoteAccountWorkspaceDiscovery } from "./remote-workspace-selection.js";
@@ -26,6 +26,8 @@ export declare class RemoteSkillsAuthClient {
26
26
  constructor(apiUrl: string);
27
27
  /** One fresh workspace-bound session for an entire publication. No credentials are saved. */
28
28
  openPrivatePublications(email: string, code: string, context: RemoteWorkspaceContext): Promise<RemotePrivatePublicationsClient>;
29
+ /** Fresh workspace-bound, read-only private history session; no key is created or saved. */
30
+ openPrivateSkillHistory(email: string, code: string, context: RemoteWorkspaceContext): Promise<RemotePrivateSkillHistoryClient>;
29
31
  requestInvitationEmailChallenge(input: RequestInvitationEmailChallenge): Promise<import("./remote-invitation-recovery.js").RemoteInvitationEmailChallenge>;
30
32
  acceptInvitationEmailChallenge(input: AcceptInvitationEmailChallenge): Promise<import("./remote-invitation-recovery.js").RemoteInvitationEmailAcceptance>;
31
33
  requestCode(email: string): Promise<any>;
@@ -64,6 +64,41 @@ export declare const publicationSha256: (bytes: Uint8Array | string) => string;
64
64
  export declare function checkedPrivateSkillCreate(value: unknown): Omit<Required<CreatePrivateSkillInput>, "confirm">;
65
65
  export declare function checkedPublicationDeclaration(value: unknown): PrivatePublicationDeclaration;
66
66
  export declare function checkedPublicationView(value: unknown, skillId: string, intentId?: string, declaration?: PrivatePublicationDeclaration): PrivatePublicationView;
67
+ /** Metadata-only private history uses the existing interactive UUID route,
68
+ * distinct from the public slug/API-key version contract. */
69
+ export interface PrivateSkillVersion {
70
+ id: string;
71
+ version: string;
72
+ releasedAt: string | null;
73
+ createdAt: string;
74
+ }
75
+ export interface PrivateSkillVersionHistory {
76
+ organizationId: string;
77
+ skillId: string;
78
+ slug: string;
79
+ currentVersion: string | null;
80
+ currentVersionId: string | null;
81
+ versions: readonly PrivateSkillVersion[];
82
+ nextCursor: string | null;
83
+ }
84
+ export interface PrivateSkillVersionHistoryOptions {
85
+ limit?: number;
86
+ cursor?: string;
87
+ timeoutMs?: number;
88
+ signal?: AbortSignal;
89
+ }
90
+ /** Read-only session consumer; permits viewer history without granting any publication action.
91
+ * Tokens stay in a private field and are never persisted or serialized. */
92
+ export declare class RemotePrivateSkillHistoryClient {
93
+ #private;
94
+ readonly apiOrigin: string;
95
+ readonly organizationId: string;
96
+ readonly userId: string;
97
+ readonly membershipId: string;
98
+ constructor(apiUrl: string, session: RemoteWorkspaceSession);
99
+ /** One page and one GET. Follow nextCursor explicitly; an error never prints partial history. */
100
+ listVersions(skillId: string, options?: PrivateSkillVersionHistoryOptions): Promise<PrivateSkillVersionHistory>;
101
+ }
67
102
  /** Explicit hosted contract, unrelated to registry publishSkill and its content revisions.
68
103
  * Session tokens stay in private fields and are never written to credentials. */
69
104
  export declare class RemotePrivatePublicationsClient {
@@ -49,7 +49,7 @@ var __require = import.meta.require;
49
49
  // src/runtime-prerequisites.ts
50
50
  import { isAbsolute as isAbsolute7 } from "path";
51
51
  // package.json
52
- var version = "0.10.81";
52
+ var version = "0.10.82";
53
53
  var package_default = {
54
54
  name: "@hasna/skills",
55
55
  version,
@@ -2554,7 +2554,7 @@ function hashClaudePluginCacheOrphanMarker(path, budget, changes) {
2554
2554
  if (!before)
2555
2555
  return null;
2556
2556
  const uid = process.getuid?.();
2557
- if (!before.isFile() || uid === undefined || before.uid !== uid || before.size > 32)
2557
+ if (!before.isFile() || uid === undefined || before.uid !== uid || before.size > 32 || (before.mode & 73) !== 0)
2558
2558
  throw new Error("Invalid Claude orphan marker file or owner");
2559
2559
  const fd = openSync4(path, constants4.O_RDONLY | constants4.O_NOFOLLOW | constants4.O_NONBLOCK);
2560
2560
  try {
@@ -61,7 +61,7 @@ export { WorkspaceLeaveInputError, RemoteWorkspaceLeaveError, RemoteWorkspaceLea
61
61
  export type { RemoteWorkspaceInvitation, RemoteWorkspaceInvitationsPage, RemoteWorkspaceInvitationResult, RemoteWorkspaceInvitationAcceptance, ListRemoteWorkspaceInvitations, IssueRemoteWorkspaceInvitation, ResendRemoteWorkspaceInvitation, RevokeRemoteWorkspaceInvitation, AcceptRemoteWorkspaceInvitation, RemoteWorkspaceInvitationErrorCode } from "../lib/remote-invitations.js";
62
62
  export { WorkspaceInvitationInputError, RemoteWorkspaceInvitationError, RemoteWorkspaceInvitationUnconfirmedError, RemoteWorkspaceInvitationReadError } from "../lib/remote-invitations.js";
63
63
  export { InvitationEmailInputError, RemoteInvitationEmailError, RemoteInvitationEmailUnconfirmedError, type RequestInvitationEmailChallenge, type AcceptInvitationEmailChallenge, type RemoteInvitationEmailChallenge, type RemoteInvitationEmailAcceptance, type RemoteInvitationEmailErrorCode } from "../lib/remote-invitation-recovery.js";
64
- export { RemotePrivatePublicationsClient, PrivatePublicationError, PRIVATE_PUBLICATION_MAX_BYTES, type CreatePrivateSkillInput, type CreatedPrivateSkill, type PrivatePublicationDeclaration, type PrivatePublicationView, type PrivatePublicationState, type PrivatePublishingCapability } from "../lib/remote-private-publications.js";
64
+ export { RemotePrivatePublicationsClient, RemotePrivateSkillHistoryClient, PrivatePublicationError, PRIVATE_PUBLICATION_MAX_BYTES, type PrivateSkillVersion, type PrivateSkillVersionHistory, type PrivateSkillVersionHistoryOptions, type CreatePrivateSkillInput, type CreatedPrivateSkill, type PrivatePublicationDeclaration, type PrivatePublicationView, type PrivatePublicationState, type PrivatePublishingCapability } from "../lib/remote-private-publications.js";
65
65
  export { preparePrivatePublication, readPrivatePublicationRecovery, continuePrivatePublication, inspectPrivatePublication, type PrivatePublicationRecovery, type PrivatePublicationResult } from "../lib/private-publication-recovery.js";
66
66
  export { createSkillOperationClient, SkillOperationClientError, SKILL_OPERATION_LIMITS, type SkillOperationClient, type SkillOperationTransport, type SkillOperationRequest, type SkillOperationResult, type SkillOperationJson, type SkillOperationRefusal, type SkillOperationClientErrorCode } from "./operations.js";
67
67
  export type { RemoteCreditCheckoutOptions, RemoteCreditCheckout, RemoteCreditCheckoutErrorCode } from "../lib/remote-credit-checkout.js";