@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/bin/index.js CHANGED
@@ -4528,7 +4528,7 @@ function mementosCommandSuffix(value) {
4528
4528
  return value.enabled ? ` --enable-mementos${value.secretRef ? ` --openrouter-secret-ref ${value.secretRef}` : ""}` : " --disable-mementos";
4529
4529
  }
4530
4530
  function retainedMementosRegistration(commands) {
4531
- const values = commands.filter((command) => /^hooks run mementos-context(?:\s|$)/.test(command)).map((command) => {
4531
+ const values = commands.map((command) => command.trim()).filter((command) => /^hooks run mementos-context(?:\s|$)/.test(command)).map((command) => {
4532
4532
  const args = command.trim().split(/\s+/).slice(3);
4533
4533
  const flags = {};
4534
4534
  const seen = new Set;
@@ -5790,8 +5790,36 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
5790
5790
  if (!settings.hooks)
5791
5791
  settings.hooks = {};
5792
5792
  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;
5793
- removeHookFromAllEvents(settings, name);
5794
5793
  const hookCommand = (profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`) + mementosCommandSuffix(mementos ?? retained);
5794
+ if (target === "codex") {
5795
+ const positions = [];
5796
+ for (const [event, groups] of Object.entries(settings.hooks)) {
5797
+ for (const entry of groups) {
5798
+ for (let index = 0;index < entry.hooks.length; index++) {
5799
+ const command = entry.hooks[index]?.command;
5800
+ const match = typeof command === "string" && command.trim().match(/^(?:hooks run ([\w-]+)(?:\s|$)|hook-([\w-]+)$)/);
5801
+ if (match && (match[1] ?? match[2]) === name) {
5802
+ if (registrationName(command) !== name)
5803
+ throw new Error("Unrecognized Codex registration; reconcile it before updating this hook.");
5804
+ positions.push({ event, entry, index });
5805
+ }
5806
+ }
5807
+ }
5808
+ }
5809
+ if (positions.length) {
5810
+ if (positions.length !== uniqueEventKeys.length || uniqueEventKeys.some((event) => positions.filter((position) => position.event === event).length !== 1) || positions.some((position) => (position.entry.matcher ?? "") !== (meta.matcher ?? ""))) {
5811
+ throw new Error("Codex hook events, matchers or duplicate registrations require explicit reconciliation; no settings were changed.");
5812
+ }
5813
+ for (const { entry, index } of positions) {
5814
+ entry.hooks[index] = { ...entry.hooks[index], type: "command", command: hookCommand };
5815
+ if (typeof meta.timeoutSeconds === "number" && meta.timeoutSeconds > 0)
5816
+ entry.hooks[index].timeout = meta.timeoutSeconds;
5817
+ }
5818
+ writeSettings(settings, scope, target);
5819
+ return;
5820
+ }
5821
+ }
5822
+ removeHookFromAllEvents(settings, name);
5795
5823
  for (const eventKey of uniqueEventKeys) {
5796
5824
  if (!settings.hooks[eventKey])
5797
5825
  settings.hooks[eventKey] = [];
@@ -6324,14 +6352,19 @@ var init_profiles = __esm(() => {
6324
6352
  init_app_home();
6325
6353
  });
6326
6354
 
6327
- // ../../node_modules/.bun/@hasna+contracts@1.0.2+e0db24ae03127387/node_modules/@hasna/contracts/dist/client/transport.js
6355
+ // ../contracts/dist/client/transport.js
6328
6356
  import { isIP as isIP2 } from "net";
6329
6357
  import { spawnSync } from "child_process";
6330
- import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as readFileSync7 } from "fs";
6331
- import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
6332
- 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";
6333
6360
  import { hostname as osHostname } from "os";
6334
- 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";
6335
6368
  function envToken(name) {
6336
6369
  return name.toUpperCase().replace(/-/g, "_");
6337
6370
  }
@@ -6348,6 +6381,112 @@ function credentialOverrideEnvKey(name) {
6348
6381
  function credentialPointerEnvKey(name) {
6349
6382
  return `HASNA_${envToken(name)}_API_KEY_REF`;
6350
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
+ }
6351
6490
  function homeDir(env) {
6352
6491
  const home = env.HOME?.trim();
6353
6492
  return home ? home : null;
@@ -6361,14 +6500,14 @@ function hasnaHomeDir(env) {
6361
6500
  if (override)
6362
6501
  return override;
6363
6502
  const home = homeDir(env);
6364
- return home ? join12(home, HASNA_HOME_DIR) : null;
6503
+ return home ? join22(home, HASNA_HOME_DIR) : null;
6365
6504
  }
6366
6505
  function appConfigDir(name, env) {
6367
6506
  const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
6368
6507
  if (configRoot)
6369
- return join12(configRoot, name);
6508
+ return join22(configRoot, name);
6370
6509
  const root = hasnaHomeDir(env);
6371
- return root ? join12(root, name, CONFIG_SUBDIR) : null;
6510
+ return root ? join22(root, name, CONFIG_SUBDIR) : null;
6372
6511
  }
6373
6512
  function credentialDiskSourceList(name, env, profile = null) {
6374
6513
  if (!SAFE_APP_SLUG.test(name))
@@ -6377,7 +6516,7 @@ function credentialDiskSourceList(name, env, profile = null) {
6377
6516
  if (!directory)
6378
6517
  return [];
6379
6518
  const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
6380
- return [{ path: join12(directory, file), tier: "disk" }];
6519
+ return [{ path: join22(directory, file), tier: "disk" }];
6381
6520
  }
6382
6521
  function credentialDiskSources(name, env) {
6383
6522
  return credentialDiskSourceList(name, env, null).map((s) => s.path);
@@ -6431,7 +6570,7 @@ function readAppConfigFile(path) {
6431
6570
  };
6432
6571
  let fd = -1;
6433
6572
  try {
6434
- fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
6573
+ fd = openSync2(path, O_RDONLY2 | O_NOFOLLOW2 | O_NONBLOCK2);
6435
6574
  } catch (error) {
6436
6575
  const code = error.code;
6437
6576
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -6441,7 +6580,7 @@ function readAppConfigFile(path) {
6441
6580
  unsafe(`the path could not be opened (${code ?? "unknown error"})`);
6442
6581
  }
6443
6582
  try {
6444
- const before = fstatSync(fd);
6583
+ const before = fstatSync2(fd);
6445
6584
  if (!before.isFile())
6446
6585
  unsafe("the path is not a regular file");
6447
6586
  if (!configFileModeAllowed(before.mode)) {
@@ -6452,8 +6591,8 @@ function readAppConfigFile(path) {
6452
6591
  unsafe("the file is not owned by the current user");
6453
6592
  if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
6454
6593
  unsafe("the file exceeds the size limit");
6455
- const bytes = readFileSync7(fd);
6456
- const after = fstatSync(fd);
6594
+ const bytes = readFileSync22(fd);
6595
+ const after = fstatSync2(fd);
6457
6596
  if (!configFileReadsCoherent(before, after)) {
6458
6597
  unsafe("the file changed while being read");
6459
6598
  }
@@ -6463,20 +6602,28 @@ function readAppConfigFile(path) {
6463
6602
  closeSync2(fd);
6464
6603
  }
6465
6604
  }
6466
- function readCredentialFile(path, apiKeyKeys) {
6605
+ function readCredentialFile(path, apiKeyKeys, pointerKey) {
6467
6606
  const parsed = readAppConfigFile(path);
6468
6607
  if (!parsed)
6469
6608
  return null;
6470
- for (const key of apiKeyKeys) {
6609
+ for (const key of [...apiKeyKeys, pointerKey]) {
6471
6610
  if (parsed.unusable.has(key)) {
6472
6611
  throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
6473
6612
  }
6474
6613
  }
6475
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
+ }
6476
6623
  if (new Set(values).size > 1) {
6477
6624
  throw new CredentialFileUnsafeError(path, "credential aliases disagree");
6478
6625
  }
6479
- return values[0] ?? null;
6626
+ return values[0] === undefined ? null : { apiKey: values[0] };
6480
6627
  }
6481
6628
  function appConfigDiskValue(name, env, keys) {
6482
6629
  const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
@@ -6548,6 +6695,18 @@ function sealCredential(fields) {
6548
6695
  });
6549
6696
  return Object.freeze(sealed);
6550
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
+ }
6551
6710
  function firstEnvValue(env, keys) {
6552
6711
  for (const key of keys) {
6553
6712
  if (!Object.prototype.hasOwnProperty.call(env, key))
@@ -6733,12 +6892,13 @@ function resolveCredential(name, env, options = {}) {
6733
6892
  }
6734
6893
  const paths = profileDiskSources(name, env, profile);
6735
6894
  for (const path of paths) {
6736
- const value = readCredentialFile(path, apiKeyKeys);
6895
+ const value = readCredentialFile(path, apiKeyKeys, pointerKeyName);
6737
6896
  if (value) {
6738
- assertUsableCredential(name, path, value);
6897
+ if (!value.pointerVaultKey)
6898
+ assertUsableCredential(name, path, value.apiKey);
6739
6899
  return sealCredential({
6740
- apiKey: value,
6741
- tier: "profile",
6900
+ ...value,
6901
+ tier: value.pointerVaultKey ? "pointer" : "profile",
6742
6902
  source: path,
6743
6903
  deliberate: true,
6744
6904
  diskCandidates: paths,
@@ -6746,7 +6906,7 @@ function resolveCredential(name, env, options = {}) {
6746
6906
  });
6747
6907
  }
6748
6908
  }
6749
- 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);
6750
6910
  }
6751
6911
  const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
6752
6912
  const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
@@ -6771,17 +6931,27 @@ function resolveCredential(name, env, options = {}) {
6771
6931
  });
6772
6932
  }
6773
6933
  const diskSourceList = credentialDiskSourceList(name, env, null);
6774
- 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);
6775
6935
  if (diskHits.length > 0) {
6776
6936
  const winner = diskHits[0];
6777
- 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);
6778
6948
  const divergentSources = [
6779
- ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
6780
- ...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] : []
6781
6951
  ];
6782
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;
6783
6953
  return sealCredential({
6784
- apiKey: winner.value,
6954
+ apiKey: winner.value.apiKey,
6785
6955
  tier: winner.src.tier,
6786
6956
  source: winner.src.path,
6787
6957
  deliberate: false,
@@ -6802,6 +6972,53 @@ function resolveCredential(name, env, options = {}) {
6802
6972
  }
6803
6973
  return null;
6804
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
+ }
6805
7022
  function defaultFleetGatewayBaseUrl(name) {
6806
7023
  return `${DEFAULT_FLEET_GATEWAY_ORIGIN}/${validateAppSlug(name)}`;
6807
7024
  }
@@ -7004,8 +7221,9 @@ function credentialDiskSourcesForMessage(name, env) {
7004
7221
  const paths = credentialDiskSources(name, env);
7005
7222
  return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
7006
7223
  }
7007
- 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;
7008
7225
  var init_transport = __esm(() => {
7226
+ MAX_PACKAGE_BYTES2 = 1024 * 1024;
7009
7227
  CredentialResolutionError = class CredentialResolutionError extends Error {
7010
7228
  appName;
7011
7229
  attempted;
@@ -7033,7 +7251,6 @@ var init_transport = __esm(() => {
7033
7251
  INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
7034
7252
  CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
7035
7253
  AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
7036
- SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
7037
7254
  requireSecretsSdk2 = createRequire2(import.meta.url);
7038
7255
  ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
7039
7256
  DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
@@ -7122,7 +7339,9 @@ var exports_transport = {};
7122
7339
  __export(exports_transport, {
7123
7340
  rethrowHooksAuthorityFailure: () => rethrowHooksAuthorityFailure,
7124
7341
  resolveHooksTransport: () => resolveHooksTransport,
7342
+ resolveHooksServePublishKeyAsync: () => resolveHooksServePublishKeyAsync,
7125
7343
  resolveHooksServePublishKey: () => resolveHooksServePublishKey,
7344
+ resolveHooksRequestAuthority: () => resolveHooksRequestAuthority,
7126
7345
  hooksRegistryOrigin: () => hooksRegistryOrigin,
7127
7346
  announceHooksLocalMode: () => announceHooksLocalMode,
7128
7347
  __resetHooksLocalNotice: () => __resetHooksLocalNotice
@@ -7161,6 +7380,40 @@ function rethrowHooksAuthorityFailure(error) {
7161
7380
  }
7162
7381
  throw new Error(`REMOTE_API_URL_INVALID: ${message} local mode fallback is disabled`, { cause: error });
7163
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
+ }
7164
7417
  function resolveHooksTransport(env = process.env, options = {}) {
7165
7418
  if (selectsHooksLocalStore(env)) {
7166
7419
  announceLocal(options.notice, "HASNA_HOOKS_LOCAL is set and nothing configures a registry authority");
@@ -7174,7 +7427,7 @@ function resolveHooksTransport(env = process.env, options = {}) {
7174
7427
  try {
7175
7428
  credential = resolveCredential("hooks", resolverEnv, credentialOptions);
7176
7429
  resolution = resolveClientTransport("hooks", resolverEnv, {
7177
- credentials: credential ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
7430
+ credentials: credential && credential.tier !== "pointer" ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
7178
7431
  });
7179
7432
  } catch (error) {
7180
7433
  rethrowHooksAuthorityFailure(error);
@@ -7182,31 +7435,50 @@ function resolveHooksTransport(env = process.env, options = {}) {
7182
7435
  if (!credential) {
7183
7436
  throw new Error("REMOTE_API_CONFIG_MISSING: no hooks registry credential resolved; " + "local mode fallback is disabled");
7184
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 });
7185
7448
  return {
7186
7449
  mode: "remote",
7187
7450
  source: `${credential.source}+${resolution.apiUrlSource ?? "default"}`,
7188
- authority: {
7189
- origin: hooksRegistryOrigin(resolution.baseUrl),
7190
- apiKey: credential.apiKey,
7191
- apiUrlSource: resolution.apiUrlSource,
7192
- apiKeySource: credential.source,
7193
- apiKeyTier: credential.tier,
7194
- v1BaseUrl: resolution.baseUrl,
7195
- warning: resolution.warning
7196
- }
7451
+ authority
7197
7452
  };
7198
7453
  }
7199
7454
  function resolveHooksServePublishKey(env = process.env, options = {}) {
7200
7455
  const { env: resolverEnv, credentials } = hooksResolverInputs(env, options.credentials);
7201
7456
  const tier1 = options.credentials?.apiKey;
7202
7457
  const credentialOptions = tier1 !== undefined ? { ...credentials, apiKey: tier1 } : credentials;
7203
- 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;
7204
7462
  }
7205
- 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;
7206
7477
  var init_transport2 = __esm(() => {
7207
7478
  init_transport();
7208
7479
  init_config();
7209
7480
  init_local_opt_in();
7481
+ authorityBindings = new WeakMap;
7210
7482
  });
7211
7483
 
7212
7484
  // src/lib/db-writer.ts
@@ -7328,8 +7600,8 @@ var init_db_writer = __esm(() => {
7328
7600
  });
7329
7601
 
7330
7602
  // src/lib/hook-env.ts
7331
- import { dirname as dirname5, isAbsolute as isAbsolute3 } from "path";
7332
- 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";
7333
7605
  import { tmpdir } from "os";
7334
7606
  function isDeniedEnvName(name) {
7335
7607
  const upper = name.toUpperCase();
@@ -7358,7 +7630,7 @@ function isUnsafePathEntry(entry, home) {
7358
7630
  return true;
7359
7631
  const canonical = (path) => {
7360
7632
  try {
7361
- return realpathSync2(path);
7633
+ return realpathSync3(path);
7362
7634
  } catch {
7363
7635
  return path;
7364
7636
  }
@@ -7370,7 +7642,7 @@ function isUnsafePathEntry(entry, home) {
7370
7642
  return true;
7371
7643
  }
7372
7644
  try {
7373
- if ((statSync(entry).mode & 2) !== 0)
7645
+ if ((statSync2(entry).mode & 2) !== 0)
7374
7646
  return true;
7375
7647
  } catch {}
7376
7648
  return false;
@@ -7379,7 +7651,7 @@ function sanitizeHookPath(pathValue, home) {
7379
7651
  const baseline = PATH_BASELINE.slice();
7380
7652
  if (process.platform === "darwin" && existsSync13("/opt/homebrew/bin"))
7381
7653
  baseline.push("/opt/homebrew/bin");
7382
- const bunDir = dirname5(process.execPath);
7654
+ const bunDir = dirname6(process.execPath);
7383
7655
  const seen = new Set;
7384
7656
  const out = [];
7385
7657
  for (const dir of baseline) {
@@ -7504,9 +7776,9 @@ __export(exports_run, {
7504
7776
  });
7505
7777
  import { randomBytes as randomBytes2 } from "crypto";
7506
7778
  import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
7507
- 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";
7508
7780
  function extensionOf(scriptPath) {
7509
- const base = basename2(scriptPath);
7781
+ const base = basename3(scriptPath);
7510
7782
  const dot = base.lastIndexOf(".");
7511
7783
  return dot > 0 ? base.slice(dot + 1) : "ts";
7512
7784
  }
@@ -7518,9 +7790,9 @@ function shebangInterpreter(firstLine) {
7518
7790
  return null;
7519
7791
  const parts = rest.split(/\s+/);
7520
7792
  if (parts[0] === "env" || parts[0] === "/usr/bin/env") {
7521
- 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;
7522
7794
  }
7523
- return basename2(parts[0]);
7795
+ return basename3(parts[0]);
7524
7796
  }
7525
7797
  function interpreterFor(name, scriptPath, content) {
7526
7798
  const ext = extensionOf(scriptPath);
@@ -7554,8 +7826,8 @@ async function readPipeWithDeadline(stream, deadlineMs) {
7554
7826
  result = await Promise.race([
7555
7827
  reader.read(),
7556
7828
  new Promise((_, reject) => {
7557
- const t = setTimeout(() => reject(new Error("pipe-drain-deadline")), remaining);
7558
- t.unref?.();
7829
+ const t2 = setTimeout(() => reject(new Error("pipe-drain-deadline")), remaining);
7830
+ t2.unref?.();
7559
7831
  })
7560
7832
  ]);
7561
7833
  } catch {
@@ -7573,7 +7845,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
7573
7845
  return chunks.join("");
7574
7846
  }
7575
7847
  async function executeVerifiedScript(options) {
7576
- const scriptDir = dirname6(options.scriptPath);
7848
+ const scriptDir = dirname7(options.scriptPath);
7577
7849
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
7578
7850
  const tempPath = join13(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
7579
7851
  let proc = null;
@@ -7808,7 +8080,14 @@ function collectBundledCatalog() {
7808
8080
  }
7809
8081
  async function fetchJson(base, path, apiKey, opts) {
7810
8082
  const headers = { accept: "application/json" };
7811
- 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)
7812
8091
  headers["x-api-key"] = apiKey;
7813
8092
  const res = await fetch(`${base}${path}`, {
7814
8093
  headers,
@@ -7876,8 +8155,8 @@ async function planSync(options = {}) {
7876
8155
  credentials: options.credentials
7877
8156
  });
7878
8157
  if (transport.mode === "remote" && transport.authority) {
7879
- const { origin, apiKey } = transport.authority;
7880
- const { catalog, remoteLock } = await fetchRemoteState(origin, apiKey);
8158
+ const { origin } = transport.authority;
8159
+ const { catalog, remoteLock } = await fetchRemoteState(origin, transport.authority);
7881
8160
  return { apiUrl: origin, dryRun: options.dryRun ?? false, diff: computeDiff(catalog, remoteLock) };
7882
8161
  }
7883
8162
  const diff = computeDiff(collectBundledCatalog(), null);
@@ -7905,10 +8184,10 @@ async function syncHooks(options = {}) {
7905
8184
  }
7906
8185
  return plan;
7907
8186
  }
7908
- const { origin, apiKey } = transport.authority;
7909
- const { catalog, remoteLock } = await fetchRemoteState(origin, apiKey);
8187
+ const { origin } = transport.authority;
8188
+ const { catalog, remoteLock } = await fetchRemoteState(origin, transport.authority);
7910
8189
  const diff = computeDiff(catalog, remoteLock);
7911
- const staged = await stageSyncArtifacts(origin, apiKey, [...diff.added, ...diff.updated], remoteLock);
8190
+ const staged = await stageSyncArtifacts(origin, transport.authority, [...diff.added, ...diff.updated], remoteLock);
7912
8191
  await commitSyncArtifacts(staged, origin, remoteLock);
7913
8192
  return { apiUrl: origin, dryRun: false, diff };
7914
8193
  }
@@ -8569,8 +8848,8 @@ function secureEqual(a, b) {
8569
8848
  const ab = enc.encode(a);
8570
8849
  const bb = enc.encode(b);
8571
8850
  let diff = ab.length ^ bb.length;
8572
- const n = Math.max(ab.length, bb.length);
8573
- for (let i = 0;i < n; i++) {
8851
+ const n2 = Math.max(ab.length, bb.length);
8852
+ for (let i = 0;i < n2; i++) {
8574
8853
  diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
8575
8854
  }
8576
8855
  return diff === 0;
@@ -8741,11 +9020,11 @@ __export(exports_serve, {
8741
9020
  DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
8742
9021
  });
8743
9022
  import { readFileSync as readFileSync10 } from "fs";
8744
- import { dirname as dirname7, join as join15 } from "path";
8745
- import { fileURLToPath as fileURLToPath3 } from "url";
9023
+ import { dirname as dirname8, join as join15 } from "path";
9024
+ import { fileURLToPath as fileURLToPath4 } from "url";
8746
9025
  function packageVersion() {
8747
9026
  try {
8748
- const pkgPath = join15(dirname7(fileURLToPath3(import.meta.url)), "..", "package.json");
9027
+ const pkgPath = join15(dirname8(fileURLToPath4(import.meta.url)), "..", "package.json");
8749
9028
  return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
8750
9029
  } catch {
8751
9030
  return "0.0.0";
@@ -8907,7 +9186,7 @@ function startServeServer(options) {
8907
9186
  hostname: host,
8908
9187
  port,
8909
9188
  async fetch(req) {
8910
- return handleServeRequest(req, resolveHooksServePublishKey());
9189
+ return handleServeRequest(req, await resolveHooksServePublishKeyAsync());
8911
9190
  }
8912
9191
  });
8913
9192
  console.error(`hooks registry listening on http://${host}:${server.port} (publish requires an API key)`);
@@ -9048,8 +9327,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
9048
9327
  import { z as z2 } from "zod";
9049
9328
  import { createServer } from "http";
9050
9329
  import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
9051
- import { join as join16, dirname as dirname8 } from "path";
9052
- import { fileURLToPath as fileURLToPath4 } from "url";
9330
+ import { join as join16, dirname as dirname9 } from "path";
9331
+ import { fileURLToPath as fileURLToPath5 } from "url";
9053
9332
  async function runVerifiedHook(name, stdin, timeoutMs) {
9054
9333
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
9055
9334
  const resolved = resolveHook2(name);
@@ -9123,8 +9402,8 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
9123
9402
  return ran;
9124
9403
  }
9125
9404
  function formatInstallResults(results, extra) {
9126
- const installed = results.filter((r) => r.success).map((r) => r.hook);
9127
- 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 }));
9128
9407
  return {
9129
9408
  content: [{
9130
9409
  type: "text",
@@ -9487,8 +9766,8 @@ function createHooksServer() {
9487
9766
  }
9488
9767
  return installHook(name, { scope, overwrite: true });
9489
9768
  });
9490
- const updated = results.filter((r) => r.success).map((r) => r.hook);
9491
- 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 }));
9492
9771
  return { content: [{ type: "text", text: JSON.stringify({ updated, failed, total: results.length }) }] };
9493
9772
  });
9494
9773
  defineTool("hooks_context", "Get compact agent context in one call: installed hooks, active profile summary, settings path, and doctor status.", {
@@ -9575,7 +9854,7 @@ function createHooksServer() {
9575
9854
  return { name, decision: "block", error: err instanceof Error ? err.message : String(err) };
9576
9855
  }
9577
9856
  }));
9578
- const blocked = results.find((r) => r.decision === "block");
9857
+ const blocked = results.find((r2) => r2.decision === "block");
9579
9858
  return {
9580
9859
  content: [{
9581
9860
  type: "text",
@@ -9609,8 +9888,8 @@ function createHooksServer() {
9609
9888
  const target = agent_type === "gemini" ? "gemini" : "claude";
9610
9889
  const toInstall = hooks && hooks.length > 0 ? hooks : ["gitguard", "checkpoint", "checktests", "protectfiles"];
9611
9890
  const results = toInstall.map((h) => installHook(h, { scope, overwrite: false, profile: profile.agent_id, target }));
9612
- const installed = results.filter((r) => r.success).map((r) => r.hook);
9613
- 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 }));
9614
9893
  return {
9615
9894
  content: [{
9616
9895
  type: "text",
@@ -9668,8 +9947,8 @@ function createHooksServer() {
9668
9947
  disabled.push(name);
9669
9948
  settings.hooks.__disabled = disabled;
9670
9949
  const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync8 } = await import("fs");
9671
- const { dirname: dirname9 } = await import("path");
9672
- mkdirSync8(dirname9(settingsPath), { recursive: true });
9950
+ const { dirname: dirname10 } = await import("path");
9951
+ mkdirSync8(dirname10(settingsPath), { recursive: true });
9673
9952
  writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
9674
9953
  `);
9675
9954
  return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
@@ -9685,7 +9964,7 @@ function createHooksServer() {
9685
9964
  settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
9686
9965
  } catch {}
9687
9966
  if (settings.hooks?.__disabled) {
9688
- settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
9967
+ settings.hooks.__disabled = settings.hooks.__disabled.filter((n2) => n2 !== name);
9689
9968
  if (settings.hooks.__disabled.length === 0)
9690
9969
  delete settings.hooks.__disabled;
9691
9970
  const { writeFileSync: writeFileSync7 } = await import("fs");
@@ -9742,9 +10021,9 @@ function createHooksServer() {
9742
10021
  const m = s.match(/^(\d+)(s|m|h|d)$/);
9743
10022
  if (!m)
9744
10023
  return null;
9745
- const n = parseInt(m[1]);
10024
+ const n2 = parseInt(m[1]);
9746
10025
  const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
9747
- return new Date(Date.now() - n * ms).toISOString();
10026
+ return new Date(Date.now() - n2 * ms).toISOString();
9748
10027
  }
9749
10028
  let sql = "SELECT * FROM hook_events WHERE 1=1";
9750
10029
  const params = [];
@@ -9782,10 +10061,10 @@ function createHooksServer() {
9782
10061
  defineTool("hooks_log_tail", "Show recent hook events from SQLite. Compact summaries by default.", {
9783
10062
  n: z2.number().default(20).describe("Number of most recent events to return"),
9784
10063
  compact: z2.boolean().default(true).describe("Return compact event summaries by default. Set false for full rows.")
9785
- }, async ({ n, compact }) => {
10064
+ }, async ({ n: n2, compact }) => {
9786
10065
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
9787
10066
  const db = getDb2();
9788
- const maxRows = boundedLimit(n, 20, compact ? 100 : 500);
10067
+ const maxRows = boundedLimit(n2, 20, compact ? 100 : 500);
9789
10068
  const rows = db.query("SELECT * FROM hook_events ORDER BY timestamp DESC LIMIT ?").all(maxRows);
9790
10069
  const projected = projectLogRows(rows);
9791
10070
  return {
@@ -9812,9 +10091,9 @@ function createHooksServer() {
9812
10091
  const m = s.match(/^(\d+)(s|m|h|d)$/);
9813
10092
  if (!m)
9814
10093
  return s;
9815
- const n = parseInt(m[1]);
10094
+ const n2 = parseInt(m[1]);
9816
10095
  const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
9817
- return new Date(Date.now() - n * ms).toISOString();
10096
+ return new Date(Date.now() - n2 * ms).toISOString();
9818
10097
  }
9819
10098
  const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
9820
10099
  const rows = db.query("SELECT * FROM hook_events WHERE error IS NOT NULL AND timestamp >= ? ORDER BY timestamp DESC LIMIT ?").all(ts, maxRows);
@@ -9840,20 +10119,20 @@ function createHooksServer() {
9840
10119
  const m = s.match(/^(\d+)(s|m|h|d)$/);
9841
10120
  if (!m)
9842
10121
  return s;
9843
- const n = parseInt(m[1]);
10122
+ const n2 = parseInt(m[1]);
9844
10123
  const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
9845
- return new Date(Date.now() - n * ms).toISOString();
10124
+ return new Date(Date.now() - n2 * ms).toISOString();
9846
10125
  }
9847
10126
  const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
9848
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);
9849
- const summary = totals.map((r) => ({
9850
- hook_name: r.hook_name,
9851
- total: r.total,
9852
- errors: r.errors,
9853
- 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%"
9854
10133
  }));
9855
- const grandTotal = totals.reduce((s, r) => s + r.total, 0);
9856
- 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);
9857
10136
  return {
9858
10137
  content: [{
9859
10138
  type: "text",
@@ -9884,8 +10163,8 @@ function createHooksServer() {
9884
10163
  pkg.version
9885
10164
  ]);
9886
10165
  return { content: [{ type: "text", text: "Feedback saved. Thank you!" }] };
9887
- } catch (e) {
9888
- return { content: [{ type: "text", text: String(e) }], isError: true };
10166
+ } catch (e2) {
10167
+ return { content: [{ type: "text", text: String(e2) }], isError: true };
9889
10168
  }
9890
10169
  });
9891
10170
  defineTool("register_agent", "Register an agent session. Returns agent_id. Auto-triggers a heartbeat.", {
@@ -10052,7 +10331,7 @@ var init_server = __esm(() => {
10052
10331
  init_redact();
10053
10332
  init_authority();
10054
10333
  init_storage();
10055
- __dirname3 = dirname8(fileURLToPath4(import.meta.url));
10334
+ __dirname3 = dirname9(fileURLToPath5(import.meta.url));
10056
10335
  pkg = { name: "@hasna/hooks", version: "0.0.0" };
10057
10336
  try {
10058
10337
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
@@ -11801,8 +12080,8 @@ var {
11801
12080
  // src/cli/index.tsx
11802
12081
  import chalk2 from "chalk";
11803
12082
  import { existsSync as existsSync16, readFileSync as readFileSync12, rmSync as rmSync4 } from "fs";
11804
- import { join as join17, dirname as dirname9 } from "path";
11805
- import { fileURLToPath as fileURLToPath5 } from "url";
12083
+ import { join as join17, dirname as dirname10 } from "path";
12084
+ import { fileURLToPath as fileURLToPath6 } from "url";
11806
12085
 
11807
12086
  // src/cli/components/App.tsx
11808
12087
  import { useState as useState7 } from "react";
@@ -12989,7 +13268,7 @@ init_local_opt_in();
12989
13268
  init_transport2();
12990
13269
  init_db();
12991
13270
  import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
12992
- var __dirname4 = dirname9(fileURLToPath5(import.meta.url));
13271
+ var __dirname4 = dirname10(fileURLToPath6(import.meta.url));
12993
13272
  var pkgPath = existsSync16(join17(__dirname4, "..", "package.json")) ? join17(__dirname4, "..", "package.json") : join17(__dirname4, "..", "..", "package.json");
12994
13273
  var pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
12995
13274
  var program2 = new Command;
@@ -13116,19 +13395,19 @@ function printDisclosureHint(hidden, detailCommand, options = {}) {
13116
13395
  }
13117
13396
  }
13118
13397
  function editDistance(a, b) {
13119
- const m = a.length, n = b.length;
13120
- const dp = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
13121
- 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++)
13122
13401
  dp[0][j] = j;
13123
13402
  for (let i = 1;i <= m; i++) {
13124
- for (let j = 1;j <= n; j++) {
13403
+ for (let j = 1;j <= n2; j++) {
13125
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]);
13126
13405
  }
13127
13406
  }
13128
- return dp[m][n];
13407
+ return dp[m][n2];
13129
13408
  }
13130
13409
  function suggestHooks(name, max = 3) {
13131
- 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);
13132
13411
  }
13133
13412
  program2.name("hooks").description("Install hooks for AI coding agents").version(pkg2.version);
13134
13413
  program2.command("interactive", { isDefault: true }).alias("i").description("Interactive hook browser").action(() => {
@@ -13409,10 +13688,10 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
13409
13688
  return { name, version };
13410
13689
  }
13411
13690
  const pinnedRequests = toInstall.map((arg) => ({ arg, pinned: parseNameVersion(arg) })).filter((x) => x.pinned !== null);
13412
- const customSources = toInstall.filter((n) => !getHook(n) && isCustomSource2(n));
13691
+ const customSources = toInstall.filter((n2) => !getHook(n2) && isCustomSource2(n2));
13413
13692
  if (options.dryRun) {
13414
- const known = toInstall.filter((n) => getHook(n));
13415
- 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));
13416
13695
  if (options.json) {
13417
13696
  console.log(JSON.stringify({
13418
13697
  dryRun: true,
@@ -13486,7 +13765,7 @@ Dry run \u2014 would install (${scope}, ${target}):
13486
13765
  for (const { arg, pinned } of pinnedRequests) {
13487
13766
  try {
13488
13767
  const transport = resolveHooksTransport2();
13489
- 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);
13490
13769
  installedPinned.push(pinnedInstall.name);
13491
13770
  if (options.json) {
13492
13771
  console.log(JSON.stringify({ pinned: { request: arg, name: pinnedInstall.name, version: pinnedInstall.version, sha256: pinnedInstall.sha256, source: pinnedInstall.source } }));
@@ -13505,7 +13784,7 @@ Dry run \u2014 would install (${scope}, ${target}):
13505
13784
  }
13506
13785
  }
13507
13786
  const namesToRegister = [...new Set([
13508
- ...toInstall.filter((n) => !isCustomSource2(n) && !parseNameVersion(n)),
13787
+ ...toInstall.filter((n2) => !isCustomSource2(n2) && !parseNameVersion(n2)),
13509
13788
  ...installedCustom,
13510
13789
  ...installedPinned
13511
13790
  ])];
@@ -13529,22 +13808,22 @@ Dry run \u2014 would install (${scope}, ${target}):
13529
13808
  }
13530
13809
  if (options.json) {
13531
13810
  console.log(JSON.stringify({
13532
- installed: results.filter((r) => r.success).map((r) => r.hook),
13533
- failed: results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error })),
13534
- 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 })),
13535
13814
  total: results.length,
13536
- success: results.filter((r) => r.success).length,
13815
+ success: results.filter((r2) => r2.success).length,
13537
13816
  scope,
13538
13817
  target,
13539
- applied: results.some((r) => r.applied),
13540
- ...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) } : {}
13541
13820
  }));
13542
- if (results.some((r) => !r.success)) {
13821
+ if (results.some((r2) => !r2.success)) {
13543
13822
  process.exitCode = 1;
13544
13823
  }
13545
13824
  return;
13546
13825
  }
13547
- const successCount = results.filter((r) => r.success).length;
13826
+ const successCount = results.filter((r2) => r2.success).length;
13548
13827
  const settingsFile = target === "codewith" ? options.applyCodewith ? options.codewithConfig : "TOML fragment only (configs should apply)" : formatSettingsPath(scope, target);
13549
13828
  console.log(chalk2.bold(`
13550
13829
  Installing hooks (${scope}, ${target})...
@@ -13996,7 +14275,7 @@ program2.command("update").argument("[hooks...]", "Hooks to update (defaults to
13996
14275
  if (pinned) {
13997
14276
  try {
13998
14277
  const transport = resolveHooksTransport2();
13999
- 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);
14000
14279
  const registered = installHook(pinnedInstall.name, { scope, overwrite: true });
14001
14280
  if (!registered.success) {
14002
14281
  results.push({ hook: arg, success: false, error: registered.error ?? "registration failed" });
@@ -14037,10 +14316,10 @@ program2.command("update").argument("[hooks...]", "Hooks to update (defaults to
14037
14316
  }
14038
14317
  if (options.json) {
14039
14318
  console.log(JSON.stringify({
14040
- updated: results.filter((r) => r.success).map((r) => r.hook),
14041
- 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 }))
14042
14321
  }));
14043
- if (results.some((r) => !r.success)) {
14322
+ if (results.some((r2) => !r2.success)) {
14044
14323
  process.exitCode = 1;
14045
14324
  }
14046
14325
  return;
@@ -14055,7 +14334,7 @@ Updating hooks...
14055
14334
  console.log(chalk2.red(`\u2717 ${result.hook}: ${result.error}`));
14056
14335
  }
14057
14336
  }
14058
- if (results.some((r) => !r.success)) {
14337
+ if (results.some((r2) => !r2.success)) {
14059
14338
  process.exitCode = 1;
14060
14339
  }
14061
14340
  });
@@ -14400,16 +14679,16 @@ logCmd.command("errors").description("Show hook events that contain errors").opt
14400
14679
  const m = s.match(/^(\d+)(s|m|h|d)$/);
14401
14680
  if (!m)
14402
14681
  return 86400000;
14403
- const n = parseInt(m[1]);
14682
+ const n2 = parseInt(m[1]);
14404
14683
  switch (m[2]) {
14405
14684
  case "s":
14406
- return n * 1000;
14685
+ return n2 * 1000;
14407
14686
  case "m":
14408
- return n * 60 * 1000;
14687
+ return n2 * 60 * 1000;
14409
14688
  case "h":
14410
- return n * 60 * 60 * 1000;
14689
+ return n2 * 60 * 60 * 1000;
14411
14690
  case "d":
14412
- return n * 24 * 60 * 60 * 1000;
14691
+ return n2 * 24 * 60 * 60 * 1000;
14413
14692
  default:
14414
14693
  return 86400000;
14415
14694
  }