@hasna/hooks 0.9.3 → 0.9.5

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
@@ -5101,14 +5101,19 @@ var init_profiles = __esm(() => {
5101
5101
  init_app_home();
5102
5102
  });
5103
5103
 
5104
- // ../../node_modules/.bun/@hasna+contracts@1.0.2+e0db24ae03127387/node_modules/@hasna/contracts/dist/client/transport.js
5104
+ // ../contracts/dist/client/transport.js
5105
5105
  import { isIP } from "net";
5106
5106
  import { spawnSync } from "child_process";
5107
- import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as readFileSync7 } from "fs";
5108
- import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
5109
- import { createRequire } from "module";
5107
+ import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync22 } from "fs";
5108
+ import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
5110
5109
  import { hostname as osHostname } from "os";
5111
- import { isAbsolute as isAbsolute2, join as join9 } from "path";
5110
+ import { isAbsolute as isAbsolute2, join as join22 } from "path";
5111
+ import { createRequire } from "module";
5112
+ import { fileURLToPath as fileURLToPath22 } from "url";
5113
+ import { closeSync as closeSync3, fstatSync, lstatSync as lstatSync2, openSync as openSync3, readFileSync as readFileSync7, realpathSync as realpathSync2, statSync } from "fs";
5114
+ import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
5115
+ import { basename as basename2, dirname as dirname5, join as join9, relative, sep as sep2 } from "path";
5116
+ import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
5112
5117
  function envToken(name) {
5113
5118
  return name.toUpperCase().replace(/-/g, "_");
5114
5119
  }
@@ -5125,6 +5130,112 @@ function credentialOverrideEnvKey(name) {
5125
5130
  function credentialPointerEnvKey(name) {
5126
5131
  return `HASNA_${envToken(name)}_API_KEY_REF`;
5127
5132
  }
5133
+ function e(e2, n, r) {
5134
+ throw new Error(r ? `No known conditions for "${n}" specifier in "${e2}" package` : `Missing "${n}" specifier in "${e2}" package`);
5135
+ }
5136
+ function n(n2, i, o, f) {
5137
+ let s, u, l = r(n2, o), c = function(e2) {
5138
+ let n3 = new Set(["default", ...e2.conditions || []]);
5139
+ return e2.unsafe || n3.add(e2.require ? "require" : "import"), e2.unsafe || n3.add(e2.browser ? "browser" : "node"), n3;
5140
+ }(f || {}), a = i[l];
5141
+ if (a === undefined) {
5142
+ let e2, n3, r, t;
5143
+ for (t in i)
5144
+ n3 && t.length < n3.length || (t[t.length - 1] === "/" && l.startsWith(t) ? (u = l.substring(t.length), n3 = t) : t.length > 1 && (r = t.indexOf("*", 1), ~r && (e2 = RegExp("^" + t.substring(0, r) + "(.*)" + t.substring(1 + r) + "$").exec(l), e2 && e2[1] && (u = e2[1], n3 = t))));
5145
+ a = i[n3];
5146
+ }
5147
+ return a || e(n2, l), s = t(a, c), s || e(n2, l, 1), u && function(e2, n3) {
5148
+ let r, t = 0, i2 = e2.length, o2 = /[*]/g, f2 = /[/]$/;
5149
+ for (;t < i2; t++)
5150
+ e2[t] = o2.test(r = e2[t]) ? r.replace(o2, n3) : f2.test(r) ? r + n3 : r;
5151
+ }(s, u), s;
5152
+ }
5153
+ function r(e2, n2, r2) {
5154
+ if (e2 === n2 || n2 === ".")
5155
+ return ".";
5156
+ let t = e2 + "/", i = t.length, o = n2.slice(0, i) === t, f = o ? n2.slice(i) : n2;
5157
+ return f[0] === "#" ? f : o || !r2 ? f.slice(0, 2) === "./" ? f : "./" + f : f;
5158
+ }
5159
+ function t(e2, n2, r2) {
5160
+ if (e2) {
5161
+ if (typeof e2 == "string")
5162
+ return r2 && r2.add(e2), [e2];
5163
+ let i, o;
5164
+ if (Array.isArray(e2)) {
5165
+ for (o = r2 || new Set, i = 0;i < e2.length; i++)
5166
+ t(e2[i], n2, o);
5167
+ if (!r2 && o.size)
5168
+ return [...o];
5169
+ } else
5170
+ for (i in e2)
5171
+ if (n2.has(i))
5172
+ return t(e2[i], n2, r2);
5173
+ }
5174
+ }
5175
+ function o(e2, r2, t2) {
5176
+ let i, o2 = e2.exports;
5177
+ if (o2) {
5178
+ if (typeof o2 == "string")
5179
+ o2 = { ".": o2 };
5180
+ else
5181
+ for (i in o2) {
5182
+ i[0] !== "." && (o2 = { ".": o2 });
5183
+ break;
5184
+ }
5185
+ return n(e2.name, o2, r2 || ".", t2);
5186
+ }
5187
+ }
5188
+ function entryPoint(directory) {
5189
+ const root = realpathSync2(directory);
5190
+ const fd = openSync3(join9(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
5191
+ let pkg;
5192
+ try {
5193
+ const stat = fstatSync(fd);
5194
+ if (!stat.isFile() || stat.size > MAX_PACKAGE_BYTES)
5195
+ throw new Error("Invalid Secrets package metadata");
5196
+ pkg = JSON.parse(readFileSync7(fd, "utf8"));
5197
+ } finally {
5198
+ closeSync3(fd);
5199
+ }
5200
+ if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== PACKAGE) {
5201
+ throw new Error("Invalid Secrets package identity");
5202
+ }
5203
+ const target = o(pkg, ".")?.[0];
5204
+ if (typeof target !== "string" || !target.startsWith("./"))
5205
+ throw new Error("Missing Secrets import export");
5206
+ const parts = target.slice(2).split("/");
5207
+ if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
5208
+ throw new Error("Invalid Secrets import export");
5209
+ }
5210
+ const file = realpathSync2(join9(root, ...parts));
5211
+ const within = relative(root, file);
5212
+ if (!within || within === ".." || within.startsWith(`..${sep2}`) || !statSync(file).isFile()) {
5213
+ throw new Error("Secrets import export escapes its package or is not a file");
5214
+ }
5215
+ return pathToFileURL(file).href;
5216
+ }
5217
+ function resolveInstalledSecrets(parentUrl) {
5218
+ let directory = dirname5(fileURLToPath3(parentUrl));
5219
+ for (;; ) {
5220
+ if (basename2(directory) !== "node_modules") {
5221
+ const candidate = join9(directory, "node_modules", PACKAGE);
5222
+ let absent = false;
5223
+ try {
5224
+ lstatSync2(candidate);
5225
+ } catch (error) {
5226
+ if (error.code !== "ENOENT")
5227
+ throw error;
5228
+ absent = true;
5229
+ }
5230
+ if (!absent)
5231
+ return entryPoint(candidate);
5232
+ }
5233
+ const parent = dirname5(directory);
5234
+ if (parent === directory)
5235
+ throw new Error("Secrets SDK is not installed for this consumer");
5236
+ directory = parent;
5237
+ }
5238
+ }
5128
5239
  function homeDir(env) {
5129
5240
  const home = env.HOME?.trim();
5130
5241
  return home ? home : null;
@@ -5138,14 +5249,14 @@ function hasnaHomeDir(env) {
5138
5249
  if (override)
5139
5250
  return override;
5140
5251
  const home = homeDir(env);
5141
- return home ? join9(home, HASNA_HOME_DIR) : null;
5252
+ return home ? join22(home, HASNA_HOME_DIR) : null;
5142
5253
  }
5143
5254
  function appConfigDir(name, env) {
5144
5255
  const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
5145
5256
  if (configRoot)
5146
- return join9(configRoot, name);
5257
+ return join22(configRoot, name);
5147
5258
  const root = hasnaHomeDir(env);
5148
- return root ? join9(root, name, CONFIG_SUBDIR) : null;
5259
+ return root ? join22(root, name, CONFIG_SUBDIR) : null;
5149
5260
  }
5150
5261
  function credentialDiskSourceList(name, env, profile = null) {
5151
5262
  if (!SAFE_APP_SLUG.test(name))
@@ -5154,7 +5265,7 @@ function credentialDiskSourceList(name, env, profile = null) {
5154
5265
  if (!directory)
5155
5266
  return [];
5156
5267
  const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
5157
- return [{ path: join9(directory, file), tier: "disk" }];
5268
+ return [{ path: join22(directory, file), tier: "disk" }];
5158
5269
  }
5159
5270
  function credentialDiskSources(name, env) {
5160
5271
  return credentialDiskSourceList(name, env, null).map((s) => s.path);
@@ -5208,7 +5319,7 @@ function readAppConfigFile(path) {
5208
5319
  };
5209
5320
  let fd = -1;
5210
5321
  try {
5211
- fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
5322
+ fd = openSync2(path, O_RDONLY2 | O_NOFOLLOW2 | O_NONBLOCK2);
5212
5323
  } catch (error) {
5213
5324
  const code = error.code;
5214
5325
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -5218,7 +5329,7 @@ function readAppConfigFile(path) {
5218
5329
  unsafe(`the path could not be opened (${code ?? "unknown error"})`);
5219
5330
  }
5220
5331
  try {
5221
- const before = fstatSync(fd);
5332
+ const before = fstatSync2(fd);
5222
5333
  if (!before.isFile())
5223
5334
  unsafe("the path is not a regular file");
5224
5335
  if (!configFileModeAllowed(before.mode)) {
@@ -5229,8 +5340,8 @@ function readAppConfigFile(path) {
5229
5340
  unsafe("the file is not owned by the current user");
5230
5341
  if (before.size > MAX_CREDENTIAL_FILE_BYTES)
5231
5342
  unsafe("the file exceeds the size limit");
5232
- const bytes = readFileSync7(fd);
5233
- const after = fstatSync(fd);
5343
+ const bytes = readFileSync22(fd);
5344
+ const after = fstatSync2(fd);
5234
5345
  if (!configFileReadsCoherent(before, after)) {
5235
5346
  unsafe("the file changed while being read");
5236
5347
  }
@@ -5240,20 +5351,28 @@ function readAppConfigFile(path) {
5240
5351
  closeSync2(fd);
5241
5352
  }
5242
5353
  }
5243
- function readCredentialFile(path, apiKeyKeys) {
5354
+ function readCredentialFile(path, apiKeyKeys, pointerKey) {
5244
5355
  const parsed = readAppConfigFile(path);
5245
5356
  if (!parsed)
5246
5357
  return null;
5247
- for (const key of apiKeyKeys) {
5358
+ for (const key of [...apiKeyKeys, pointerKey]) {
5248
5359
  if (parsed.unusable.has(key)) {
5249
5360
  throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
5250
5361
  }
5251
5362
  }
5252
5363
  const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
5364
+ const pointer = parsed.values.get(pointerKey)?.trim();
5365
+ if (pointer !== undefined) {
5366
+ if (!VAULT_POINTER_SHAPE.test(pointer))
5367
+ throw new CredentialFileUnsafeError(path, `${pointerKey} must name a vault item`);
5368
+ if (values.length)
5369
+ throw new CredentialFileUnsafeError(path, "a credential file cannot select both a literal key and a vault reference");
5370
+ return { apiKey: "", pointerVaultKey: pointer };
5371
+ }
5253
5372
  if (new Set(values).size > 1) {
5254
5373
  throw new CredentialFileUnsafeError(path, "credential aliases disagree");
5255
5374
  }
5256
- return values[0] ?? null;
5375
+ return values[0] === undefined ? null : { apiKey: values[0] };
5257
5376
  }
5258
5377
  function appConfigDiskValue(name, env, keys) {
5259
5378
  const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
@@ -5325,6 +5444,18 @@ function sealCredential(fields) {
5325
5444
  });
5326
5445
  return Object.freeze(sealed);
5327
5446
  }
5447
+ function explicitCredential(appName, apiKey) {
5448
+ const source = "explicit apiKey option";
5449
+ assertUsableCredential(appName, source, apiKey);
5450
+ return sealCredential({
5451
+ apiKey,
5452
+ tier: "argument",
5453
+ source,
5454
+ deliberate: true,
5455
+ diskCandidates: [],
5456
+ warning: null
5457
+ });
5458
+ }
5328
5459
  function firstEnvValue(env, keys) {
5329
5460
  for (const key of keys) {
5330
5461
  if (!Object.prototype.hasOwnProperty.call(env, key))
@@ -5510,12 +5641,13 @@ function resolveCredential(name, env, options = {}) {
5510
5641
  }
5511
5642
  const paths = profileDiskSources(name, env, profile);
5512
5643
  for (const path of paths) {
5513
- const value = readCredentialFile(path, apiKeyKeys);
5644
+ const value = readCredentialFile(path, apiKeyKeys, pointerKeyName);
5514
5645
  if (value) {
5515
- assertUsableCredential(name, path, value);
5646
+ if (!value.pointerVaultKey)
5647
+ assertUsableCredential(name, path, value.apiKey);
5516
5648
  return sealCredential({
5517
- apiKey: value,
5518
- tier: "profile",
5649
+ ...value,
5650
+ tier: value.pointerVaultKey ? "pointer" : "profile",
5519
5651
  source: path,
5520
5652
  deliberate: true,
5521
5653
  diskCandidates: paths,
@@ -5523,7 +5655,7 @@ function resolveCredential(name, env, options = {}) {
5523
5655
  });
5524
5656
  }
5525
5657
  }
5526
- throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY}.`, paths);
5658
+ throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} or ${pointerKeyName} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY}.`, paths);
5527
5659
  }
5528
5660
  const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
5529
5661
  const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
@@ -5548,17 +5680,27 @@ function resolveCredential(name, env, options = {}) {
5548
5680
  });
5549
5681
  }
5550
5682
  const diskSourceList = credentialDiskSourceList(name, env, null);
5551
- const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
5683
+ const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys, pointerKeyName) })).filter((hit) => hit.value !== null);
5552
5684
  if (diskHits.length > 0) {
5553
5685
  const winner = diskHits[0];
5554
- assertUsableCredential(name, winner.src.path, winner.value);
5686
+ if (winner.value.pointerVaultKey) {
5687
+ return sealCredential({
5688
+ ...winner.value,
5689
+ tier: "pointer",
5690
+ source: winner.src.path,
5691
+ deliberate: false,
5692
+ diskCandidates: diskPaths,
5693
+ warning: null
5694
+ });
5695
+ }
5696
+ assertUsableCredential(name, winner.src.path, winner.value.apiKey);
5555
5697
  const divergentSources = [
5556
- ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
5557
- ...envHit && envHit.value !== winner.value ? [envHit.key] : []
5698
+ ...diskHits.slice(1).filter((hit) => hit.value.apiKey !== winner.value.apiKey || hit.value.pointerVaultKey !== winner.value.pointerVaultKey).map((hit) => hit.src.path),
5699
+ ...envHit && envHit.value !== winner.value.apiKey ? [envHit.key] : []
5558
5700
  ];
5559
5701
  const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.src.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.src.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
5560
5702
  return sealCredential({
5561
- apiKey: winner.value,
5703
+ apiKey: winner.value.apiKey,
5562
5704
  tier: winner.src.tier,
5563
5705
  source: winner.src.path,
5564
5706
  deliberate: false,
@@ -5579,6 +5721,53 @@ function resolveCredential(name, env, options = {}) {
5579
5721
  }
5580
5722
  return null;
5581
5723
  }
5724
+ async function completePointerCredential(name, pointerResolution, env = process.env) {
5725
+ const secretsEnv = snapshotClientEnvironment("secrets", env);
5726
+ const vaultKey = pointerResolution.pointerVaultKey;
5727
+ const pointerEnvKey = pointerResolution.source;
5728
+ if (!vaultKey) {
5729
+ throw new CredentialResolutionError(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
5730
+ }
5731
+ if (name === "secrets") {
5732
+ throw new CredentialResolutionError(name, "The Secrets bootstrap credential cannot reference the same hosted vault; configure an independent bootstrap provider.", [pointerEnvKey]);
5733
+ }
5734
+ let secretsSdk;
5735
+ try {
5736
+ const sdkUrl = resolveInstalledSecrets(import.meta.url);
5737
+ secretsSdk = requireSecretsSdk(fileURLToPath22(sdkUrl));
5738
+ } catch {
5739
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
5740
+ }
5741
+ let client;
5742
+ try {
5743
+ const bootstrap = resolveCredential("secrets", secretsEnv);
5744
+ if (!bootstrap || bootstrap.tier === "pointer") {
5745
+ throw new CredentialResolutionError("secrets", "The Secrets vault requires an independent, non-reference bootstrap credential.", bootstrap ? [bootstrap.source] : []);
5746
+ }
5747
+ client = secretsSdk.createSecretsClientFromEnv(secretsEnv);
5748
+ } catch {
5749
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets client could not be configured from this ` + `environment (the secrets service URL and key env are missing or invalid). A vault pointer is TERMINAL and ` + `never falls through to a literal or disk credential.`, [pointerEnvKey]);
5750
+ }
5751
+ let secret;
5752
+ try {
5753
+ secret = await client.getSecret({ key: vaultKey });
5754
+ } catch {
5755
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the vault could not be reached or the item is ` + `unavailable. A vault pointer is TERMINAL and never falls through to a literal or disk credential.`, [pointerEnvKey]);
5756
+ }
5757
+ const value = secret.value;
5758
+ if (!value) {
5759
+ throw new CredentialResolutionError(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
5760
+ }
5761
+ assertUsableCredential(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
5762
+ return sealCredential({
5763
+ apiKey: value,
5764
+ tier: "pointer",
5765
+ source: `${pointerEnvKey} -> vault:${vaultKey}`,
5766
+ deliberate: pointerResolution.deliberate,
5767
+ diskCandidates: pointerResolution.diskCandidates,
5768
+ warning: null
5769
+ });
5770
+ }
5582
5771
  function defaultFleetGatewayBaseUrl(name) {
5583
5772
  return `${DEFAULT_FLEET_GATEWAY_ORIGIN}/${validateAppSlug(name)}`;
5584
5773
  }
@@ -5781,8 +5970,9 @@ function credentialDiskSourcesForMessage(name, env) {
5781
5970
  const paths = credentialDiskSources(name, env);
5782
5971
  return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
5783
5972
  }
5784
- var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
5973
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", PACKAGE = "@hasna/secrets", MAX_PACKAGE_BYTES, CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, requireSecretsSdk, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
5785
5974
  var init_transport = __esm(() => {
5975
+ MAX_PACKAGE_BYTES = 1024 * 1024;
5786
5976
  CredentialResolutionError = class CredentialResolutionError extends Error {
5787
5977
  appName;
5788
5978
  attempted;
@@ -5810,7 +6000,6 @@ var init_transport = __esm(() => {
5810
6000
  INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
5811
6001
  CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
5812
6002
  AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
5813
- SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
5814
6003
  requireSecretsSdk = createRequire(import.meta.url);
5815
6004
  ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
5816
6005
  DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
@@ -6013,8 +6202,8 @@ var init_db_writer = __esm(() => {
6013
6202
  });
6014
6203
 
6015
6204
  // src/lib/hook-env.ts
6016
- import { dirname as dirname6, isAbsolute as isAbsolute3 } from "path";
6017
- import { existsSync as existsSync11, realpathSync as realpathSync2, statSync } from "fs";
6205
+ import { dirname as dirname7, isAbsolute as isAbsolute3 } from "path";
6206
+ import { existsSync as existsSync11, realpathSync as realpathSync3, statSync as statSync2 } from "fs";
6018
6207
  import { tmpdir as tmpdir2 } from "os";
6019
6208
  function isDeniedEnvName(name) {
6020
6209
  const upper = name.toUpperCase();
@@ -6043,7 +6232,7 @@ function isUnsafePathEntry(entry, home) {
6043
6232
  return true;
6044
6233
  const canonical = (path) => {
6045
6234
  try {
6046
- return realpathSync2(path);
6235
+ return realpathSync3(path);
6047
6236
  } catch {
6048
6237
  return path;
6049
6238
  }
@@ -6055,7 +6244,7 @@ function isUnsafePathEntry(entry, home) {
6055
6244
  return true;
6056
6245
  }
6057
6246
  try {
6058
- if ((statSync(entry).mode & 2) !== 0)
6247
+ if ((statSync2(entry).mode & 2) !== 0)
6059
6248
  return true;
6060
6249
  } catch {}
6061
6250
  return false;
@@ -6064,7 +6253,7 @@ function sanitizeHookPath(pathValue, home) {
6064
6253
  const baseline = PATH_BASELINE.slice();
6065
6254
  if (process.platform === "darwin" && existsSync11("/opt/homebrew/bin"))
6066
6255
  baseline.push("/opt/homebrew/bin");
6067
- const bunDir = dirname6(process.execPath);
6256
+ const bunDir = dirname7(process.execPath);
6068
6257
  const seen = new Set;
6069
6258
  const out = [];
6070
6259
  for (const dir of baseline) {
@@ -6189,9 +6378,9 @@ __export(exports_run, {
6189
6378
  });
6190
6379
  import { randomBytes as randomBytes2 } from "crypto";
6191
6380
  import { rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
6192
- import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute4, join as join12 } from "path";
6381
+ import { basename as basename3, dirname as dirname8, isAbsolute as isAbsolute4, join as join12 } from "path";
6193
6382
  function extensionOf(scriptPath) {
6194
- const base = basename2(scriptPath);
6383
+ const base = basename3(scriptPath);
6195
6384
  const dot = base.lastIndexOf(".");
6196
6385
  return dot > 0 ? base.slice(dot + 1) : "ts";
6197
6386
  }
@@ -6203,9 +6392,9 @@ function shebangInterpreter(firstLine) {
6203
6392
  return null;
6204
6393
  const parts = rest.split(/\s+/);
6205
6394
  if (parts[0] === "env" || parts[0] === "/usr/bin/env") {
6206
- return parts[1] && parts[1] !== "-S" ? basename2(parts[1]) : parts[2] ? basename2(parts[2]) : null;
6395
+ return parts[1] && parts[1] !== "-S" ? basename3(parts[1]) : parts[2] ? basename3(parts[2]) : null;
6207
6396
  }
6208
- return basename2(parts[0]);
6397
+ return basename3(parts[0]);
6209
6398
  }
6210
6399
  function interpreterFor(name, scriptPath, content) {
6211
6400
  const ext = extensionOf(scriptPath);
@@ -6239,8 +6428,8 @@ async function readPipeWithDeadline(stream, deadlineMs) {
6239
6428
  result = await Promise.race([
6240
6429
  reader.read(),
6241
6430
  new Promise((_, reject) => {
6242
- const t = setTimeout(() => reject(new Error("pipe-drain-deadline")), remaining);
6243
- t.unref?.();
6431
+ const t2 = setTimeout(() => reject(new Error("pipe-drain-deadline")), remaining);
6432
+ t2.unref?.();
6244
6433
  })
6245
6434
  ]);
6246
6435
  } catch {
@@ -6258,7 +6447,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
6258
6447
  return chunks.join("");
6259
6448
  }
6260
6449
  async function executeVerifiedScript(options) {
6261
- const scriptDir = dirname7(options.scriptPath);
6450
+ const scriptDir = dirname8(options.scriptPath);
6262
6451
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
6263
6452
  const tempPath = join12(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
6264
6453
  let proc = null;
@@ -7473,7 +7662,7 @@ function mementosCommandSuffix(value) {
7473
7662
  return value.enabled ? ` --enable-mementos${value.secretRef ? ` --openrouter-secret-ref ${value.secretRef}` : ""}` : " --disable-mementos";
7474
7663
  }
7475
7664
  function retainedMementosRegistration(commands) {
7476
- const values = commands.filter((command) => /^hooks run mementos-context(?:\s|$)/.test(command)).map((command) => {
7665
+ const values = commands.map((command) => command.trim()).filter((command) => /^hooks run mementos-context(?:\s|$)/.test(command)).map((command) => {
7477
7666
  const args = command.trim().split(/\s+/).slice(3);
7478
7667
  const flags = {};
7479
7668
  const seen = new Set;
@@ -8015,8 +8204,36 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
8015
8204
  if (!settings.hooks)
8016
8205
  settings.hooks = {};
8017
8206
  const retained = name === "mementos-context" && mementos === undefined ? retainedMementosRegistration(Object.values(settings.hooks).flatMap((groups) => Array.isArray(groups) ? groups.flatMap((group) => Array.isArray(group.hooks) ? group.hooks.map((hook) => hook.command).filter((command) => typeof command === "string") : []) : [])) : undefined;
8018
- removeHookFromAllEvents(settings, name);
8019
8207
  const hookCommand = (profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`) + mementosCommandSuffix(mementos ?? retained);
8208
+ if (target === "codex") {
8209
+ const positions = [];
8210
+ for (const [event, groups] of Object.entries(settings.hooks)) {
8211
+ for (const entry of groups) {
8212
+ for (let index = 0;index < entry.hooks.length; index++) {
8213
+ const command = entry.hooks[index]?.command;
8214
+ const match = typeof command === "string" && command.trim().match(/^(?:hooks run ([\w-]+)(?:\s|$)|hook-([\w-]+)$)/);
8215
+ if (match && (match[1] ?? match[2]) === name) {
8216
+ if (registrationName(command) !== name)
8217
+ throw new Error("Unrecognized Codex registration; reconcile it before updating this hook.");
8218
+ positions.push({ event, entry, index });
8219
+ }
8220
+ }
8221
+ }
8222
+ }
8223
+ if (positions.length) {
8224
+ if (positions.length !== uniqueEventKeys.length || uniqueEventKeys.some((event) => positions.filter((position) => position.event === event).length !== 1) || positions.some((position) => (position.entry.matcher ?? "") !== (meta.matcher ?? ""))) {
8225
+ throw new Error("Codex hook events, matchers or duplicate registrations require explicit reconciliation; no settings were changed.");
8226
+ }
8227
+ for (const { entry, index } of positions) {
8228
+ entry.hooks[index] = { ...entry.hooks[index], type: "command", command: hookCommand };
8229
+ if (typeof meta.timeoutSeconds === "number" && meta.timeoutSeconds > 0)
8230
+ entry.hooks[index].timeout = meta.timeoutSeconds;
8231
+ }
8232
+ writeSettings(settings, scope, target);
8233
+ return;
8234
+ }
8235
+ }
8236
+ removeHookFromAllEvents(settings, name);
8020
8237
  for (const eventKey of uniqueEventKeys) {
8021
8238
  if (!settings.hooks[eventKey])
8022
8239
  settings.hooks[eventKey] = [];
@@ -8293,6 +8510,41 @@ function rethrowHooksAuthorityFailure(error) {
8293
8510
  }
8294
8511
  throw new Error(`REMOTE_API_URL_INVALID: ${message} local mode fallback is disabled`, { cause: error });
8295
8512
  }
8513
+ var authorityBindings = new WeakMap;
8514
+ function sameCredential(left, right) {
8515
+ return left === null || right === null ? left === right : left.apiKey === right.apiKey && left.pointerVaultKey === right.pointerVaultKey && left.source === right.source && left.tier === right.tier;
8516
+ }
8517
+ function changedBinding() {
8518
+ throw new Error("REMOTE_API_CREDENTIAL_INVALID: The configured Hooks authority or credential changed while preparing a request; no request was sent.");
8519
+ }
8520
+ async function resolveHooksRequestAuthority(authority) {
8521
+ if (authority.origin !== hooksRegistryOrigin(authority.v1BaseUrl))
8522
+ changedBinding();
8523
+ const binding = authorityBindings.get(authority);
8524
+ if (!binding)
8525
+ return { ...authority, apiKey: explicitCredential("hooks", authority.apiKey).apiKey };
8526
+ const snapshot = () => {
8527
+ const next = resolveHooksTransport(binding.env, binding.options);
8528
+ if (next.mode !== "remote" || !next.authority || next.authority.v1BaseUrl !== authority.v1BaseUrl)
8529
+ changedBinding();
8530
+ return { authority: next.authority, binding: authorityBindings.get(next.authority) };
8531
+ };
8532
+ const first = snapshot();
8533
+ const reviewed = snapshot();
8534
+ if (!sameCredential(first.binding.credential, reviewed.binding.credential))
8535
+ changedBinding();
8536
+ let credential = reviewed.binding.credential;
8537
+ try {
8538
+ if (credential.tier === "pointer")
8539
+ credential = await completePointerCredential("hooks", credential, binding.env);
8540
+ } catch (error) {
8541
+ rethrowHooksAuthorityFailure(error);
8542
+ }
8543
+ const final = snapshot();
8544
+ if (!sameCredential(reviewed.binding.credential, final.binding.credential))
8545
+ changedBinding();
8546
+ return { ...reviewed.authority, apiKey: credential.apiKey };
8547
+ }
8296
8548
  function resolveHooksTransport(env = process.env, options = {}) {
8297
8549
  if (selectsHooksLocalStore(env)) {
8298
8550
  announceLocal(options.notice, "HASNA_HOOKS_LOCAL is set and nothing configures a registry authority");
@@ -8306,7 +8558,7 @@ function resolveHooksTransport(env = process.env, options = {}) {
8306
8558
  try {
8307
8559
  credential = resolveCredential("hooks", resolverEnv, credentialOptions);
8308
8560
  resolution = resolveClientTransport("hooks", resolverEnv, {
8309
- credentials: credential ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
8561
+ credentials: credential && credential.tier !== "pointer" ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
8310
8562
  });
8311
8563
  } catch (error) {
8312
8564
  rethrowHooksAuthorityFailure(error);
@@ -8314,25 +8566,43 @@ function resolveHooksTransport(env = process.env, options = {}) {
8314
8566
  if (!credential) {
8315
8567
  throw new Error("REMOTE_API_CONFIG_MISSING: no hooks registry credential resolved; " + "local mode fallback is disabled");
8316
8568
  }
8569
+ const authority = {
8570
+ origin: hooksRegistryOrigin(resolution.baseUrl),
8571
+ apiKey: credential.apiKey,
8572
+ apiUrlSource: resolution.apiUrlSource,
8573
+ apiKeySource: credential.source,
8574
+ apiKeyTier: credential.tier,
8575
+ v1BaseUrl: resolution.baseUrl,
8576
+ warning: resolution.warning
8577
+ };
8578
+ authorityBindings.set(authority, { env, options, credential });
8317
8579
  return {
8318
8580
  mode: "remote",
8319
8581
  source: `${credential.source}+${resolution.apiUrlSource ?? "default"}`,
8320
- authority: {
8321
- origin: hooksRegistryOrigin(resolution.baseUrl),
8322
- apiKey: credential.apiKey,
8323
- apiUrlSource: resolution.apiUrlSource,
8324
- apiKeySource: credential.source,
8325
- apiKeyTier: credential.tier,
8326
- v1BaseUrl: resolution.baseUrl,
8327
- warning: resolution.warning
8328
- }
8582
+ authority
8329
8583
  };
8330
8584
  }
8331
8585
  function resolveHooksServePublishKey(env = process.env, options = {}) {
8332
8586
  const { env: resolverEnv, credentials } = hooksResolverInputs(env, options.credentials);
8333
8587
  const tier1 = options.credentials?.apiKey;
8334
8588
  const credentialOptions = tier1 !== undefined ? { ...credentials, apiKey: tier1 } : credentials;
8335
- return resolveCredential("hooks", resolverEnv, credentialOptions)?.apiKey;
8589
+ const credential = resolveCredential("hooks", resolverEnv, credentialOptions);
8590
+ if (credential?.tier === "pointer")
8591
+ throw new Error("Vault references require resolveHooksServePublishKeyAsync().");
8592
+ return credential?.apiKey;
8593
+ }
8594
+ async function resolveHooksServePublishKeyAsync(env = process.env, options = {}) {
8595
+ const { env: resolverEnv, credentials } = hooksResolverInputs(env, options.credentials);
8596
+ const read = () => resolveCredential("hooks", resolverEnv, credentials);
8597
+ const selected = read();
8598
+ if (!selected || selected.tier !== "pointer")
8599
+ return selected?.apiKey;
8600
+ if (!sameCredential(selected, read()))
8601
+ changedBinding();
8602
+ const completed = await completePointerCredential("hooks", selected, env);
8603
+ if (!sameCredential(selected, read()))
8604
+ changedBinding();
8605
+ return completed.apiKey;
8336
8606
  }
8337
8607
 
8338
8608
  // src/index.ts
@@ -8471,7 +8741,14 @@ function collectBundledCatalog() {
8471
8741
  }
8472
8742
  async function fetchJson(base, path, apiKey, opts) {
8473
8743
  const headers = { accept: "application/json" };
8474
- if (apiKey)
8744
+ if (typeof apiKey === "object") {
8745
+ if (apiKey.origin !== base)
8746
+ throw new Error("Hooks registry authority does not match the request target");
8747
+ const resolved = await resolveHooksRequestAuthority(apiKey);
8748
+ if (resolved.origin !== base)
8749
+ throw new Error("Hooks registry authority changed before dispatch");
8750
+ headers["x-api-key"] = resolved.apiKey;
8751
+ } else if (apiKey)
8475
8752
  headers["x-api-key"] = apiKey;
8476
8753
  const res = await fetch(`${base}${path}`, {
8477
8754
  headers,
@@ -8539,8 +8816,8 @@ async function planSync(options = {}) {
8539
8816
  credentials: options.credentials
8540
8817
  });
8541
8818
  if (transport.mode === "remote" && transport.authority) {
8542
- const { origin, apiKey } = transport.authority;
8543
- const { catalog, remoteLock } = await fetchRemoteState(origin, apiKey);
8819
+ const { origin } = transport.authority;
8820
+ const { catalog, remoteLock } = await fetchRemoteState(origin, transport.authority);
8544
8821
  return { apiUrl: origin, dryRun: options.dryRun ?? false, diff: computeDiff(catalog, remoteLock) };
8545
8822
  }
8546
8823
  const diff = computeDiff(collectBundledCatalog(), null);
@@ -8568,10 +8845,10 @@ async function syncHooks(options = {}) {
8568
8845
  }
8569
8846
  return plan;
8570
8847
  }
8571
- const { origin, apiKey } = transport.authority;
8572
- const { catalog, remoteLock } = await fetchRemoteState(origin, apiKey);
8848
+ const { origin } = transport.authority;
8849
+ const { catalog, remoteLock } = await fetchRemoteState(origin, transport.authority);
8573
8850
  const diff = computeDiff(catalog, remoteLock);
8574
- const staged = await stageSyncArtifacts(origin, apiKey, [...diff.added, ...diff.updated], remoteLock);
8851
+ const staged = await stageSyncArtifacts(origin, transport.authority, [...diff.added, ...diff.updated], remoteLock);
8575
8852
  await commitSyncArtifacts(staged, origin, remoteLock);
8576
8853
  return { apiUrl: origin, dryRun: false, diff };
8577
8854
  }
@@ -8748,8 +9025,8 @@ async function installPinnedFromBundled(name, version) {
8748
9025
  init_manifest();
8749
9026
  init_store();
8750
9027
  import { readFileSync as readFileSync10 } from "fs";
8751
- import { dirname as dirname5, join as join11 } from "path";
8752
- import { fileURLToPath as fileURLToPath3 } from "url";
9028
+ import { dirname as dirname6, join as join11 } from "path";
9029
+ import { fileURLToPath as fileURLToPath4 } from "url";
8753
9030
 
8754
9031
  // src/lib/secure-compare.ts
8755
9032
  function secureEqual(a, b) {
@@ -8757,8 +9034,8 @@ function secureEqual(a, b) {
8757
9034
  const ab = enc.encode(a);
8758
9035
  const bb = enc.encode(b);
8759
9036
  let diff = ab.length ^ bb.length;
8760
- const n = Math.max(ab.length, bb.length);
8761
- for (let i = 0;i < n; i++) {
9037
+ const n2 = Math.max(ab.length, bb.length);
9038
+ for (let i = 0;i < n2; i++) {
8762
9039
  diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
8763
9040
  }
8764
9041
  return diff === 0;
@@ -8922,7 +9199,7 @@ var SERVE_HOST = "127.0.0.1";
8922
9199
  var SERVE_SERVICE_NAME = "hooks-registry";
8923
9200
  function packageVersion() {
8924
9201
  try {
8925
- const pkgPath = join11(dirname5(fileURLToPath3(import.meta.url)), "..", "package.json");
9202
+ const pkgPath = join11(dirname6(fileURLToPath4(import.meta.url)), "..", "package.json");
8926
9203
  return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
8927
9204
  } catch {
8928
9205
  return "0.0.0";
@@ -9084,7 +9361,7 @@ function startServeServer(options) {
9084
9361
  hostname: host,
9085
9362
  port,
9086
9363
  async fetch(req) {
9087
- return handleServeRequest(req, resolveHooksServePublishKey());
9364
+ return handleServeRequest(req, await resolveHooksServePublishKeyAsync());
9088
9365
  }
9089
9366
  });
9090
9367
  console.error(`hooks registry listening on http://${host}:${server.port} (publish requires an API key)`);
@@ -9306,7 +9583,9 @@ export {
9306
9583
  resolveScriptPath,
9307
9584
  resolveScript,
9308
9585
  resolveHooksTransport,
9586
+ resolveHooksServePublishKeyAsync,
9309
9587
  resolveHooksServePublishKey,
9588
+ resolveHooksRequestAuthority,
9310
9589
  resolveHookMeta,
9311
9590
  resolveHookEventSink,
9312
9591
  resolveHookDir,