@hasna/skills 0.10.90 → 0.10.92

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
@@ -16369,7 +16369,7 @@ import { basename as basename15, dirname as dirname27, isAbsolute as isAbsolute2
16369
16369
  import { homedir as homedir5 } from "os";
16370
16370
  import { isAbsolute as isAbsolute25, join as join39, resolve as resolve25 } from "path";
16371
16371
  // package.json
16372
- var version = "0.10.90";
16372
+ var version = "0.10.92";
16373
16373
  var package_default = {
16374
16374
  name: "@hasna/skills",
16375
16375
  version,
@@ -16416,6 +16416,7 @@ var package_default = {
16416
16416
  "docs/runtime-activation.md",
16417
16417
  "schemas/",
16418
16418
  "LICENSE",
16419
+ "THIRD_PARTY_NOTICES",
16419
16420
  "README.md",
16420
16421
  "!dist/**/*test-utils.d.ts",
16421
16422
  "!dist/**/*fixtures.d.ts"
@@ -16423,9 +16424,9 @@ var package_default = {
16423
16424
  main: "./dist/index.js",
16424
16425
  types: "./dist/index.d.ts",
16425
16426
  scripts: {
16426
- clean: "rm -rf bin/ dist/",
16427
- build: "bun run clean && bun build --external @hasna/contracts/kernel-lock ./src/cli/index.tsx --outdir ./bin --target bun && bun build --external @modelcontextprotocol/sdk ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/maintenance.ts --outfile ./bin/maintenance.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts",
16428
- "build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts",
16427
+ clean: "rm -rf bin/ dist/ THIRD_PARTY_NOTICES",
16428
+ build: "bun run clean && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/cli/index.tsx --outdir ./bin --target bun && bun build --external @hasna/secrets --external @modelcontextprotocol/sdk ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/maintenance.ts --outfile ./bin/maintenance.js --target bun && bun build --external @hasna/secrets ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts && bun scripts/third-party-notices.ts",
16429
+ "build:js": "rm -rf dist && bun build --external @hasna/secrets ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts",
16429
16430
  test: "bun test --timeout 30000",
16430
16431
  dev: "bun run ./src/cli/index.tsx",
16431
16432
  "dev:watch": "bun --watch run ./src/cli/index.tsx",
@@ -16476,7 +16477,6 @@ var package_default = {
16476
16477
  "@aws-sdk/client-ecs": "^3.1079.0",
16477
16478
  "@aws-sdk/client-s3": "^3.1079.0",
16478
16479
  "@hasna/events": "0.1.18",
16479
- "@hasna/secrets": "0.4.2",
16480
16480
  "@modelcontextprotocol/sdk": "^1.26.0",
16481
16481
  chalk: "^5.3.0",
16482
16482
  commander: "^12.1.0",
@@ -16488,6 +16488,9 @@ var package_default = {
16488
16488
  zod: "^4.3.6",
16489
16489
  "@hasna/contracts": "1.3.5"
16490
16490
  },
16491
+ optionalDependencies: {
16492
+ "@hasna/secrets": "0.4.2"
16493
+ },
16491
16494
  engines: {
16492
16495
  bun: ">=1.0.0"
16493
16496
  },
@@ -17715,6 +17718,9 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
17715
17718
  completed = await completePointerCredential(SKILLS_APP, pointer, explicitVault ? snapshotEnv : env);
17716
17719
  } catch (error) {
17717
17720
  const translated = asSkillsFleetCredentialError(error, pointer.pointerVaultKey);
17721
+ if (translated?.message.includes("(@hasna/secrets) is not installed")) {
17722
+ throw new SkillsFleetCredentialError(`${translated.message} @hasna/secrets is optional and private: install it with access to it.`, "MISSING_API_CREDENTIAL");
17723
+ }
17718
17724
  if (translated)
17719
17725
  throw translated;
17720
17726
  throw error;
@@ -26727,9 +26733,96 @@ function nativeDiscoveryAliasTargets(roots, home) {
26727
26733
  }
26728
26734
  return [...targets];
26729
26735
  }
26736
+ function hooksAreManagedBridge(agent, hooks, managed) {
26737
+ const table = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
26738
+ const exactKeys3 = (value, keys2) => Object.keys(value).sort().join("\x00") === [...keys2].sort().join("\x00");
26739
+ if (!table(hooks) || typeof managed.command !== "string" || !isAbsolute28(managed.command))
26740
+ return false;
26741
+ const events3 = agent === "gemini" ? ["BeforeAgent", "SessionStart", "BeforeTool"] : ["beforeSubmitPrompt", "sessionStart"];
26742
+ for (const [event, entries] of Object.entries(hooks)) {
26743
+ if (!events3.includes(event) || !Array.isArray(entries) || entries.length !== 1)
26744
+ return false;
26745
+ let expected;
26746
+ try {
26747
+ expected = renderAgentHookCommand(managed.command, agent, managed.profile, event);
26748
+ } catch {
26749
+ return false;
26750
+ }
26751
+ const entry = entries[0];
26752
+ if (!table(entry))
26753
+ return false;
26754
+ if (agent === "cursor") {
26755
+ if (!exactKeys3(entry, ["command", "timeout"]) || entry.timeout !== 15 || entry.command !== expected)
26756
+ return false;
26757
+ continue;
26758
+ }
26759
+ if (!exactKeys3(entry, event === "BeforeTool" ? ["hooks", "matcher"] : ["hooks"]) || event === "BeforeTool" && entry.matcher !== "activate_skill" || !Array.isArray(entry.hooks) || entry.hooks.length !== 1)
26760
+ return false;
26761
+ const hook = entry.hooks[0];
26762
+ if (!table(hook) || !exactKeys3(hook, ["command", "timeout", "type"]) || hook.type !== "command" || hook.timeout !== 15000 || hook.command !== expected)
26763
+ return false;
26764
+ }
26765
+ return true;
26766
+ }
26767
+ function sumiPluginLoadsOnlyBuiltins(text2) {
26768
+ for (const match of text2.matchAll(/\b(?:import|require)\s*\(\s*(.?)/g)) {
26769
+ const rest = text2.slice(match.index + match[0].length - match[1].length);
26770
+ if (!/^(['"])node:[A-Za-z0-9_/.-]+\1/.test(rest))
26771
+ return false;
26772
+ }
26773
+ for (const match of text2.matchAll(/\b(?:from|import)\s*(['"])([^'"]*)\1/g))
26774
+ if (!match[2].startsWith("node:"))
26775
+ return false;
26776
+ return true;
26777
+ }
26778
+ var CODEX_MARKETPLACE_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
26779
+ function codexRootFreeControls(config) {
26780
+ const table = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date));
26781
+ const adapted = [];
26782
+ const servers = config.mcp_servers;
26783
+ if (table(servers) && Object.keys(servers).length <= AGENT_POLICY_LIMITS.discoveryRoots && Object.entries(servers).every(([name, row]) => name !== "codex_apps" && name !== "_codex_apps" && isCodexLocalStdioMcpServer(name, row)))
26784
+ adapted.push("mcp_servers");
26785
+ const agents = config.agents;
26786
+ if (table(agents) && Object.keys(agents).length <= AGENT_POLICY_LIMITS.fields && Object.values(agents).every((value) => typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)))
26787
+ adapted.push("agents");
26788
+ return adapted;
26789
+ }
26790
+ var CODEX_HOOK_EVENTS = new Set(["PreToolUse", "PermissionRequest", "PostToolUse", "PreCompact", "PostCompact", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart", "SubagentStop", "Stop", "Interrupt"]);
26791
+ function codexPluginHookHandlers(value) {
26792
+ const table = (item) => Boolean(item && typeof item === "object" && !Array.isArray(item));
26793
+ const identifier6 = (item) => typeof item === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(item);
26794
+ const text2 = (item) => typeof item === "string" && item.length <= AGENT_POLICY_LIMITS.pathCharacters && !item.includes("\x00");
26795
+ if (!table(value) || !Object.keys(value).every((key2) => key2 === "hooks" || key2 === "description") || value.description !== undefined && !text2(value.description) || !table(value.hooks) || Object.keys(value.hooks).length > 32)
26796
+ return null;
26797
+ const result = { commands: 0, servers: new Set };
26798
+ for (const [event, groups] of Object.entries(value.hooks)) {
26799
+ if (!CODEX_HOOK_EVENTS.has(event) || !Array.isArray(groups) || groups.length > 32)
26800
+ return null;
26801
+ for (const group of groups) {
26802
+ if (!table(group) || !Object.keys(group).every((key2) => key2 === "hooks" || key2 === "matcher") || group.matcher !== undefined && !text2(group.matcher) || !Array.isArray(group.hooks) || group.hooks.length > 32)
26803
+ return null;
26804
+ for (const handler of group.hooks) {
26805
+ if (!table(handler) || handler.timeout !== undefined && !(Number.isSafeInteger(handler.timeout) && handler.timeout >= 0) || handler.statusMessage !== undefined && !text2(handler.statusMessage))
26806
+ return null;
26807
+ if (handler.type === "command") {
26808
+ if (!text2(handler.command) || !handler.command || !Object.keys(handler).every((key2) => ["type", "command", "commandWindows", "command_windows", "timeout", "async", "statusMessage", "additionalContextLimit"].includes(key2)))
26809
+ return null;
26810
+ result.commands++;
26811
+ } else if (handler.type === "mcp_tool") {
26812
+ if (!identifier6(handler.server) || !identifier6(handler.tool) || handler.input !== undefined && !table(handler.input) || !Object.keys(handler).every((key2) => ["type", "server", "tool", "input", "timeout", "statusMessage"].includes(key2)))
26813
+ return null;
26814
+ result.servers.add(handler.server);
26815
+ } else
26816
+ return null;
26817
+ }
26818
+ }
26819
+ }
26820
+ return result;
26821
+ }
26730
26822
  function resolveDiscovery(options, exclusion) {
26731
26823
  const canonical9 = options.canonical ?? resolve28, home = resolve28(options.home), agent = options.agent;
26732
26824
  const sources = [], roots = new Set, builtinNames = [];
26825
+ const sumiLocalPluginTexts = new Map;
26733
26826
  const budget3 = discoveryByteBudget();
26734
26827
  let remainingEntries = AGENT_POLICY_LIMITS.discoveryDirectoryEntries;
26735
26828
  const entries = (path) => {
@@ -26806,12 +26899,38 @@ function resolveDiscovery(options, exclusion) {
26806
26899
  const controls = agent === "claude" ? claudeDiscoveryControlKeys(configText ?? "{}") : agent === "codex" ? codexDiscoveryControlKeys(config) : Object.keys(config).filter((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2));
26807
26900
  const fieldKey = (key2) => /^[A-Za-z_$][A-Za-z0-9_$.-]{0,22}$/.test(key2) && !/(?:sk|rk|pk)_(?:live|test)_|whsec_/i.test(key2) ? JSON.stringify(key2) : "[withheld field key]";
26808
26901
  const unsupportedFields = (names, label) => unresolved(`unknown discovery controls have no root-exclusion adapter for ${label}${names.slice(0, 8).map(fieldKey).join(", ")}${names.length > 8 ? ", additional keys withheld" : ""} in ${quotedPath(canonical9(configPath))}`);
26809
- const unsupportedControls = controls.filter((key2) => !supported[agent].includes(key2));
26902
+ const adapted = agent === "codex" ? codexRootFreeControls(config) : agent === "opencode" && Array.isArray(config.instructions) && config.instructions.length === 0 ? ["instructions"] : [];
26903
+ const unsupportedControls = controls.filter((key2) => !supported[agent].includes(key2) && !adapted.includes(key2));
26810
26904
  if (unsupportedControls.length)
26811
26905
  unsupportedFields(unsupportedControls, "");
26812
26906
  if (config.hooks !== undefined && !object6(config.hooks))
26813
26907
  unresolved("hooks must be an object");
26814
- const hasHooks = controls.includes("hooks") && config.hooks !== undefined && Object.keys(config.hooks).length > 0;
26908
+ const managedSupervisorOnly = () => {
26909
+ const supervisor = exclusion.managedHook?.supervisor;
26910
+ if (agent !== "hermes" || !supervisor || typeof supervisor.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(supervisor.sha256) || !object6(config.hooks))
26911
+ return false;
26912
+ let expected;
26913
+ try {
26914
+ expected = hermesHookDefinitions(supervisor);
26915
+ } catch {
26916
+ return false;
26917
+ }
26918
+ for (const [event, entries2] of Object.entries(config.hooks)) {
26919
+ const want = expected[event];
26920
+ if (!want || !Array.isArray(entries2) || entries2.length !== 1 || !object6(entries2[0]))
26921
+ return false;
26922
+ const entry = entries2[0];
26923
+ if (Object.keys(entry).sort().join("\x00") !== Object.keys(want).sort().join("\x00") || Object.keys(want).some((key2) => entry[key2] !== want[key2]))
26924
+ return false;
26925
+ }
26926
+ try {
26927
+ return hashRawDiscoveryFile(canonical9(supervisor.path), budget3) === supervisor.sha256;
26928
+ } catch {
26929
+ return false;
26930
+ }
26931
+ };
26932
+ const managedBridgeOnly = (agent === "gemini" || agent === "cursor") && exclusion.managedHook !== undefined && hooksAreManagedBridge(agent, config.hooks, exclusion.managedHook) || managedSupervisorOnly();
26933
+ const hasHooks = controls.includes("hooks") && config.hooks !== undefined && Object.keys(config.hooks).length > 0 && !managedBridgeOnly;
26815
26934
  let hasPluginConfigs = false, hasFunctionHookEnvironment = false;
26816
26935
  const verifyCurrentOpaqueSettings = (label) => {
26817
26936
  const retained = exclusion.retained;
@@ -26916,7 +27035,9 @@ function resolveDiscovery(options, exclusion) {
26916
27035
  unresolved("malformed Sumi plugins");
26917
27036
  }
26918
27037
  if (agent === "hermes" && config.skills !== undefined) {
26919
- keys2(config.skills, ["external_dirs"], "Hermes skills");
27038
+ keys2(config.skills, ["external_dirs", "creation_nudge_interval"], "Hermes skills");
27039
+ if (config.skills.creation_nudge_interval !== undefined && !(Number.isSafeInteger(config.skills.creation_nudge_interval) && config.skills.creation_nudge_interval >= 0))
27040
+ unresolved("malformed Hermes skills creation_nudge_interval");
26920
27041
  if (config.skills.external_dirs !== undefined)
26921
27042
  paths(typeof config.skills.external_dirs === "string" ? [config.skills.external_dirs] : config.skills.external_dirs, "Hermes external directories");
26922
27043
  }
@@ -26936,10 +27057,21 @@ function resolveDiscovery(options, exclusion) {
26936
27057
  unresolved(`malformed Codex ${key2}`);
26937
27058
  for (const value of Object.values(config.plugins ?? {}))
26938
27059
  keys2(value, ["enabled"], "Codex plugin registration");
26939
- for (const value of Object.values(config.marketplaces ?? {}))
26940
- keys2(value, ["source_type", "source"], "Codex marketplace registration");
27060
+ for (const value of Object.values(config.marketplaces ?? {})) {
27061
+ keys2(value, ["source_type", "source", "last_updated"], "Codex marketplace registration");
27062
+ if (value.last_updated !== undefined && (typeof value.last_updated !== "string" || !CODEX_MARKETPLACE_TIMESTAMP.test(value.last_updated)))
27063
+ unresolved("malformed Codex marketplace refresh time");
27064
+ }
26941
27065
  if (config.skills !== undefined)
26942
- keys2(config.skills, ["config", "bundled"], "Codex skills");
27066
+ keys2(config.skills, ["config", "bundled", "native_allowed_paths"], "Codex skills");
27067
+ if (config.skills?.native_allowed_paths !== undefined) {
27068
+ for (const value of paths(config.skills.native_allowed_paths, "Codex native allowed skill paths")) {
27069
+ if (!isAbsolute28(value))
27070
+ unresolved("relative Codex native allowed skill path");
27071
+ const path = canonical9(value);
27072
+ roots.add(path.endsWith(`${sep15}SKILL.md`) ? dirname29(path) : path);
27073
+ }
27074
+ }
26943
27075
  if (config.skills?.bundled !== undefined) {
26944
27076
  keys2(config.skills.bundled, ["enabled"], "Codex bundled skills");
26945
27077
  if (typeof config.skills.bundled.enabled !== "boolean")
@@ -26997,7 +27129,9 @@ function resolveDiscovery(options, exclusion) {
26997
27129
  const target = join41(path, name);
26998
27130
  if (!lstatSync34(target).isFile())
26999
27131
  unresolved("local plugin packages need a dedicated discovery adapter");
27000
- witness(target);
27132
+ const text2 = witness(target);
27133
+ if (exclusion && text2 !== null)
27134
+ sumiLocalPluginTexts.set(canonical9(target), text2);
27001
27135
  }
27002
27136
  }
27003
27137
  for (const root2 of roots) {
@@ -27131,16 +27265,88 @@ function resolveDiscovery(options, exclusion) {
27131
27265
  const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual9(candidate2, source)) === index);
27132
27266
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
27133
27267
  }
27134
- function plugin(root2) {
27268
+ const reviewedManifestAbsence = (root2, manifestPath) => {
27269
+ const retained = exclusion?.retained;
27270
+ if (agent !== "claude" || retained?.method !== "reviewed" || ![join41(root2, "skills"), root2].some((path) => retained.roots.includes(path)))
27271
+ return false;
27272
+ const saved = retained.sources.filter((source) => source.path === canonical9(manifestPath) && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined);
27273
+ return saved.length > 0 && saved.every((source) => projected(source, undefined, budget3) === source.sha256);
27274
+ };
27275
+ const reviewedInstalledCopy = (root2, path, identity5) => {
27276
+ const retained = exclusion?.retained;
27277
+ if (agent !== "codex" || retained?.method !== "reviewed")
27278
+ return false;
27279
+ const inside = relative10(root2, canonical9(path));
27280
+ if (!inside || inside === ".." || inside.startsWith(`..${sep15}`) || isAbsolute28(inside))
27281
+ return false;
27282
+ const parent = canonical9(join41(nativeHome, "plugins/cache", identity5.marketplace, identity5.name));
27283
+ const current = hashRawDiscoveryFile(canonical9(path), budget3);
27284
+ if (current === null)
27285
+ return false;
27286
+ return retained.sources.some((source) => {
27287
+ if (source.sha256 !== current || source.format !== undefined || source.fields !== undefined || ![undefined, "bytes"].includes(source.hashMode) || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || !source.path.startsWith(parent + sep15))
27288
+ return false;
27289
+ const [version2, ...rest] = source.path.slice(parent.length + 1).split(sep15);
27290
+ return Boolean(version2) && version2 !== "." && version2 !== ".." && rest.join(sep15) === inside;
27291
+ });
27292
+ };
27293
+ const verifyCodexPluginHookClosure = (root2, identity5, marketplaceSource, servers, declarations) => {
27294
+ const retained = exclusion.retained;
27295
+ if (retained.codexSemanticCache || retained.codexInstallationInputs || retained.codexRetiredMaterializations)
27296
+ return verifyOpaqueClosure(declarations);
27297
+ const within2 = (base, path) => path === base || path.startsWith(base + sep15);
27298
+ const executables = [];
27299
+ for (const server of servers) {
27300
+ const row = config.mcp_servers?.[server];
27301
+ if (server === "codex_apps" || server === "_codex_apps" || !isCodexLocalStdioMcpServer(server, row))
27302
+ unresolved("plugin hook MCP server is not a configured local stdio server");
27303
+ for (const value of [row.command, ...row.args ?? []])
27304
+ for (const candidate2 of [value, value.slice(value.indexOf("=") + 1)]) {
27305
+ if (!isAbsolute28(candidate2) || /[\0$]/.test(candidate2) || executables.length >= AGENT_POLICY_LIMITS.discoveryRoots)
27306
+ continue;
27307
+ const path = canonical9(candidate2);
27308
+ if (lstatSync34(path, { throwIfNoEntry: false })?.isFile())
27309
+ executables.push(path);
27310
+ }
27311
+ }
27312
+ const cacheRoot = canonical9(join41(nativeHome, "plugins/cache"));
27313
+ const ownCache = canonical9(join41(nativeHome, "plugins/cache", identity5.marketplace, identity5.name));
27314
+ const ownCatalog = canonical9(join41(marketplaceSource, ".agents/plugins/marketplace.json"));
27315
+ const account = canonical9(home);
27316
+ const marketplaceRoots = Object.values(config.marketplaces ?? {}).filter((market) => market?.source_type === "local" && typeof market.source === "string" && isAbsolute28(market.source)).map((market) => canonical9(market.source)).filter((path) => !within2(path, account));
27317
+ const otherPlugin = (path) => !within2(root2, path) && !within2(ownCache, path) && path !== ownCatalog && !executables.includes(path) && (within2(cacheRoot, path) || marketplaceRoots.some((base) => within2(base, path)));
27318
+ const sources2 = retained.sources.filter((source) => source.path !== canonical9(configPath) && !otherPlugin(source.path));
27319
+ if (sources2.some((source) => source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || ![undefined, "bytes", "path-bytes", "executable-v1"].includes(source.hashMode)))
27320
+ return verifyOpaqueClosure(declarations);
27321
+ const declared = declarations.map((path) => relative10(root2, path));
27322
+ const declaration = (path) => declarations.includes(path) || path.startsWith(ownCache + sep15) && declared.includes(path.slice(ownCache.length + 1).split(sep15).slice(1).join(sep15));
27323
+ if (!sources2.some((source) => !declaration(source.path) && source.sha256 !== null))
27324
+ unresolved("opaque executable dependency closure is unavailable");
27325
+ for (const source of sources2)
27326
+ if (projected(source, undefined, budget3) !== source.sha256) {
27327
+ throw new NativeDiscoveryWitnessChange("input", source.path, `Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
27328
+ }
27329
+ verifyDiscoveryDirectories((retained.directories ?? []).filter((directory2) => !otherPlugin(directory2.path)));
27330
+ };
27331
+ const readCodexHookDeclaration = (text2, path) => {
27332
+ if (text2 === null)
27333
+ return null;
27334
+ try {
27335
+ return codexPluginHookHandlers(parseConfig(text2, path));
27336
+ } catch {
27337
+ return null;
27338
+ }
27339
+ };
27340
+ function plugin(root2, codex) {
27135
27341
  root2 = canonical9(root2);
27136
27342
  safe6(root2);
27137
27343
  if (exclusion)
27138
27344
  roots.add(root2);
27139
27345
  const manifestPath = join41(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
27140
27346
  const raw = witness(manifestPath, undefined, undefined, agent === "claude" ? "claude-plugin-manifest-v1" : undefined);
27141
- if (raw === null)
27347
+ if (raw === null && !reviewedManifestAbsence(root2, manifestPath))
27142
27348
  unresolved("plugin manifest missing");
27143
- const manifest = parseConfig(raw, manifestPath);
27349
+ const manifest = raw === null ? {} : parseConfig(raw, manifestPath);
27144
27350
  if (exclusion && Object.keys(manifest).some((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2) && !["skills", "hooks"].includes(key2)))
27145
27351
  unresolved("unknown plugin discovery controls");
27146
27352
  const hooks = witness(join41(root2, "hooks/hooks.json"));
@@ -27148,20 +27354,35 @@ function resolveDiscovery(options, exclusion) {
27148
27354
  if (!exclusion || exclusion.retained?.method !== "reviewed")
27149
27355
  unresolved("plugin hooks require a separate no-skill-injection review");
27150
27356
  const required = [manifestPath, ...hooks !== null ? [join41(root2, "hooks/hooks.json")] : []];
27357
+ const scoped2 = agent === "codex" && codex !== undefined ? [] : undefined;
27151
27358
  if (manifest.hooks !== undefined) {
27152
- if (typeof manifest.hooks !== "string")
27153
- unresolved("unsupported plugin hooks target");
27154
- const hook = resolve28(root2, manifest.hooks);
27155
- if (!hook.startsWith(root2 + sep15))
27156
- unresolved("plugin hooks target escapes its root");
27157
- required.push(hook);
27158
- }
27359
+ if (scoped2 && manifest.hooks && typeof manifest.hooks === "object" && !Array.isArray(manifest.hooks)) {
27360
+ const inline = codexPluginHookHandlers(manifest.hooks);
27361
+ if (!inline)
27362
+ unresolved("unsupported inline plugin hooks");
27363
+ scoped2.push(inline);
27364
+ } else {
27365
+ if (typeof manifest.hooks !== "string")
27366
+ unresolved("unsupported plugin hooks target");
27367
+ const hook = resolve28(root2, manifest.hooks);
27368
+ if (!hook.startsWith(root2 + sep15))
27369
+ unresolved("plugin hooks target escapes its root");
27370
+ required.push(hook);
27371
+ if (scoped2)
27372
+ scoped2.push(readCodexHookDeclaration(witness(hook), hook));
27373
+ }
27374
+ }
27375
+ if (scoped2 && hooks !== null)
27376
+ scoped2.push(readCodexHookDeclaration(hooks, join41(root2, "hooks/hooks.json")));
27159
27377
  for (const path of required) {
27160
27378
  const saved = exclusion.retained.sources.filter((source) => source.path === canonical9(path) && source.sha256 !== null && source.format === undefined && source.fields === undefined);
27161
- if (!saved.length || saved.some((source) => projected(source, undefined, budget3) !== source.sha256))
27379
+ if (saved.length ? saved.some((source) => projected(source, undefined, budget3) !== source.sha256) : !(scoped2 && reviewedInstalledCopy(root2, path, codex)))
27162
27380
  unresolved("plugin hooks require current independently reviewed sources");
27163
27381
  }
27164
- verifyOpaqueClosure(required.map((path) => canonical9(path)));
27382
+ if (scoped2 && scoped2.every((item) => item !== null && item.commands === 0)) {
27383
+ verifyCodexPluginHookClosure(root2, codex, codex.source, new Set(scoped2.flatMap((item) => [...item.servers])), required.map((path) => canonical9(path)));
27384
+ } else
27385
+ verifyOpaqueClosure(required.map((path) => canonical9(path)));
27165
27386
  }
27166
27387
  const commands = join41(root2, "commands");
27167
27388
  safe6(commands);
@@ -27217,7 +27438,7 @@ function resolveDiscovery(options, exclusion) {
27217
27438
  const catalog = parseConfig(catalogText, join41(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
27218
27439
  if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
27219
27440
  unresolved("plugin catalog source is missing or ambiguous");
27220
- plugin(resolve28(market.source, matches[0].source.path));
27441
+ plugin(resolve28(market.source, matches[0].source.path), exclusion ? { marketplace, name, source: market.source } : undefined);
27221
27442
  }
27222
27443
  } else if (agent === "opencode") {
27223
27444
  if (witness(join41(home, ".config/opencode/opencode.jsonc")) !== null || witness(join41(home, ".config/opencode/config.json")) !== null)
@@ -27234,7 +27455,11 @@ function resolveDiscovery(options, exclusion) {
27234
27455
  if (lstatSync34(localPlugins, { throwIfNoEntry: false }) && entries(localPlugins).some((name) => name !== "skills-cli.js"))
27235
27456
  unresolved("additional local plugin hooks require review");
27236
27457
  } else if (agent === "sumi") {
27237
- if ((config.plugin?.length ?? 0) > 0 || (config.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(join41(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null))
27458
+ const pinnedLocalPlugin = (source) => {
27459
+ const retained = exclusion?.retained, text2 = sumiLocalPluginTexts.get(source.path);
27460
+ return retained?.method === "reviewed" && text2 !== undefined && sumiPluginLoadsOnlyBuiltins(text2) && retained.sources.some((saved) => saved.path === source.path && saved.sha256 === source.sha256 && saved.sha256 !== null && [undefined, "bytes"].includes(saved.hashMode) && saved.format === undefined && saved.fields === undefined && saved.managedPlugins === undefined && saved.reviewArtifact === undefined);
27461
+ };
27462
+ if ((config.plugin?.length ?? 0) > 0 || (config.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(join41(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null && !pinnedLocalPlugin(source)))
27238
27463
  unresolved("external or foreign local plugins require review");
27239
27464
  } else if (agent === "hermes") {
27240
27465
  for (const directory2 of [join41(home, ".hermes/plugins"), join41(home, ".hermes/hermes-agent")]) {
@@ -27342,7 +27567,10 @@ function configuredRoots(home, explicitHome) {
27342
27567
  if (recorded && (!executable2 || executable2.target !== recorded.target || onPath && onPath.target !== recorded.target) || !executable2 && (binding.builtinNames?.length || binding.agent === "gemini" && binding.sources.some((source) => basename18(source.path) === "package.json"))) {
27343
27568
  throw new Error("Native discovery runtime target is unresolved or changed");
27344
27569
  }
27345
- roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: binding.agent, canonical: physicalPath, retained: binding, executable: executable2, narrowed }));
27570
+ const managedCommand = current.commands?.[agent], managedProfile = current.profiles?.[agent];
27571
+ const supervisor = current.supervisors?.[agent];
27572
+ const managedHook = (binding.agent === "gemini" || binding.agent === "cursor") && typeof managedCommand === "string" && typeof managedProfile === "string" ? { command: managedCommand, profile: managedProfile } : binding.agent === "hermes" && typeof managedCommand === "string" && typeof managedProfile === "string" && supervisor && typeof supervisor === "object" && typeof supervisor.path === "string" && typeof supervisor.runtime === "string" && typeof supervisor.sha256 === "string" ? { command: managedCommand, profile: managedProfile, supervisor: { path: supervisor.path, runtime: supervisor.runtime, sha256: supervisor.sha256 } } : undefined;
27573
+ roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: binding.agent, canonical: physicalPath, retained: binding, executable: executable2, narrowed, ...managedHook ? { managedHook } : {} }));
27346
27574
  }
27347
27575
  };
27348
27576
  if (homePolicy)
@@ -1,4 +1,4 @@
1
- import type { IntegrationAgent } from "./agent-adapters.js";
1
+ import { type IntegrationAgent } from "./agent-adapters.js";
2
2
  import { type DiscoveryDirectory } from "./agent-discovery-directories.js";
3
3
  import { type CodexReviewedHookSource } from "./codex-hook-mcp-dependencies.js";
4
4
  import { CLAUDE_ORPHAN_MARKER_MODE } from "./claude-plugin-cache-marker.js";
@@ -214,8 +214,21 @@ export declare function resolveAgentDiscoveryExclusionRoots(options: Omit<Discov
214
214
  retained?: AgentDiscoveryBinding;
215
215
  narrowed?: NarrowedWitnessLedger;
216
216
  nativeHome?: string;
217
+ managedHook?: ManagedBridgeHook;
217
218
  }): readonly string[];
218
219
  /** Metadata-only closure of aliases inside the selected native discovery
219
220
  * trees. Never search the account generally or load a payload/plugin. Each
220
221
  * resolved alias remains excluded while unrelated portable paths stay usable. */
221
222
  export declare function nativeDiscoveryAliasTargets(roots: readonly string[], home: string): string[];
223
+ /** The Skills bridge command and selection profile the policy records for an
224
+ * agent (`bridge.commands` and `bridge.profiles`), from which hook install
225
+ * renders its managed hook commands. */
226
+ export interface ManagedBridgeHook {
227
+ command: string;
228
+ profile: string;
229
+ supervisor?: {
230
+ path: string;
231
+ runtime: string;
232
+ sha256: string;
233
+ };
234
+ }
@@ -28,7 +28,23 @@ export interface SelectedSecretScope {
28
28
  profileId: string;
29
29
  cwd: string;
30
30
  }
31
- export declare function selectedSecretBindingsTemplate(scope: SelectedSecretScope, names: string[], createClient?: () => SelectedSecretsClient): SelectedSecretBindings;
31
+ /**
32
+ * The part of the @hasna/secrets SDK entry Skills uses. Skills typechecks and
33
+ * builds without the optional package: src/types/hasna-secrets-sdk.d.ts declares
34
+ * this much of the module, and src/lib/secrets-sdk-types.test.ts checks both
35
+ * against the real package wherever that is installed.
36
+ */
37
+ export interface SecretsSdk {
38
+ createSecretsClientFromEnv(env?: Record<string, string | undefined>): SelectedSecretsClient;
39
+ }
40
+ /**
41
+ * The independently configured Secrets SDK. @hasna/secrets is an optional
42
+ * dependency that only an install with access to it receives (SH-00058), and
43
+ * the build keeps it external, so it is loaded here, when a vault grant needs
44
+ * it, and nowhere else.
45
+ */
46
+ export declare function defaultSecretsClientFactory(load?: () => Promise<SecretsSdk>): Promise<() => SelectedSecretsClient>;
47
+ export declare function selectedSecretBindingsTemplate(scope: SelectedSecretScope, names: string[], createClient: () => SelectedSecretsClient): SelectedSecretBindings;
32
48
  /** Runtime controls must not be supplied as credentials, even by a reviewed binding. */
33
49
  export declare function declaredSecretNames(value: unknown): string[];
34
50
  /** Reading is bounded and rejects symlinks/devices. Parsing errors never echo file contents. */
@@ -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.90";
52
+ var version = "0.10.92";
53
53
  var package_default = {
54
54
  name: "@hasna/skills",
55
55
  version,
@@ -96,6 +96,7 @@ var package_default = {
96
96
  "docs/runtime-activation.md",
97
97
  "schemas/",
98
98
  "LICENSE",
99
+ "THIRD_PARTY_NOTICES",
99
100
  "README.md",
100
101
  "!dist/**/*test-utils.d.ts",
101
102
  "!dist/**/*fixtures.d.ts"
@@ -103,9 +104,9 @@ var package_default = {
103
104
  main: "./dist/index.js",
104
105
  types: "./dist/index.d.ts",
105
106
  scripts: {
106
- clean: "rm -rf bin/ dist/",
107
- build: "bun run clean && bun build --external @hasna/contracts/kernel-lock ./src/cli/index.tsx --outdir ./bin --target bun && bun build --external @modelcontextprotocol/sdk ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/maintenance.ts --outfile ./bin/maintenance.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts",
108
- "build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts",
107
+ clean: "rm -rf bin/ dist/ THIRD_PARTY_NOTICES",
108
+ build: "bun run clean && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/cli/index.tsx --outdir ./bin --target bun && bun build --external @hasna/secrets --external @modelcontextprotocol/sdk ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/maintenance.ts --outfile ./bin/maintenance.js --target bun && bun build --external @hasna/secrets ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts && bun scripts/third-party-notices.ts",
109
+ "build:js": "rm -rf dist && bun build --external @hasna/secrets ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts",
109
110
  test: "bun test --timeout 30000",
110
111
  dev: "bun run ./src/cli/index.tsx",
111
112
  "dev:watch": "bun --watch run ./src/cli/index.tsx",
@@ -156,7 +157,6 @@ var package_default = {
156
157
  "@aws-sdk/client-ecs": "^3.1079.0",
157
158
  "@aws-sdk/client-s3": "^3.1079.0",
158
159
  "@hasna/events": "0.1.18",
159
- "@hasna/secrets": "0.4.2",
160
160
  "@modelcontextprotocol/sdk": "^1.26.0",
161
161
  chalk: "^5.3.0",
162
162
  commander: "^12.1.0",
@@ -168,6 +168,9 @@ var package_default = {
168
168
  zod: "^4.3.6",
169
169
  "@hasna/contracts": "1.3.5"
170
170
  },
171
+ optionalDependencies: {
172
+ "@hasna/secrets": "0.4.2"
173
+ },
171
174
  engines: {
172
175
  bun: ">=1.0.0"
173
176
  },