@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/bin/mcp.js CHANGED
@@ -20019,7 +20019,7 @@ var package_default;
20019
20019
  var init_package = __esm(() => {
20020
20020
  package_default = {
20021
20021
  name: "@hasna/skills",
20022
- version: "0.10.11",
20022
+ version: "0.10.14",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
package/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join5 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.10.11",
10
+ version: "0.10.14",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
package/bin/server.js CHANGED
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
24122
24122
  // package.json
24123
24123
  var package_default = {
24124
24124
  name: "@hasna/skills",
24125
- version: "0.10.11",
24125
+ version: "0.10.14",
24126
24126
  description: "Skills library for AI coding agents",
24127
24127
  type: "module",
24128
24128
  bin: {
@@ -43384,9 +43384,9 @@ class PostgresSkillsStore {
43384
43384
  }
43385
43385
  backend = { kind: "postgres", durable: true, label: "postgres" };
43386
43386
  sql;
43387
- constructor(databaseUrl) {
43387
+ constructor(databaseUrl, connectionOptions = {}) {
43388
43388
  const bunWithSql = Bun;
43389
- this.sql = new bunWithSql.SQL(databaseUrl, { max: resolvePoolMax() });
43389
+ this.sql = new bunWithSql.SQL(databaseUrl, { max: resolvePoolMax(), ...connectionOptions });
43390
43390
  }
43391
43391
  async verifyConnectivity() {
43392
43392
  try {
@@ -43481,7 +43481,7 @@ class PostgresSkillsStore {
43481
43481
  }
43482
43482
  await tx`
43483
43483
  INSERT INTO skills_audit_events (org_id, user_id, api_key_id, action, target_type, target_id, metadata_json)
43484
- 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)
43484
+ 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)
43485
43485
  `;
43486
43486
  return { kind: "updated", scopes: parseJsonArray(updated[0].scopes_json) };
43487
43487
  });
@@ -43489,10 +43489,12 @@ class PostgresSkillsStore {
43489
43489
  async casUpdateApiKeyScopes(tx, keyId, orgId, expectedScopes, addScopes) {
43490
43490
  const current = expectedScopes;
43491
43491
  const scopes = [...current, ...addScopes.filter((scope2) => !current.includes(scope2))];
43492
+ const expectedScopesJson = JSON.stringify(expectedScopes);
43492
43493
  return tx`
43493
- UPDATE api_keys SET scopes_json = ${JSON.stringify(scopes)}::jsonb
43494
+ UPDATE api_keys SET scopes_json = ${JSON.stringify(scopes)}::text::jsonb
43494
43495
  WHERE id = ${keyId} AND org_id = ${orgId} AND revoked_at IS NULL
43495
- AND scopes_json = ${JSON.stringify(expectedScopes)}::jsonb
43496
+ AND (scopes_json = ${expectedScopesJson}::text::jsonb
43497
+ OR scopes_json = to_jsonb(${expectedScopesJson}::text))
43496
43498
  RETURNING scopes_json
43497
43499
  `;
43498
43500
  }
@@ -43552,7 +43554,7 @@ class PostgresSkillsStore {
43552
43554
  operator_task_arn: input.operatorTaskArn,
43553
43555
  target_manifest_digest: input.manifestDigest,
43554
43556
  station_id: input.stationId
43555
- })}::jsonb)
43557
+ })}::text::jsonb)
43556
43558
  `;
43557
43559
  return { kind: "updated", scopes };
43558
43560
  });
package/bin/worker.js CHANGED
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24125
24125
  // package.json
24126
24126
  var package_default = {
24127
24127
  name: "@hasna/skills",
24128
- version: "0.10.11",
24128
+ version: "0.10.14",
24129
24129
  description: "Skills library for AI coding agents",
24130
24130
  type: "module",
24131
24131
  bin: {
@@ -36913,9 +36913,9 @@ class PostgresSkillsStore {
36913
36913
  }
36914
36914
  backend = { kind: "postgres", durable: true, label: "postgres" };
36915
36915
  sql;
36916
- constructor(databaseUrl) {
36916
+ constructor(databaseUrl, connectionOptions = {}) {
36917
36917
  const bunWithSql = Bun;
36918
- this.sql = new bunWithSql.SQL(databaseUrl, { max: resolvePoolMax() });
36918
+ this.sql = new bunWithSql.SQL(databaseUrl, { max: resolvePoolMax(), ...connectionOptions });
36919
36919
  }
36920
36920
  async verifyConnectivity() {
36921
36921
  try {
@@ -37010,7 +37010,7 @@ class PostgresSkillsStore {
37010
37010
  }
37011
37011
  await tx`
37012
37012
  INSERT INTO skills_audit_events (org_id, user_id, api_key_id, action, target_type, target_id, metadata_json)
37013
- 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)
37013
+ 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)
37014
37014
  `;
37015
37015
  return { kind: "updated", scopes: parseJsonArray(updated[0].scopes_json) };
37016
37016
  });
@@ -37018,10 +37018,12 @@ class PostgresSkillsStore {
37018
37018
  async casUpdateApiKeyScopes(tx, keyId, orgId, expectedScopes, addScopes) {
37019
37019
  const current = expectedScopes;
37020
37020
  const scopes = [...current, ...addScopes.filter((scope) => !current.includes(scope))];
37021
+ const expectedScopesJson = JSON.stringify(expectedScopes);
37021
37022
  return tx`
37022
- UPDATE api_keys SET scopes_json = ${JSON.stringify(scopes)}::jsonb
37023
+ UPDATE api_keys SET scopes_json = ${JSON.stringify(scopes)}::text::jsonb
37023
37024
  WHERE id = ${keyId} AND org_id = ${orgId} AND revoked_at IS NULL
37024
- AND scopes_json = ${JSON.stringify(expectedScopes)}::jsonb
37025
+ AND (scopes_json = ${expectedScopesJson}::text::jsonb
37026
+ OR scopes_json = to_jsonb(${expectedScopesJson}::text))
37025
37027
  RETURNING scopes_json
37026
37028
  `;
37027
37029
  }
@@ -37081,7 +37083,7 @@ class PostgresSkillsStore {
37081
37083
  operator_task_arn: input.operatorTaskArn,
37082
37084
  target_manifest_digest: input.manifestDigest,
37083
37085
  station_id: input.stationId
37084
- })}::jsonb)
37086
+ })}::text::jsonb)
37085
37087
  `;
37086
37088
  return { kind: "updated", scopes };
37087
37089
  });
package/dist/index.js CHANGED
@@ -9611,7 +9611,7 @@ import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from
9611
9611
  // package.json
9612
9612
  var package_default = {
9613
9613
  name: "@hasna/skills",
9614
- version: "0.10.11",
9614
+ version: "0.10.14",
9615
9615
  description: "Skills library for AI coding agents",
9616
9616
  type: "module",
9617
9617
  bin: {
@@ -12812,6 +12812,196 @@ function isOwnedCliBridge(path, expectedPaths) {
12812
12812
  // src/lib/agent-codex.ts
12813
12813
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
12814
12814
  var CODEX_SKILL_CONFIG_SECTIONS = /^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm;
12815
+ 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");
12816
+ function splitTopLevel(value, delimiter) {
12817
+ const result = [];
12818
+ let start = 0, square = 0, curly = 0, quote = null, escaped = false;
12819
+ for (let index = 0;index < value.length; index++) {
12820
+ const char = value[index];
12821
+ if (quote === "double") {
12822
+ if (escaped)
12823
+ escaped = false;
12824
+ else if (char === "\\")
12825
+ escaped = true;
12826
+ else if (char === '"')
12827
+ quote = null;
12828
+ continue;
12829
+ }
12830
+ if (quote === "literal") {
12831
+ if (char === "'")
12832
+ quote = null;
12833
+ continue;
12834
+ }
12835
+ if (char === '"') {
12836
+ quote = "double";
12837
+ continue;
12838
+ }
12839
+ if (char === "'") {
12840
+ quote = "literal";
12841
+ continue;
12842
+ }
12843
+ if (char === "[")
12844
+ square++;
12845
+ else if (char === "]") {
12846
+ if (--square < 0)
12847
+ return null;
12848
+ } else if (char === "{")
12849
+ curly++;
12850
+ else if (char === "}") {
12851
+ if (--curly < 0)
12852
+ return null;
12853
+ } else if (char === delimiter && square === 0 && curly === 0) {
12854
+ result.push(value.slice(start, index));
12855
+ start = index + 1;
12856
+ }
12857
+ }
12858
+ if (quote !== null || square !== 0 || curly !== 0)
12859
+ return null;
12860
+ result.push(value.slice(start));
12861
+ return result;
12862
+ }
12863
+ function inlineArrayEnd(value) {
12864
+ if (!value.startsWith("["))
12865
+ return null;
12866
+ let square = 0, curly = 0, quote = null, escaped = false;
12867
+ for (let index = 0;index < value.length; index++) {
12868
+ const char = value[index];
12869
+ if (quote === "double") {
12870
+ if (escaped)
12871
+ escaped = false;
12872
+ else if (char === "\\")
12873
+ escaped = true;
12874
+ else if (char === '"')
12875
+ quote = null;
12876
+ continue;
12877
+ }
12878
+ if (quote === "literal") {
12879
+ if (char === "'")
12880
+ quote = null;
12881
+ continue;
12882
+ }
12883
+ if (char === '"') {
12884
+ quote = "double";
12885
+ continue;
12886
+ }
12887
+ if (char === "'") {
12888
+ quote = "literal";
12889
+ continue;
12890
+ }
12891
+ if (char === "[")
12892
+ square++;
12893
+ else if (char === "]") {
12894
+ if (--square < 0)
12895
+ return null;
12896
+ if (square === 0 && curly === 0)
12897
+ return index;
12898
+ } else if (char === "{")
12899
+ curly++;
12900
+ else if (char === "}") {
12901
+ if (--curly < 0)
12902
+ return null;
12903
+ }
12904
+ }
12905
+ return null;
12906
+ }
12907
+ function inlineRowsAsTables(value, newline) {
12908
+ const end = inlineArrayEnd(value);
12909
+ if (end === null || value.slice(end + 1).trim() !== "" || value.includes(`
12910
+ `) || value.includes("\r"))
12911
+ return null;
12912
+ const elements = splitTopLevel(value.slice(1, end), ",");
12913
+ if (!elements)
12914
+ return null;
12915
+ const rows = elements.map((element) => element.trim()).filter(Boolean);
12916
+ if (rows.length === 0)
12917
+ return "";
12918
+ const sections = [];
12919
+ for (const row of rows) {
12920
+ if (!row.startsWith("{") || !row.endsWith("}"))
12921
+ return null;
12922
+ const fields = splitTopLevel(row.slice(1, -1), ",");
12923
+ if (!fields)
12924
+ return null;
12925
+ const lines = fields.map((field) => field.trim()).filter(Boolean);
12926
+ if (lines.some((line) => !/^(?:path|name|enabled)\s*=/.test(line)))
12927
+ return null;
12928
+ const keys = lines.map((line) => line.slice(0, line.indexOf("=")).trim());
12929
+ if (new Set(keys).size !== keys.length)
12930
+ return null;
12931
+ sections.push(`[[skills.config]]${newline}${lines.join(newline)}${newline}`);
12932
+ }
12933
+ return sections.join("");
12934
+ }
12935
+ function normalizeCodexInlinePathConfig(text2) {
12936
+ const before = Bun.TOML.parse(text2);
12937
+ if (!Array.isArray(before.skills?.config))
12938
+ return text2;
12939
+ if (before.skills.config.length === 0)
12940
+ throw codexPathConfigRefusal();
12941
+ const expectedWithoutConfig = structuredClone(before);
12942
+ delete expectedWithoutConfig.skills.config;
12943
+ try {
12944
+ if (isDeepStrictEqual2(Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, "")), expectedWithoutConfig))
12945
+ return text2;
12946
+ } catch {}
12947
+ const matches = [];
12948
+ for (const assignment of text2.matchAll(/^([ \t]*config[ \t]*=[ \t]*)(.*?)(\r?)$/gm)) {
12949
+ const rawValue = assignment[2].trimEnd();
12950
+ let quote = null, escaped = false, comment = "";
12951
+ for (let index = 0;index < rawValue.length; index++) {
12952
+ const char = rawValue[index];
12953
+ if (quote === "double") {
12954
+ if (escaped)
12955
+ escaped = false;
12956
+ else if (char === "\\")
12957
+ escaped = true;
12958
+ else if (char === '"')
12959
+ quote = null;
12960
+ continue;
12961
+ }
12962
+ if (quote === "literal") {
12963
+ if (char === "'")
12964
+ quote = null;
12965
+ continue;
12966
+ }
12967
+ if (char === '"')
12968
+ quote = "double";
12969
+ else if (char === "'")
12970
+ quote = "literal";
12971
+ else if (char === "#") {
12972
+ comment = rawValue.slice(index);
12973
+ break;
12974
+ }
12975
+ }
12976
+ const value = comment ? rawValue.slice(0, rawValue.length - comment.length).trimEnd() : rawValue;
12977
+ const tableSections = inlineRowsAsTables(value, text2.includes(`\r
12978
+ `) ? `\r
12979
+ ` : `
12980
+ `);
12981
+ if (tableSections === null)
12982
+ continue;
12983
+ const start = assignment.index;
12984
+ const end = start + assignment[0].length;
12985
+ const withoutAssignment = text2.slice(0, start) + (comment ? comment : "") + assignment[3] + text2.slice(end);
12986
+ const separator = withoutAssignment.endsWith(`
12987
+ `) ? withoutAssignment.endsWith(`
12988
+
12989
+ `) ? "" : `
12990
+ ` : `
12991
+
12992
+ `;
12993
+ const candidate = withoutAssignment + (tableSections ? separator + tableSections : "");
12994
+ try {
12995
+ const after = Bun.TOML.parse(candidate);
12996
+ const expected = structuredClone(before);
12997
+ if (isDeepStrictEqual2(after, expected))
12998
+ matches.push(candidate);
12999
+ } catch {}
13000
+ }
13001
+ if (matches.length !== 1)
13002
+ throw codexPathConfigRefusal();
13003
+ return matches[0];
13004
+ }
12815
13005
  function assertCodexPathConfigEditable(text2) {
12816
13006
  const before = Bun.TOML.parse(text2);
12817
13007
  if (before.skills?.config === undefined)
@@ -12822,8 +13012,12 @@ function assertCodexPathConfigEditable(text2) {
12822
13012
  const withoutSections = Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
12823
13013
  if (isDeepStrictEqual2(withoutSections, expected))
12824
13014
  return;
13015
+ const normalized = normalizeCodexInlinePathConfig(text2);
13016
+ const withoutNormalizedSections = Bun.TOML.parse(normalized.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
13017
+ if (isDeepStrictEqual2(withoutNormalizedSections, expected))
13018
+ return;
12825
13019
  } catch {}
12826
- 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");
13020
+ throw codexPathConfigRefusal();
12827
13021
  }
12828
13022
  function disableCodexBundledSkills(text2) {
12829
13023
  const before = Bun.TOML.parse(text2);
@@ -13398,11 +13592,12 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
13398
13592
  return false;
13399
13593
  if (scan.hasSkills) {
13400
13594
  const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target + sep4)));
13595
+ if (path === options.reviewedCacheAlias && targetSkills.length > 0)
13596
+ reviewedCacheAliasFound = true;
13401
13597
  if (agent === "codex" && targetSkills.length > 0 && targetSkills.every((entry) => disabledVendorPaths.has(entry.path)))
13402
13598
  return true;
13403
13599
  if (path !== options.reviewedCacheAlias)
13404
13600
  return false;
13405
- reviewedCacheAliasFound = true;
13406
13601
  }
13407
13602
  return true;
13408
13603
  }
@@ -13601,7 +13796,7 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
13601
13796
  }
13602
13797
  function disableCodexSkills(text2, skills, aliases, bridgePath) {
13603
13798
  const bundled = disableCodexBundledSkills(text2);
13604
- let result = bundled;
13799
+ let result = normalizeCodexInlinePathConfig(bundled);
13605
13800
  const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve9(bridgePath), resolve9(join16(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
13606
13801
  const previousConfig = Bun.TOML.parse(bundled).skills?.config;
13607
13802
  if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
@@ -13670,7 +13865,7 @@ function planAgentIntegration(options) {
13670
13865
  return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
13671
13866
  });
13672
13867
  const codexConfig = options.agents.includes("codex") ? Bun.TOML.parse(readOptional(canonicalAgentPath(join16(home, ".codex", "config.toml"), aliases)) ?? "") : {};
13673
- 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) });
13868
+ 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) });
13674
13869
  const changes = [];
13675
13870
  for (const agent of [...new Set(options.agents)]) {
13676
13871
  if (!INTEGRATION_AGENTS.includes(agent))
@@ -1,7 +1,11 @@
1
1
  export declare const CODEX_SKILL_CONFIG_SECTIONS: RegExp;
2
- /** The path editor supports ordinary array-of-table sections only. Removing
3
- * those sections must remove exactly the parsed path array, with all other
4
- * values unchanged. This also excludes header-like text inside user strings. */
2
+ /** Convert only Codex's native, single-line inline array representation into
3
+ * equivalent array-of-table sections. The replacement is accepted only when
4
+ * the complete parsed TOML value is deeply identical before and after. */
5
+ export declare function normalizeCodexInlinePathConfig(text: string): string;
6
+ /** Removing supported path sections must remove exactly the parsed skill path
7
+ * array while preserving every other TOML value. Native app-server inline
8
+ * arrays are first normalized by a targeted, semantically verified edit. */
5
9
  export declare function assertCodexPathConfigEditable(text: string): void;
6
10
  /** Preserve user TOML verbatim except for the supported bundled-skill control.
7
11
  * Bun's parser accepts some illegal table reopenings, so never append a table
@@ -99,6 +99,7 @@ export declare function planAgentIntegration(options: {
99
99
  projectDir?: string;
100
100
  discoveryInputs?: ReviewedDiscoveryInputs;
101
101
  allowRootAliases?: boolean;
102
+ reviewedCacheAlias?: string;
102
103
  }): AgentIntegrationPlan;
103
104
  /** Plan an explicitly authorized Stop hook replacement without adopting drift.
104
105
  * The caller owns authorization of its exact Stop hook delta; Skills refuses
@@ -1,4 +1,4 @@
1
- export declare const SUPPORTED_CODEX_HOOK_VERSIONS: readonly ["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.155.0", "codex-cli 0.155.1", "codex-cli 0.156.1", "codex-cli 0.157.0", "codex-cli 0.157.1"];
1
+ export declare const SUPPORTED_CODEX_HOOK_VERSIONS: readonly ["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.155.0", "codex-cli 0.155.1", "codex-cli 0.156.1", "codex-cli 0.157.0", "codex-cli 0.157.1", "codex-cli 0.158.0", "codex-cli 0.159.0"];
2
2
  export interface CodexHookRpc {
3
3
  version: string;
4
4
  processId?: number;