@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/README.md CHANGED
@@ -181,6 +181,17 @@ export HASNA_HOOKS_API_KEY_REF=<vault-key-name> # resolved through the vault a
181
181
  secrets exec <vault-key-name> --as HASNA_HOOKS_API_KEY -- hooks serve
182
182
  ```
183
183
 
184
+ The owner-only `~/.hasna/hooks/config/credentials` file also accepts a reference:
185
+
186
+ ```dotenv
187
+ HASNA_HOOKS_API_URL=https://registry.example.com
188
+ HASNA_HOOKS_API_KEY_REF=your-team/hooks/live/api-key
189
+ ```
190
+
191
+ Keep this file at mode `0400` or `0600`, and configure Secrets through its normal independent credential resolver. Hooks resolves the selected reference through the Secrets SDK for each registry request and each server publish-key check. References retain their existing precedence: a locked Keychain remains a refusal ahead of a file reference, and a missing reference never falls through to a stale literal key. A file cannot contain both a literal key and a reference.
192
+
193
+ `hooks list` displays catalog metadata and does not prove that the remote registry is reachable. Use `hooks sync --dry-run` to exercise registry reads without changing installed hooks. SDK consumers use `createHooksClient()`; callers working directly with transport metadata must await `resolveHooksRequestAuthority()` before sending a request. The server uses `resolveHooksServePublishKeyAsync()` for references.
194
+
184
195
  **Cloudflare provisioning.** `hooks cf deploy` creates the D1 database and R2 bucket via the Cloudflare API, then prints the exact wrangler commands for the worker upload (the worker needs the workerd target, which only wrangler can bundle):
185
196
 
186
197
  ```bash
@@ -193,8 +204,8 @@ The worker (`src/cf/worker.ts`) implements the same API routes against D1 + R2,
193
204
 
194
205
  ## Storage
195
206
 
196
- Hooks stores data locally by default in `~/.hasna/hooks/` and uses SQLite
197
- directly for hook event history. The package owns its database schema and
207
+ When local mode is explicitly selected, Hooks stores data in `~/.hasna/hooks/`
208
+ and uses SQLite for hook event history. The package owns its database schema and
198
209
  migrations; it does not depend on the deprecated shared runtime or its CLI.
199
210
  The repo includes its own PostgreSQL migration definitions for the optional
200
211
  `hooks storage push|pull|sync` commands. Use the `hooks log` commands to inspect
package/bin/hooks-mcp.js CHANGED
@@ -1418,14 +1418,14 @@ var init_redact = __esm(() => {
1418
1418
  ];
1419
1419
  });
1420
1420
 
1421
- // ../../node_modules/.bun/@hasna+contracts@1.0.2+e0db24ae03127387/node_modules/@hasna/contracts/dist/client/transport.js
1421
+ // ../contracts/dist/client/transport.js
1422
1422
  import { isIP } from "net";
1423
1423
  import { spawnSync } from "child_process";
1424
- import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as readFileSync7 } from "fs";
1425
- import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
1426
- import { createRequire } from "module";
1424
+ import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync22 } from "fs";
1425
+ import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
1427
1426
  import { hostname as osHostname } from "os";
1428
- import { isAbsolute as isAbsolute2, join as join9 } from "path";
1427
+ import { isAbsolute as isAbsolute2, join as join22 } from "path";
1428
+ import { createRequire } from "module";
1429
1429
  function envToken(name) {
1430
1430
  return name.toUpperCase().replace(/-/g, "_");
1431
1431
  }
@@ -1455,14 +1455,14 @@ function hasnaHomeDir(env) {
1455
1455
  if (override)
1456
1456
  return override;
1457
1457
  const home = homeDir(env);
1458
- return home ? join9(home, HASNA_HOME_DIR) : null;
1458
+ return home ? join22(home, HASNA_HOME_DIR) : null;
1459
1459
  }
1460
1460
  function appConfigDir(name, env) {
1461
1461
  const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
1462
1462
  if (configRoot)
1463
- return join9(configRoot, name);
1463
+ return join22(configRoot, name);
1464
1464
  const root = hasnaHomeDir(env);
1465
- return root ? join9(root, name, CONFIG_SUBDIR) : null;
1465
+ return root ? join22(root, name, CONFIG_SUBDIR) : null;
1466
1466
  }
1467
1467
  function credentialDiskSourceList(name, env, profile = null) {
1468
1468
  if (!SAFE_APP_SLUG.test(name))
@@ -1471,7 +1471,7 @@ function credentialDiskSourceList(name, env, profile = null) {
1471
1471
  if (!directory)
1472
1472
  return [];
1473
1473
  const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
1474
- return [{ path: join9(directory, file), tier: "disk" }];
1474
+ return [{ path: join22(directory, file), tier: "disk" }];
1475
1475
  }
1476
1476
  function credentialDiskSources(name, env) {
1477
1477
  return credentialDiskSourceList(name, env, null).map((s) => s.path);
@@ -1525,7 +1525,7 @@ function readAppConfigFile(path) {
1525
1525
  };
1526
1526
  let fd = -1;
1527
1527
  try {
1528
- fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
1528
+ fd = openSync2(path, O_RDONLY2 | O_NOFOLLOW2 | O_NONBLOCK2);
1529
1529
  } catch (error) {
1530
1530
  const code = error.code;
1531
1531
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -1535,7 +1535,7 @@ function readAppConfigFile(path) {
1535
1535
  unsafe(`the path could not be opened (${code ?? "unknown error"})`);
1536
1536
  }
1537
1537
  try {
1538
- const before = fstatSync(fd);
1538
+ const before = fstatSync2(fd);
1539
1539
  if (!before.isFile())
1540
1540
  unsafe("the path is not a regular file");
1541
1541
  if (!configFileModeAllowed(before.mode)) {
@@ -1546,8 +1546,8 @@ function readAppConfigFile(path) {
1546
1546
  unsafe("the file is not owned by the current user");
1547
1547
  if (before.size > MAX_CREDENTIAL_FILE_BYTES)
1548
1548
  unsafe("the file exceeds the size limit");
1549
- const bytes = readFileSync7(fd);
1550
- const after = fstatSync(fd);
1549
+ const bytes = readFileSync22(fd);
1550
+ const after = fstatSync2(fd);
1551
1551
  if (!configFileReadsCoherent(before, after)) {
1552
1552
  unsafe("the file changed while being read");
1553
1553
  }
@@ -1557,20 +1557,28 @@ function readAppConfigFile(path) {
1557
1557
  closeSync2(fd);
1558
1558
  }
1559
1559
  }
1560
- function readCredentialFile(path, apiKeyKeys) {
1560
+ function readCredentialFile(path, apiKeyKeys, pointerKey) {
1561
1561
  const parsed = readAppConfigFile(path);
1562
1562
  if (!parsed)
1563
1563
  return null;
1564
- for (const key of apiKeyKeys) {
1564
+ for (const key of [...apiKeyKeys, pointerKey]) {
1565
1565
  if (parsed.unusable.has(key)) {
1566
1566
  throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
1567
1567
  }
1568
1568
  }
1569
1569
  const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
1570
+ const pointer = parsed.values.get(pointerKey)?.trim();
1571
+ if (pointer !== undefined) {
1572
+ if (!VAULT_POINTER_SHAPE.test(pointer))
1573
+ throw new CredentialFileUnsafeError(path, `${pointerKey} must name a vault item`);
1574
+ if (values.length)
1575
+ throw new CredentialFileUnsafeError(path, "a credential file cannot select both a literal key and a vault reference");
1576
+ return { apiKey: "", pointerVaultKey: pointer };
1577
+ }
1570
1578
  if (new Set(values).size > 1) {
1571
1579
  throw new CredentialFileUnsafeError(path, "credential aliases disagree");
1572
1580
  }
1573
- return values[0] ?? null;
1581
+ return values[0] === undefined ? null : { apiKey: values[0] };
1574
1582
  }
1575
1583
  function appConfigDiskValue(name, env, keys) {
1576
1584
  const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
@@ -1827,12 +1835,13 @@ function resolveCredential(name, env, options = {}) {
1827
1835
  }
1828
1836
  const paths = profileDiskSources(name, env, profile);
1829
1837
  for (const path of paths) {
1830
- const value = readCredentialFile(path, apiKeyKeys);
1838
+ const value = readCredentialFile(path, apiKeyKeys, pointerKeyName);
1831
1839
  if (value) {
1832
- assertUsableCredential(name, path, value);
1840
+ if (!value.pointerVaultKey)
1841
+ assertUsableCredential(name, path, value.apiKey);
1833
1842
  return sealCredential({
1834
- apiKey: value,
1835
- tier: "profile",
1843
+ ...value,
1844
+ tier: value.pointerVaultKey ? "pointer" : "profile",
1836
1845
  source: path,
1837
1846
  deliberate: true,
1838
1847
  diskCandidates: paths,
@@ -1840,7 +1849,7 @@ function resolveCredential(name, env, options = {}) {
1840
1849
  });
1841
1850
  }
1842
1851
  }
1843
- 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);
1852
+ 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);
1844
1853
  }
1845
1854
  const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
1846
1855
  const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
@@ -1865,17 +1874,27 @@ function resolveCredential(name, env, options = {}) {
1865
1874
  });
1866
1875
  }
1867
1876
  const diskSourceList = credentialDiskSourceList(name, env, null);
1868
- const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
1877
+ const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys, pointerKeyName) })).filter((hit) => hit.value !== null);
1869
1878
  if (diskHits.length > 0) {
1870
1879
  const winner = diskHits[0];
1871
- assertUsableCredential(name, winner.src.path, winner.value);
1880
+ if (winner.value.pointerVaultKey) {
1881
+ return sealCredential({
1882
+ ...winner.value,
1883
+ tier: "pointer",
1884
+ source: winner.src.path,
1885
+ deliberate: false,
1886
+ diskCandidates: diskPaths,
1887
+ warning: null
1888
+ });
1889
+ }
1890
+ assertUsableCredential(name, winner.src.path, winner.value.apiKey);
1872
1891
  const divergentSources = [
1873
- ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
1874
- ...envHit && envHit.value !== winner.value ? [envHit.key] : []
1892
+ ...diskHits.slice(1).filter((hit) => hit.value.apiKey !== winner.value.apiKey || hit.value.pointerVaultKey !== winner.value.pointerVaultKey).map((hit) => hit.src.path),
1893
+ ...envHit && envHit.value !== winner.value.apiKey ? [envHit.key] : []
1875
1894
  ];
1876
1895
  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;
1877
1896
  return sealCredential({
1878
- apiKey: winner.value,
1897
+ apiKey: winner.value.apiKey,
1879
1898
  tier: winner.src.tier,
1880
1899
  source: winner.src.path,
1881
1900
  deliberate: false,
@@ -2098,8 +2117,9 @@ function credentialDiskSourcesForMessage(name, env) {
2098
2117
  const paths = credentialDiskSources(name, env);
2099
2118
  return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
2100
2119
  }
2101
- var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
2120
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", MAX_PACKAGE_BYTES, CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, requireSecretsSdk, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
2102
2121
  var init_transport = __esm(() => {
2122
+ MAX_PACKAGE_BYTES = 1024 * 1024;
2103
2123
  CredentialResolutionError = class CredentialResolutionError extends Error {
2104
2124
  appName;
2105
2125
  attempted;
@@ -2127,7 +2147,6 @@ var init_transport = __esm(() => {
2127
2147
  INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
2128
2148
  CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
2129
2149
  AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
2130
- SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
2131
2150
  requireSecretsSdk = createRequire(import.meta.url);
2132
2151
  ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
2133
2152
  DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
@@ -2385,7 +2404,7 @@ __export(exports_run, {
2385
2404
  });
2386
2405
  import { randomBytes as randomBytes2 } from "crypto";
2387
2406
  import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
2388
- import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute4, join as join10 } from "path";
2407
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute4, join as join9 } from "path";
2389
2408
  function extensionOf(scriptPath) {
2390
2409
  const base = basename2(scriptPath);
2391
2410
  const dot = base.lastIndexOf(".");
@@ -2456,7 +2475,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
2456
2475
  async function executeVerifiedScript(options) {
2457
2476
  const scriptDir = dirname6(options.scriptPath);
2458
2477
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
2459
- const tempPath = join10(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
2478
+ const tempPath = join9(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
2460
2479
  let proc = null;
2461
2480
  let timedOut = false;
2462
2481
  const timeoutTimer = options.timeout ? setTimeout(() => {
@@ -2664,8 +2683,8 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
2664
2683
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2665
2684
  import { z as z2 } from "zod";
2666
2685
  init_registry();
2667
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
2668
- import { join as join11, dirname as dirname7 } from "path";
2686
+ import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
2687
+ import { join as join10, dirname as dirname7 } from "path";
2669
2688
  import { fileURLToPath as fileURLToPath3 } from "url";
2670
2689
 
2671
2690
  // src/lib/installer.ts
@@ -3670,6 +3689,7 @@ function rethrowHooksAuthorityFailure(error) {
3670
3689
  }
3671
3690
  throw new Error(`REMOTE_API_URL_INVALID: ${message} local mode fallback is disabled`, { cause: error });
3672
3691
  }
3692
+ var authorityBindings = new WeakMap;
3673
3693
  function resolveHooksTransport(env = process.env, options = {}) {
3674
3694
  if (selectsHooksLocalStore(env)) {
3675
3695
  announceLocal(options.notice, "HASNA_HOOKS_LOCAL is set and nothing configures a registry authority");
@@ -3683,7 +3703,7 @@ function resolveHooksTransport(env = process.env, options = {}) {
3683
3703
  try {
3684
3704
  credential = resolveCredential("hooks", resolverEnv, credentialOptions);
3685
3705
  resolution = resolveClientTransport("hooks", resolverEnv, {
3686
- credentials: credential ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
3706
+ credentials: credential && credential.tier !== "pointer" ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
3687
3707
  });
3688
3708
  } catch (error) {
3689
3709
  rethrowHooksAuthorityFailure(error);
@@ -3691,18 +3711,20 @@ function resolveHooksTransport(env = process.env, options = {}) {
3691
3711
  if (!credential) {
3692
3712
  throw new Error("REMOTE_API_CONFIG_MISSING: no hooks registry credential resolved; " + "local mode fallback is disabled");
3693
3713
  }
3714
+ const authority = {
3715
+ origin: hooksRegistryOrigin(resolution.baseUrl),
3716
+ apiKey: credential.apiKey,
3717
+ apiUrlSource: resolution.apiUrlSource,
3718
+ apiKeySource: credential.source,
3719
+ apiKeyTier: credential.tier,
3720
+ v1BaseUrl: resolution.baseUrl,
3721
+ warning: resolution.warning
3722
+ };
3723
+ authorityBindings.set(authority, { env, options, credential });
3694
3724
  return {
3695
3725
  mode: "remote",
3696
3726
  source: `${credential.source}+${resolution.apiUrlSource ?? "default"}`,
3697
- authority: {
3698
- origin: hooksRegistryOrigin(resolution.baseUrl),
3699
- apiKey: credential.apiKey,
3700
- apiUrlSource: resolution.apiUrlSource,
3701
- apiKeySource: credential.source,
3702
- apiKeyTier: credential.tier,
3703
- v1BaseUrl: resolution.baseUrl,
3704
- warning: resolution.warning
3705
- }
3727
+ authority
3706
3728
  };
3707
3729
  }
3708
3730
 
@@ -4154,9 +4176,9 @@ var __dirname3 = dirname7(fileURLToPath3(import.meta.url));
4154
4176
  var pkg = { name: "@hasna/hooks", version: "0.0.0" };
4155
4177
  try {
4156
4178
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
4157
- const p = join11(__dirname3, rel);
4179
+ const p = join10(__dirname3, rel);
4158
4180
  if (existsSync11(p)) {
4159
- pkg = JSON.parse(readFileSync8(p, "utf-8"));
4181
+ pkg = JSON.parse(readFileSync7(p, "utf-8"));
4160
4182
  break;
4161
4183
  }
4162
4184
  }
@@ -4168,7 +4190,7 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
4168
4190
  if (!resolved || !hookScript || !existsSync11(hookScript)) {
4169
4191
  throw new Error(`Hook '${name}' not found`);
4170
4192
  }
4171
- const content = readFileSync8(hookScript);
4193
+ const content = readFileSync7(hookScript);
4172
4194
  const sha = sha256Of(content);
4173
4195
  const check = checkScriptHash(name, sha);
4174
4196
  if (!check.ok) {
@@ -4394,14 +4416,14 @@ function createHooksServer() {
4394
4416
  continue;
4395
4417
  }
4396
4418
  const hookDir = getHookPath(name);
4397
- if (!existsSync11(join11(hookDir, "src", "hook.ts"))) {
4419
+ if (!existsSync11(join10(hookDir, "src", "hook.ts"))) {
4398
4420
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
4399
4421
  hookHealthy = false;
4400
4422
  }
4401
4423
  }
4402
4424
  if (meta && settingsExist) {
4403
4425
  try {
4404
- const settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
4426
+ const settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
4405
4427
  if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
4406
4428
  const eventName = meta.event.split(":")[0];
4407
4429
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
@@ -4438,10 +4460,10 @@ function createHooksServer() {
4438
4460
  return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
4439
4461
  }
4440
4462
  const hookPath = getHookPath(name);
4441
- const readmePath = join11(hookPath, "README.md");
4463
+ const readmePath = join10(hookPath, "README.md");
4442
4464
  let readme = "";
4443
4465
  if (existsSync11(readmePath)) {
4444
- readme = readFileSync8(readmePath, "utf-8");
4466
+ readme = readFileSync7(readmePath, "utf-8");
4445
4467
  }
4446
4468
  if (verbose) {
4447
4469
  return { content: [{ type: "text", text: JSON.stringify({ ...meta, readme }) }] };
@@ -4771,7 +4793,7 @@ function createHooksServer() {
4771
4793
  let settings = {};
4772
4794
  try {
4773
4795
  if (existsSync11(settingsPath))
4774
- settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
4796
+ settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
4775
4797
  } catch {}
4776
4798
  if (!settings.hooks)
4777
4799
  settings.hooks = {};
@@ -4794,7 +4816,7 @@ function createHooksServer() {
4794
4816
  let settings = {};
4795
4817
  try {
4796
4818
  if (existsSync11(settingsPath))
4797
- settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
4819
+ settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
4798
4820
  } catch {}
4799
4821
  if (settings.hooks?.__disabled) {
4800
4822
  settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);