@hasna/skills 0.10.11 → 0.10.14

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/sdk/index.js CHANGED
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
32373
32373
  // package.json
32374
32374
  var package_default = {
32375
32375
  name: "@hasna/skills",
32376
- version: "0.10.11",
32376
+ version: "0.10.14",
32377
32377
  description: "Skills library for AI coding agents",
32378
32378
  type: "module",
32379
32379
  bin: {
@@ -40984,6 +40984,196 @@ export default async ({ directory }) => {
40984
40984
  // src/lib/agent-codex.ts
40985
40985
  import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
40986
40986
  var CODEX_SKILL_CONFIG_SECTIONS = /^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm;
40987
+ var codexPathConfigRefusal = () => new Error("Codex skill path controls require ordinary [[skills.config]] TOML tables or a native single-line inline array; preserve and review unsupported inline, dotted, or malformed definitions before running skills hook install");
40988
+ function splitTopLevel(value, delimiter) {
40989
+ const result = [];
40990
+ let start = 0, square = 0, curly = 0, quote2 = null, escaped = false;
40991
+ for (let index = 0;index < value.length; index++) {
40992
+ const char = value[index];
40993
+ if (quote2 === "double") {
40994
+ if (escaped)
40995
+ escaped = false;
40996
+ else if (char === "\\")
40997
+ escaped = true;
40998
+ else if (char === '"')
40999
+ quote2 = null;
41000
+ continue;
41001
+ }
41002
+ if (quote2 === "literal") {
41003
+ if (char === "'")
41004
+ quote2 = null;
41005
+ continue;
41006
+ }
41007
+ if (char === '"') {
41008
+ quote2 = "double";
41009
+ continue;
41010
+ }
41011
+ if (char === "'") {
41012
+ quote2 = "literal";
41013
+ continue;
41014
+ }
41015
+ if (char === "[")
41016
+ square++;
41017
+ else if (char === "]") {
41018
+ if (--square < 0)
41019
+ return null;
41020
+ } else if (char === "{")
41021
+ curly++;
41022
+ else if (char === "}") {
41023
+ if (--curly < 0)
41024
+ return null;
41025
+ } else if (char === delimiter && square === 0 && curly === 0) {
41026
+ result.push(value.slice(start, index));
41027
+ start = index + 1;
41028
+ }
41029
+ }
41030
+ if (quote2 !== null || square !== 0 || curly !== 0)
41031
+ return null;
41032
+ result.push(value.slice(start));
41033
+ return result;
41034
+ }
41035
+ function inlineArrayEnd(value) {
41036
+ if (!value.startsWith("["))
41037
+ return null;
41038
+ let square = 0, curly = 0, quote2 = null, escaped = false;
41039
+ for (let index = 0;index < value.length; index++) {
41040
+ const char = value[index];
41041
+ if (quote2 === "double") {
41042
+ if (escaped)
41043
+ escaped = false;
41044
+ else if (char === "\\")
41045
+ escaped = true;
41046
+ else if (char === '"')
41047
+ quote2 = null;
41048
+ continue;
41049
+ }
41050
+ if (quote2 === "literal") {
41051
+ if (char === "'")
41052
+ quote2 = null;
41053
+ continue;
41054
+ }
41055
+ if (char === '"') {
41056
+ quote2 = "double";
41057
+ continue;
41058
+ }
41059
+ if (char === "'") {
41060
+ quote2 = "literal";
41061
+ continue;
41062
+ }
41063
+ if (char === "[")
41064
+ square++;
41065
+ else if (char === "]") {
41066
+ if (--square < 0)
41067
+ return null;
41068
+ if (square === 0 && curly === 0)
41069
+ return index;
41070
+ } else if (char === "{")
41071
+ curly++;
41072
+ else if (char === "}") {
41073
+ if (--curly < 0)
41074
+ return null;
41075
+ }
41076
+ }
41077
+ return null;
41078
+ }
41079
+ function inlineRowsAsTables(value, newline) {
41080
+ const end = inlineArrayEnd(value);
41081
+ if (end === null || value.slice(end + 1).trim() !== "" || value.includes(`
41082
+ `) || value.includes("\r"))
41083
+ return null;
41084
+ const elements = splitTopLevel(value.slice(1, end), ",");
41085
+ if (!elements)
41086
+ return null;
41087
+ const rows = elements.map((element) => element.trim()).filter(Boolean);
41088
+ if (rows.length === 0)
41089
+ return "";
41090
+ const sections = [];
41091
+ for (const row of rows) {
41092
+ if (!row.startsWith("{") || !row.endsWith("}"))
41093
+ return null;
41094
+ const fields = splitTopLevel(row.slice(1, -1), ",");
41095
+ if (!fields)
41096
+ return null;
41097
+ const lines = fields.map((field2) => field2.trim()).filter(Boolean);
41098
+ if (lines.some((line) => !/^(?:path|name|enabled)\s*=/.test(line)))
41099
+ return null;
41100
+ const keys2 = lines.map((line) => line.slice(0, line.indexOf("=")).trim());
41101
+ if (new Set(keys2).size !== keys2.length)
41102
+ return null;
41103
+ sections.push(`[[skills.config]]${newline}${lines.join(newline)}${newline}`);
41104
+ }
41105
+ return sections.join("");
41106
+ }
41107
+ function normalizeCodexInlinePathConfig(text2) {
41108
+ const before = Bun.TOML.parse(text2);
41109
+ if (!Array.isArray(before.skills?.config))
41110
+ return text2;
41111
+ if (before.skills.config.length === 0)
41112
+ throw codexPathConfigRefusal();
41113
+ const expectedWithoutConfig = structuredClone(before);
41114
+ delete expectedWithoutConfig.skills.config;
41115
+ try {
41116
+ if (isDeepStrictEqual4(Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, "")), expectedWithoutConfig))
41117
+ return text2;
41118
+ } catch {}
41119
+ const matches = [];
41120
+ for (const assignment of text2.matchAll(/^([ \t]*config[ \t]*=[ \t]*)(.*?)(\r?)$/gm)) {
41121
+ const rawValue = assignment[2].trimEnd();
41122
+ let quote2 = null, escaped = false, comment = "";
41123
+ for (let index = 0;index < rawValue.length; index++) {
41124
+ const char = rawValue[index];
41125
+ if (quote2 === "double") {
41126
+ if (escaped)
41127
+ escaped = false;
41128
+ else if (char === "\\")
41129
+ escaped = true;
41130
+ else if (char === '"')
41131
+ quote2 = null;
41132
+ continue;
41133
+ }
41134
+ if (quote2 === "literal") {
41135
+ if (char === "'")
41136
+ quote2 = null;
41137
+ continue;
41138
+ }
41139
+ if (char === '"')
41140
+ quote2 = "double";
41141
+ else if (char === "'")
41142
+ quote2 = "literal";
41143
+ else if (char === "#") {
41144
+ comment = rawValue.slice(index);
41145
+ break;
41146
+ }
41147
+ }
41148
+ const value = comment ? rawValue.slice(0, rawValue.length - comment.length).trimEnd() : rawValue;
41149
+ const tableSections = inlineRowsAsTables(value, text2.includes(`\r
41150
+ `) ? `\r
41151
+ ` : `
41152
+ `);
41153
+ if (tableSections === null)
41154
+ continue;
41155
+ const start = assignment.index;
41156
+ const end = start + assignment[0].length;
41157
+ const withoutAssignment = text2.slice(0, start) + (comment ? comment : "") + assignment[3] + text2.slice(end);
41158
+ const separator = withoutAssignment.endsWith(`
41159
+ `) ? withoutAssignment.endsWith(`
41160
+
41161
+ `) ? "" : `
41162
+ ` : `
41163
+
41164
+ `;
41165
+ const candidate2 = withoutAssignment + (tableSections ? separator + tableSections : "");
41166
+ try {
41167
+ const after = Bun.TOML.parse(candidate2);
41168
+ const expected = structuredClone(before);
41169
+ if (isDeepStrictEqual4(after, expected))
41170
+ matches.push(candidate2);
41171
+ } catch {}
41172
+ }
41173
+ if (matches.length !== 1)
41174
+ throw codexPathConfigRefusal();
41175
+ return matches[0];
41176
+ }
40987
41177
  function assertCodexPathConfigEditable(text2) {
40988
41178
  const before = Bun.TOML.parse(text2);
40989
41179
  if (before.skills?.config === undefined)
@@ -40994,8 +41184,12 @@ function assertCodexPathConfigEditable(text2) {
40994
41184
  const withoutSections = Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
40995
41185
  if (isDeepStrictEqual4(withoutSections, expected))
40996
41186
  return;
41187
+ const normalized = normalizeCodexInlinePathConfig(text2);
41188
+ const withoutNormalizedSections = Bun.TOML.parse(normalized.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
41189
+ if (isDeepStrictEqual4(withoutNormalizedSections, expected))
41190
+ return;
40997
41191
  } catch {}
40998
- throw new Error("Codex skill path controls require ordinary [[skills.config]] TOML tables; preserve and convert inline or dotted config arrays before running skills hook install");
41192
+ throw codexPathConfigRefusal();
40999
41193
  }
41000
41194
  function disableCodexBundledSkills(text2) {
41001
41195
  const before = Bun.TOML.parse(text2);
@@ -41271,11 +41465,12 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
41271
41465
  return false;
41272
41466
  if (scan.hasSkills) {
41273
41467
  const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target + sep4)));
41468
+ if (path === options.reviewedCacheAlias && targetSkills.length > 0)
41469
+ reviewedCacheAliasFound = true;
41274
41470
  if (agent === "codex" && targetSkills.length > 0 && targetSkills.every((entry) => disabledVendorPaths.has(entry.path)))
41275
41471
  return true;
41276
41472
  if (path !== options.reviewedCacheAlias)
41277
41473
  return false;
41278
- reviewedCacheAliasFound = true;
41279
41474
  }
41280
41475
  return true;
41281
41476
  }
@@ -41474,7 +41669,7 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
41474
41669
  }
41475
41670
  function disableCodexSkills(text2, skills, aliases, bridgePath) {
41476
41671
  const bundled = disableCodexBundledSkills(text2);
41477
- let result = bundled;
41672
+ let result = normalizeCodexInlinePathConfig(bundled);
41478
41673
  const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve14(bridgePath), resolve14(join21(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
41479
41674
  const previousConfig = Bun.TOML.parse(bundled).skills?.config;
41480
41675
  if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
@@ -41543,7 +41738,7 @@ function planAgentIntegration(options) {
41543
41738
  return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
41544
41739
  });
41545
41740
  const codexConfig = options.agents.includes("codex") ? Bun.TOML.parse(readOptional(canonicalAgentPath(join21(home, ".codex", "config.toml"), aliases)) ?? "") : {};
41546
- const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases, disabledVendorPaths: disabledCodexSkillPaths(codexConfig, aliases) });
41741
+ const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, disabledVendorPaths: disabledCodexSkillPaths(codexConfig, aliases) });
41547
41742
  const changes = [];
41548
41743
  for (const agent of [...new Set(options.agents)]) {
41549
41744
  if (!INTEGRATION_AGENTS.includes(agent))
@@ -64005,9 +64200,9 @@ class PostgresSkillsStore {
64005
64200
  }
64006
64201
  backend = { kind: "postgres", durable: true, label: "postgres" };
64007
64202
  sql;
64008
- constructor(databaseUrl) {
64203
+ constructor(databaseUrl, connectionOptions = {}) {
64009
64204
  const bunWithSql = Bun;
64010
- this.sql = new bunWithSql.SQL(databaseUrl, { max: resolvePoolMax() });
64205
+ this.sql = new bunWithSql.SQL(databaseUrl, { max: resolvePoolMax(), ...connectionOptions });
64011
64206
  }
64012
64207
  async verifyConnectivity() {
64013
64208
  try {
@@ -64102,7 +64297,7 @@ class PostgresSkillsStore {
64102
64297
  }
64103
64298
  await tx`
64104
64299
  INSERT INTO skills_audit_events (org_id, user_id, api_key_id, action, target_type, target_id, metadata_json)
64105
- VALUES (${actor.orgId}, ${actor.userId}, ${actor.apiKeyId}, ${"api_key_scopes_added"}, ${"api_key"}, ${keyId}, ${JSON.stringify({ added: addScopes, scopes: parseJsonArray(updated[0].scopes_json) })}::jsonb)
64300
+ VALUES (${actor.orgId}, ${actor.userId}, ${actor.apiKeyId}, ${"api_key_scopes_added"}, ${"api_key"}, ${keyId}, ${JSON.stringify({ added: addScopes, scopes: parseJsonArray(updated[0].scopes_json) })}::text::jsonb)
64106
64301
  `;
64107
64302
  return { kind: "updated", scopes: parseJsonArray(updated[0].scopes_json) };
64108
64303
  });
@@ -64110,10 +64305,12 @@ class PostgresSkillsStore {
64110
64305
  async casUpdateApiKeyScopes(tx, keyId, orgId, expectedScopes, addScopes) {
64111
64306
  const current = expectedScopes;
64112
64307
  const scopes = [...current, ...addScopes.filter((scope2) => !current.includes(scope2))];
64308
+ const expectedScopesJson = JSON.stringify(expectedScopes);
64113
64309
  return tx`
64114
- UPDATE api_keys SET scopes_json = ${JSON.stringify(scopes)}::jsonb
64310
+ UPDATE api_keys SET scopes_json = ${JSON.stringify(scopes)}::text::jsonb
64115
64311
  WHERE id = ${keyId} AND org_id = ${orgId} AND revoked_at IS NULL
64116
- AND scopes_json = ${JSON.stringify(expectedScopes)}::jsonb
64312
+ AND (scopes_json = ${expectedScopesJson}::text::jsonb
64313
+ OR scopes_json = to_jsonb(${expectedScopesJson}::text))
64117
64314
  RETURNING scopes_json
64118
64315
  `;
64119
64316
  }
@@ -64173,7 +64370,7 @@ class PostgresSkillsStore {
64173
64370
  operator_task_arn: input.operatorTaskArn,
64174
64371
  target_manifest_digest: input.manifestDigest,
64175
64372
  station_id: input.stationId
64176
- })}::jsonb)
64373
+ })}::text::jsonb)
64177
64374
  `;
64178
64375
  return { kind: "updated", scopes };
64179
64376
  });
@@ -0,0 +1,26 @@
1
+ export interface EnrollmentInspectionInput {
2
+ keyId: string;
3
+ orgId: string;
4
+ enrollmentOperationId: string;
5
+ enrollmentManifestDigest: string;
6
+ }
7
+ export interface EnrollmentInspectionResult {
8
+ status: "inspected";
9
+ target: {
10
+ state: "found";
11
+ scopes: string[];
12
+ } | {
13
+ state: "absent" | "revoked" | "mismatched" | "ambiguous";
14
+ };
15
+ audit: {
16
+ state: "absent" | "matching" | "mismatched" | "ambiguous";
17
+ };
18
+ }
19
+ type InspectionCode = "INVALID_INSPECTION_INPUT" | "POSTGRES_REQUIRED" | "INVALID_INSPECTION_RESULT" | "INSPECTION_FAILED" | "INSPECTION_SCHEMA_UNAVAILABLE" | "INSPECTION_ACCESS_DENIED" | "INSPECTION_WRITE_REFUSED" | "INSPECTION_TIMEOUT" | "INSPECTION_CONNECTION_FAILED";
20
+ export declare class EnrollmentInspectionError extends Error {
21
+ readonly code: InspectionCode;
22
+ constructor(code: InspectionCode);
23
+ }
24
+ /** Inspect metadata only. Deliberately does not import or initialize the application store. */
25
+ export declare function inspectEnrollment(databaseUrl: string, input: EnrollmentInspectionInput): Promise<EnrollmentInspectionResult>;
26
+ export {};
@@ -1,2 +1,15 @@
1
1
  import { Command } from "commander";
2
+ import { type EnrollmentInspectionInput } from "./enrollment-inspection.js";
3
+ type InspectionManifest = EnrollmentInspectionInput & {
4
+ app: "skills";
5
+ operation: "inspect-enrollment";
6
+ manifestVersion: 1;
7
+ operationId: string;
8
+ stationId: string;
9
+ expiresAt: string;
10
+ manifestDigest: string;
11
+ };
12
+ /** Inspection has its own authorization; a failed enrollment manifest cannot authorize it. */
13
+ export declare function readInspectionManifest(path: string, receiptPath: string): InspectionManifest;
2
14
  export declare function registerMaintenance(parent: Command): void;
15
+ export {};
@@ -25,6 +25,9 @@ export interface StoreOptions {
25
25
  * - anything else -> throws, naming what is supported
26
26
  */
27
27
  export declare function createStore(options?: StoreOptions): Promise<SkillsProductStore>;
28
+ export type OperatorScopeMaintenanceStore = Required<Pick<SkillsProductStore, "inspectOperatorScopeTarget" | "enrollPublishScopeByOperator" | "close">>;
29
+ /** Open only the existing scope-maintenance surface; never initialize or backfill the application store. */
30
+ export declare function openOperatorScopeMaintenance(databaseUrl: string): OperatorScopeMaintenanceStore;
28
31
  export declare class MemorySkillsStore implements SkillsProductStore {
29
32
  readonly selectionStore: MemorySkillSelectionStore;
30
33
  readonly executionGrantStore: MemoryExecutionGrantStore;
@@ -102,7 +105,12 @@ export declare class PostgresSkillsStore implements SkillsProductStore {
102
105
  get executionGrantStore(): PostgresExecutionGrantStore;
103
106
  readonly backend: StoreBackendInfo;
104
107
  private sql;
105
- constructor(databaseUrl: string);
108
+ constructor(databaseUrl: string, connectionOptions?: {
109
+ max?: number;
110
+ connectionTimeout?: number;
111
+ idleTimeout?: number;
112
+ connection?: Record<string, string>;
113
+ });
106
114
  /**
107
115
  * Bun's SQL client connects lazily, so constructing this store proves nothing about
108
116
  * the database being there. Without an explicit probe, a wrong host or a down
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.10.11",
3
+ "version": "0.10.14",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {