@hasna/hooks 0.9.4 → 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/bin/index.js CHANGED
@@ -6352,14 +6352,19 @@ var init_profiles = __esm(() => {
6352
6352
  init_app_home();
6353
6353
  });
6354
6354
 
6355
- // ../../node_modules/.bun/@hasna+contracts@1.0.2+e0db24ae03127387/node_modules/@hasna/contracts/dist/client/transport.js
6355
+ // ../contracts/dist/client/transport.js
6356
6356
  import { isIP as isIP2 } from "net";
6357
6357
  import { spawnSync } from "child_process";
6358
- import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as readFileSync7 } from "fs";
6359
- import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
6360
- import { createRequire as createRequire2 } from "module";
6358
+ import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync22 } from "fs";
6359
+ import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
6361
6360
  import { hostname as osHostname } from "os";
6362
- import { isAbsolute as isAbsolute2, join as join12 } from "path";
6361
+ import { isAbsolute as isAbsolute2, join as join22 } from "path";
6362
+ import { createRequire as createRequire2 } from "module";
6363
+ import { fileURLToPath as fileURLToPath22 } from "url";
6364
+ import { closeSync as closeSync3, fstatSync, lstatSync as lstatSync2, openSync as openSync3, readFileSync as readFileSync7, realpathSync as realpathSync2, statSync } from "fs";
6365
+ import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
6366
+ import { basename as basename2, dirname as dirname5, join as join12, relative, sep as sep2 } from "path";
6367
+ import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
6363
6368
  function envToken(name) {
6364
6369
  return name.toUpperCase().replace(/-/g, "_");
6365
6370
  }
@@ -6376,6 +6381,112 @@ function credentialOverrideEnvKey(name) {
6376
6381
  function credentialPointerEnvKey(name) {
6377
6382
  return `HASNA_${envToken(name)}_API_KEY_REF`;
6378
6383
  }
6384
+ function e(e2, n, r) {
6385
+ throw new Error(r ? `No known conditions for "${n}" specifier in "${e2}" package` : `Missing "${n}" specifier in "${e2}" package`);
6386
+ }
6387
+ function n(n2, i, o, f) {
6388
+ let s, u, l = r(n2, o), c = function(e2) {
6389
+ let n3 = new Set(["default", ...e2.conditions || []]);
6390
+ return e2.unsafe || n3.add(e2.require ? "require" : "import"), e2.unsafe || n3.add(e2.browser ? "browser" : "node"), n3;
6391
+ }(f || {}), a = i[l];
6392
+ if (a === undefined) {
6393
+ let e2, n3, r, t;
6394
+ for (t in i)
6395
+ 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))));
6396
+ a = i[n3];
6397
+ }
6398
+ return a || e(n2, l), s = t(a, c), s || e(n2, l, 1), u && function(e2, n3) {
6399
+ let r, t = 0, i2 = e2.length, o2 = /[*]/g, f2 = /[/]$/;
6400
+ for (;t < i2; t++)
6401
+ e2[t] = o2.test(r = e2[t]) ? r.replace(o2, n3) : f2.test(r) ? r + n3 : r;
6402
+ }(s, u), s;
6403
+ }
6404
+ function r(e2, n2, r2) {
6405
+ if (e2 === n2 || n2 === ".")
6406
+ return ".";
6407
+ let t = e2 + "/", i = t.length, o = n2.slice(0, i) === t, f = o ? n2.slice(i) : n2;
6408
+ return f[0] === "#" ? f : o || !r2 ? f.slice(0, 2) === "./" ? f : "./" + f : f;
6409
+ }
6410
+ function t(e2, n2, r2) {
6411
+ if (e2) {
6412
+ if (typeof e2 == "string")
6413
+ return r2 && r2.add(e2), [e2];
6414
+ let i, o;
6415
+ if (Array.isArray(e2)) {
6416
+ for (o = r2 || new Set, i = 0;i < e2.length; i++)
6417
+ t(e2[i], n2, o);
6418
+ if (!r2 && o.size)
6419
+ return [...o];
6420
+ } else
6421
+ for (i in e2)
6422
+ if (n2.has(i))
6423
+ return t(e2[i], n2, r2);
6424
+ }
6425
+ }
6426
+ function o(e2, r2, t2) {
6427
+ let i, o2 = e2.exports;
6428
+ if (o2) {
6429
+ if (typeof o2 == "string")
6430
+ o2 = { ".": o2 };
6431
+ else
6432
+ for (i in o2) {
6433
+ i[0] !== "." && (o2 = { ".": o2 });
6434
+ break;
6435
+ }
6436
+ return n(e2.name, o2, r2 || ".", t2);
6437
+ }
6438
+ }
6439
+ function entryPoint(directory) {
6440
+ const root = realpathSync2(directory);
6441
+ const fd = openSync3(join12(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
6442
+ let pkg;
6443
+ try {
6444
+ const stat = fstatSync(fd);
6445
+ if (!stat.isFile() || stat.size > MAX_PACKAGE_BYTES2)
6446
+ throw new Error("Invalid Secrets package metadata");
6447
+ pkg = JSON.parse(readFileSync7(fd, "utf8"));
6448
+ } finally {
6449
+ closeSync3(fd);
6450
+ }
6451
+ if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== PACKAGE) {
6452
+ throw new Error("Invalid Secrets package identity");
6453
+ }
6454
+ const target = o(pkg, ".")?.[0];
6455
+ if (typeof target !== "string" || !target.startsWith("./"))
6456
+ throw new Error("Missing Secrets import export");
6457
+ const parts = target.slice(2).split("/");
6458
+ if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
6459
+ throw new Error("Invalid Secrets import export");
6460
+ }
6461
+ const file = realpathSync2(join12(root, ...parts));
6462
+ const within = relative(root, file);
6463
+ if (!within || within === ".." || within.startsWith(`..${sep2}`) || !statSync(file).isFile()) {
6464
+ throw new Error("Secrets import export escapes its package or is not a file");
6465
+ }
6466
+ return pathToFileURL(file).href;
6467
+ }
6468
+ function resolveInstalledSecrets(parentUrl) {
6469
+ let directory = dirname5(fileURLToPath3(parentUrl));
6470
+ for (;; ) {
6471
+ if (basename2(directory) !== "node_modules") {
6472
+ const candidate = join12(directory, "node_modules", PACKAGE);
6473
+ let absent = false;
6474
+ try {
6475
+ lstatSync2(candidate);
6476
+ } catch (error) {
6477
+ if (error.code !== "ENOENT")
6478
+ throw error;
6479
+ absent = true;
6480
+ }
6481
+ if (!absent)
6482
+ return entryPoint(candidate);
6483
+ }
6484
+ const parent = dirname5(directory);
6485
+ if (parent === directory)
6486
+ throw new Error("Secrets SDK is not installed for this consumer");
6487
+ directory = parent;
6488
+ }
6489
+ }
6379
6490
  function homeDir(env) {
6380
6491
  const home = env.HOME?.trim();
6381
6492
  return home ? home : null;
@@ -6389,14 +6500,14 @@ function hasnaHomeDir(env) {
6389
6500
  if (override)
6390
6501
  return override;
6391
6502
  const home = homeDir(env);
6392
- return home ? join12(home, HASNA_HOME_DIR) : null;
6503
+ return home ? join22(home, HASNA_HOME_DIR) : null;
6393
6504
  }
6394
6505
  function appConfigDir(name, env) {
6395
6506
  const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
6396
6507
  if (configRoot)
6397
- return join12(configRoot, name);
6508
+ return join22(configRoot, name);
6398
6509
  const root = hasnaHomeDir(env);
6399
- return root ? join12(root, name, CONFIG_SUBDIR) : null;
6510
+ return root ? join22(root, name, CONFIG_SUBDIR) : null;
6400
6511
  }
6401
6512
  function credentialDiskSourceList(name, env, profile = null) {
6402
6513
  if (!SAFE_APP_SLUG.test(name))
@@ -6405,7 +6516,7 @@ function credentialDiskSourceList(name, env, profile = null) {
6405
6516
  if (!directory)
6406
6517
  return [];
6407
6518
  const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
6408
- return [{ path: join12(directory, file), tier: "disk" }];
6519
+ return [{ path: join22(directory, file), tier: "disk" }];
6409
6520
  }
6410
6521
  function credentialDiskSources(name, env) {
6411
6522
  return credentialDiskSourceList(name, env, null).map((s) => s.path);
@@ -6459,7 +6570,7 @@ function readAppConfigFile(path) {
6459
6570
  };
6460
6571
  let fd = -1;
6461
6572
  try {
6462
- fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
6573
+ fd = openSync2(path, O_RDONLY2 | O_NOFOLLOW2 | O_NONBLOCK2);
6463
6574
  } catch (error) {
6464
6575
  const code = error.code;
6465
6576
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -6469,7 +6580,7 @@ function readAppConfigFile(path) {
6469
6580
  unsafe(`the path could not be opened (${code ?? "unknown error"})`);
6470
6581
  }
6471
6582
  try {
6472
- const before = fstatSync(fd);
6583
+ const before = fstatSync2(fd);
6473
6584
  if (!before.isFile())
6474
6585
  unsafe("the path is not a regular file");
6475
6586
  if (!configFileModeAllowed(before.mode)) {
@@ -6480,8 +6591,8 @@ function readAppConfigFile(path) {
6480
6591
  unsafe("the file is not owned by the current user");
6481
6592
  if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
6482
6593
  unsafe("the file exceeds the size limit");
6483
- const bytes = readFileSync7(fd);
6484
- const after = fstatSync(fd);
6594
+ const bytes = readFileSync22(fd);
6595
+ const after = fstatSync2(fd);
6485
6596
  if (!configFileReadsCoherent(before, after)) {
6486
6597
  unsafe("the file changed while being read");
6487
6598
  }
@@ -6491,20 +6602,28 @@ function readAppConfigFile(path) {
6491
6602
  closeSync2(fd);
6492
6603
  }
6493
6604
  }
6494
- function readCredentialFile(path, apiKeyKeys) {
6605
+ function readCredentialFile(path, apiKeyKeys, pointerKey) {
6495
6606
  const parsed = readAppConfigFile(path);
6496
6607
  if (!parsed)
6497
6608
  return null;
6498
- for (const key of apiKeyKeys) {
6609
+ for (const key of [...apiKeyKeys, pointerKey]) {
6499
6610
  if (parsed.unusable.has(key)) {
6500
6611
  throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
6501
6612
  }
6502
6613
  }
6503
6614
  const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
6615
+ const pointer = parsed.values.get(pointerKey)?.trim();
6616
+ if (pointer !== undefined) {
6617
+ if (!VAULT_POINTER_SHAPE.test(pointer))
6618
+ throw new CredentialFileUnsafeError(path, `${pointerKey} must name a vault item`);
6619
+ if (values.length)
6620
+ throw new CredentialFileUnsafeError(path, "a credential file cannot select both a literal key and a vault reference");
6621
+ return { apiKey: "", pointerVaultKey: pointer };
6622
+ }
6504
6623
  if (new Set(values).size > 1) {
6505
6624
  throw new CredentialFileUnsafeError(path, "credential aliases disagree");
6506
6625
  }
6507
- return values[0] ?? null;
6626
+ return values[0] === undefined ? null : { apiKey: values[0] };
6508
6627
  }
6509
6628
  function appConfigDiskValue(name, env, keys) {
6510
6629
  const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
@@ -6576,6 +6695,18 @@ function sealCredential(fields) {
6576
6695
  });
6577
6696
  return Object.freeze(sealed);
6578
6697
  }
6698
+ function explicitCredential(appName, apiKey) {
6699
+ const source = "explicit apiKey option";
6700
+ assertUsableCredential(appName, source, apiKey);
6701
+ return sealCredential({
6702
+ apiKey,
6703
+ tier: "argument",
6704
+ source,
6705
+ deliberate: true,
6706
+ diskCandidates: [],
6707
+ warning: null
6708
+ });
6709
+ }
6579
6710
  function firstEnvValue(env, keys) {
6580
6711
  for (const key of keys) {
6581
6712
  if (!Object.prototype.hasOwnProperty.call(env, key))
@@ -6761,12 +6892,13 @@ function resolveCredential(name, env, options = {}) {
6761
6892
  }
6762
6893
  const paths = profileDiskSources(name, env, profile);
6763
6894
  for (const path of paths) {
6764
- const value = readCredentialFile(path, apiKeyKeys);
6895
+ const value = readCredentialFile(path, apiKeyKeys, pointerKeyName);
6765
6896
  if (value) {
6766
- assertUsableCredential(name, path, value);
6897
+ if (!value.pointerVaultKey)
6898
+ assertUsableCredential(name, path, value.apiKey);
6767
6899
  return sealCredential({
6768
- apiKey: value,
6769
- tier: "profile",
6900
+ ...value,
6901
+ tier: value.pointerVaultKey ? "pointer" : "profile",
6770
6902
  source: path,
6771
6903
  deliberate: true,
6772
6904
  diskCandidates: paths,
@@ -6774,7 +6906,7 @@ function resolveCredential(name, env, options = {}) {
6774
6906
  });
6775
6907
  }
6776
6908
  }
6777
- 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);
6909
+ 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);
6778
6910
  }
6779
6911
  const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
6780
6912
  const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
@@ -6799,17 +6931,27 @@ function resolveCredential(name, env, options = {}) {
6799
6931
  });
6800
6932
  }
6801
6933
  const diskSourceList = credentialDiskSourceList(name, env, null);
6802
- const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
6934
+ const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys, pointerKeyName) })).filter((hit) => hit.value !== null);
6803
6935
  if (diskHits.length > 0) {
6804
6936
  const winner = diskHits[0];
6805
- assertUsableCredential(name, winner.src.path, winner.value);
6937
+ if (winner.value.pointerVaultKey) {
6938
+ return sealCredential({
6939
+ ...winner.value,
6940
+ tier: "pointer",
6941
+ source: winner.src.path,
6942
+ deliberate: false,
6943
+ diskCandidates: diskPaths,
6944
+ warning: null
6945
+ });
6946
+ }
6947
+ assertUsableCredential(name, winner.src.path, winner.value.apiKey);
6806
6948
  const divergentSources = [
6807
- ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
6808
- ...envHit && envHit.value !== winner.value ? [envHit.key] : []
6949
+ ...diskHits.slice(1).filter((hit) => hit.value.apiKey !== winner.value.apiKey || hit.value.pointerVaultKey !== winner.value.pointerVaultKey).map((hit) => hit.src.path),
6950
+ ...envHit && envHit.value !== winner.value.apiKey ? [envHit.key] : []
6809
6951
  ];
6810
6952
  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;
6811
6953
  return sealCredential({
6812
- apiKey: winner.value,
6954
+ apiKey: winner.value.apiKey,
6813
6955
  tier: winner.src.tier,
6814
6956
  source: winner.src.path,
6815
6957
  deliberate: false,
@@ -6830,6 +6972,53 @@ function resolveCredential(name, env, options = {}) {
6830
6972
  }
6831
6973
  return null;
6832
6974
  }
6975
+ async function completePointerCredential(name, pointerResolution, env = process.env) {
6976
+ const secretsEnv = snapshotClientEnvironment("secrets", env);
6977
+ const vaultKey = pointerResolution.pointerVaultKey;
6978
+ const pointerEnvKey = pointerResolution.source;
6979
+ if (!vaultKey) {
6980
+ throw new CredentialResolutionError(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
6981
+ }
6982
+ if (name === "secrets") {
6983
+ throw new CredentialResolutionError(name, "The Secrets bootstrap credential cannot reference the same hosted vault; configure an independent bootstrap provider.", [pointerEnvKey]);
6984
+ }
6985
+ let secretsSdk;
6986
+ try {
6987
+ const sdkUrl = resolveInstalledSecrets(import.meta.url);
6988
+ secretsSdk = requireSecretsSdk2(fileURLToPath22(sdkUrl));
6989
+ } catch {
6990
+ 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]);
6991
+ }
6992
+ let client;
6993
+ try {
6994
+ const bootstrap = resolveCredential("secrets", secretsEnv);
6995
+ if (!bootstrap || bootstrap.tier === "pointer") {
6996
+ throw new CredentialResolutionError("secrets", "The Secrets vault requires an independent, non-reference bootstrap credential.", bootstrap ? [bootstrap.source] : []);
6997
+ }
6998
+ client = secretsSdk.createSecretsClientFromEnv(secretsEnv);
6999
+ } catch {
7000
+ 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]);
7001
+ }
7002
+ let secret;
7003
+ try {
7004
+ secret = await client.getSecret({ key: vaultKey });
7005
+ } catch {
7006
+ 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]);
7007
+ }
7008
+ const value = secret.value;
7009
+ if (!value) {
7010
+ throw new CredentialResolutionError(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
7011
+ }
7012
+ assertUsableCredential(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
7013
+ return sealCredential({
7014
+ apiKey: value,
7015
+ tier: "pointer",
7016
+ source: `${pointerEnvKey} -> vault:${vaultKey}`,
7017
+ deliberate: pointerResolution.deliberate,
7018
+ diskCandidates: pointerResolution.diskCandidates,
7019
+ warning: null
7020
+ });
7021
+ }
6833
7022
  function defaultFleetGatewayBaseUrl(name) {
6834
7023
  return `${DEFAULT_FLEET_GATEWAY_ORIGIN}/${validateAppSlug(name)}`;
6835
7024
  }
@@ -7032,8 +7221,9 @@ function credentialDiskSourcesForMessage(name, env) {
7032
7221
  const paths = credentialDiskSources(name, env);
7033
7222
  return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
7034
7223
  }
7035
- 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_BYTES2, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, AMBIENT_ENVIRONMENT2, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk2, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2;
7224
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", PACKAGE = "@hasna/secrets", MAX_PACKAGE_BYTES2, 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_BYTES2, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, AMBIENT_ENVIRONMENT2, requireSecretsSdk2, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2;
7036
7225
  var init_transport = __esm(() => {
7226
+ MAX_PACKAGE_BYTES2 = 1024 * 1024;
7037
7227
  CredentialResolutionError = class CredentialResolutionError extends Error {
7038
7228
  appName;
7039
7229
  attempted;
@@ -7061,7 +7251,6 @@ var init_transport = __esm(() => {
7061
7251
  INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
7062
7252
  CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
7063
7253
  AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
7064
- SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
7065
7254
  requireSecretsSdk2 = createRequire2(import.meta.url);
7066
7255
  ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
7067
7256
  DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
@@ -7150,7 +7339,9 @@ var exports_transport = {};
7150
7339
  __export(exports_transport, {
7151
7340
  rethrowHooksAuthorityFailure: () => rethrowHooksAuthorityFailure,
7152
7341
  resolveHooksTransport: () => resolveHooksTransport,
7342
+ resolveHooksServePublishKeyAsync: () => resolveHooksServePublishKeyAsync,
7153
7343
  resolveHooksServePublishKey: () => resolveHooksServePublishKey,
7344
+ resolveHooksRequestAuthority: () => resolveHooksRequestAuthority,
7154
7345
  hooksRegistryOrigin: () => hooksRegistryOrigin,
7155
7346
  announceHooksLocalMode: () => announceHooksLocalMode,
7156
7347
  __resetHooksLocalNotice: () => __resetHooksLocalNotice
@@ -7189,6 +7380,40 @@ function rethrowHooksAuthorityFailure(error) {
7189
7380
  }
7190
7381
  throw new Error(`REMOTE_API_URL_INVALID: ${message} local mode fallback is disabled`, { cause: error });
7191
7382
  }
7383
+ function sameCredential(left, right) {
7384
+ return left === null || right === null ? left === right : left.apiKey === right.apiKey && left.pointerVaultKey === right.pointerVaultKey && left.source === right.source && left.tier === right.tier;
7385
+ }
7386
+ function changedBinding() {
7387
+ throw new Error("REMOTE_API_CREDENTIAL_INVALID: The configured Hooks authority or credential changed while preparing a request; no request was sent.");
7388
+ }
7389
+ async function resolveHooksRequestAuthority(authority) {
7390
+ if (authority.origin !== hooksRegistryOrigin(authority.v1BaseUrl))
7391
+ changedBinding();
7392
+ const binding = authorityBindings.get(authority);
7393
+ if (!binding)
7394
+ return { ...authority, apiKey: explicitCredential("hooks", authority.apiKey).apiKey };
7395
+ const snapshot = () => {
7396
+ const next = resolveHooksTransport(binding.env, binding.options);
7397
+ if (next.mode !== "remote" || !next.authority || next.authority.v1BaseUrl !== authority.v1BaseUrl)
7398
+ changedBinding();
7399
+ return { authority: next.authority, binding: authorityBindings.get(next.authority) };
7400
+ };
7401
+ const first = snapshot();
7402
+ const reviewed = snapshot();
7403
+ if (!sameCredential(first.binding.credential, reviewed.binding.credential))
7404
+ changedBinding();
7405
+ let credential = reviewed.binding.credential;
7406
+ try {
7407
+ if (credential.tier === "pointer")
7408
+ credential = await completePointerCredential("hooks", credential, binding.env);
7409
+ } catch (error) {
7410
+ rethrowHooksAuthorityFailure(error);
7411
+ }
7412
+ const final = snapshot();
7413
+ if (!sameCredential(reviewed.binding.credential, final.binding.credential))
7414
+ changedBinding();
7415
+ return { ...reviewed.authority, apiKey: credential.apiKey };
7416
+ }
7192
7417
  function resolveHooksTransport(env = process.env, options = {}) {
7193
7418
  if (selectsHooksLocalStore(env)) {
7194
7419
  announceLocal(options.notice, "HASNA_HOOKS_LOCAL is set and nothing configures a registry authority");
@@ -7202,7 +7427,7 @@ function resolveHooksTransport(env = process.env, options = {}) {
7202
7427
  try {
7203
7428
  credential = resolveCredential("hooks", resolverEnv, credentialOptions);
7204
7429
  resolution = resolveClientTransport("hooks", resolverEnv, {
7205
- credentials: credential ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
7430
+ credentials: credential && credential.tier !== "pointer" ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
7206
7431
  });
7207
7432
  } catch (error) {
7208
7433
  rethrowHooksAuthorityFailure(error);
@@ -7210,31 +7435,50 @@ function resolveHooksTransport(env = process.env, options = {}) {
7210
7435
  if (!credential) {
7211
7436
  throw new Error("REMOTE_API_CONFIG_MISSING: no hooks registry credential resolved; " + "local mode fallback is disabled");
7212
7437
  }
7438
+ const authority = {
7439
+ origin: hooksRegistryOrigin(resolution.baseUrl),
7440
+ apiKey: credential.apiKey,
7441
+ apiUrlSource: resolution.apiUrlSource,
7442
+ apiKeySource: credential.source,
7443
+ apiKeyTier: credential.tier,
7444
+ v1BaseUrl: resolution.baseUrl,
7445
+ warning: resolution.warning
7446
+ };
7447
+ authorityBindings.set(authority, { env, options, credential });
7213
7448
  return {
7214
7449
  mode: "remote",
7215
7450
  source: `${credential.source}+${resolution.apiUrlSource ?? "default"}`,
7216
- authority: {
7217
- origin: hooksRegistryOrigin(resolution.baseUrl),
7218
- apiKey: credential.apiKey,
7219
- apiUrlSource: resolution.apiUrlSource,
7220
- apiKeySource: credential.source,
7221
- apiKeyTier: credential.tier,
7222
- v1BaseUrl: resolution.baseUrl,
7223
- warning: resolution.warning
7224
- }
7451
+ authority
7225
7452
  };
7226
7453
  }
7227
7454
  function resolveHooksServePublishKey(env = process.env, options = {}) {
7228
7455
  const { env: resolverEnv, credentials } = hooksResolverInputs(env, options.credentials);
7229
7456
  const tier1 = options.credentials?.apiKey;
7230
7457
  const credentialOptions = tier1 !== undefined ? { ...credentials, apiKey: tier1 } : credentials;
7231
- return resolveCredential("hooks", resolverEnv, credentialOptions)?.apiKey;
7458
+ const credential = resolveCredential("hooks", resolverEnv, credentialOptions);
7459
+ if (credential?.tier === "pointer")
7460
+ throw new Error("Vault references require resolveHooksServePublishKeyAsync().");
7461
+ return credential?.apiKey;
7232
7462
  }
7233
- var localNoticePrinted = false;
7463
+ async function resolveHooksServePublishKeyAsync(env = process.env, options = {}) {
7464
+ const { env: resolverEnv, credentials } = hooksResolverInputs(env, options.credentials);
7465
+ const read = () => resolveCredential("hooks", resolverEnv, credentials);
7466
+ const selected = read();
7467
+ if (!selected || selected.tier !== "pointer")
7468
+ return selected?.apiKey;
7469
+ if (!sameCredential(selected, read()))
7470
+ changedBinding();
7471
+ const completed = await completePointerCredential("hooks", selected, env);
7472
+ if (!sameCredential(selected, read()))
7473
+ changedBinding();
7474
+ return completed.apiKey;
7475
+ }
7476
+ var localNoticePrinted = false, authorityBindings;
7234
7477
  var init_transport2 = __esm(() => {
7235
7478
  init_transport();
7236
7479
  init_config();
7237
7480
  init_local_opt_in();
7481
+ authorityBindings = new WeakMap;
7238
7482
  });
7239
7483
 
7240
7484
  // src/lib/db-writer.ts
@@ -7356,8 +7600,8 @@ var init_db_writer = __esm(() => {
7356
7600
  });
7357
7601
 
7358
7602
  // src/lib/hook-env.ts
7359
- import { dirname as dirname5, isAbsolute as isAbsolute3 } from "path";
7360
- import { existsSync as existsSync13, realpathSync as realpathSync2, statSync } from "fs";
7603
+ import { dirname as dirname6, isAbsolute as isAbsolute3 } from "path";
7604
+ import { existsSync as existsSync13, realpathSync as realpathSync3, statSync as statSync2 } from "fs";
7361
7605
  import { tmpdir } from "os";
7362
7606
  function isDeniedEnvName(name) {
7363
7607
  const upper = name.toUpperCase();
@@ -7386,7 +7630,7 @@ function isUnsafePathEntry(entry, home) {
7386
7630
  return true;
7387
7631
  const canonical = (path) => {
7388
7632
  try {
7389
- return realpathSync2(path);
7633
+ return realpathSync3(path);
7390
7634
  } catch {
7391
7635
  return path;
7392
7636
  }
@@ -7398,7 +7642,7 @@ function isUnsafePathEntry(entry, home) {
7398
7642
  return true;
7399
7643
  }
7400
7644
  try {
7401
- if ((statSync(entry).mode & 2) !== 0)
7645
+ if ((statSync2(entry).mode & 2) !== 0)
7402
7646
  return true;
7403
7647
  } catch {}
7404
7648
  return false;
@@ -7407,7 +7651,7 @@ function sanitizeHookPath(pathValue, home) {
7407
7651
  const baseline = PATH_BASELINE.slice();
7408
7652
  if (process.platform === "darwin" && existsSync13("/opt/homebrew/bin"))
7409
7653
  baseline.push("/opt/homebrew/bin");
7410
- const bunDir = dirname5(process.execPath);
7654
+ const bunDir = dirname6(process.execPath);
7411
7655
  const seen = new Set;
7412
7656
  const out = [];
7413
7657
  for (const dir of baseline) {
@@ -7532,9 +7776,9 @@ __export(exports_run, {
7532
7776
  });
7533
7777
  import { randomBytes as randomBytes2 } from "crypto";
7534
7778
  import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
7535
- import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute4, join as join13 } from "path";
7779
+ import { basename as basename3, dirname as dirname7, isAbsolute as isAbsolute4, join as join13 } from "path";
7536
7780
  function extensionOf(scriptPath) {
7537
- const base = basename2(scriptPath);
7781
+ const base = basename3(scriptPath);
7538
7782
  const dot = base.lastIndexOf(".");
7539
7783
  return dot > 0 ? base.slice(dot + 1) : "ts";
7540
7784
  }
@@ -7546,9 +7790,9 @@ function shebangInterpreter(firstLine) {
7546
7790
  return null;
7547
7791
  const parts = rest.split(/\s+/);
7548
7792
  if (parts[0] === "env" || parts[0] === "/usr/bin/env") {
7549
- return parts[1] && parts[1] !== "-S" ? basename2(parts[1]) : parts[2] ? basename2(parts[2]) : null;
7793
+ return parts[1] && parts[1] !== "-S" ? basename3(parts[1]) : parts[2] ? basename3(parts[2]) : null;
7550
7794
  }
7551
- return basename2(parts[0]);
7795
+ return basename3(parts[0]);
7552
7796
  }
7553
7797
  function interpreterFor(name, scriptPath, content) {
7554
7798
  const ext = extensionOf(scriptPath);
@@ -7582,8 +7826,8 @@ async function readPipeWithDeadline(stream, deadlineMs) {
7582
7826
  result = await Promise.race([
7583
7827
  reader.read(),
7584
7828
  new Promise((_, reject) => {
7585
- const t = setTimeout(() => reject(new Error("pipe-drain-deadline")), remaining);
7586
- t.unref?.();
7829
+ const t2 = setTimeout(() => reject(new Error("pipe-drain-deadline")), remaining);
7830
+ t2.unref?.();
7587
7831
  })
7588
7832
  ]);
7589
7833
  } catch {
@@ -7601,7 +7845,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
7601
7845
  return chunks.join("");
7602
7846
  }
7603
7847
  async function executeVerifiedScript(options) {
7604
- const scriptDir = dirname6(options.scriptPath);
7848
+ const scriptDir = dirname7(options.scriptPath);
7605
7849
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
7606
7850
  const tempPath = join13(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
7607
7851
  let proc = null;
@@ -7836,7 +8080,14 @@ function collectBundledCatalog() {
7836
8080
  }
7837
8081
  async function fetchJson(base, path, apiKey, opts) {
7838
8082
  const headers = { accept: "application/json" };
7839
- if (apiKey)
8083
+ if (typeof apiKey === "object") {
8084
+ if (apiKey.origin !== base)
8085
+ throw new Error("Hooks registry authority does not match the request target");
8086
+ const resolved = await resolveHooksRequestAuthority(apiKey);
8087
+ if (resolved.origin !== base)
8088
+ throw new Error("Hooks registry authority changed before dispatch");
8089
+ headers["x-api-key"] = resolved.apiKey;
8090
+ } else if (apiKey)
7840
8091
  headers["x-api-key"] = apiKey;
7841
8092
  const res = await fetch(`${base}${path}`, {
7842
8093
  headers,
@@ -7904,8 +8155,8 @@ async function planSync(options = {}) {
7904
8155
  credentials: options.credentials
7905
8156
  });
7906
8157
  if (transport.mode === "remote" && transport.authority) {
7907
- const { origin, apiKey } = transport.authority;
7908
- const { catalog, remoteLock } = await fetchRemoteState(origin, apiKey);
8158
+ const { origin } = transport.authority;
8159
+ const { catalog, remoteLock } = await fetchRemoteState(origin, transport.authority);
7909
8160
  return { apiUrl: origin, dryRun: options.dryRun ?? false, diff: computeDiff(catalog, remoteLock) };
7910
8161
  }
7911
8162
  const diff = computeDiff(collectBundledCatalog(), null);
@@ -7933,10 +8184,10 @@ async function syncHooks(options = {}) {
7933
8184
  }
7934
8185
  return plan;
7935
8186
  }
7936
- const { origin, apiKey } = transport.authority;
7937
- const { catalog, remoteLock } = await fetchRemoteState(origin, apiKey);
8187
+ const { origin } = transport.authority;
8188
+ const { catalog, remoteLock } = await fetchRemoteState(origin, transport.authority);
7938
8189
  const diff = computeDiff(catalog, remoteLock);
7939
- const staged = await stageSyncArtifacts(origin, apiKey, [...diff.added, ...diff.updated], remoteLock);
8190
+ const staged = await stageSyncArtifacts(origin, transport.authority, [...diff.added, ...diff.updated], remoteLock);
7940
8191
  await commitSyncArtifacts(staged, origin, remoteLock);
7941
8192
  return { apiUrl: origin, dryRun: false, diff };
7942
8193
  }
@@ -8597,8 +8848,8 @@ function secureEqual(a, b) {
8597
8848
  const ab = enc.encode(a);
8598
8849
  const bb = enc.encode(b);
8599
8850
  let diff = ab.length ^ bb.length;
8600
- const n = Math.max(ab.length, bb.length);
8601
- for (let i = 0;i < n; i++) {
8851
+ const n2 = Math.max(ab.length, bb.length);
8852
+ for (let i = 0;i < n2; i++) {
8602
8853
  diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
8603
8854
  }
8604
8855
  return diff === 0;
@@ -8769,11 +9020,11 @@ __export(exports_serve, {
8769
9020
  DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
8770
9021
  });
8771
9022
  import { readFileSync as readFileSync10 } from "fs";
8772
- import { dirname as dirname7, join as join15 } from "path";
8773
- import { fileURLToPath as fileURLToPath3 } from "url";
9023
+ import { dirname as dirname8, join as join15 } from "path";
9024
+ import { fileURLToPath as fileURLToPath4 } from "url";
8774
9025
  function packageVersion() {
8775
9026
  try {
8776
- const pkgPath = join15(dirname7(fileURLToPath3(import.meta.url)), "..", "package.json");
9027
+ const pkgPath = join15(dirname8(fileURLToPath4(import.meta.url)), "..", "package.json");
8777
9028
  return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
8778
9029
  } catch {
8779
9030
  return "0.0.0";
@@ -8935,7 +9186,7 @@ function startServeServer(options) {
8935
9186
  hostname: host,
8936
9187
  port,
8937
9188
  async fetch(req) {
8938
- return handleServeRequest(req, resolveHooksServePublishKey());
9189
+ return handleServeRequest(req, await resolveHooksServePublishKeyAsync());
8939
9190
  }
8940
9191
  });
8941
9192
  console.error(`hooks registry listening on http://${host}:${server.port} (publish requires an API key)`);
@@ -9076,8 +9327,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
9076
9327
  import { z as z2 } from "zod";
9077
9328
  import { createServer } from "http";
9078
9329
  import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
9079
- import { join as join16, dirname as dirname8 } from "path";
9080
- import { fileURLToPath as fileURLToPath4 } from "url";
9330
+ import { join as join16, dirname as dirname9 } from "path";
9331
+ import { fileURLToPath as fileURLToPath5 } from "url";
9081
9332
  async function runVerifiedHook(name, stdin, timeoutMs) {
9082
9333
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
9083
9334
  const resolved = resolveHook2(name);
@@ -9151,8 +9402,8 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
9151
9402
  return ran;
9152
9403
  }
9153
9404
  function formatInstallResults(results, extra) {
9154
- const installed = results.filter((r) => r.success).map((r) => r.hook);
9155
- const failed = results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error }));
9405
+ const installed = results.filter((r2) => r2.success).map((r2) => r2.hook);
9406
+ const failed = results.filter((r2) => !r2.success).map((r2) => ({ hook: r2.hook, error: r2.error }));
9156
9407
  return {
9157
9408
  content: [{
9158
9409
  type: "text",
@@ -9515,8 +9766,8 @@ function createHooksServer() {
9515
9766
  }
9516
9767
  return installHook(name, { scope, overwrite: true });
9517
9768
  });
9518
- const updated = results.filter((r) => r.success).map((r) => r.hook);
9519
- const failed = results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error }));
9769
+ const updated = results.filter((r2) => r2.success).map((r2) => r2.hook);
9770
+ const failed = results.filter((r2) => !r2.success).map((r2) => ({ hook: r2.hook, error: r2.error }));
9520
9771
  return { content: [{ type: "text", text: JSON.stringify({ updated, failed, total: results.length }) }] };
9521
9772
  });
9522
9773
  defineTool("hooks_context", "Get compact agent context in one call: installed hooks, active profile summary, settings path, and doctor status.", {
@@ -9603,7 +9854,7 @@ function createHooksServer() {
9603
9854
  return { name, decision: "block", error: err instanceof Error ? err.message : String(err) };
9604
9855
  }
9605
9856
  }));
9606
- const blocked = results.find((r) => r.decision === "block");
9857
+ const blocked = results.find((r2) => r2.decision === "block");
9607
9858
  return {
9608
9859
  content: [{
9609
9860
  type: "text",
@@ -9637,8 +9888,8 @@ function createHooksServer() {
9637
9888
  const target = agent_type === "gemini" ? "gemini" : "claude";
9638
9889
  const toInstall = hooks && hooks.length > 0 ? hooks : ["gitguard", "checkpoint", "checktests", "protectfiles"];
9639
9890
  const results = toInstall.map((h) => installHook(h, { scope, overwrite: false, profile: profile.agent_id, target }));
9640
- const installed = results.filter((r) => r.success).map((r) => r.hook);
9641
- const failed = results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error }));
9891
+ const installed = results.filter((r2) => r2.success).map((r2) => r2.hook);
9892
+ const failed = results.filter((r2) => !r2.success).map((r2) => ({ hook: r2.hook, error: r2.error }));
9642
9893
  return {
9643
9894
  content: [{
9644
9895
  type: "text",
@@ -9696,8 +9947,8 @@ function createHooksServer() {
9696
9947
  disabled.push(name);
9697
9948
  settings.hooks.__disabled = disabled;
9698
9949
  const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync8 } = await import("fs");
9699
- const { dirname: dirname9 } = await import("path");
9700
- mkdirSync8(dirname9(settingsPath), { recursive: true });
9950
+ const { dirname: dirname10 } = await import("path");
9951
+ mkdirSync8(dirname10(settingsPath), { recursive: true });
9701
9952
  writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
9702
9953
  `);
9703
9954
  return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
@@ -9713,7 +9964,7 @@ function createHooksServer() {
9713
9964
  settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
9714
9965
  } catch {}
9715
9966
  if (settings.hooks?.__disabled) {
9716
- settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
9967
+ settings.hooks.__disabled = settings.hooks.__disabled.filter((n2) => n2 !== name);
9717
9968
  if (settings.hooks.__disabled.length === 0)
9718
9969
  delete settings.hooks.__disabled;
9719
9970
  const { writeFileSync: writeFileSync7 } = await import("fs");
@@ -9770,9 +10021,9 @@ function createHooksServer() {
9770
10021
  const m = s.match(/^(\d+)(s|m|h|d)$/);
9771
10022
  if (!m)
9772
10023
  return null;
9773
- const n = parseInt(m[1]);
10024
+ const n2 = parseInt(m[1]);
9774
10025
  const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
9775
- return new Date(Date.now() - n * ms).toISOString();
10026
+ return new Date(Date.now() - n2 * ms).toISOString();
9776
10027
  }
9777
10028
  let sql = "SELECT * FROM hook_events WHERE 1=1";
9778
10029
  const params = [];
@@ -9810,10 +10061,10 @@ function createHooksServer() {
9810
10061
  defineTool("hooks_log_tail", "Show recent hook events from SQLite. Compact summaries by default.", {
9811
10062
  n: z2.number().default(20).describe("Number of most recent events to return"),
9812
10063
  compact: z2.boolean().default(true).describe("Return compact event summaries by default. Set false for full rows.")
9813
- }, async ({ n, compact }) => {
10064
+ }, async ({ n: n2, compact }) => {
9814
10065
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
9815
10066
  const db = getDb2();
9816
- const maxRows = boundedLimit(n, 20, compact ? 100 : 500);
10067
+ const maxRows = boundedLimit(n2, 20, compact ? 100 : 500);
9817
10068
  const rows = db.query("SELECT * FROM hook_events ORDER BY timestamp DESC LIMIT ?").all(maxRows);
9818
10069
  const projected = projectLogRows(rows);
9819
10070
  return {
@@ -9840,9 +10091,9 @@ function createHooksServer() {
9840
10091
  const m = s.match(/^(\d+)(s|m|h|d)$/);
9841
10092
  if (!m)
9842
10093
  return s;
9843
- const n = parseInt(m[1]);
10094
+ const n2 = parseInt(m[1]);
9844
10095
  const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
9845
- return new Date(Date.now() - n * ms).toISOString();
10096
+ return new Date(Date.now() - n2 * ms).toISOString();
9846
10097
  }
9847
10098
  const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
9848
10099
  const rows = db.query("SELECT * FROM hook_events WHERE error IS NOT NULL AND timestamp >= ? ORDER BY timestamp DESC LIMIT ?").all(ts, maxRows);
@@ -9868,20 +10119,20 @@ function createHooksServer() {
9868
10119
  const m = s.match(/^(\d+)(s|m|h|d)$/);
9869
10120
  if (!m)
9870
10121
  return s;
9871
- const n = parseInt(m[1]);
10122
+ const n2 = parseInt(m[1]);
9872
10123
  const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
9873
- return new Date(Date.now() - n * ms).toISOString();
10124
+ return new Date(Date.now() - n2 * ms).toISOString();
9874
10125
  }
9875
10126
  const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
9876
10127
  const totals = db.query("SELECT hook_name, COUNT(*) as total, SUM(CASE WHEN error IS NOT NULL THEN 1 ELSE 0 END) as errors FROM hook_events WHERE timestamp >= ? GROUP BY hook_name ORDER BY total DESC").all(ts);
9877
- const summary = totals.map((r) => ({
9878
- hook_name: r.hook_name,
9879
- total: r.total,
9880
- errors: r.errors,
9881
- error_rate: r.total > 0 ? (r.errors / r.total * 100).toFixed(1) + "%" : "0%"
10128
+ const summary = totals.map((r2) => ({
10129
+ hook_name: r2.hook_name,
10130
+ total: r2.total,
10131
+ errors: r2.errors,
10132
+ error_rate: r2.total > 0 ? (r2.errors / r2.total * 100).toFixed(1) + "%" : "0%"
9882
10133
  }));
9883
- const grandTotal = totals.reduce((s, r) => s + r.total, 0);
9884
- const grandErrors = totals.reduce((s, r) => s + r.errors, 0);
10134
+ const grandTotal = totals.reduce((s, r2) => s + r2.total, 0);
10135
+ const grandErrors = totals.reduce((s, r2) => s + r2.errors, 0);
9885
10136
  return {
9886
10137
  content: [{
9887
10138
  type: "text",
@@ -9912,8 +10163,8 @@ function createHooksServer() {
9912
10163
  pkg.version
9913
10164
  ]);
9914
10165
  return { content: [{ type: "text", text: "Feedback saved. Thank you!" }] };
9915
- } catch (e) {
9916
- return { content: [{ type: "text", text: String(e) }], isError: true };
10166
+ } catch (e2) {
10167
+ return { content: [{ type: "text", text: String(e2) }], isError: true };
9917
10168
  }
9918
10169
  });
9919
10170
  defineTool("register_agent", "Register an agent session. Returns agent_id. Auto-triggers a heartbeat.", {
@@ -10080,7 +10331,7 @@ var init_server = __esm(() => {
10080
10331
  init_redact();
10081
10332
  init_authority();
10082
10333
  init_storage();
10083
- __dirname3 = dirname8(fileURLToPath4(import.meta.url));
10334
+ __dirname3 = dirname9(fileURLToPath5(import.meta.url));
10084
10335
  pkg = { name: "@hasna/hooks", version: "0.0.0" };
10085
10336
  try {
10086
10337
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
@@ -11829,8 +12080,8 @@ var {
11829
12080
  // src/cli/index.tsx
11830
12081
  import chalk2 from "chalk";
11831
12082
  import { existsSync as existsSync16, readFileSync as readFileSync12, rmSync as rmSync4 } from "fs";
11832
- import { join as join17, dirname as dirname9 } from "path";
11833
- import { fileURLToPath as fileURLToPath5 } from "url";
12083
+ import { join as join17, dirname as dirname10 } from "path";
12084
+ import { fileURLToPath as fileURLToPath6 } from "url";
11834
12085
 
11835
12086
  // src/cli/components/App.tsx
11836
12087
  import { useState as useState7 } from "react";
@@ -13017,7 +13268,7 @@ init_local_opt_in();
13017
13268
  init_transport2();
13018
13269
  init_db();
13019
13270
  import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
13020
- var __dirname4 = dirname9(fileURLToPath5(import.meta.url));
13271
+ var __dirname4 = dirname10(fileURLToPath6(import.meta.url));
13021
13272
  var pkgPath = existsSync16(join17(__dirname4, "..", "package.json")) ? join17(__dirname4, "..", "package.json") : join17(__dirname4, "..", "..", "package.json");
13022
13273
  var pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
13023
13274
  var program2 = new Command;
@@ -13144,19 +13395,19 @@ function printDisclosureHint(hidden, detailCommand, options = {}) {
13144
13395
  }
13145
13396
  }
13146
13397
  function editDistance(a, b) {
13147
- const m = a.length, n = b.length;
13148
- const dp = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
13149
- for (let j = 0;j <= n; j++)
13398
+ const m = a.length, n2 = b.length;
13399
+ const dp = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n2).fill(0)]);
13400
+ for (let j = 0;j <= n2; j++)
13150
13401
  dp[0][j] = j;
13151
13402
  for (let i = 1;i <= m; i++) {
13152
- for (let j = 1;j <= n; j++) {
13403
+ for (let j = 1;j <= n2; j++) {
13153
13404
  dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
13154
13405
  }
13155
13406
  }
13156
- return dp[m][n];
13407
+ return dp[m][n2];
13157
13408
  }
13158
13409
  function suggestHooks(name, max = 3) {
13159
- return HOOKS.map((h) => ({ name: h.name, dist: editDistance(name.toLowerCase(), h.name.toLowerCase()) })).filter(({ dist }) => dist <= 4).sort((a, b) => a.dist - b.dist).slice(0, max).map(({ name: n }) => n);
13410
+ return HOOKS.map((h) => ({ name: h.name, dist: editDistance(name.toLowerCase(), h.name.toLowerCase()) })).filter(({ dist }) => dist <= 4).sort((a, b) => a.dist - b.dist).slice(0, max).map(({ name: n2 }) => n2);
13160
13411
  }
13161
13412
  program2.name("hooks").description("Install hooks for AI coding agents").version(pkg2.version);
13162
13413
  program2.command("interactive", { isDefault: true }).alias("i").description("Interactive hook browser").action(() => {
@@ -13437,10 +13688,10 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
13437
13688
  return { name, version };
13438
13689
  }
13439
13690
  const pinnedRequests = toInstall.map((arg) => ({ arg, pinned: parseNameVersion(arg) })).filter((x) => x.pinned !== null);
13440
- const customSources = toInstall.filter((n) => !getHook(n) && isCustomSource2(n));
13691
+ const customSources = toInstall.filter((n2) => !getHook(n2) && isCustomSource2(n2));
13441
13692
  if (options.dryRun) {
13442
- const known = toInstall.filter((n) => getHook(n));
13443
- const unknown = toInstall.filter((n) => !getHook(n) && !isCustomSource2(n) && !parseNameVersion(n) && !readCustomManifest2(n));
13693
+ const known = toInstall.filter((n2) => getHook(n2));
13694
+ const unknown = toInstall.filter((n2) => !getHook(n2) && !isCustomSource2(n2) && !parseNameVersion(n2) && !readCustomManifest2(n2));
13444
13695
  if (options.json) {
13445
13696
  console.log(JSON.stringify({
13446
13697
  dryRun: true,
@@ -13514,7 +13765,7 @@ Dry run \u2014 would install (${scope}, ${target}):
13514
13765
  for (const { arg, pinned } of pinnedRequests) {
13515
13766
  try {
13516
13767
  const transport = resolveHooksTransport2();
13517
- const pinnedInstall = transport.mode === "remote" && transport.authority ? await fetchPinnedHook2(pinned.name, pinned.version, transport.authority.origin, transport.authority.apiKey) : await installPinnedFromBundled2(pinned.name, pinned.version);
13768
+ const pinnedInstall = transport.mode === "remote" && transport.authority ? await fetchPinnedHook2(pinned.name, pinned.version, transport.authority.origin, transport.authority) : await installPinnedFromBundled2(pinned.name, pinned.version);
13518
13769
  installedPinned.push(pinnedInstall.name);
13519
13770
  if (options.json) {
13520
13771
  console.log(JSON.stringify({ pinned: { request: arg, name: pinnedInstall.name, version: pinnedInstall.version, sha256: pinnedInstall.sha256, source: pinnedInstall.source } }));
@@ -13533,7 +13784,7 @@ Dry run \u2014 would install (${scope}, ${target}):
13533
13784
  }
13534
13785
  }
13535
13786
  const namesToRegister = [...new Set([
13536
- ...toInstall.filter((n) => !isCustomSource2(n) && !parseNameVersion(n)),
13787
+ ...toInstall.filter((n2) => !isCustomSource2(n2) && !parseNameVersion(n2)),
13537
13788
  ...installedCustom,
13538
13789
  ...installedPinned
13539
13790
  ])];
@@ -13557,22 +13808,22 @@ Dry run \u2014 would install (${scope}, ${target}):
13557
13808
  }
13558
13809
  if (options.json) {
13559
13810
  console.log(JSON.stringify({
13560
- installed: results.filter((r) => r.success).map((r) => r.hook),
13561
- failed: results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error })),
13562
- fragments: results.filter((r) => r.success && r.fragment).map((r) => ({ hook: r.hook, fragment: r.fragment, applied: r.applied, configPath: r.configPath, note: r.note })),
13811
+ installed: results.filter((r2) => r2.success).map((r2) => r2.hook),
13812
+ failed: results.filter((r2) => !r2.success).map((r2) => ({ hook: r2.hook, error: r2.error })),
13813
+ fragments: results.filter((r2) => r2.success && r2.fragment).map((r2) => ({ hook: r2.hook, fragment: r2.fragment, applied: r2.applied, configPath: r2.configPath, note: r2.note })),
13563
13814
  total: results.length,
13564
- success: results.filter((r) => r.success).length,
13815
+ success: results.filter((r2) => r2.success).length,
13565
13816
  scope,
13566
13817
  target,
13567
- applied: results.some((r) => r.applied),
13568
- ...target === "codex" ? { notes: results.filter((r) => r.success && r.note).map((r) => r.note) } : {}
13818
+ applied: results.some((r2) => r2.applied),
13819
+ ...target === "codex" ? { notes: results.filter((r2) => r2.success && r2.note).map((r2) => r2.note) } : {}
13569
13820
  }));
13570
- if (results.some((r) => !r.success)) {
13821
+ if (results.some((r2) => !r2.success)) {
13571
13822
  process.exitCode = 1;
13572
13823
  }
13573
13824
  return;
13574
13825
  }
13575
- const successCount = results.filter((r) => r.success).length;
13826
+ const successCount = results.filter((r2) => r2.success).length;
13576
13827
  const settingsFile = target === "codewith" ? options.applyCodewith ? options.codewithConfig : "TOML fragment only (configs should apply)" : formatSettingsPath(scope, target);
13577
13828
  console.log(chalk2.bold(`
13578
13829
  Installing hooks (${scope}, ${target})...
@@ -14024,7 +14275,7 @@ program2.command("update").argument("[hooks...]", "Hooks to update (defaults to
14024
14275
  if (pinned) {
14025
14276
  try {
14026
14277
  const transport = resolveHooksTransport2();
14027
- const pinnedInstall = transport.mode === "remote" && transport.authority ? await fetchPinnedHook2(pinned.name, pinned.version, transport.authority.origin, transport.authority.apiKey) : await installPinnedFromBundled2(pinned.name, pinned.version);
14278
+ const pinnedInstall = transport.mode === "remote" && transport.authority ? await fetchPinnedHook2(pinned.name, pinned.version, transport.authority.origin, transport.authority) : await installPinnedFromBundled2(pinned.name, pinned.version);
14028
14279
  const registered = installHook(pinnedInstall.name, { scope, overwrite: true });
14029
14280
  if (!registered.success) {
14030
14281
  results.push({ hook: arg, success: false, error: registered.error ?? "registration failed" });
@@ -14065,10 +14316,10 @@ program2.command("update").argument("[hooks...]", "Hooks to update (defaults to
14065
14316
  }
14066
14317
  if (options.json) {
14067
14318
  console.log(JSON.stringify({
14068
- updated: results.filter((r) => r.success).map((r) => r.hook),
14069
- failed: results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error }))
14319
+ updated: results.filter((r2) => r2.success).map((r2) => r2.hook),
14320
+ failed: results.filter((r2) => !r2.success).map((r2) => ({ hook: r2.hook, error: r2.error }))
14070
14321
  }));
14071
- if (results.some((r) => !r.success)) {
14322
+ if (results.some((r2) => !r2.success)) {
14072
14323
  process.exitCode = 1;
14073
14324
  }
14074
14325
  return;
@@ -14083,7 +14334,7 @@ Updating hooks...
14083
14334
  console.log(chalk2.red(`\u2717 ${result.hook}: ${result.error}`));
14084
14335
  }
14085
14336
  }
14086
- if (results.some((r) => !r.success)) {
14337
+ if (results.some((r2) => !r2.success)) {
14087
14338
  process.exitCode = 1;
14088
14339
  }
14089
14340
  });
@@ -14428,16 +14679,16 @@ logCmd.command("errors").description("Show hook events that contain errors").opt
14428
14679
  const m = s.match(/^(\d+)(s|m|h|d)$/);
14429
14680
  if (!m)
14430
14681
  return 86400000;
14431
- const n = parseInt(m[1]);
14682
+ const n2 = parseInt(m[1]);
14432
14683
  switch (m[2]) {
14433
14684
  case "s":
14434
- return n * 1000;
14685
+ return n2 * 1000;
14435
14686
  case "m":
14436
- return n * 60 * 1000;
14687
+ return n2 * 60 * 1000;
14437
14688
  case "h":
14438
- return n * 60 * 60 * 1000;
14689
+ return n2 * 60 * 60 * 1000;
14439
14690
  case "d":
14440
- return n * 24 * 60 * 60 * 1000;
14691
+ return n2 * 24 * 60 * 60 * 1000;
14441
14692
  default:
14442
14693
  return 86400000;
14443
14694
  }