@hasna/hooks 0.6.6 → 0.7.0

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.
Files changed (36) hide show
  1. package/README.md +1 -1
  2. package/bin/index.js +1024 -461
  3. package/bin/serve.js +626 -158
  4. package/dist/config.d.ts +7 -5
  5. package/dist/db/pg-migrations.d.ts +1 -1
  6. package/dist/index.js +898 -340
  7. package/dist/lib/hook-env.d.ts +60 -4
  8. package/dist/lib/manifest.d.ts +11 -0
  9. package/dist/lib/semver.d.ts +27 -0
  10. package/dist/openapi.d.ts +216 -0
  11. package/dist/storage.js +34 -34
  12. package/hooks/codewith-native-common.test.ts +25 -25
  13. package/hooks/codewith-native-common.ts +1 -1
  14. package/hooks/hook-autoformat/package.json +1 -1
  15. package/hooks/hook-desktopnotify/package.json +1 -1
  16. package/hooks/hook-envsetup/package.json +1 -1
  17. package/hooks/hook-knowledge-context/README.md +1 -1
  18. package/hooks/hook-knowledge-context/src/hook.test.ts +1 -1
  19. package/hooks/hook-permissionguard/package.json +1 -1
  20. package/hooks/hook-protectfiles/package.json +1 -1
  21. package/hooks/hook-spiral-detector/LICENSE +191 -0
  22. package/hooks/hook-spiral-detector/README.md +23 -0
  23. package/hooks/hook-spiral-detector/package.json +12 -0
  24. package/hooks/hook-spiral-detector/src/hook.test.ts +72 -0
  25. package/hooks/hook-spiral-detector/src/hook.ts +93 -0
  26. package/hooks/hook-workspace-repos-guard/README.md +4 -3
  27. package/hooks/hook-workspace-repos-guard/src/hook.test.ts +7 -7
  28. package/hooks/hook-workspace-repos-guard/src/hook.ts +5 -3
  29. package/hooks/mention-context/src/hasna-mention-context.py +2 -2
  30. package/hooks/mention-context/src/hasna-mention-warm.py +1 -1
  31. package/hooks/pre-bash/README.md +1 -1
  32. package/hooks/prompt-guard/README.md +1 -1
  33. package/hooks/session-start/README.md +1 -1
  34. package/hooks/stop-sync/README.md +1 -1
  35. package/hooks/worktree-guard/README.md +1 -1
  36. package/package.json +10 -6
package/bin/index.js CHANGED
@@ -20,12 +20,14 @@ var __toESM = (mod, isNodeMode, target) => {
20
20
  }
21
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
22
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
- for (let key of __getOwnPropNames(mod))
24
- if (!__hasOwnProp.call(to, key))
25
- __defProp(to, key, {
26
- get: __accessProp.bind(mod, key),
27
- enumerable: true
28
- });
23
+ if (mod && typeof mod === "object" || typeof mod === "function") {
24
+ for (let key of __getOwnPropNames(mod))
25
+ if (!__hasOwnProp.call(to, key))
26
+ __defProp(to, key, {
27
+ get: __accessProp.bind(mod, key),
28
+ enumerable: true
29
+ });
30
+ }
29
31
  if (canCache)
30
32
  cache.set(mod, to);
31
33
  return to;
@@ -47,8 +49,8 @@ var __export = (target, all) => {
47
49
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
50
  var __require = import.meta.require;
49
51
 
50
- // node_modules/commander/lib/error.js
51
- var require_error = __commonJS((exports) => {
52
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/error.js
53
+ var require_error = __commonJS(function(exports) {
52
54
  class CommanderError extends Error {
53
55
  constructor(exitCode, code, message) {
54
56
  super(message);
@@ -71,8 +73,8 @@ var require_error = __commonJS((exports) => {
71
73
  exports.InvalidArgumentError = InvalidArgumentError;
72
74
  });
73
75
 
74
- // node_modules/commander/lib/argument.js
75
- var require_argument = __commonJS((exports) => {
76
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/argument.js
77
+ var require_argument = __commonJS(function(exports) {
76
78
  var { InvalidArgumentError } = require_error();
77
79
 
78
80
  class Argument {
@@ -150,8 +152,8 @@ var require_argument = __commonJS((exports) => {
150
152
  exports.humanReadableArgName = humanReadableArgName;
151
153
  });
152
154
 
153
- // node_modules/commander/lib/help.js
154
- var require_help = __commonJS((exports) => {
155
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/help.js
156
+ var require_help = __commonJS(function(exports) {
155
157
  var { humanReadableArgName } = require_argument();
156
158
 
157
159
  class Help {
@@ -399,8 +401,8 @@ var require_help = __commonJS((exports) => {
399
401
  exports.Help = Help;
400
402
  });
401
403
 
402
- // node_modules/commander/lib/option.js
403
- var require_option = __commonJS((exports) => {
404
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/option.js
405
+ var require_option = __commonJS(function(exports) {
404
406
  var { InvalidArgumentError } = require_error();
405
407
 
406
408
  class Option {
@@ -550,8 +552,8 @@ var require_option = __commonJS((exports) => {
550
552
  exports.DualOptions = DualOptions;
551
553
  });
552
554
 
553
- // node_modules/commander/lib/suggestSimilar.js
554
- var require_suggestSimilar = __commonJS((exports) => {
555
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/suggestSimilar.js
556
+ var require_suggestSimilar = __commonJS(function(exports) {
555
557
  var maxDistance = 3;
556
558
  function editDistance(a, b) {
557
559
  if (Math.abs(a.length - b.length) > maxDistance)
@@ -623,8 +625,8 @@ var require_suggestSimilar = __commonJS((exports) => {
623
625
  exports.suggestSimilar = suggestSimilar;
624
626
  });
625
627
 
626
- // node_modules/commander/lib/command.js
627
- var require_command = __commonJS((exports) => {
628
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/command.js
629
+ var require_command = __commonJS(function(exports) {
628
630
  var EventEmitter = __require("events").EventEmitter;
629
631
  var childProcess = __require("child_process");
630
632
  var path = __require("path");
@@ -1866,8 +1868,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1866
1868
  exports.Command = Command;
1867
1869
  });
1868
1870
 
1869
- // node_modules/commander/index.js
1870
- var require_commander = __commonJS((exports) => {
1871
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/index.js
1872
+ var require_commander = __commonJS(function(exports) {
1871
1873
  var { Argument } = require_argument();
1872
1874
  var { Command } = require_command();
1873
1875
  var { CommanderError, InvalidArgumentError } = require_error();
@@ -2266,6 +2268,16 @@ var init_registry = __esm(() => {
2266
2268
  matcher: "",
2267
2269
  tags: ["errors", "failures", "logging", "debugging"]
2268
2270
  },
2271
+ {
2272
+ name: "spiral-detector",
2273
+ displayName: "Spiral Detector",
2274
+ description: "Interrupts sessions after five consecutive identical command failures",
2275
+ version: "0.1.0",
2276
+ category: "Observability",
2277
+ event: "PostToolUse",
2278
+ matcher: "",
2279
+ tags: ["spiral", "failures", "retries", "safety", "interrupt"]
2280
+ },
2269
2281
  {
2270
2282
  name: "stylescheck",
2271
2283
  displayName: "Styles Check",
@@ -2419,8 +2431,8 @@ var init_registry = __esm(() => {
2419
2431
  ];
2420
2432
  });
2421
2433
 
2422
- // node_modules/cli-spinners/spinners.json
2423
- var require_spinners = __commonJS((exports, module) => {
2434
+ // ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/spinners.json
2435
+ var require_spinners = __commonJS(function(exports, module) {
2424
2436
  module.exports = {
2425
2437
  dots: {
2426
2438
  interval: 80,
@@ -4045,8 +4057,8 @@ var require_spinners = __commonJS((exports, module) => {
4045
4057
  };
4046
4058
  });
4047
4059
 
4048
- // node_modules/cli-spinners/index.js
4049
- var require_cli_spinners = __commonJS((exports, module) => {
4060
+ // ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/index.js
4061
+ var require_cli_spinners = __commonJS(function(exports, module) {
4050
4062
  var spinners = Object.assign({}, require_spinners());
4051
4063
  var spinnersList = Object.keys(spinners);
4052
4064
  Object.defineProperty(spinners, "random", {
@@ -4059,55 +4071,329 @@ var require_cli_spinners = __commonJS((exports, module) => {
4059
4071
  module.exports = spinners;
4060
4072
  });
4061
4073
 
4074
+ // ../contracts/dist/client/transport.js
4075
+ import { readFileSync, statSync } from "fs";
4076
+ import { join as join2 } from "path";
4077
+ function envToken(name) {
4078
+ return name.toUpperCase().replace(/-/g, "_");
4079
+ }
4080
+ function clientTransportEnvKeys(name) {
4081
+ const envSegment = envToken(name);
4082
+ return {
4083
+ apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
4084
+ apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
4085
+ };
4086
+ }
4087
+ function credentialOverrideEnvKey(name) {
4088
+ return `HASNA_${envToken(name)}_API_KEY_OVERRIDE`;
4089
+ }
4090
+ function homeDir(env) {
4091
+ const home = env.HOME?.trim();
4092
+ return home ? home : null;
4093
+ }
4094
+ function credentialDiskSources(name, env) {
4095
+ return profileDiskSources(name, env, null);
4096
+ }
4097
+ function profileDiskSources(name, env, profile) {
4098
+ const home = homeDir(env);
4099
+ if (!home || !SAFE_APP_SLUG.test(name))
4100
+ return [];
4101
+ const stem = profile ? `${name}.${profile}` : name;
4102
+ const configStem = profile ? `${name}-${profile}` : name;
4103
+ return [
4104
+ join2(home, HASNA_STATE_DIR, FLEET_CREDENTIAL_DIR, `${stem}.env`),
4105
+ join2(home, CONFIG_DIR, CONFIG_NAMESPACE, `${configStem}-cloud.env`)
4106
+ ];
4107
+ }
4108
+ function parseEnvFile(text) {
4109
+ const values = new Map;
4110
+ for (const rawLine of text.split(/\r?\n/)) {
4111
+ const line = rawLine.trim();
4112
+ if (line.length === 0 || line.startsWith("#"))
4113
+ continue;
4114
+ const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
4115
+ const equals = withoutExport.indexOf("=");
4116
+ if (equals <= 0)
4117
+ continue;
4118
+ const key = withoutExport.slice(0, equals).trim();
4119
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
4120
+ continue;
4121
+ let value = withoutExport.slice(equals + 1).trim();
4122
+ const quote = value[0];
4123
+ if (quote === '"' || quote === "'") {
4124
+ if (value.length < 2 || !value.endsWith(quote))
4125
+ continue;
4126
+ value = value.slice(1, -1);
4127
+ }
4128
+ if (value.length === 0)
4129
+ continue;
4130
+ values.set(key, value);
4131
+ }
4132
+ return values;
4133
+ }
4134
+ function readAppConfigFile(path) {
4135
+ let text;
4136
+ try {
4137
+ const stats = statSync(path);
4138
+ if (!stats.isFile() || stats.size > MAX_CREDENTIAL_FILE_BYTES)
4139
+ return null;
4140
+ text = readFileSync(path, "utf8");
4141
+ } catch {
4142
+ return null;
4143
+ }
4144
+ return parseEnvFile(text);
4145
+ }
4146
+ function readCredentialFile(path, apiKeyKeys) {
4147
+ const values = readAppConfigFile(path);
4148
+ if (!values)
4149
+ return null;
4150
+ for (const key of apiKeyKeys) {
4151
+ const value = values.get(key)?.trim();
4152
+ if (value)
4153
+ return value;
4154
+ }
4155
+ return null;
4156
+ }
4157
+ function assertUsableCredential(appName, source, value) {
4158
+ if (!ILLEGAL_IN_HEADER_VALUE.test(value))
4159
+ return;
4160
+ throw new CredentialResolutionError(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
4161
+ }
4162
+ function sealCredential(fields) {
4163
+ const { apiKey } = fields;
4164
+ const visible = {
4165
+ tier: fields.tier,
4166
+ source: fields.source,
4167
+ deliberate: fields.deliberate,
4168
+ deprecated: fields.deprecated,
4169
+ diskCandidates: Object.freeze([...fields.diskCandidates]),
4170
+ warning: fields.warning
4171
+ };
4172
+ const sealed = { ...visible };
4173
+ Object.defineProperty(sealed, "apiKey", {
4174
+ value: apiKey,
4175
+ enumerable: false,
4176
+ writable: false,
4177
+ configurable: false
4178
+ });
4179
+ Object.defineProperty(sealed, INSPECT_CUSTOM, {
4180
+ value: () => ({ ...visible, apiKey: "[redacted]" }),
4181
+ enumerable: false,
4182
+ writable: false,
4183
+ configurable: false
4184
+ });
4185
+ Object.defineProperty(sealed, CREDENTIAL_SEAL, {
4186
+ value: true,
4187
+ enumerable: false,
4188
+ writable: false,
4189
+ configurable: false
4190
+ });
4191
+ return Object.freeze(sealed);
4192
+ }
4193
+ function firstEnvValue(env, keys) {
4194
+ for (const key of keys) {
4195
+ const value = env[key]?.trim();
4196
+ if (value)
4197
+ return { key, value };
4198
+ }
4199
+ return null;
4200
+ }
4201
+ function deprecationNotified() {
4202
+ const host = globalThis;
4203
+ const existing = host[DEPRECATION_REGISTRY];
4204
+ if (existing instanceof Set)
4205
+ return existing;
4206
+ const created = new Set;
4207
+ host[DEPRECATION_REGISTRY] = created;
4208
+ return created;
4209
+ }
4210
+ function defaultDeprecationSink(message) {
4211
+ if (typeof process !== "undefined" && process.stderr) {
4212
+ process.stderr.write(`${message}
4213
+ `);
4214
+ }
4215
+ }
4216
+ function resolveCredential(name, env, options = {}) {
4217
+ const { apiKeyKeys } = clientTransportEnvKeys(name);
4218
+ const diskPaths = credentialDiskSources(name, env);
4219
+ const explicitKey = options.apiKey?.trim();
4220
+ if (explicitKey) {
4221
+ assertUsableCredential(name, "the explicit apiKey argument", explicitKey);
4222
+ return sealCredential({
4223
+ apiKey: explicitKey,
4224
+ tier: "argument",
4225
+ source: "explicit apiKey argument",
4226
+ deliberate: true,
4227
+ deprecated: false,
4228
+ diskCandidates: diskPaths,
4229
+ warning: null
4230
+ });
4231
+ }
4232
+ const overrideKeyName = credentialOverrideEnvKey(name);
4233
+ const overrideRaw = env[overrideKeyName];
4234
+ if (overrideRaw !== undefined) {
4235
+ const override = overrideRaw.trim();
4236
+ if (!override) {
4237
+ throw new CredentialResolutionError(name, `${overrideKeyName} is set but empty. It is a deliberate override, so it is not resolved around: ` + `either give it a real key or unset it to fall back to the credential on disk.`, [overrideKeyName]);
4238
+ }
4239
+ assertUsableCredential(name, overrideKeyName, override);
4240
+ return sealCredential({
4241
+ apiKey: override,
4242
+ tier: "override",
4243
+ source: overrideKeyName,
4244
+ deliberate: true,
4245
+ deprecated: false,
4246
+ diskCandidates: diskPaths,
4247
+ warning: null
4248
+ });
4249
+ }
4250
+ const profile = options.profile?.trim() || env[CREDENTIAL_PROFILE_ENV_KEY]?.trim();
4251
+ if (profile) {
4252
+ const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY;
4253
+ if (!SAFE_PROFILE.test(profile)) {
4254
+ throw new CredentialResolutionError(name, `Profile name from ${profileSource} is not usable in a path. ` + `Use letters, digits, dot, dash, or underscore.`, [profileSource]);
4255
+ }
4256
+ const paths = profileDiskSources(name, env, profile);
4257
+ for (const path of paths) {
4258
+ const value = readCredentialFile(path, apiKeyKeys);
4259
+ if (value) {
4260
+ assertUsableCredential(name, path, value);
4261
+ return sealCredential({
4262
+ apiKey: value,
4263
+ tier: "profile",
4264
+ source: path,
4265
+ deliberate: true,
4266
+ deprecated: false,
4267
+ diskCandidates: paths,
4268
+ warning: null
4269
+ });
4270
+ }
4271
+ }
4272
+ 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);
4273
+ }
4274
+ const diskHits = diskPaths.map((path) => ({ path, value: readCredentialFile(path, apiKeyKeys) })).filter((hit) => hit.value !== null);
4275
+ if (diskHits.length > 0) {
4276
+ const winner = diskHits[0];
4277
+ assertUsableCredential(name, winner.path, winner.value);
4278
+ const divergentSources = [
4279
+ ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.path),
4280
+ ...(() => {
4281
+ const legacyHit = firstEnvValue(env, apiKeyKeys);
4282
+ return legacyHit && legacyHit.value !== winner.value ? [legacyHit.key] : [];
4283
+ })()
4284
+ ];
4285
+ const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.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;
4286
+ return sealCredential({
4287
+ apiKey: winner.value,
4288
+ tier: "disk",
4289
+ source: winner.path,
4290
+ deliberate: false,
4291
+ deprecated: false,
4292
+ diskCandidates: diskPaths,
4293
+ warning
4294
+ });
4295
+ }
4296
+ const legacy = firstEnvValue(env, apiKeyKeys);
4297
+ if (legacy) {
4298
+ assertUsableCredential(name, legacy.key, legacy.value);
4299
+ const where = diskPaths.length > 0 ? `Put the current key in ${diskPaths[0]} \u2014 it is re-read on every call, so rotations take effect immediately.` : `This environment has no HOME, so no credential file could be consulted at all; the disk tier is ` + `unavailable here and this process will keep using the environment snapshot.`;
4300
+ const message = `[${name}] DEPRECATED: the API key came from ${legacy.key} in this process's environment. ` + `Environment variables are a snapshot taken when this process started, so a shell that started ` + `before a key rotation keeps using the old key until it exits. ${where}`;
4301
+ const sink = options.onDeprecation ?? defaultDeprecationSink;
4302
+ const notified = deprecationNotified();
4303
+ if (!notified.has(name)) {
4304
+ notified.add(name);
4305
+ sink(message);
4306
+ }
4307
+ return sealCredential({
4308
+ apiKey: legacy.value,
4309
+ tier: "legacy-env",
4310
+ source: legacy.key,
4311
+ deliberate: false,
4312
+ deprecated: true,
4313
+ diskCandidates: diskPaths,
4314
+ warning: message
4315
+ });
4316
+ }
4317
+ return null;
4318
+ }
4319
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, HASNA_STATE_DIR = ".hasna", FLEET_CREDENTIAL_DIR = "cloud", CONFIG_DIR = ".config", CONFIG_NAMESPACE = "hasna", MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, INSPECT_CUSTOM, CREDENTIAL_SEAL, DEPRECATION_REGISTRY, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
4320
+ var init_transport = __esm(() => {
4321
+ CredentialResolutionError = class CredentialResolutionError extends Error {
4322
+ appName;
4323
+ attempted;
4324
+ constructor(appName, message, attempted) {
4325
+ super(message);
4326
+ this.name = "CredentialResolutionError";
4327
+ this.appName = appName;
4328
+ this.attempted = attempted;
4329
+ }
4330
+ };
4331
+ MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
4332
+ SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
4333
+ SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
4334
+ ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
4335
+ INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
4336
+ CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
4337
+ DEPRECATION_REGISTRY = Symbol.for("hasna:contracts:credentialDeprecationNotices");
4338
+ IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
4339
+ AUTHORITY_OVERRIDE_HEADERS = new Set([
4340
+ "host",
4341
+ ":authority",
4342
+ "forwarded",
4343
+ "x-forwarded-host",
4344
+ "x-original-host"
4345
+ ]);
4346
+ });
4347
+
4062
4348
  // src/config.ts
4063
4349
  var exports_config = {};
4064
4350
  __export(exports_config, {
4065
- writeConfig: () => writeConfig,
4066
- resolveApiUrl: () => resolveApiUrl,
4067
- resolveApiKey: () => resolveApiKey,
4068
- readConfig: () => readConfig,
4069
- getLockPath: () => getLockPath,
4070
- getHooksDataDir: () => getHooksDataDir,
4351
+ getConfigPath: () => getConfigPath,
4071
4352
  getCustomHooksDir: () => getCustomHooksDir,
4072
- getConfigPath: () => getConfigPath
4353
+ getHooksDataDir: () => getHooksDataDir,
4354
+ getLockPath: () => getLockPath,
4355
+ readConfig: () => readConfig,
4356
+ resolveApiKey: () => resolveApiKey,
4357
+ resolveApiUrl: () => resolveApiUrl,
4358
+ writeConfig: () => writeConfig
4073
4359
  });
4074
- import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
4075
- import { join as join2 } from "path";
4360
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
4361
+ import { join as join3 } from "path";
4076
4362
  import { homedir as homedir2 } from "os";
4077
4363
  function getHooksDataDir() {
4078
4364
  const explicit = process.env.HASNA_HOOKS_DATA_DIR ?? process.env.HOOKS_DATA_DIR;
4079
4365
  if (explicit)
4080
4366
  return explicit;
4081
- return join2(homedir2(), ".hasna", "hooks");
4367
+ return join3(homedir2(), ".hasna", "hooks");
4082
4368
  }
4083
4369
  function getCustomHooksDir() {
4084
- return join2(getHooksDataDir(), "hooks");
4370
+ return join3(getHooksDataDir(), "hooks");
4085
4371
  }
4086
4372
  function getLockPath() {
4087
4373
  const explicit = process.env.HASNA_HOOKS_LOCK_PATH ?? process.env.HOOKS_LOCK_PATH;
4088
4374
  if (explicit)
4089
4375
  return explicit;
4090
- return join2(getHooksDataDir(), "hooks.lock");
4376
+ return join3(getHooksDataDir(), "hooks.lock");
4091
4377
  }
4092
4378
  function getConfigPath() {
4093
4379
  const explicit = process.env.HASNA_HOOKS_CONFIG_PATH ?? process.env.HOOKS_CONFIG_PATH;
4094
4380
  if (explicit)
4095
4381
  return explicit;
4096
- return join2(getHooksDataDir(), "config.json");
4382
+ return join3(getHooksDataDir(), "config.json");
4097
4383
  }
4098
4384
  function readConfig() {
4099
4385
  try {
4100
4386
  const path = getConfigPath();
4101
4387
  if (!existsSync2(path))
4102
4388
  return {};
4103
- return JSON.parse(readFileSync(path, "utf-8"));
4389
+ return JSON.parse(readFileSync2(path, "utf-8"));
4104
4390
  } catch {
4105
4391
  return {};
4106
4392
  }
4107
4393
  }
4108
4394
  function writeConfig(config) {
4109
4395
  const path = getConfigPath();
4110
- mkdirSync(join2(path, ".."), { recursive: true });
4396
+ mkdirSync(join3(path, ".."), { recursive: true });
4111
4397
  writeFileSync(path, JSON.stringify(config, null, 2) + `
4112
4398
  `, "utf-8");
4113
4399
  return path;
@@ -4120,36 +4406,38 @@ function resolveApiUrl() {
4120
4406
  return cfg.api_url ? cfg.api_url.replace(/\/+$/, "") : undefined;
4121
4407
  }
4122
4408
  function resolveApiKey() {
4123
- return process.env.HASNA_HOOKS_API_KEY ?? process.env.HOOKS_API_KEY;
4409
+ return resolveCredential("hooks", process.env)?.apiKey;
4124
4410
  }
4125
- var init_config = () => {};
4411
+ var init_config = __esm(() => {
4412
+ init_transport();
4413
+ });
4126
4414
 
4127
4415
  // src/lib/semver.ts
4128
- var SEMVER_PATTERN;
4416
+ var NUMERIC_ID = "(0|[1-9]\\d{0,15})", ALNUM_ID = "\\d*[A-Za-z-][0-9A-Za-z-]*", SEMVER_PATTERN;
4129
4417
  var init_semver = __esm(() => {
4130
- SEMVER_PATTERN = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
4418
+ SEMVER_PATTERN = new RegExp(`^${NUMERIC_ID}\\.${NUMERIC_ID}\\.${NUMERIC_ID}` + `(?:-(?:${NUMERIC_ID}|${ALNUM_ID})(?:\\.(?:${NUMERIC_ID}|${ALNUM_ID}))*)?` + `(?:\\+[0-9A-Za-z.-]+)?$`);
4131
4419
  });
4132
4420
 
4133
4421
  // src/lib/manifest.ts
4134
4422
  var exports_manifest = {};
4135
4423
  __export(exports_manifest, {
4136
- writeCustomHook: () => writeCustomHook,
4137
- shortManifestName: () => shortManifestName,
4138
- scriptRelFor: () => scriptRelFor,
4139
- resolveScript: () => resolveScript,
4140
- readCustomManifest: () => readCustomManifest,
4141
- parseManifest: () => parseManifest,
4142
- normalizeManifestName: () => normalizeManifestName,
4143
- manifestSchema: () => manifestSchema,
4144
- listCustomHooks: () => listCustomHooks,
4145
- isInlineScript: () => isInlineScript,
4146
- customHookDir: () => customHookDir,
4147
- assertContained: () => assertContained,
4424
+ HOOK_NAME_RE: () => HOOK_NAME_RE,
4148
4425
  ScriptContainmentError: () => ScriptContainmentError,
4149
- HOOK_NAME_RE: () => HOOK_NAME_RE
4426
+ assertContained: () => assertContained,
4427
+ customHookDir: () => customHookDir,
4428
+ isInlineScript: () => isInlineScript,
4429
+ listCustomHooks: () => listCustomHooks,
4430
+ manifestSchema: () => manifestSchema,
4431
+ normalizeManifestName: () => normalizeManifestName,
4432
+ parseManifest: () => parseManifest,
4433
+ readCustomManifest: () => readCustomManifest,
4434
+ resolveScript: () => resolveScript,
4435
+ scriptRelFor: () => scriptRelFor,
4436
+ shortManifestName: () => shortManifestName,
4437
+ writeCustomHook: () => writeCustomHook
4150
4438
  });
4151
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, realpathSync, writeFileSync as writeFileSync2 } from "fs";
4152
- import { basename, dirname, isAbsolute, join as join3, normalize, resolve, sep } from "path";
4439
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync, realpathSync, writeFileSync as writeFileSync2 } from "fs";
4440
+ import { basename, dirname, isAbsolute, join as join4, normalize, resolve, sep } from "path";
4153
4441
  import { z } from "zod";
4154
4442
  function parseManifest(json) {
4155
4443
  const parsed = JSON.parse(json);
@@ -4182,7 +4470,7 @@ function realpathNearest(path) {
4182
4470
  tail.unshift(basename(cur));
4183
4471
  cur = parent;
4184
4472
  }
4185
- return join3(realpathSync(cur), ...tail);
4473
+ return join4(realpathSync(cur), ...tail);
4186
4474
  }
4187
4475
  function assertContainedReal(root, target, raw) {
4188
4476
  const rootReal = realpathSync(root);
@@ -4215,25 +4503,25 @@ function resolveScript(manifest, manifestDir) {
4215
4503
  if (isInlineScript(manifest))
4216
4504
  return { path: scriptRelFor(manifest), content: manifest.script };
4217
4505
  const rel = scriptRelFor(manifest);
4218
- const resolved = assertContained(join3(manifestDir, rel), manifestDir, manifest.script);
4506
+ const resolved = assertContained(join4(manifestDir, rel), manifestDir, manifest.script);
4219
4507
  if (!existsSync3(resolved)) {
4220
4508
  throw new Error(`manifest script file not found: ${resolved}`);
4221
4509
  }
4222
- return { path: rel, content: readFileSync2(resolved, "utf-8") };
4510
+ return { path: rel, content: readFileSync3(resolved, "utf-8") };
4223
4511
  }
4224
4512
  function customHookDir(name) {
4225
- return join3(getCustomHooksDir(), shortManifestName(name));
4513
+ return join4(getCustomHooksDir(), shortManifestName(name));
4226
4514
  }
4227
4515
  function readCustomManifest(name) {
4228
4516
  const dir = customHookDir(name);
4229
- const manifestPath = join3(dir, "manifest.json");
4517
+ const manifestPath = join4(dir, "manifest.json");
4230
4518
  if (!existsSync3(manifestPath))
4231
4519
  return;
4232
4520
  try {
4233
- const manifest = parseManifest(readFileSync2(manifestPath, "utf-8"));
4521
+ const manifest = parseManifest(readFileSync3(manifestPath, "utf-8"));
4234
4522
  const script = resolveScript(manifest, dir);
4235
4523
  const scriptIsInline = isInlineScript(manifest);
4236
- return { manifest, scriptPath: join3(dir, script.path), scriptContent: script.content, scriptIsInline };
4524
+ return { manifest, scriptPath: join4(dir, script.path), scriptContent: script.content, scriptIsInline };
4237
4525
  } catch (err) {
4238
4526
  if (err instanceof ScriptContainmentError)
4239
4527
  throw err;
@@ -4257,7 +4545,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
4257
4545
  if (isAbsolute(scriptRelPath)) {
4258
4546
  throw new ScriptContainmentError(scriptRelPath, dir);
4259
4547
  }
4260
- const scriptPath = scriptRelPath.includes("/") ? join3(dir, scriptRelPath) : join3(dir, scriptRelPath);
4548
+ const scriptPath = scriptRelPath.includes("/") ? join4(dir, scriptRelPath) : join4(dir, scriptRelPath);
4261
4549
  assertContainedLexical(scriptPath, dir, scriptRelPath);
4262
4550
  mkdirSync2(dir, { recursive: true });
4263
4551
  assertContainedReal(dir, scriptPath, scriptRelPath);
@@ -4265,7 +4553,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
4265
4553
  if (scriptDir !== dir)
4266
4554
  mkdirSync2(scriptDir, { recursive: true });
4267
4555
  writeFileSync2(scriptPath, scriptContent, "utf-8");
4268
- writeFileSync2(join3(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + `
4556
+ writeFileSync2(join4(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + `
4269
4557
  `, "utf-8");
4270
4558
  return { dir, scriptPath };
4271
4559
  }
@@ -4289,7 +4577,8 @@ var init_manifest = __esm(() => {
4289
4577
  script: z.string().min(1, "script must name a relative path or inline content"),
4290
4578
  script_kind: z.enum(["inline", "file"]).optional(),
4291
4579
  args: z.array(z.string()).optional(),
4292
- timeout_ms: z.number().int().positive().optional()
4580
+ timeout_ms: z.number().int().positive().optional(),
4581
+ env: z.record(z.string(), z.string()).optional()
4293
4582
  });
4294
4583
  ScriptContainmentError = class ScriptContainmentError extends Error {
4295
4584
  constructor(path, hookDir, raw) {
@@ -4302,20 +4591,20 @@ var init_manifest = __esm(() => {
4302
4591
  // src/lib/resolve.ts
4303
4592
  var exports_resolve = {};
4304
4593
  __export(exports_resolve, {
4305
- resolveScriptPath: () => resolveScriptPath,
4306
- resolveHookMeta: () => resolveHookMeta,
4307
- resolveHookDir: () => resolveHookDir,
4594
+ resolveBundledHooksDir: () => resolveBundledHooksDir,
4308
4595
  resolveHook: () => resolveHook,
4309
- resolveBundledHooksDir: () => resolveBundledHooksDir
4596
+ resolveHookDir: () => resolveHookDir,
4597
+ resolveHookMeta: () => resolveHookMeta,
4598
+ resolveScriptPath: () => resolveScriptPath
4310
4599
  });
4311
- import { dirname as dirname2, join as join4 } from "path";
4600
+ import { dirname as dirname2, join as join5 } from "path";
4312
4601
  import { existsSync as existsSync4 } from "fs";
4313
4602
  import { fileURLToPath } from "url";
4314
4603
  function resolveBundledHooksDir(moduleDir = dirname2(fileURLToPath(import.meta.url))) {
4315
- const sourceLayout = join4(moduleDir, "..", "..", "hooks");
4316
- if (existsSync4(join4(sourceLayout, "hook-gitguard")))
4604
+ const sourceLayout = join5(moduleDir, "..", "..", "hooks");
4605
+ if (existsSync4(join5(sourceLayout, "hook-gitguard")))
4317
4606
  return sourceLayout;
4318
- return join4(moduleDir, "..", "hooks");
4607
+ return join5(moduleDir, "..", "hooks");
4319
4608
  }
4320
4609
  function resolveHookDir(name) {
4321
4610
  const custom = readCustomManifest(name);
@@ -4326,10 +4615,10 @@ function resolveHookDir(name) {
4326
4615
  return;
4327
4616
  const short = shortManifestName(name);
4328
4617
  const bundledBase = resolveBundledHooksDir();
4329
- const direct = join4(bundledBase, short);
4618
+ const direct = join5(bundledBase, short);
4330
4619
  if (existsSync4(direct))
4331
4620
  return direct;
4332
- const prefixed = join4(bundledBase, `hook-${short}`);
4621
+ const prefixed = join5(bundledBase, `hook-${short}`);
4333
4622
  return existsSync4(prefixed) ? prefixed : undefined;
4334
4623
  }
4335
4624
  function resolveScriptPath(name) {
@@ -4339,7 +4628,7 @@ function resolveScriptPath(name) {
4339
4628
  const dir = resolveHookDir(name);
4340
4629
  if (!dir)
4341
4630
  return;
4342
- const script = join4(dir, "src", "hook.ts");
4631
+ const script = join5(dir, "src", "hook.ts");
4343
4632
  return existsSync4(script) ? script : undefined;
4344
4633
  }
4345
4634
  function resolveHookMeta(name) {
@@ -4429,7 +4718,7 @@ function up(db) {
4429
4718
  db.exec(idx);
4430
4719
  }
4431
4720
  }
4432
- var init_001_initial = __esm(() => {
4721
+ var init__001_initial = __esm(() => {
4433
4722
  init_schema();
4434
4723
  });
4435
4724
 
@@ -4458,7 +4747,7 @@ function up2(db) {
4458
4747
  throw err;
4459
4748
  }
4460
4749
  }
4461
- var init_002_session_events = __esm(() => {
4750
+ var init__002_session_events = __esm(() => {
4462
4751
  init_schema();
4463
4752
  });
4464
4753
 
@@ -4487,7 +4776,7 @@ function up3(db) {
4487
4776
  throw err;
4488
4777
  }
4489
4778
  }
4490
- var init_003_user_prompt_submit_event = __esm(() => {
4779
+ var init__003_user_prompt_submit_event = __esm(() => {
4491
4780
  init_schema();
4492
4781
  });
4493
4782
 
@@ -4511,7 +4800,7 @@ var CREATE_HOOKS_TABLE = `
4511
4800
  last_verified_at TEXT
4512
4801
  )
4513
4802
  `, CREATE_HOOKS_INDEXES;
4514
- var init_004_hooks_table = __esm(() => {
4803
+ var init__004_hooks_table = __esm(() => {
4515
4804
  CREATE_HOOKS_INDEXES = [
4516
4805
  `CREATE INDEX IF NOT EXISTS idx_hooks_name ON hooks (name)`
4517
4806
  ];
@@ -4542,7 +4831,7 @@ function up5(db) {
4542
4831
  throw err;
4543
4832
  }
4544
4833
  }
4545
- var init_005_subagent_start_event = __esm(() => {
4834
+ var init__005_subagent_start_event = __esm(() => {
4546
4835
  init_schema();
4547
4836
  });
4548
4837
 
@@ -4574,11 +4863,11 @@ function runMigrations(db) {
4574
4863
  }
4575
4864
  var MIGRATIONS;
4576
4865
  var init_migrations = __esm(() => {
4577
- init_001_initial();
4578
- init_002_session_events();
4579
- init_003_user_prompt_submit_event();
4580
- init_004_hooks_table();
4581
- init_005_subagent_start_event();
4866
+ init__001_initial();
4867
+ init__002_session_events();
4868
+ init__003_user_prompt_submit_event();
4869
+ init__004_hooks_table();
4870
+ init__005_subagent_start_event();
4582
4871
  MIGRATIONS = [
4583
4872
  { version: "001_initial", up },
4584
4873
  { version: "002_session_events", up: up2 },
@@ -4589,8 +4878,8 @@ var init_migrations = __esm(() => {
4589
4878
  });
4590
4879
 
4591
4880
  // src/db/legacy-import.ts
4592
- import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
4593
- import { join as join5 } from "path";
4881
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
4882
+ import { join as join6 } from "path";
4594
4883
  import { homedir as homedir3 } from "os";
4595
4884
  function ensureMetaTable(db) {
4596
4885
  db.exec(`
@@ -4614,7 +4903,7 @@ function nanoid() {
4614
4903
  function importJsonlFile(db, filePath) {
4615
4904
  let count = 0;
4616
4905
  try {
4617
- const lines = readFileSync3(filePath, "utf-8").split(`
4906
+ const lines = readFileSync4(filePath, "utf-8").split(`
4618
4907
  `).filter(Boolean);
4619
4908
  for (const line of lines) {
4620
4909
  try {
@@ -4640,7 +4929,7 @@ function importJsonlFile(db, filePath) {
4640
4929
  function importErrorsLog(db, filePath) {
4641
4930
  let count = 0;
4642
4931
  try {
4643
- const lines = readFileSync3(filePath, "utf-8").split(`
4932
+ const lines = readFileSync4(filePath, "utf-8").split(`
4644
4933
  `).filter(Boolean);
4645
4934
  const linePattern = /^\[(.+?)\]\s+(?:\[session:(\S+)\]\s+)?(.+?)\s+\u2014\s+(.+)$/;
4646
4935
  for (const line of lines) {
@@ -4665,25 +4954,25 @@ function importErrorsLog(db, filePath) {
4665
4954
  } catch {}
4666
4955
  return count;
4667
4956
  }
4668
- function runLegacyImport(db, homeDir = homedir3()) {
4957
+ function runLegacyImport(db, homeDir2 = homedir3()) {
4669
4958
  try {
4670
4959
  if (isAlreadyDone(db))
4671
4960
  return;
4672
4961
  let total = 0;
4673
- const claudeProjectsDir = join5(homeDir, ".claude", "projects");
4962
+ const claudeProjectsDir = join6(homeDir2, ".claude", "projects");
4674
4963
  if (existsSync5(claudeProjectsDir)) {
4675
4964
  try {
4676
4965
  const projectDirs = readdirSync2(claudeProjectsDir);
4677
4966
  for (const dir of projectDirs) {
4678
- const projectDir = join5(claudeProjectsDir, dir);
4967
+ const projectDir = join6(claudeProjectsDir, dir);
4679
4968
  try {
4680
4969
  const files = readdirSync2(projectDir);
4681
4970
  for (const file of files) {
4682
4971
  if (file.match(/^session-log-\d{4}-\d{2}-\d{2}\.jsonl$/)) {
4683
- total += importJsonlFile(db, join5(projectDir, file));
4972
+ total += importJsonlFile(db, join6(projectDir, file));
4684
4973
  }
4685
4974
  if (file === "errors.log") {
4686
- total += importErrorsLog(db, join5(projectDir, file));
4975
+ total += importErrorsLog(db, join6(projectDir, file));
4687
4976
  }
4688
4977
  }
4689
4978
  } catch {}
@@ -4720,23 +5009,23 @@ function runRetention(db, days) {
4720
5009
  // src/db/index.ts
4721
5010
  var exports_db = {};
4722
5011
  __export(exports_db, {
4723
- getDbPath: () => getDbPath,
4724
- getDb: () => getDb,
5012
+ closeDb: () => closeDb,
4725
5013
  createTestDb: () => createTestDb,
4726
- closeDb: () => closeDb
5014
+ getDb: () => getDb,
5015
+ getDbPath: () => getDbPath
4727
5016
  });
4728
5017
  import { Database } from "bun:sqlite";
4729
5018
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, cpSync } from "fs";
4730
- import { join as join6 } from "path";
5019
+ import { join as join7 } from "path";
4731
5020
  import { homedir as homedir4 } from "os";
4732
5021
  function resolveDataDir() {
4733
5022
  const explicit = process.env.HASNA_HOOKS_DATA_DIR ?? process.env.HOOKS_DATA_DIR;
4734
5023
  if (explicit)
4735
5024
  return explicit;
4736
- const newDir = join6(homedir4(), ".hasna", "hooks");
4737
- const oldDir = join6(homedir4(), ".hooks");
5025
+ const newDir = join7(homedir4(), ".hasna", "hooks");
5026
+ const oldDir = join7(homedir4(), ".hooks");
4738
5027
  if (!existsSync6(newDir) && existsSync6(oldDir)) {
4739
- mkdirSync3(join6(homedir4(), ".hasna"), { recursive: true });
5028
+ mkdirSync3(join7(homedir4(), ".hasna"), { recursive: true });
4740
5029
  cpSync(oldDir, newDir, { recursive: true });
4741
5030
  }
4742
5031
  return newDir;
@@ -4746,7 +5035,7 @@ function getDbPath() {
4746
5035
  if (explicitDb)
4747
5036
  return explicitDb;
4748
5037
  const dataDir = resolveDataDir();
4749
- return join6(dataDir, "hooks.db");
5038
+ return join7(dataDir, "hooks.db");
4750
5039
  }
4751
5040
  function ensureDir(dbPath) {
4752
5041
  const dir = dbPath.substring(0, dbPath.lastIndexOf("/"));
@@ -4801,26 +5090,26 @@ var init_db = __esm(() => {
4801
5090
  // src/lib/store.ts
4802
5091
  var exports_store = {};
4803
5092
  __export(exports_store, {
4804
- writeLock: () => writeLock,
4805
- verifyScriptHash: () => verifyScriptHash,
4806
- upsertHookRecord: () => upsertHookRecord,
4807
- sha256Of: () => sha256Of,
4808
- sha256File: () => sha256File,
4809
- setPinnedHook: () => setPinnedHook,
4810
- retrustHook: () => retrustHook,
4811
- removePinnedHook: () => removePinnedHook,
4812
- removeHookRecord: () => removeHookRecord,
4813
- removeHookFromStore: () => removeHookFromStore,
4814
- readLock: () => readLock,
4815
- pinInstalledHook: () => pinInstalledHook,
4816
- listHookRecords: () => listHookRecords,
4817
- getPinnedHook: () => getPinnedHook,
4818
- getHookRecord: () => getHookRecord,
5093
+ LockFileError: () => LockFileError,
4819
5094
  checkScriptHash: () => checkScriptHash,
4820
- LockFileError: () => LockFileError
5095
+ getHookRecord: () => getHookRecord,
5096
+ getPinnedHook: () => getPinnedHook,
5097
+ listHookRecords: () => listHookRecords,
5098
+ pinInstalledHook: () => pinInstalledHook,
5099
+ readLock: () => readLock,
5100
+ removeHookFromStore: () => removeHookFromStore,
5101
+ removeHookRecord: () => removeHookRecord,
5102
+ removePinnedHook: () => removePinnedHook,
5103
+ retrustHook: () => retrustHook,
5104
+ setPinnedHook: () => setPinnedHook,
5105
+ sha256File: () => sha256File,
5106
+ sha256Of: () => sha256Of,
5107
+ upsertHookRecord: () => upsertHookRecord,
5108
+ verifyScriptHash: () => verifyScriptHash,
5109
+ writeLock: () => writeLock
4821
5110
  });
4822
5111
  import { createHash } from "crypto";
4823
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync } from "fs";
5112
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync } from "fs";
4824
5113
  import { readFile as readFile2 } from "fs/promises";
4825
5114
  import { randomBytes } from "crypto";
4826
5115
  function sha256Of(input) {
@@ -4886,7 +5175,7 @@ function readLock() {
4886
5175
  return { hooks: {} };
4887
5176
  let raw;
4888
5177
  try {
4889
- raw = readFileSync4(path, "utf-8");
5178
+ raw = readFileSync5(path, "utf-8");
4890
5179
  } catch {
4891
5180
  throw new LockFileError(`hooks.lock (${path}) could not be read. Repair: fix permissions or move the file aside and run 'hooks sync'.`);
4892
5181
  }
@@ -4983,7 +5272,7 @@ async function verifyScriptHash(name, scriptPath) {
4983
5272
  return checkScriptHash(name, await sha256File(scriptPath));
4984
5273
  }
4985
5274
  function retrustHook(name, scriptPath, version, source) {
4986
- const actual = sha256Of(readFileSync4(scriptPath));
5275
+ const actual = sha256Of(readFileSync5(scriptPath));
4987
5276
  const db = getDb();
4988
5277
  upsertHookRecord(db, {
4989
5278
  name,
@@ -5010,22 +5299,22 @@ var init_store = __esm(() => {
5010
5299
  // src/lib/installer.ts
5011
5300
  var exports_installer = {};
5012
5301
  __export(exports_installer, {
5013
- uninstallHook: () => uninstallHook,
5014
- removeHook: () => removeHook,
5015
- removeCodewithHookEntry: () => removeCodewithHookEntry,
5016
- isEventSupported: () => isEventSupported,
5017
- installHooks: () => installHooks,
5018
- installHook: () => installHook,
5019
- hookExists: () => hookExists,
5020
- getSettingsPath: () => getSettingsPath,
5021
- getRegisteredHooksForTarget: () => getRegisteredHooksForTarget,
5022
- getRegisteredHooks: () => getRegisteredHooks,
5023
- getInstalledHooks: () => getInstalledHooks,
5302
+ buildCodewithTomlFragment: () => buildCodewithTomlFragment,
5024
5303
  getHookPath: () => getHookPath,
5025
- buildCodewithTomlFragment: () => buildCodewithTomlFragment
5304
+ getInstalledHooks: () => getInstalledHooks,
5305
+ getRegisteredHooks: () => getRegisteredHooks,
5306
+ getRegisteredHooksForTarget: () => getRegisteredHooksForTarget,
5307
+ getSettingsPath: () => getSettingsPath,
5308
+ hookExists: () => hookExists,
5309
+ installHook: () => installHook,
5310
+ installHooks: () => installHooks,
5311
+ isEventSupported: () => isEventSupported,
5312
+ removeCodewithHookEntry: () => removeCodewithHookEntry,
5313
+ removeHook: () => removeHook,
5314
+ uninstallHook: () => uninstallHook
5026
5315
  });
5027
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync4 } from "fs";
5028
- import { join as join7, dirname as dirname3 } from "path";
5316
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync4 } from "fs";
5317
+ import { join as join8, dirname as dirname3 } from "path";
5029
5318
  import { homedir as homedir5 } from "os";
5030
5319
  import { fileURLToPath as fileURLToPath2 } from "url";
5031
5320
  function normalizeHookName(name) {
@@ -5065,16 +5354,16 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
5065
5354
  return globalOverride;
5066
5355
  const dir = getTargetSettingsDir(target);
5067
5356
  if (scope === "project") {
5068
- return target === "codewith" ? join7(process.cwd(), dir, "config.toml") : join7(process.cwd(), dir, "settings.json");
5357
+ return target === "codewith" ? join8(process.cwd(), dir, "config.toml") : join8(process.cwd(), dir, "settings.json");
5069
5358
  }
5070
- return target === "codewith" ? join7(homedir5(), dir, "config.toml") : join7(homedir5(), dir, "settings.json");
5359
+ return target === "codewith" ? join8(homedir5(), dir, "config.toml") : join8(homedir5(), dir, "settings.json");
5071
5360
  }
5072
5361
  function getHookPath(name) {
5073
5362
  const shortName = shortHookName(name);
5074
5363
  const resolved = resolveHookDir(shortName);
5075
5364
  if (resolved)
5076
5365
  return resolved;
5077
- return join7(HOOKS_DIR, normalizeHookName(shortName));
5366
+ return join8(HOOKS_DIR, normalizeHookName(shortName));
5078
5367
  }
5079
5368
  function hookExists(name) {
5080
5369
  return existsSync8(getHookPath(name));
@@ -5083,7 +5372,7 @@ function readSettings(scope = "global", target = "claude") {
5083
5372
  const path = getSettingsPath(scope, target);
5084
5373
  try {
5085
5374
  if (existsSync8(path)) {
5086
- return JSON.parse(readFileSync5(path, "utf-8"));
5375
+ return JSON.parse(readFileSync6(path, "utf-8"));
5087
5376
  }
5088
5377
  } catch (error) {
5089
5378
  console.warn(`[hooks] Failed to read settings at ${path}: ${error instanceof Error ? error.message : String(error)}`);
@@ -5182,7 +5471,7 @@ function buildCodewithTomlFragment(name, profile) {
5182
5471
  function readCodewithConfig(scope, configPath) {
5183
5472
  const path = getSettingsPath(scope, "codewith", configPath);
5184
5473
  try {
5185
- return existsSync8(path) ? readFileSync5(path, "utf-8") : "";
5474
+ return existsSync8(path) ? readFileSync6(path, "utf-8") : "";
5186
5475
  } catch {
5187
5476
  return "";
5188
5477
  }
@@ -5192,7 +5481,7 @@ function appendCodewithFragment(fragment, scope, configPath) {
5192
5481
  const dir = dirname3(path);
5193
5482
  if (!existsSync8(dir))
5194
5483
  mkdirSync5(dir, { recursive: true });
5195
- const existing = existsSync8(path) ? readFileSync5(path, "utf-8") : "";
5484
+ const existing = existsSync8(path) ? readFileSync6(path, "utf-8") : "";
5196
5485
  const sep2 = existing.trim() ? `
5197
5486
 
5198
5487
  ` : "";
@@ -5237,7 +5526,7 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
5237
5526
  target,
5238
5527
  fragment,
5239
5528
  applied: false,
5240
- note: "Codewith install is fragment-only by default; open-configs should own applying this TOML."
5529
+ note: "Codewith install is fragment-only by default; configs should own applying this TOML."
5241
5530
  };
5242
5531
  }
5243
5532
  if (!codewithConfigPath) {
@@ -5264,7 +5553,7 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
5264
5553
  fragment,
5265
5554
  applied: true,
5266
5555
  configPath: path,
5267
- note: "Direct Codewith config write was explicitly requested; prefer open-configs for managed machines."
5556
+ note: "Direct Codewith config write was explicitly requested; prefer configs for managed machines."
5268
5557
  };
5269
5558
  } catch (error) {
5270
5559
  return {
@@ -5495,7 +5784,7 @@ function codewithHasHookEntry(name, scope = "global") {
5495
5784
  if (!existsSync8(path))
5496
5785
  return false;
5497
5786
  try {
5498
- return codewithHasHookEntryInText(readFileSync5(path, "utf-8"), name);
5787
+ return codewithHasHookEntryInText(readFileSync6(path, "utf-8"), name);
5499
5788
  } catch {
5500
5789
  return false;
5501
5790
  }
@@ -5527,7 +5816,7 @@ function uninstallHook(name, scope = "global", target = "claude") {
5527
5816
  if (target === "codewith" || target === "all") {
5528
5817
  const codewithPath = getSettingsPath(scope, "codewith");
5529
5818
  if (existsSync8(codewithPath)) {
5530
- const before = readFileSync5(codewithPath, "utf-8");
5819
+ const before = readFileSync6(codewithPath, "utf-8");
5531
5820
  const after = removeCodewithHookEntry(before, shortName);
5532
5821
  if (after.removed) {
5533
5822
  writeFileSync4(codewithPath, after.text, "utf-8");
@@ -5573,7 +5862,7 @@ var init_installer = __esm(() => {
5573
5862
  init_manifest();
5574
5863
  init_store();
5575
5864
  __dirname2 = dirname3(fileURLToPath2(import.meta.url));
5576
- HOOKS_DIR = existsSync8(join7(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join7(__dirname2, "..", "..", "hooks") : join7(__dirname2, "..", "hooks");
5865
+ HOOKS_DIR = existsSync8(join8(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join8(__dirname2, "..", "..", "hooks") : join8(__dirname2, "..", "hooks");
5577
5866
  EVENT_MAP = {
5578
5867
  claude: {
5579
5868
  PreToolUse: "PreToolUse",
@@ -5720,14 +6009,14 @@ var init_redact = __esm(() => {
5720
6009
  });
5721
6010
 
5722
6011
  // src/lib/profiles.ts
5723
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync3, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
5724
- import { join as join8 } from "path";
6012
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync3, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
6013
+ import { join as join9 } from "path";
5725
6014
  import { homedir as homedir6 } from "os";
5726
6015
  function resolveProfilesDir() {
5727
- const newDir = join8(homedir6(), ".hasna", "hooks", "profiles");
5728
- const oldDir = join8(homedir6(), ".hooks", "profiles");
6016
+ const newDir = join9(homedir6(), ".hasna", "hooks", "profiles");
6017
+ const oldDir = join9(homedir6(), ".hooks", "profiles");
5729
6018
  if (!existsSync9(newDir) && existsSync9(oldDir)) {
5730
- mkdirSync6(join8(homedir6(), ".hasna", "hooks"), { recursive: true });
6019
+ mkdirSync6(join9(homedir6(), ".hasna", "hooks"), { recursive: true });
5731
6020
  cpSync2(oldDir, newDir, { recursive: true });
5732
6021
  }
5733
6022
  return newDir;
@@ -5738,7 +6027,7 @@ function ensureProfilesDir() {
5738
6027
  }
5739
6028
  }
5740
6029
  function profilePath(id) {
5741
- return join8(PROFILES_DIR, `${id}.json`);
6030
+ return join9(PROFILES_DIR, `${id}.json`);
5742
6031
  }
5743
6032
  function shortUuid() {
5744
6033
  return crypto.randomUUID().slice(0, 8);
@@ -5766,7 +6055,7 @@ function getProfile(id) {
5766
6055
  try {
5767
6056
  if (!existsSync9(path))
5768
6057
  return null;
5769
- return JSON.parse(readFileSync6(path, "utf-8"));
6058
+ return JSON.parse(readFileSync7(path, "utf-8"));
5770
6059
  } catch {
5771
6060
  return null;
5772
6061
  }
@@ -5779,7 +6068,7 @@ function listProfiles() {
5779
6068
  const profiles = [];
5780
6069
  for (const file of files) {
5781
6070
  try {
5782
- const content = readFileSync6(join8(PROFILES_DIR, file), "utf-8");
6071
+ const content = readFileSync7(join9(PROFILES_DIR, file), "utf-8");
5783
6072
  profiles.push(JSON.parse(content));
5784
6073
  } catch {}
5785
6074
  }
@@ -5827,10 +6116,10 @@ var init_profiles = __esm(() => {
5827
6116
  // src/lib/db-writer.ts
5828
6117
  var exports_db_writer = {};
5829
6118
  __export(exports_db_writer, {
5830
- writeHookEvent: () => writeHookEvent,
5831
- resolveEventType: () => resolveEventType,
6119
+ normalizeEventType: () => normalizeEventType,
5832
6120
  recordHookRun: () => recordHookRun,
5833
- normalizeEventType: () => normalizeEventType
6121
+ resolveEventType: () => resolveEventType,
6122
+ writeHookEvent: () => writeHookEvent
5834
6123
  });
5835
6124
  function nanoid2() {
5836
6125
  return crypto.randomUUID().replace(/-/g, "").slice(0, 21);
@@ -5921,6 +6210,8 @@ var init_db_writer = __esm(() => {
5921
6210
  });
5922
6211
 
5923
6212
  // src/lib/hook-env.ts
6213
+ import { dirname as dirname4, isAbsolute as isAbsolute2 } from "path";
6214
+ import { existsSync as existsSync10, statSync as statSync2 } from "fs";
5924
6215
  function isDeniedEnvName(name) {
5925
6216
  const upper = name.toUpperCase();
5926
6217
  if (DENY_PREFIXES.some((prefix) => upper.startsWith(prefix)))
@@ -5929,13 +6220,74 @@ function isDeniedEnvName(name) {
5929
6220
  return true;
5930
6221
  return DENY_CONTAINS.some((token) => upper.includes(token));
5931
6222
  }
6223
+ function isInterpreterInjectionEnvName(name) {
6224
+ if (INTERPRETER_INJECTION.has(name))
6225
+ return true;
6226
+ return INTERPRETER_INJECTION_PREFIXES.some((prefix) => name.startsWith(prefix));
6227
+ }
6228
+ function isBashFunctionEnvName(name) {
6229
+ return name.startsWith("BASH_FUNC_");
6230
+ }
6231
+ function isUnsafePathEntry(entry, home) {
6232
+ if (entry === "" || !isAbsolute2(entry))
6233
+ return true;
6234
+ if (home && (entry === home || entry.startsWith(home + "/")))
6235
+ return true;
6236
+ if (entry === "/tmp" || entry.startsWith("/tmp/"))
6237
+ return true;
6238
+ if (entry === "/var/tmp" || entry.startsWith("/var/tmp/"))
6239
+ return true;
6240
+ try {
6241
+ if ((statSync2(entry).mode & 2) !== 0)
6242
+ return true;
6243
+ } catch {}
6244
+ return false;
6245
+ }
6246
+ function sanitizeHookPath(pathValue, home) {
6247
+ const baseline = PATH_BASELINE.slice();
6248
+ if (process.platform === "darwin" && existsSync10("/opt/homebrew/bin"))
6249
+ baseline.push("/opt/homebrew/bin");
6250
+ const bunDir = dirname4(process.execPath);
6251
+ const seen = new Set;
6252
+ const out = [];
6253
+ for (const dir of baseline) {
6254
+ if (seen.has(dir))
6255
+ continue;
6256
+ seen.add(dir);
6257
+ out.push(dir);
6258
+ }
6259
+ if (!seen.has(bunDir)) {
6260
+ seen.add(bunDir);
6261
+ out.push(bunDir);
6262
+ }
6263
+ for (const entry of pathValue.split(":")) {
6264
+ if (seen.has(entry))
6265
+ continue;
6266
+ if (isUnsafePathEntry(entry, home))
6267
+ continue;
6268
+ seen.add(entry);
6269
+ out.push(entry);
6270
+ }
6271
+ return out.join(":");
6272
+ }
5932
6273
  function buildHookEnv(source, extra) {
5933
6274
  const out = {};
5934
6275
  for (const name of ALLOWLIST) {
6276
+ if (name === "PATH")
6277
+ continue;
5935
6278
  const value = source[name] ?? extra?.[name];
5936
6279
  if (value !== undefined && value !== null)
5937
6280
  out[name] = value;
5938
6281
  }
6282
+ const pathOverride = extra?.PATH;
6283
+ if (pathOverride !== undefined && pathOverride !== null) {
6284
+ out.PATH = pathOverride;
6285
+ } else {
6286
+ const parentPath = source.PATH;
6287
+ if (parentPath !== undefined && parentPath !== null) {
6288
+ out.PATH = sanitizeHookPath(parentPath, source.HOME ?? extra?.HOME);
6289
+ }
6290
+ }
5939
6291
  for (const [from, to] of CONFIG_PROJECTIONS) {
5940
6292
  const value = source[from] ?? extra?.[from];
5941
6293
  if (value !== undefined && value !== null)
@@ -5947,14 +6299,44 @@ function buildHookEnv(source, extra) {
5947
6299
  continue;
5948
6300
  if (isDeniedEnvName(name))
5949
6301
  continue;
6302
+ if (isInterpreterInjectionEnvName(name))
6303
+ continue;
6304
+ if (isBashFunctionEnvName(name))
6305
+ continue;
5950
6306
  if (value !== undefined && value !== null)
5951
6307
  out[name] = value;
5952
6308
  }
5953
6309
  return out;
5954
6310
  }
5955
- var ALLOWLIST, DENY_SUFFIXES, DENY_PREFIXES, DENY_CONTAINS, CONFIG_PROJECTIONS;
6311
+ var ALLOWLIST, INTERPRETER_INJECTION, INTERPRETER_INJECTION_PREFIXES, DENY_SUFFIXES, DENY_PREFIXES, DENY_CONTAINS, PATH_BASELINE, CONFIG_PROJECTIONS;
5956
6312
  var init_hook_env = __esm(() => {
5957
6313
  ALLOWLIST = new Set(["PATH", "HOME", "LANG", "TZ", "SHELL", "TERM", "USER", "PWD"]);
6314
+ INTERPRETER_INJECTION = new Set([
6315
+ "BASH_ENV",
6316
+ "ENV",
6317
+ "BASHOPTS",
6318
+ "SHELLOPTS",
6319
+ "NODE_OPTIONS",
6320
+ "NODE_PATH",
6321
+ "PYTHONSTARTUP",
6322
+ "PYTHONINSPECT",
6323
+ "PYTHONPATH",
6324
+ "PYTHONHOME",
6325
+ "LD_PRELOAD",
6326
+ "LD_LIBRARY_PATH",
6327
+ "GCONV_PATH",
6328
+ "LOCPATH",
6329
+ "PERL5OPT",
6330
+ "RUBYOPT",
6331
+ "NODE_EXTRA_CA_CERTS",
6332
+ "NODE_TLS_REJECT_UNAUTHORIZED",
6333
+ "SSL_CERT_FILE",
6334
+ "SSL_CERT_DIR",
6335
+ "CURL_CA_BUNDLE",
6336
+ "GIT_SSL_CAINFO",
6337
+ "AWS_CA_BUNDLE"
6338
+ ]);
6339
+ INTERPRETER_INJECTION_PREFIXES = ["GIT_CONFIG_"];
5958
6340
  DENY_SUFFIXES = ["KEY", "TOKEN", "SECRET", "PASSWORD", "PASSWD", "CREDENTIAL", "CREDENTIALS", "URL", "URI"];
5959
6341
  DENY_PREFIXES = [
5960
6342
  "HASNA_",
@@ -5972,6 +6354,7 @@ var init_hook_env = __esm(() => {
5972
6354
  "MEMENTOS_"
5973
6355
  ];
5974
6356
  DENY_CONTAINS = ["DATABASE_URL"];
6357
+ PATH_BASELINE = ["/usr/bin", "/bin", "/usr/local/bin", "/sbin", "/usr/sbin"];
5975
6358
  CONFIG_PROJECTIONS = [
5976
6359
  ["HASNA_HOOKS_DATA_DIR", "HOOKS_DATA_DIR"],
5977
6360
  ["HASNA_HOOKS_DB_PATH", "HOOKS_DB_PATH"],
@@ -5983,12 +6366,12 @@ var init_hook_env = __esm(() => {
5983
6366
  // src/lib/run.ts
5984
6367
  var exports_run = {};
5985
6368
  __export(exports_run, {
5986
- executeVerifiedScript: () => executeVerifiedScript,
5987
- HookTimeoutError: () => HookTimeoutError
6369
+ HookTimeoutError: () => HookTimeoutError,
6370
+ executeVerifiedScript: () => executeVerifiedScript
5988
6371
  });
5989
6372
  import { randomBytes as randomBytes2 } from "crypto";
5990
6373
  import { rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
5991
- import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute2, join as join9 } from "path";
6374
+ import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute3, join as join10 } from "path";
5992
6375
  function extensionOf(scriptPath) {
5993
6376
  const base = basename2(scriptPath);
5994
6377
  const dot = base.lastIndexOf(".");
@@ -6015,13 +6398,13 @@ function interpreterFor(name, scriptPath, content) {
6015
6398
  if (interp === "bash" || interp === "sh")
6016
6399
  return { command: ["/bin/bash"], tempExt: "sh" };
6017
6400
  if (interp === "node" || interp === "bun")
6018
- return { command: ["bun", "run"], tempExt: BUN_EXTENSIONS.has(ext) ? ext : "ts" };
6401
+ return { command: [process.execPath, "run"], tempExt: BUN_EXTENSIONS.has(ext) ? ext : "ts" };
6019
6402
  throw new Error(`Refusing to run hook '${name}': shebang '${firstLine}' is not a recognized interpreter (supported: bash/sh, node/bun)`);
6020
6403
  }
6021
6404
  if (BASH_EXTENSIONS.has(ext))
6022
6405
  return { command: ["/bin/bash"], tempExt: "sh" };
6023
6406
  if (BUN_EXTENSIONS.has(ext))
6024
- return { command: ["bun", "run"], tempExt: ext };
6407
+ return { command: [process.execPath, "run"], tempExt: ext };
6025
6408
  throw new Error(`Refusing to run hook '${name}': unsupported script extension '.${ext}' (supported: .sh .bash .ts .tsx .js .jsx .mjs .cjs .mts .cts, or a recognized shebang)`);
6026
6409
  }
6027
6410
  async function readPipeWithDeadline(stream, deadlineMs) {
@@ -6057,9 +6440,9 @@ async function readPipeWithDeadline(stream, deadlineMs) {
6057
6440
  return chunks.join("");
6058
6441
  }
6059
6442
  async function executeVerifiedScript(options) {
6060
- const scriptDir = dirname4(options.scriptPath);
6443
+ const scriptDir = dirname5(options.scriptPath);
6061
6444
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
6062
- const tempPath = join9(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
6445
+ const tempPath = join10(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
6063
6446
  let proc = null;
6064
6447
  let timedOut = false;
6065
6448
  const timeoutTimer = options.timeout ? setTimeout(() => {
@@ -6068,7 +6451,7 @@ async function executeVerifiedScript(options) {
6068
6451
  }, options.timeout) : null;
6069
6452
  try {
6070
6453
  writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
6071
- proc = Bun.spawn([...interpreter.command, isAbsolute2(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
6454
+ proc = Bun.spawn([...interpreter.command, isAbsolute3(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
6072
6455
  stdin: new Response(options.stdin),
6073
6456
  stdout: "pipe",
6074
6457
  stderr: "pipe",
@@ -6146,19 +6529,19 @@ var init_run = __esm(() => {
6146
6529
  // src/lib/custom-install.ts
6147
6530
  var exports_custom_install = {};
6148
6531
  __export(exports_custom_install, {
6149
- isCustomSource: () => isCustomSource,
6532
+ ensureCustomDir: () => ensureCustomDir,
6150
6533
  installCustomSource: () => installCustomSource,
6151
- ensureCustomDir: () => ensureCustomDir
6534
+ isCustomSource: () => isCustomSource
6152
6535
  });
6153
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync4 } from "fs";
6154
- import { join as join10 } from "path";
6536
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
6537
+ import { join as join11 } from "path";
6155
6538
  import { tmpdir } from "os";
6156
6539
  function manifestAt(dir) {
6157
- const path = join10(dir, "manifest.json");
6158
- if (!existsSync10(path))
6540
+ const path = join11(dir, "manifest.json");
6541
+ if (!existsSync11(path))
6159
6542
  return;
6160
6543
  try {
6161
- return parseManifest(readFileSync7(path, "utf-8"));
6544
+ return parseManifest(readFileSync8(path, "utf-8"));
6162
6545
  } catch {
6163
6546
  return;
6164
6547
  }
@@ -6177,8 +6560,8 @@ function writeFromManifest(manifest, srcDir, source, kind) {
6177
6560
  return { name, version: manifest.version, kind, source, dir, scriptPath };
6178
6561
  }
6179
6562
  async function cloneToTemp(gitUrl) {
6180
- const tmp = mkdtempSync(join10(tmpdir(), "hooks-install-"));
6181
- const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join10(tmp, "repo")], {
6563
+ const tmp = mkdtempSync(join11(tmpdir(), "hooks-install-"));
6564
+ const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join11(tmp, "repo")], {
6182
6565
  stdout: "pipe",
6183
6566
  stderr: "pipe"
6184
6567
  });
@@ -6191,7 +6574,7 @@ async function cloneToTemp(gitUrl) {
6191
6574
  rmSync4(tmp, { recursive: true, force: true });
6192
6575
  throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
6193
6576
  }
6194
- return join10(tmp, "repo");
6577
+ return join11(tmp, "repo");
6195
6578
  }
6196
6579
  function isGitUrl(value) {
6197
6580
  return value.startsWith("git@") || value.startsWith("ssh://") || value.startsWith("file://") || value.endsWith(".git");
@@ -6201,7 +6584,7 @@ function isCustomSource(value) {
6201
6584
  return true;
6202
6585
  if (value.startsWith("http://") || value.startsWith("https://"))
6203
6586
  return true;
6204
- return existsSync10(value);
6587
+ return existsSync11(value);
6205
6588
  }
6206
6589
  async function installCustomSource(source) {
6207
6590
  if (isGitUrl(source)) {
@@ -6215,11 +6598,11 @@ async function installCustomSource(source) {
6215
6598
  if (source.startsWith("http://") || source.startsWith("https://")) {
6216
6599
  return installManifestUrl(source);
6217
6600
  }
6218
- if (!existsSync10(source)) {
6601
+ if (!existsSync11(source)) {
6219
6602
  throw new Error(`No such local path: ${source}`);
6220
6603
  }
6221
6604
  if (source.endsWith("manifest.json")) {
6222
- const manifest = parseManifest(readFileSync7(source, "utf-8"));
6605
+ const manifest = parseManifest(readFileSync8(source, "utf-8"));
6223
6606
  return writeFromManifest(manifest, source.substring(0, source.lastIndexOf("/")), source, "local");
6224
6607
  }
6225
6608
  return copyManifestDir(source, source, "local");
@@ -6261,19 +6644,19 @@ var init_custom_install = __esm(() => {
6261
6644
  // src/lib/sync.ts
6262
6645
  var exports_sync = {};
6263
6646
  __export(exports_sync, {
6264
- syncHooks: () => syncHooks,
6265
- stageSyncArtifacts: () => stageSyncArtifacts,
6266
- planSync: () => planSync,
6647
+ commitSyncArtifacts: () => commitSyncArtifacts,
6267
6648
  fetchPinnedHook: () => fetchPinnedHook,
6268
- commitSyncArtifacts: () => commitSyncArtifacts
6649
+ planSync: () => planSync,
6650
+ stageSyncArtifacts: () => stageSyncArtifacts,
6651
+ syncHooks: () => syncHooks
6269
6652
  });
6270
6653
  import { createHash as createHash2 } from "crypto";
6271
- import { readFileSync as readFileSync8 } from "fs";
6654
+ import { readFileSync as readFileSync9 } from "fs";
6272
6655
  function sha256OfText(text) {
6273
6656
  return createHash2("sha256").update(text).digest("hex");
6274
6657
  }
6275
6658
  function sha256OfPath(path) {
6276
- return createHash2("sha256").update(readFileSync8(path)).digest("hex");
6659
+ return createHash2("sha256").update(readFileSync9(path)).digest("hex");
6277
6660
  }
6278
6661
  function collectBundledCatalog() {
6279
6662
  const out = [];
@@ -6974,31 +7357,31 @@ var init_storage_sync = __esm(() => {
6974
7357
  // src/storage.ts
6975
7358
  var exports_storage = {};
6976
7359
  __export(exports_storage, {
6977
- storageSync: () => storageSync,
6978
- storagePush: () => storagePush,
6979
- storagePull: () => storagePull,
6980
- runStorageMigrations: () => runStorageMigrations,
6981
- resolveTables: () => resolveTables,
6982
- parseStorageTables: () => parseStorageTables,
6983
- getSyncMetaAll: () => getSyncMetaAll,
6984
- getStorageStatus: () => getStorageStatus,
6985
- getStoragePg: () => getStoragePg,
6986
- getStorageDatabaseUrl: () => getStorageDatabaseUrl,
6987
- getStorageDatabaseEnvName: () => getStorageDatabaseEnvName,
6988
- getStorageDatabaseEnv: () => getStorageDatabaseEnv,
6989
- getStorageBackend: () => getStorageBackend,
6990
- STORAGE_TABLES: () => STORAGE_TABLES,
6991
- STORAGE_DATABASE_ENV: () => STORAGE_DATABASE_ENV,
6992
- STORAGE_BACKEND_ENV: () => STORAGE_BACKEND_ENV,
6993
- STORAGE_BACKENDS: () => STORAGE_BACKENDS,
6994
- RETIRED_STORAGE_MODE_ENV: () => RETIRED_STORAGE_MODE_ENV,
6995
- PgAdapterAsync: () => PgAdapterAsync,
6996
- PG_MIGRATIONS: () => PG_MIGRATIONS,
6997
- HOOKS_STORAGE_TABLES: () => HOOKS_STORAGE_TABLES,
6998
- HOOKS_STORAGE_FALLBACK_ENV: () => HOOKS_STORAGE_FALLBACK_ENV,
6999
- HOOKS_STORAGE_ENV: () => HOOKS_STORAGE_ENV,
7360
+ HOOKS_STORAGE_BACKEND_ENV: () => HOOKS_STORAGE_BACKEND_ENV,
7000
7361
  HOOKS_STORAGE_BACKEND_FALLBACK_ENV: () => HOOKS_STORAGE_BACKEND_FALLBACK_ENV,
7001
- HOOKS_STORAGE_BACKEND_ENV: () => HOOKS_STORAGE_BACKEND_ENV
7362
+ HOOKS_STORAGE_ENV: () => HOOKS_STORAGE_ENV,
7363
+ HOOKS_STORAGE_FALLBACK_ENV: () => HOOKS_STORAGE_FALLBACK_ENV,
7364
+ HOOKS_STORAGE_TABLES: () => HOOKS_STORAGE_TABLES,
7365
+ PG_MIGRATIONS: () => PG_MIGRATIONS,
7366
+ PgAdapterAsync: () => PgAdapterAsync,
7367
+ RETIRED_STORAGE_MODE_ENV: () => RETIRED_STORAGE_MODE_ENV,
7368
+ STORAGE_BACKENDS: () => STORAGE_BACKENDS,
7369
+ STORAGE_BACKEND_ENV: () => STORAGE_BACKEND_ENV,
7370
+ STORAGE_DATABASE_ENV: () => STORAGE_DATABASE_ENV,
7371
+ STORAGE_TABLES: () => STORAGE_TABLES,
7372
+ getStorageBackend: () => getStorageBackend,
7373
+ getStorageDatabaseEnv: () => getStorageDatabaseEnv,
7374
+ getStorageDatabaseEnvName: () => getStorageDatabaseEnvName,
7375
+ getStorageDatabaseUrl: () => getStorageDatabaseUrl,
7376
+ getStoragePg: () => getStoragePg,
7377
+ getStorageStatus: () => getStorageStatus,
7378
+ getSyncMetaAll: () => getSyncMetaAll,
7379
+ parseStorageTables: () => parseStorageTables,
7380
+ resolveTables: () => resolveTables,
7381
+ runStorageMigrations: () => runStorageMigrations,
7382
+ storagePull: () => storagePull,
7383
+ storagePush: () => storagePush,
7384
+ storageSync: () => storageSync
7002
7385
  });
7003
7386
  var init_storage = __esm(() => {
7004
7387
  init_storage_sync();
@@ -7019,15 +7402,180 @@ function secureEqual(a, b) {
7019
7402
  return diff === 0;
7020
7403
  }
7021
7404
 
7405
+ // src/openapi.ts
7406
+ var openApiDocument;
7407
+ var init_openapi = __esm(() => {
7408
+ openApiDocument = {
7409
+ openapi: "3.1.0",
7410
+ info: {
7411
+ title: "Hooks Registry API",
7412
+ version: "0.1.0",
7413
+ description: "Local registry HTTP API for @hasna/hooks: catalog, lock, and artifact reads are open; publishing (PUT /api/v1/hooks) requires the API key (authorization: Bearer or x-api-key)."
7414
+ },
7415
+ servers: [{ url: "/" }],
7416
+ paths: {
7417
+ "/health": {
7418
+ get: {
7419
+ summary: "Liveness probe",
7420
+ operationId: "getHealth",
7421
+ responses: {
7422
+ "200": {
7423
+ description: "Service is alive",
7424
+ content: {
7425
+ "application/json": {
7426
+ schema: { type: "object", additionalProperties: true }
7427
+ }
7428
+ }
7429
+ }
7430
+ }
7431
+ }
7432
+ },
7433
+ "/ready": {
7434
+ get: {
7435
+ summary: "Readiness probe \u2014 the hooks.lock store is readable",
7436
+ operationId: "getReady",
7437
+ responses: {
7438
+ "200": { description: "Lock store is readable" },
7439
+ "503": { description: "Lock store is not readable" }
7440
+ }
7441
+ }
7442
+ },
7443
+ "/version": {
7444
+ get: {
7445
+ summary: "Package version",
7446
+ operationId: "getVersion",
7447
+ responses: {
7448
+ "200": {
7449
+ description: "The installed @hasna/hooks version",
7450
+ content: {
7451
+ "application/json": {
7452
+ schema: { type: "object", properties: { version: { type: "string" } } }
7453
+ }
7454
+ }
7455
+ }
7456
+ }
7457
+ }
7458
+ },
7459
+ "/openapi.json": {
7460
+ get: {
7461
+ summary: "This OpenAPI document",
7462
+ operationId: "getOpenApi",
7463
+ responses: { "200": { description: "OpenAPI 3.1 document" } }
7464
+ }
7465
+ },
7466
+ "/api/v1/catalog": {
7467
+ get: {
7468
+ summary: "List the full hook catalog with versions and sha256",
7469
+ operationId: "getCatalog",
7470
+ responses: {
7471
+ "200": {
7472
+ description: "Catalog entries",
7473
+ content: {
7474
+ "application/json": {
7475
+ schema: {
7476
+ type: "object",
7477
+ properties: {
7478
+ hooks: {
7479
+ type: "array",
7480
+ items: {
7481
+ type: "object",
7482
+ properties: {
7483
+ name: { type: "string" },
7484
+ version: { type: "string" },
7485
+ sha256: { type: "string" },
7486
+ events: { type: "array", items: { type: "string" } }
7487
+ }
7488
+ }
7489
+ }
7490
+ }
7491
+ }
7492
+ }
7493
+ }
7494
+ }
7495
+ }
7496
+ }
7497
+ },
7498
+ "/api/v1/lock": {
7499
+ get: {
7500
+ summary: "Read the published hooks.lock with per-hook versions",
7501
+ operationId: "getLock",
7502
+ responses: { "200": { description: "The lock file" } }
7503
+ }
7504
+ },
7505
+ "/api/v1/hooks/{name}/{version}": {
7506
+ get: {
7507
+ summary: "Fetch one hook's manifest and script",
7508
+ operationId: "getHookArtifact",
7509
+ parameters: [
7510
+ { name: "name", in: "path", required: true, schema: { type: "string" } },
7511
+ { name: "version", in: "path", required: true, schema: { type: "string" } }
7512
+ ],
7513
+ responses: {
7514
+ "200": {
7515
+ description: "Hook artifact; x-hook-sha256 header carries the script hash"
7516
+ },
7517
+ "400": { description: "Invalid URL encoding or semver version" },
7518
+ "404": { description: "Hook not found locally" }
7519
+ }
7520
+ }
7521
+ },
7522
+ "/api/v1/hooks": {
7523
+ put: {
7524
+ summary: "Publish/retrust a hook (requires the API key)",
7525
+ operationId: "putHook",
7526
+ requestBody: {
7527
+ required: true,
7528
+ content: {
7529
+ "application/json": {
7530
+ schema: {
7531
+ type: "object",
7532
+ properties: { name: { type: "string" }, version: { type: "string" } }
7533
+ }
7534
+ }
7535
+ }
7536
+ },
7537
+ responses: {
7538
+ "200": { description: "Hook retrusted and pin updated" },
7539
+ "401": { description: "Missing or invalid API key" },
7540
+ "404": { description: "Hook not found in local store" },
7541
+ "409": { description: "Version mismatch with the local store" }
7542
+ },
7543
+ security: [{ apiKey: [] }]
7544
+ }
7545
+ }
7546
+ },
7547
+ components: {
7548
+ securitySchemes: {
7549
+ apiKey: {
7550
+ type: "http",
7551
+ scheme: "bearer",
7552
+ description: "The registry API key (HASNA_HOOKS_API_KEY / HOOKS_API_KEY)."
7553
+ }
7554
+ }
7555
+ }
7556
+ };
7557
+ });
7558
+
7022
7559
  // src/serve.ts
7023
7560
  var exports_serve = {};
7024
7561
  __export(exports_serve, {
7025
- startServeServer: () => startServeServer,
7026
- handleServeRequest: () => handleServeRequest,
7027
- SERVE_SERVICE_NAME: () => SERVE_SERVICE_NAME,
7562
+ DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT,
7028
7563
  SERVE_HOST: () => SERVE_HOST,
7029
- DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
7564
+ SERVE_SERVICE_NAME: () => SERVE_SERVICE_NAME,
7565
+ handleServeRequest: () => handleServeRequest,
7566
+ startServeServer: () => startServeServer
7030
7567
  });
7568
+ import { readFileSync as readFileSync10 } from "fs";
7569
+ import { dirname as dirname6, join as join12 } from "path";
7570
+ import { fileURLToPath as fileURLToPath3 } from "url";
7571
+ function packageVersion() {
7572
+ try {
7573
+ const pkgPath = join12(dirname6(fileURLToPath3(import.meta.url)), "..", "package.json");
7574
+ return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
7575
+ } catch {
7576
+ return "0.0.0";
7577
+ }
7578
+ }
7031
7579
  async function buildCatalog() {
7032
7580
  const byName = new Map;
7033
7581
  for (const meta of HOOKS) {
@@ -7100,6 +7648,20 @@ function handleServeRequest(req, apiKey) {
7100
7648
  if (url.pathname === "/health" && req.method === "GET") {
7101
7649
  return Promise.resolve(json({ status: "ok", name: SERVE_SERVICE_NAME }));
7102
7650
  }
7651
+ if (url.pathname === "/ready" && req.method === "GET") {
7652
+ try {
7653
+ readLock();
7654
+ return Promise.resolve(json({ status: "ready", name: SERVE_SERVICE_NAME }));
7655
+ } catch {
7656
+ return Promise.resolve(json({ status: "not-ready", name: SERVE_SERVICE_NAME }, 503));
7657
+ }
7658
+ }
7659
+ if (url.pathname === "/version" && req.method === "GET") {
7660
+ return Promise.resolve(json({ version: packageVersion() }));
7661
+ }
7662
+ if (url.pathname === "/openapi.json" && req.method === "GET") {
7663
+ return Promise.resolve(json(openApiDocument));
7664
+ }
7103
7665
  if (url.pathname === "/api/v1/catalog" && req.method === "GET") {
7104
7666
  return buildCatalog().then((catalog) => json({ hooks: catalog }));
7105
7667
  }
@@ -7178,6 +7740,7 @@ var init_serve = __esm(() => {
7178
7740
  init_store();
7179
7741
  init_resolve();
7180
7742
  init_config();
7743
+ init_openapi();
7181
7744
  init_semver();
7182
7745
  if (false) {}
7183
7746
  });
@@ -7266,29 +7829,29 @@ var CF_API = "https://api.cloudflare.com/client/v4";
7266
7829
  // src/mcp/server.ts
7267
7830
  var exports_server = {};
7268
7831
  __export(exports_server, {
7269
- startStdioServer: () => startStdioServer,
7270
- startSSEServer: () => startSSEServer,
7271
- sseAuthToken: () => sseAuthToken,
7272
- createHooksServer: () => createHooksServer,
7832
+ MCP_PORT: () => MCP_PORT,
7273
7833
  MCP_SSE_HOST: () => MCP_SSE_HOST,
7274
- MCP_PORT: () => MCP_PORT
7834
+ createHooksServer: () => createHooksServer,
7835
+ sseAuthToken: () => sseAuthToken,
7836
+ startSSEServer: () => startSSEServer,
7837
+ startStdioServer: () => startStdioServer
7275
7838
  });
7276
7839
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7277
7840
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
7278
7841
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7279
7842
  import { z as z2 } from "zod";
7280
7843
  import { createServer } from "http";
7281
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
7282
- import { join as join11, dirname as dirname5 } from "path";
7283
- import { fileURLToPath as fileURLToPath3 } from "url";
7844
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
7845
+ import { join as join13, dirname as dirname7 } from "path";
7846
+ import { fileURLToPath as fileURLToPath4 } from "url";
7284
7847
  async function runVerifiedHook(name, stdin, timeoutMs) {
7285
7848
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
7286
7849
  const resolved = resolveHook2(name);
7287
7850
  const hookScript = resolveScriptPath2(name);
7288
- if (!resolved || !hookScript || !existsSync11(hookScript)) {
7851
+ if (!resolved || !hookScript || !existsSync12(hookScript)) {
7289
7852
  throw new Error(`Hook '${name}' not found`);
7290
7853
  }
7291
- const content = readFileSync9(hookScript);
7854
+ const content = readFileSync11(hookScript);
7292
7855
  const sha = sha256Of(content);
7293
7856
  const check = checkScriptHash(name, sha);
7294
7857
  if (!check.ok) {
@@ -7493,7 +8056,7 @@ function createHooksServer() {
7493
8056
  const settingsPath = getSettingsPath(scope);
7494
8057
  const issues = [];
7495
8058
  const healthy = [];
7496
- const settingsExist = existsSync11(settingsPath);
8059
+ const settingsExist = existsSync12(settingsPath);
7497
8060
  if (!settingsExist) {
7498
8061
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
7499
8062
  }
@@ -7503,7 +8066,7 @@ function createHooksServer() {
7503
8066
  const meta = custom ? resolveHookMeta(name) : getHook(name);
7504
8067
  let hookHealthy = true;
7505
8068
  if (custom) {
7506
- if (!existsSync11(custom.scriptPath)) {
8069
+ if (!existsSync12(custom.scriptPath)) {
7507
8070
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
7508
8071
  hookHealthy = false;
7509
8072
  }
@@ -7513,14 +8076,14 @@ function createHooksServer() {
7513
8076
  continue;
7514
8077
  }
7515
8078
  const hookDir = getHookPath(name);
7516
- if (!existsSync11(join11(hookDir, "src", "hook.ts"))) {
8079
+ if (!existsSync12(join13(hookDir, "src", "hook.ts"))) {
7517
8080
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
7518
8081
  hookHealthy = false;
7519
8082
  }
7520
8083
  }
7521
8084
  if (meta && settingsExist) {
7522
8085
  try {
7523
- const settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
8086
+ const settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
7524
8087
  if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
7525
8088
  const eventName = meta.event.split(":")[0];
7526
8089
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
@@ -7550,10 +8113,10 @@ function createHooksServer() {
7550
8113
  return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
7551
8114
  }
7552
8115
  const hookPath = getHookPath(name);
7553
- const readmePath = join11(hookPath, "README.md");
8116
+ const readmePath = join13(hookPath, "README.md");
7554
8117
  let readme = "";
7555
- if (existsSync11(readmePath)) {
7556
- readme = readFileSync9(readmePath, "utf-8");
8118
+ if (existsSync12(readmePath)) {
8119
+ readme = readFileSync11(readmePath, "utf-8");
7557
8120
  }
7558
8121
  if (verbose) {
7559
8122
  return { content: [{ type: "text", text: JSON.stringify({ ...meta, readme }) }] };
@@ -7736,7 +8299,7 @@ function createHooksServer() {
7736
8299
  const ctx = {
7737
8300
  scope,
7738
8301
  settings_path: settingsPath,
7739
- settings_exists: existsSync11(settingsPath),
8302
+ settings_exists: existsSync12(settingsPath),
7740
8303
  registered_hooks: hooks,
7741
8304
  hook_count: hooks.length,
7742
8305
  healthy,
@@ -7882,8 +8445,8 @@ function createHooksServer() {
7882
8445
  const settingsPath = getSettingsPath(scope);
7883
8446
  let settings = {};
7884
8447
  try {
7885
- if (existsSync11(settingsPath))
7886
- settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
8448
+ if (existsSync12(settingsPath))
8449
+ settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
7887
8450
  } catch {}
7888
8451
  if (!settings.hooks)
7889
8452
  settings.hooks = {};
@@ -7892,8 +8455,8 @@ function createHooksServer() {
7892
8455
  disabled.push(name);
7893
8456
  settings.hooks.__disabled = disabled;
7894
8457
  const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync8 } = await import("fs");
7895
- const { dirname: dirname6 } = await import("path");
7896
- mkdirSync8(dirname6(settingsPath), { recursive: true });
8458
+ const { dirname: dirname8 } = await import("path");
8459
+ mkdirSync8(dirname8(settingsPath), { recursive: true });
7897
8460
  writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
7898
8461
  `);
7899
8462
  return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
@@ -7905,8 +8468,8 @@ function createHooksServer() {
7905
8468
  const settingsPath = getSettingsPath(scope);
7906
8469
  let settings = {};
7907
8470
  try {
7908
- if (existsSync11(settingsPath))
7909
- settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
8471
+ if (existsSync12(settingsPath))
8472
+ settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
7910
8473
  } catch {}
7911
8474
  if (settings.hooks?.__disabled) {
7912
8475
  settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
@@ -8266,13 +8829,13 @@ var init_server = __esm(() => {
8266
8829
  init_store();
8267
8830
  init_redact();
8268
8831
  init_storage();
8269
- __dirname3 = dirname5(fileURLToPath3(import.meta.url));
8832
+ __dirname3 = dirname7(fileURLToPath4(import.meta.url));
8270
8833
  pkg = { name: "@hasna/hooks", version: "0.0.0" };
8271
8834
  try {
8272
8835
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
8273
- const p = join11(__dirname3, rel);
8274
- if (existsSync11(p)) {
8275
- pkg = JSON.parse(readFileSync9(p, "utf-8"));
8836
+ const p = join13(__dirname3, rel);
8837
+ if (existsSync12(p)) {
8838
+ pkg = JSON.parse(readFileSync11(p, "utf-8"));
8276
8839
  break;
8277
8840
  }
8278
8841
  }
@@ -8284,14 +8847,14 @@ var init_server = __esm(() => {
8284
8847
  // src/mcp/http.ts
8285
8848
  var exports_http = {};
8286
8849
  __export(exports_http, {
8287
- startMcpHttpServer: () => startMcpHttpServer,
8288
- resolveMcpHttpPort: () => resolveMcpHttpPort,
8289
- isHttpMode: () => isHttpMode,
8290
- healthPayload: () => healthPayload,
8291
- handleMcpRequest: () => handleMcpRequest,
8292
- MCP_SERVICE_NAME: () => MCP_SERVICE_NAME,
8850
+ DEFAULT_MCP_HTTP_PORT: () => DEFAULT_MCP_HTTP_PORT,
8293
8851
  MCP_HTTP_HOST: () => MCP_HTTP_HOST,
8294
- DEFAULT_MCP_HTTP_PORT: () => DEFAULT_MCP_HTTP_PORT
8852
+ MCP_SERVICE_NAME: () => MCP_SERVICE_NAME,
8853
+ handleMcpRequest: () => handleMcpRequest,
8854
+ healthPayload: () => healthPayload,
8855
+ isHttpMode: () => isHttpMode,
8856
+ resolveMcpHttpPort: () => resolveMcpHttpPort,
8857
+ startMcpHttpServer: () => startMcpHttpServer
8295
8858
  });
8296
8859
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
8297
8860
  function isHttpMode(args) {
@@ -8340,7 +8903,7 @@ function startMcpHttpServer(options) {
8340
8903
  var DEFAULT_MCP_HTTP_PORT = 8847, MCP_HTTP_HOST = "127.0.0.1", MCP_SERVICE_NAME = "hooks";
8341
8904
  var init_http = () => {};
8342
8905
 
8343
- // node_modules/@hasna/events/dist/commander.js
8906
+ // ../events/dist/commander.js
8344
8907
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
8345
8908
  import { Buffer as Buffer2 } from "buffer";
8346
8909
  import { existsSync } from "fs";
@@ -9525,7 +10088,7 @@ function replaySummary(events, deliveries, nextCursor) {
9525
10088
  // src/cli/index.tsx
9526
10089
  import { render } from "ink";
9527
10090
 
9528
- // node_modules/commander/esm.mjs
10091
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/esm.mjs
9529
10092
  var import__ = __toESM(require_commander(), 1);
9530
10093
  var {
9531
10094
  program,
@@ -9543,19 +10106,19 @@ var {
9543
10106
 
9544
10107
  // src/cli/index.tsx
9545
10108
  import chalk2 from "chalk";
9546
- import { existsSync as existsSync12, readFileSync as readFileSync10, rmSync as rmSync5 } from "fs";
9547
- import { join as join12, dirname as dirname6 } from "path";
9548
- import { fileURLToPath as fileURLToPath4 } from "url";
10109
+ import { existsSync as existsSync13, readFileSync as readFileSync12, rmSync as rmSync5 } from "fs";
10110
+ import { join as join14, dirname as dirname8 } from "path";
10111
+ import { fileURLToPath as fileURLToPath5 } from "url";
9549
10112
 
9550
10113
  // src/cli/components/App.tsx
9551
10114
  import { useState as useState7 } from "react";
9552
10115
  import { Box as Box8, Text as Text10, useApp, useInput as useInput5 } from "ink";
9553
10116
 
9554
- // node_modules/ink-select-input/build/Indicator.js
10117
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
9555
10118
  import React from "react";
9556
10119
  import { Box, Text } from "ink";
9557
10120
 
9558
- // node_modules/is-unicode-supported/index.js
10121
+ // ../../node_modules/.bun/is-unicode-supported@2.1.0/node_modules/is-unicode-supported/index.js
9559
10122
  import process2 from "process";
9560
10123
  function isUnicodeSupported() {
9561
10124
  const { env } = process2;
@@ -9566,7 +10129,7 @@ function isUnicodeSupported() {
9566
10129
  return Boolean(env.WT_SESSION) || Boolean(env.TERMINUS_SUBLIME) || env.ConEmuTask === "{cmd::Cmder}" || TERM_PROGRAM === "Terminus-Sublime" || TERM_PROGRAM === "vscode" || TERM === "xterm-256color" || TERM === "alacritty" || TERM === "rxvt-unicode" || TERM === "rxvt-unicode-256color" || env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
9567
10130
  }
9568
10131
 
9569
- // node_modules/figures/index.js
10132
+ // ../../node_modules/.bun/figures@6.1.0/node_modules/figures/index.js
9570
10133
  var common = {
9571
10134
  circleQuestionMark: "(?)",
9572
10135
  questionMarkPrefix: "(?)",
@@ -9842,23 +10405,23 @@ var figures = shouldUseMain ? mainSymbols : fallbackSymbols;
9842
10405
  var figures_default = figures;
9843
10406
  var replacements = Object.entries(specialMainSymbols);
9844
10407
 
9845
- // node_modules/ink-select-input/build/Indicator.js
10408
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
9846
10409
  function Indicator({ isSelected = false }) {
9847
10410
  return React.createElement(Box, { marginRight: 1 }, isSelected ? React.createElement(Text, { color: "blue" }, figures_default.pointer) : React.createElement(Text, null, " "));
9848
10411
  }
9849
10412
  var Indicator_default = Indicator;
9850
- // node_modules/ink-select-input/build/Item.js
10413
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Item.js
9851
10414
  import * as React2 from "react";
9852
10415
  import { Text as Text2 } from "ink";
9853
10416
  function Item({ isSelected = false, label }) {
9854
10417
  return React2.createElement(Text2, { color: isSelected ? "blue" : undefined }, label);
9855
10418
  }
9856
10419
  var Item_default = Item;
9857
- // node_modules/ink-select-input/build/SelectInput.js
10420
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
9858
10421
  import { isDeepStrictEqual } from "util";
9859
10422
  import React3, { useState, useEffect, useRef, useCallback } from "react";
9860
10423
 
9861
- // node_modules/to-rotated/index.js
10424
+ // ../../node_modules/.bun/to-rotated@1.0.0/node_modules/to-rotated/index.js
9862
10425
  function toRotated(array, steps) {
9863
10426
  if (!Array.isArray(array)) {
9864
10427
  throw new TypeError(`Expected an array, got \`${typeof array}\`.`);
@@ -9880,7 +10443,7 @@ function toRotated(array, steps) {
9880
10443
  ];
9881
10444
  }
9882
10445
 
9883
- // node_modules/ink-select-input/build/SelectInput.js
10446
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
9884
10447
  import { Box as Box2, useInput } from "ink";
9885
10448
  function SelectInput({ items = [], isFocused = true, initialIndex = 0, indicatorComponent = Indicator_default, itemComponent = Item_default, limit: customLimit, onSelect, onHighlight }) {
9886
10449
  const hasLimit = typeof customLimit === "number" && items.length > customLimit;
@@ -9956,29 +10519,29 @@ function SelectInput({ items = [], isFocused = true, initialIndex = 0, indicator
9956
10519
  var SelectInput_default = SelectInput;
9957
10520
  // src/cli/components/Header.tsx
9958
10521
  import { Box as Box3, Text as Text3 } from "ink";
9959
- import { jsxDEV } from "react/jsx-dev-runtime";
10522
+ import { jsx, jsxs } from "react/jsx-runtime";
9960
10523
  function Header({ title = "Hooks", subtitle }) {
9961
- return /* @__PURE__ */ jsxDEV(Box3, {
10524
+ return /* @__PURE__ */ jsxs(Box3, {
9962
10525
  flexDirection: "column",
9963
10526
  marginBottom: 1,
9964
10527
  children: [
9965
- /* @__PURE__ */ jsxDEV(Text3, {
10528
+ /* @__PURE__ */ jsx(Text3, {
9966
10529
  bold: true,
9967
10530
  color: "cyan",
9968
10531
  children: title
9969
- }, undefined, false, undefined, this),
9970
- subtitle && /* @__PURE__ */ jsxDEV(Text3, {
10532
+ }),
10533
+ subtitle && /* @__PURE__ */ jsx(Text3, {
9971
10534
  dimColor: true,
9972
10535
  children: subtitle
9973
- }, undefined, false, undefined, this)
10536
+ })
9974
10537
  ]
9975
- }, undefined, true, undefined, this);
10538
+ });
9976
10539
  }
9977
10540
 
9978
10541
  // src/cli/components/CategorySelect.tsx
9979
10542
  import { Box as Box4, Text as Text4 } from "ink";
9980
10543
  init_registry();
9981
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
10544
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
9982
10545
  function CategorySelect({ onSelect, onBack }) {
9983
10546
  const items = CATEGORIES.map((cat) => ({
9984
10547
  label: `${cat} (${getHooksByCategory(cat).length})`,
@@ -9994,28 +10557,28 @@ function CategorySelect({ onSelect, onBack }) {
9994
10557
  onSelect(item.value);
9995
10558
  }
9996
10559
  };
9997
- return /* @__PURE__ */ jsxDEV2(Box4, {
10560
+ return /* @__PURE__ */ jsxs2(Box4, {
9998
10561
  flexDirection: "column",
9999
10562
  children: [
10000
- /* @__PURE__ */ jsxDEV2(Box4, {
10563
+ /* @__PURE__ */ jsx2(Box4, {
10001
10564
  marginBottom: 1,
10002
- children: /* @__PURE__ */ jsxDEV2(Text4, {
10565
+ children: /* @__PURE__ */ jsx2(Text4, {
10003
10566
  bold: true,
10004
10567
  children: "Select a category:"
10005
- }, undefined, false, undefined, this)
10006
- }, undefined, false, undefined, this),
10007
- /* @__PURE__ */ jsxDEV2(SelectInput_default, {
10568
+ })
10569
+ }),
10570
+ /* @__PURE__ */ jsx2(SelectInput_default, {
10008
10571
  items,
10009
10572
  onSelect: handleSelect
10010
- }, undefined, false, undefined, this)
10573
+ })
10011
10574
  ]
10012
- }, undefined, true, undefined, this);
10575
+ });
10013
10576
  }
10014
10577
 
10015
10578
  // src/cli/components/DataTable.tsx
10016
10579
  import { useState as useState2 } from "react";
10017
10580
  import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
10018
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
10581
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
10019
10582
  var COL_CHECK = 5;
10020
10583
  var COL_NAME = 18;
10021
10584
  var COL_VERSION = 9;
@@ -10050,18 +10613,18 @@ function DataTable({
10050
10613
  onBack();
10051
10614
  }
10052
10615
  });
10053
- return /* @__PURE__ */ jsxDEV3(Box5, {
10616
+ return /* @__PURE__ */ jsxs3(Box5, {
10054
10617
  flexDirection: "column",
10055
10618
  children: [
10056
- /* @__PURE__ */ jsxDEV3(Box5, {
10619
+ /* @__PURE__ */ jsx3(Box5, {
10057
10620
  marginBottom: 1,
10058
- children: /* @__PURE__ */ jsxDEV3(Text5, {
10621
+ children: /* @__PURE__ */ jsx3(Text5, {
10059
10622
  bold: true,
10060
10623
  children: "Select hooks (up/down to navigate, space/enter to toggle, esc to go back):"
10061
- }, undefined, false, undefined, this)
10062
- }, undefined, false, undefined, this),
10063
- /* @__PURE__ */ jsxDEV3(Box5, {
10064
- children: /* @__PURE__ */ jsxDEV3(Text5, {
10624
+ })
10625
+ }),
10626
+ /* @__PURE__ */ jsx3(Box5, {
10627
+ children: /* @__PURE__ */ jsxs3(Text5, {
10065
10628
  dimColor: true,
10066
10629
  children: [
10067
10630
  pad("", COL_CHECK),
@@ -10070,11 +10633,11 @@ function DataTable({
10070
10633
  pad("Event", COL_EVENT),
10071
10634
  "Description"
10072
10635
  ]
10073
- }, undefined, true, undefined, this)
10074
- }, undefined, false, undefined, this),
10075
- /* @__PURE__ */ jsxDEV3(Box5, {
10636
+ })
10637
+ }),
10638
+ /* @__PURE__ */ jsx3(Box5, {
10076
10639
  marginBottom: 0,
10077
- children: /* @__PURE__ */ jsxDEV3(Text5, {
10640
+ children: /* @__PURE__ */ jsxs3(Text5, {
10078
10641
  dimColor: true,
10079
10642
  children: [
10080
10643
  pad("\u2500".repeat(COL_CHECK - 1), COL_CHECK),
@@ -10083,14 +10646,14 @@ function DataTable({
10083
10646
  pad("\u2500".repeat(COL_EVENT - 1), COL_EVENT),
10084
10647
  "\u2500".repeat(COL_DESC)
10085
10648
  ]
10086
- }, undefined, true, undefined, this)
10087
- }, undefined, false, undefined, this),
10649
+ })
10650
+ }),
10088
10651
  hooks.map((hook, i) => {
10089
10652
  const isHighlighted = i === cursor;
10090
10653
  const isSelected = selected.has(hook.name);
10091
10654
  const check = isSelected ? "[x]" : "[ ]";
10092
- return /* @__PURE__ */ jsxDEV3(Box5, {
10093
- children: /* @__PURE__ */ jsxDEV3(Text5, {
10655
+ return /* @__PURE__ */ jsx3(Box5, {
10656
+ children: /* @__PURE__ */ jsxs3(Text5, {
10094
10657
  bold: isHighlighted,
10095
10658
  inverse: isHighlighted,
10096
10659
  children: [
@@ -10100,62 +10663,62 @@ function DataTable({
10100
10663
  pad(hook.event, COL_EVENT),
10101
10664
  truncate2(hook.description, COL_DESC)
10102
10665
  ]
10103
- }, undefined, true, undefined, this)
10104
- }, hook.name, false, undefined, this);
10666
+ })
10667
+ }, hook.name);
10105
10668
  }),
10106
- /* @__PURE__ */ jsxDEV3(Box5, {
10669
+ /* @__PURE__ */ jsxs3(Box5, {
10107
10670
  marginTop: 1,
10108
10671
  flexDirection: "row",
10109
10672
  gap: 2,
10110
10673
  children: [
10111
- /* @__PURE__ */ jsxDEV3(Text5, {
10674
+ /* @__PURE__ */ jsx3(Text5, {
10112
10675
  dimColor: true,
10113
10676
  children: "[esc] Back"
10114
- }, undefined, false, undefined, this),
10115
- selected.size > 0 && /* @__PURE__ */ jsxDEV3(Text5, {
10677
+ }),
10678
+ selected.size > 0 && /* @__PURE__ */ jsxs3(Text5, {
10116
10679
  color: "green",
10117
10680
  bold: true,
10118
10681
  children: [
10119
10682
  selected.size,
10120
10683
  " selected"
10121
10684
  ]
10122
- }, undefined, true, undefined, this)
10685
+ })
10123
10686
  ]
10124
- }, undefined, true, undefined, this),
10125
- selected.size > 0 && /* @__PURE__ */ jsxDEV3(Box5, {
10687
+ }),
10688
+ selected.size > 0 && /* @__PURE__ */ jsx3(Box5, {
10126
10689
  marginTop: 0,
10127
- children: /* @__PURE__ */ jsxDEV3(Text5, {
10690
+ children: /* @__PURE__ */ jsxs3(Text5, {
10128
10691
  dimColor: true,
10129
10692
  children: [
10130
10693
  "Selected: ",
10131
10694
  Array.from(selected).join(", ")
10132
10695
  ]
10133
- }, undefined, true, undefined, this)
10134
- }, undefined, false, undefined, this),
10135
- selected.size > 0 && /* @__PURE__ */ jsxDEV3(Box5, {
10696
+ })
10697
+ }),
10698
+ selected.size > 0 && /* @__PURE__ */ jsx3(Box5, {
10136
10699
  marginTop: 1,
10137
- children: /* @__PURE__ */ jsxDEV3(Text5, {
10700
+ children: /* @__PURE__ */ jsxs3(Text5, {
10138
10701
  children: [
10139
10702
  "Press",
10140
10703
  " ",
10141
- /* @__PURE__ */ jsxDEV3(Text5, {
10704
+ /* @__PURE__ */ jsx3(Text5, {
10142
10705
  bold: true,
10143
10706
  color: "cyan",
10144
10707
  children: "i"
10145
- }, undefined, false, undefined, this),
10708
+ }),
10146
10709
  " ",
10147
10710
  "to install selected (",
10148
10711
  selected.size,
10149
10712
  ")"
10150
10713
  ]
10151
- }, undefined, true, undefined, this)
10152
- }, undefined, false, undefined, this)
10714
+ })
10715
+ })
10153
10716
  ]
10154
- }, undefined, true, undefined, this);
10717
+ });
10155
10718
  }
10156
10719
 
10157
10720
  // src/cli/components/HookSelect.tsx
10158
- import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
10721
+ import { jsx as jsx4 } from "react/jsx-runtime";
10159
10722
  function HookSelect({
10160
10723
  hooks,
10161
10724
  selected,
@@ -10163,20 +10726,20 @@ function HookSelect({
10163
10726
  onConfirm,
10164
10727
  onBack
10165
10728
  }) {
10166
- return /* @__PURE__ */ jsxDEV4(DataTable, {
10729
+ return /* @__PURE__ */ jsx4(DataTable, {
10167
10730
  hooks,
10168
10731
  selected,
10169
10732
  onToggle,
10170
10733
  onConfirm,
10171
10734
  onBack
10172
- }, undefined, false, undefined, this);
10735
+ });
10173
10736
  }
10174
10737
 
10175
10738
  // src/cli/components/SearchView.tsx
10176
10739
  import { useState as useState4, useEffect as useEffect3 } from "react";
10177
10740
  import { Box as Box6, Text as Text7, useInput as useInput4 } from "ink";
10178
10741
 
10179
- // node_modules/ink-text-input/build/index.js
10742
+ // ../../node_modules/.bun/ink-text-input@6.0.0+fa875fef9f949ebd/node_modules/ink-text-input/build/index.js
10180
10743
  import React5, { useState as useState3, useEffect as useEffect2 } from "react";
10181
10744
  import { Text as Text6, useInput as useInput3 } from "ink";
10182
10745
  import chalk from "chalk";
@@ -10270,7 +10833,7 @@ var build_default = TextInput;
10270
10833
 
10271
10834
  // src/cli/components/SearchView.tsx
10272
10835
  init_registry();
10273
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
10836
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
10274
10837
  function SearchView({
10275
10838
  selected,
10276
10839
  onToggle,
@@ -10299,62 +10862,62 @@ function SearchView({
10299
10862
  setMode("select");
10300
10863
  }
10301
10864
  });
10302
- return /* @__PURE__ */ jsxDEV5(Box6, {
10865
+ return /* @__PURE__ */ jsxs4(Box6, {
10303
10866
  flexDirection: "column",
10304
10867
  children: [
10305
- /* @__PURE__ */ jsxDEV5(Box6, {
10868
+ /* @__PURE__ */ jsxs4(Box6, {
10306
10869
  marginBottom: 1,
10307
10870
  children: [
10308
- /* @__PURE__ */ jsxDEV5(Text7, {
10871
+ /* @__PURE__ */ jsx5(Text7, {
10309
10872
  bold: true,
10310
10873
  children: "Search: "
10311
- }, undefined, false, undefined, this),
10312
- mode === "search" && /* @__PURE__ */ jsxDEV5(build_default, {
10874
+ }),
10875
+ mode === "search" && /* @__PURE__ */ jsx5(build_default, {
10313
10876
  value: query,
10314
10877
  onChange: setQuery,
10315
10878
  placeholder: "Type to search hooks..."
10316
- }, undefined, false, undefined, this),
10317
- mode === "select" && /* @__PURE__ */ jsxDEV5(Text7, {
10879
+ }),
10880
+ mode === "select" && /* @__PURE__ */ jsx5(Text7, {
10318
10881
  children: query
10319
- }, undefined, false, undefined, this)
10882
+ })
10320
10883
  ]
10321
- }, undefined, true, undefined, this),
10322
- query.length < 2 && /* @__PURE__ */ jsxDEV5(Text7, {
10884
+ }),
10885
+ query.length < 2 && /* @__PURE__ */ jsx5(Text7, {
10323
10886
  dimColor: true,
10324
10887
  children: "Type at least 2 characters to search"
10325
- }, undefined, false, undefined, this),
10326
- query.length >= 2 && results.length === 0 && /* @__PURE__ */ jsxDEV5(Text7, {
10888
+ }),
10889
+ query.length >= 2 && results.length === 0 && /* @__PURE__ */ jsxs4(Text7, {
10327
10890
  dimColor: true,
10328
10891
  children: [
10329
10892
  'No hooks found for "',
10330
10893
  query,
10331
10894
  '"'
10332
10895
  ]
10333
- }, undefined, true, undefined, this),
10334
- results.length > 0 && mode === "search" && /* @__PURE__ */ jsxDEV5(Text7, {
10896
+ }),
10897
+ results.length > 0 && mode === "search" && /* @__PURE__ */ jsxs4(Text7, {
10335
10898
  dimColor: true,
10336
10899
  children: [
10337
10900
  "Found ",
10338
10901
  results.length,
10339
10902
  " hook(s) \u2014 press down arrow to browse results"
10340
10903
  ]
10341
- }, undefined, true, undefined, this),
10342
- results.length > 0 && mode === "select" && /* @__PURE__ */ jsxDEV5(DataTable, {
10904
+ }),
10905
+ results.length > 0 && mode === "select" && /* @__PURE__ */ jsx5(DataTable, {
10343
10906
  hooks: results,
10344
10907
  selected,
10345
10908
  onToggle,
10346
10909
  onConfirm,
10347
10910
  onBack: () => setMode("search")
10348
- }, undefined, false, undefined, this)
10911
+ })
10349
10912
  ]
10350
- }, undefined, true, undefined, this);
10913
+ });
10351
10914
  }
10352
10915
 
10353
10916
  // src/cli/components/InstallProgress.tsx
10354
10917
  import { useState as useState6, useEffect as useEffect5 } from "react";
10355
10918
  import { Box as Box7, Text as Text9 } from "ink";
10356
10919
 
10357
- // node_modules/ink-spinner/build/index.js
10920
+ // ../../node_modules/.bun/ink-spinner@5.0.0+fa875fef9f949ebd/node_modules/ink-spinner/build/index.js
10358
10921
  var import_cli_spinners = __toESM(require_cli_spinners(), 1);
10359
10922
  import React7, { useState as useState5, useEffect as useEffect4 } from "react";
10360
10923
  import { Text as Text8 } from "ink";
@@ -10379,7 +10942,7 @@ var build_default2 = Spinner;
10379
10942
  // src/cli/components/InstallProgress.tsx
10380
10943
  init_installer();
10381
10944
  init_registry();
10382
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
10945
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
10383
10946
  function InstallProgress({
10384
10947
  hooks,
10385
10948
  overwrite = false,
@@ -10403,55 +10966,55 @@ function InstallProgress({
10403
10966
  };
10404
10967
  install();
10405
10968
  }, [hooks, overwrite]);
10406
- return /* @__PURE__ */ jsxDEV6(Box7, {
10969
+ return /* @__PURE__ */ jsxs5(Box7, {
10407
10970
  flexDirection: "column",
10408
10971
  children: [
10409
- /* @__PURE__ */ jsxDEV6(Box7, {
10972
+ /* @__PURE__ */ jsx6(Box7, {
10410
10973
  marginBottom: 1,
10411
- children: /* @__PURE__ */ jsxDEV6(Text9, {
10974
+ children: /* @__PURE__ */ jsx6(Text9, {
10412
10975
  bold: true,
10413
10976
  children: installing ? `Installing hooks (${current + 1}/${hooks.length})...` : "Installation complete!"
10414
- }, undefined, false, undefined, this)
10415
- }, undefined, false, undefined, this),
10977
+ })
10978
+ }),
10416
10979
  hooks.map((name, i) => {
10417
10980
  const result = results[i];
10418
10981
  const isCurrent = i === current && installing;
10419
10982
  const meta = getHook(name);
10420
- return /* @__PURE__ */ jsxDEV6(Box7, {
10983
+ return /* @__PURE__ */ jsxs5(Box7, {
10421
10984
  flexDirection: "column",
10422
10985
  children: [
10423
- isCurrent && !result && /* @__PURE__ */ jsxDEV6(Text9, {
10986
+ isCurrent && !result && /* @__PURE__ */ jsxs5(Text9, {
10424
10987
  color: "cyan",
10425
10988
  children: [
10426
- /* @__PURE__ */ jsxDEV6(build_default2, {
10989
+ /* @__PURE__ */ jsx6(build_default2, {
10427
10990
  type: "dots"
10428
- }, undefined, false, undefined, this),
10991
+ }),
10429
10992
  " ",
10430
10993
  name
10431
10994
  ]
10432
- }, undefined, true, undefined, this),
10433
- result?.success && /* @__PURE__ */ jsxDEV6(Box7, {
10995
+ }),
10996
+ result?.success && /* @__PURE__ */ jsxs5(Box7, {
10434
10997
  flexDirection: "column",
10435
10998
  children: [
10436
- /* @__PURE__ */ jsxDEV6(Text9, {
10999
+ /* @__PURE__ */ jsxs5(Text9, {
10437
11000
  color: "green",
10438
11001
  children: [
10439
11002
  "\u2713",
10440
11003
  " ",
10441
11004
  name
10442
11005
  ]
10443
- }, undefined, true, undefined, this),
10444
- meta && /* @__PURE__ */ jsxDEV6(Text9, {
11006
+ }),
11007
+ meta && /* @__PURE__ */ jsxs5(Text9, {
10445
11008
  dimColor: true,
10446
11009
  children: [
10447
11010
  " ",
10448
11011
  meta.event,
10449
11012
  meta.matcher ? ` [${meta.matcher}]` : ""
10450
11013
  ]
10451
- }, undefined, true, undefined, this)
11014
+ })
10452
11015
  ]
10453
- }, undefined, true, undefined, this),
10454
- result && !result.success && /* @__PURE__ */ jsxDEV6(Text9, {
11016
+ }),
11017
+ result && !result.success && /* @__PURE__ */ jsxs5(Text9, {
10455
11018
  color: "red",
10456
11019
  children: [
10457
11020
  "\u2717",
@@ -10460,54 +11023,54 @@ function InstallProgress({
10460
11023
  " - ",
10461
11024
  result.error
10462
11025
  ]
10463
- }, undefined, true, undefined, this),
10464
- !isCurrent && !result && /* @__PURE__ */ jsxDEV6(Text9, {
11026
+ }),
11027
+ !isCurrent && !result && /* @__PURE__ */ jsxs5(Text9, {
10465
11028
  dimColor: true,
10466
11029
  children: [
10467
11030
  "\u25CB",
10468
11031
  " ",
10469
11032
  name
10470
11033
  ]
10471
- }, undefined, true, undefined, this)
11034
+ })
10472
11035
  ]
10473
- }, name, true, undefined, this);
11036
+ }, name);
10474
11037
  }),
10475
- !installing && /* @__PURE__ */ jsxDEV6(Box7, {
11038
+ !installing && /* @__PURE__ */ jsxs5(Box7, {
10476
11039
  marginTop: 1,
10477
11040
  flexDirection: "column",
10478
11041
  children: [
10479
- /* @__PURE__ */ jsxDEV6(Text9, {
11042
+ /* @__PURE__ */ jsxs5(Text9, {
10480
11043
  children: [
10481
- /* @__PURE__ */ jsxDEV6(Text9, {
11044
+ /* @__PURE__ */ jsxs5(Text9, {
10482
11045
  color: "green",
10483
11046
  children: [
10484
11047
  results.filter((r) => r.success).length,
10485
11048
  " installed"
10486
11049
  ]
10487
- }, undefined, true, undefined, this),
10488
- results.some((r) => !r.success) && /* @__PURE__ */ jsxDEV6(Text9, {
11050
+ }),
11051
+ results.some((r) => !r.success) && /* @__PURE__ */ jsxs5(Text9, {
10489
11052
  color: "red",
10490
11053
  children: [
10491
11054
  ", ",
10492
11055
  results.filter((r) => !r.success).length,
10493
11056
  " failed"
10494
11057
  ]
10495
- }, undefined, true, undefined, this)
11058
+ })
10496
11059
  ]
10497
- }, undefined, true, undefined, this),
10498
- /* @__PURE__ */ jsxDEV6(Text9, {
11060
+ }),
11061
+ /* @__PURE__ */ jsx6(Text9, {
10499
11062
  dimColor: true,
10500
11063
  children: "Hooks installed to .hooks/ and registered in ~/.claude/settings.json"
10501
- }, undefined, false, undefined, this)
11064
+ })
10502
11065
  ]
10503
- }, undefined, true, undefined, this)
11066
+ })
10504
11067
  ]
10505
- }, undefined, true, undefined, this);
11068
+ });
10506
11069
  }
10507
11070
 
10508
11071
  // src/cli/components/App.tsx
10509
11072
  init_registry();
10510
- import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
11073
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
10511
11074
  function App({ initialHooks, overwrite = false }) {
10512
11075
  const { exit } = useApp();
10513
11076
  const [view, setView] = useState7(initialHooks?.length ? "installing" : "main");
@@ -10554,44 +11117,44 @@ function App({ initialHooks, overwrite = false }) {
10554
11117
  setView(item.value);
10555
11118
  }
10556
11119
  };
10557
- return /* @__PURE__ */ jsxDEV7(Box8, {
11120
+ return /* @__PURE__ */ jsxs6(Box8, {
10558
11121
  flexDirection: "column",
10559
11122
  padding: 1,
10560
11123
  children: [
10561
- /* @__PURE__ */ jsxDEV7(Header, {
11124
+ /* @__PURE__ */ jsx7(Header, {
10562
11125
  title: "Hooks",
10563
11126
  subtitle: "Install Claude Code hooks for your project"
10564
- }, undefined, false, undefined, this),
10565
- view === "main" && /* @__PURE__ */ jsxDEV7(Box8, {
11127
+ }),
11128
+ view === "main" && /* @__PURE__ */ jsxs6(Box8, {
10566
11129
  flexDirection: "column",
10567
11130
  children: [
10568
- /* @__PURE__ */ jsxDEV7(Box8, {
11131
+ /* @__PURE__ */ jsx7(Box8, {
10569
11132
  marginBottom: 1,
10570
- children: /* @__PURE__ */ jsxDEV7(Text10, {
11133
+ children: /* @__PURE__ */ jsx7(Text10, {
10571
11134
  children: "What would you like to do?"
10572
- }, undefined, false, undefined, this)
10573
- }, undefined, false, undefined, this),
10574
- /* @__PURE__ */ jsxDEV7(SelectInput_default, {
11135
+ })
11136
+ }),
11137
+ /* @__PURE__ */ jsx7(SelectInput_default, {
10575
11138
  items: mainMenuItems,
10576
11139
  onSelect: handleMainSelect
10577
- }, undefined, false, undefined, this),
10578
- /* @__PURE__ */ jsxDEV7(Box8, {
11140
+ }),
11141
+ /* @__PURE__ */ jsx7(Box8, {
10579
11142
  marginTop: 1,
10580
- children: /* @__PURE__ */ jsxDEV7(Text10, {
11143
+ children: /* @__PURE__ */ jsx7(Text10, {
10581
11144
  dimColor: true,
10582
11145
  children: "Press q to quit"
10583
- }, undefined, false, undefined, this)
10584
- }, undefined, false, undefined, this)
11146
+ })
11147
+ })
10585
11148
  ]
10586
- }, undefined, true, undefined, this),
10587
- view === "browse" && !category && /* @__PURE__ */ jsxDEV7(CategorySelect, {
11149
+ }),
11150
+ view === "browse" && !category && /* @__PURE__ */ jsx7(CategorySelect, {
10588
11151
  onSelect: (cat) => {
10589
11152
  setCategory(cat);
10590
11153
  setView("hooks");
10591
11154
  },
10592
11155
  onBack: () => setView("main")
10593
- }, undefined, false, undefined, this),
10594
- view === "hooks" && category && /* @__PURE__ */ jsxDEV7(HookSelect, {
11156
+ }),
11157
+ view === "hooks" && category && /* @__PURE__ */ jsx7(HookSelect, {
10595
11158
  hooks: getHooksByCategory(category),
10596
11159
  selected,
10597
11160
  onToggle: handleToggle,
@@ -10600,57 +11163,57 @@ function App({ initialHooks, overwrite = false }) {
10600
11163
  setCategory(null);
10601
11164
  setView("browse");
10602
11165
  }
10603
- }, undefined, false, undefined, this),
10604
- view === "search" && /* @__PURE__ */ jsxDEV7(SearchView, {
11166
+ }),
11167
+ view === "search" && /* @__PURE__ */ jsx7(SearchView, {
10605
11168
  selected,
10606
11169
  onToggle: handleToggle,
10607
11170
  onConfirm: handleConfirm,
10608
11171
  onBack: () => setView("main")
10609
- }, undefined, false, undefined, this),
10610
- view === "installing" && /* @__PURE__ */ jsxDEV7(InstallProgress, {
11172
+ }),
11173
+ view === "installing" && /* @__PURE__ */ jsx7(InstallProgress, {
10611
11174
  hooks: Array.from(selected),
10612
11175
  overwrite,
10613
11176
  onComplete: handleComplete
10614
- }, undefined, false, undefined, this),
10615
- view === "done" && /* @__PURE__ */ jsxDEV7(Box8, {
11177
+ }),
11178
+ view === "done" && /* @__PURE__ */ jsxs6(Box8, {
10616
11179
  flexDirection: "column",
10617
11180
  children: [
10618
- /* @__PURE__ */ jsxDEV7(Box8, {
11181
+ /* @__PURE__ */ jsx7(Box8, {
10619
11182
  marginBottom: 1,
10620
- children: /* @__PURE__ */ jsxDEV7(Text10, {
11183
+ children: /* @__PURE__ */ jsx7(Text10, {
10621
11184
  bold: true,
10622
11185
  color: "green",
10623
11186
  children: "Installation complete!"
10624
- }, undefined, false, undefined, this)
10625
- }, undefined, false, undefined, this),
10626
- results.filter((r) => r.success).length > 0 && /* @__PURE__ */ jsxDEV7(Box8, {
11187
+ })
11188
+ }),
11189
+ results.filter((r) => r.success).length > 0 && /* @__PURE__ */ jsxs6(Box8, {
10627
11190
  flexDirection: "column",
10628
11191
  marginBottom: 1,
10629
11192
  children: [
10630
- /* @__PURE__ */ jsxDEV7(Text10, {
11193
+ /* @__PURE__ */ jsx7(Text10, {
10631
11194
  bold: true,
10632
11195
  children: "Installed:"
10633
- }, undefined, false, undefined, this),
10634
- results.filter((r) => r.success).map((r) => /* @__PURE__ */ jsxDEV7(Text10, {
11196
+ }),
11197
+ results.filter((r) => r.success).map((r) => /* @__PURE__ */ jsxs6(Text10, {
10635
11198
  color: "green",
10636
11199
  children: [
10637
11200
  "\u2713",
10638
11201
  " ",
10639
11202
  r.hook
10640
11203
  ]
10641
- }, r.hook, true, undefined, this))
11204
+ }, r.hook))
10642
11205
  ]
10643
- }, undefined, true, undefined, this),
10644
- results.filter((r) => !r.success).length > 0 && /* @__PURE__ */ jsxDEV7(Box8, {
11206
+ }),
11207
+ results.filter((r) => !r.success).length > 0 && /* @__PURE__ */ jsxs6(Box8, {
10645
11208
  flexDirection: "column",
10646
11209
  marginBottom: 1,
10647
11210
  children: [
10648
- /* @__PURE__ */ jsxDEV7(Text10, {
11211
+ /* @__PURE__ */ jsx7(Text10, {
10649
11212
  bold: true,
10650
11213
  color: "red",
10651
11214
  children: "Failed:"
10652
- }, undefined, false, undefined, this),
10653
- results.filter((r) => !r.success).map((r) => /* @__PURE__ */ jsxDEV7(Text10, {
11215
+ }),
11216
+ results.filter((r) => !r.success).map((r) => /* @__PURE__ */ jsxs6(Text10, {
10654
11217
  color: "red",
10655
11218
  children: [
10656
11219
  "\u2717",
@@ -10659,61 +11222,61 @@ function App({ initialHooks, overwrite = false }) {
10659
11222
  ": ",
10660
11223
  r.error
10661
11224
  ]
10662
- }, r.hook, true, undefined, this))
11225
+ }, r.hook))
10663
11226
  ]
10664
- }, undefined, true, undefined, this),
10665
- /* @__PURE__ */ jsxDEV7(Box8, {
11227
+ }),
11228
+ /* @__PURE__ */ jsxs6(Box8, {
10666
11229
  marginTop: 1,
10667
11230
  flexDirection: "column",
10668
11231
  children: [
10669
- /* @__PURE__ */ jsxDEV7(Text10, {
11232
+ /* @__PURE__ */ jsx7(Text10, {
10670
11233
  bold: true,
10671
11234
  children: "What happened:"
10672
- }, undefined, false, undefined, this),
10673
- /* @__PURE__ */ jsxDEV7(Text10, {
11235
+ }),
11236
+ /* @__PURE__ */ jsx7(Text10, {
10674
11237
  children: "1. Hook source copied to .hooks/"
10675
- }, undefined, false, undefined, this),
10676
- /* @__PURE__ */ jsxDEV7(Text10, {
11238
+ }),
11239
+ /* @__PURE__ */ jsx7(Text10, {
10677
11240
  children: "2. Hook registered in ~/.claude/settings.json"
10678
- }, undefined, false, undefined, this),
10679
- /* @__PURE__ */ jsxDEV7(Text10, {
11241
+ }),
11242
+ /* @__PURE__ */ jsx7(Text10, {
10680
11243
  children: "3. Ready to use in Claude Code sessions"
10681
- }, undefined, false, undefined, this)
11244
+ })
10682
11245
  ]
10683
- }, undefined, true, undefined, this),
10684
- /* @__PURE__ */ jsxDEV7(Box8, {
11246
+ }),
11247
+ /* @__PURE__ */ jsxs6(Box8, {
10685
11248
  marginTop: 1,
10686
11249
  flexDirection: "column",
10687
11250
  children: [
10688
- /* @__PURE__ */ jsxDEV7(Text10, {
11251
+ /* @__PURE__ */ jsx7(Text10, {
10689
11252
  bold: true,
10690
11253
  children: "Next steps:"
10691
- }, undefined, false, undefined, this),
10692
- /* @__PURE__ */ jsxDEV7(Text10, {
11254
+ }),
11255
+ /* @__PURE__ */ jsx7(Text10, {
10693
11256
  dimColor: true,
10694
11257
  children: " hooks list --registered Check active hooks"
10695
- }, undefined, false, undefined, this),
10696
- /* @__PURE__ */ jsxDEV7(Text10, {
11258
+ }),
11259
+ /* @__PURE__ */ jsxs6(Text10, {
10697
11260
  dimColor: true,
10698
11261
  children: [
10699
11262
  " hooks info ",
10700
11263
  "<name>",
10701
11264
  " View hook details"
10702
11265
  ]
10703
- }, undefined, true, undefined, this)
11266
+ })
10704
11267
  ]
10705
- }, undefined, true, undefined, this),
10706
- /* @__PURE__ */ jsxDEV7(Box8, {
11268
+ }),
11269
+ /* @__PURE__ */ jsx7(Box8, {
10707
11270
  marginTop: 1,
10708
- children: /* @__PURE__ */ jsxDEV7(Text10, {
11271
+ children: /* @__PURE__ */ jsx7(Text10, {
10709
11272
  dimColor: true,
10710
11273
  children: "Press q to exit"
10711
- }, undefined, false, undefined, this)
10712
- }, undefined, false, undefined, this)
11274
+ })
11275
+ })
10713
11276
  ]
10714
- }, undefined, true, undefined, this)
11277
+ })
10715
11278
  ]
10716
- }, undefined, true, undefined, this);
11279
+ });
10717
11280
  }
10718
11281
 
10719
11282
  // src/cli/index.tsx
@@ -10725,10 +11288,10 @@ init_manifest();
10725
11288
  init_resolve();
10726
11289
  init_store();
10727
11290
  init_semver();
10728
- import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
10729
- var __dirname4 = dirname6(fileURLToPath4(import.meta.url));
10730
- var pkgPath = existsSync12(join12(__dirname4, "..", "package.json")) ? join12(__dirname4, "..", "package.json") : join12(__dirname4, "..", "..", "package.json");
10731
- var pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
11291
+ import { jsx as jsx8 } from "react/jsx-runtime";
11292
+ var __dirname4 = dirname8(fileURLToPath5(import.meta.url));
11293
+ var pkgPath = existsSync13(join14(__dirname4, "..", "package.json")) ? join14(__dirname4, "..", "package.json") : join14(__dirname4, "..", "..", "package.json");
11294
+ var pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
10732
11295
  var program2 = new Command;
10733
11296
  function resolveScope(options) {
10734
11297
  if (options.project)
@@ -10762,7 +11325,7 @@ function projectLogRows2(rows) {
10762
11325
  function readToken(tokenFile) {
10763
11326
  if (tokenFile) {
10764
11327
  try {
10765
- return readFileSync10(tokenFile, "utf-8").trim();
11328
+ return readFileSync12(tokenFile, "utf-8").trim();
10766
11329
  } catch {
10767
11330
  return;
10768
11331
  }
@@ -10823,7 +11386,7 @@ function suggestHooks(name, max = 3) {
10823
11386
  }
10824
11387
  program2.name("hooks").description("Install hooks for AI coding agents").version(pkg2.version);
10825
11388
  program2.command("interactive", { isDefault: true }).alias("i").description("Interactive hook browser").action(() => {
10826
- render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
11389
+ render(/* @__PURE__ */ jsx8(App, {}));
10827
11390
  });
10828
11391
  program2.command("init").description("Register a new agent profile with a unique ID").option("-a, --agent <type>", "Agent type: claude, gemini, custom", "claude").option("-n, --name <name>", "Optional display name for the agent").option("-j, --json", "Output as JSON", false).option("--cloudflare", "Configure a remote registry (Cloudflare worker)", false).option("--api-url <url>", "Remote registry API URL (with --cloudflare)").option("--api-key <ref>", "Vault key NAME for the registry API key (never the value; with --cloudflare)").action(async (options) => {
10829
11392
  if (options.cloudflare) {
@@ -10895,11 +11458,11 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
10895
11458
  process.exit(1);
10896
11459
  }
10897
11460
  const hookScript = resolveScriptPath2(hook);
10898
- if (!hookScript || !existsSync12(hookScript)) {
11461
+ if (!hookScript || !existsSync13(hookScript)) {
10899
11462
  console.error(JSON.stringify({ error: `Hook script not found: ${hookScript ?? hook}` }));
10900
11463
  process.exit(1);
10901
11464
  }
10902
- const content = readFileSync10(hookScript);
11465
+ const content = readFileSync12(hookScript);
10903
11466
  const sha = sha256Of2(content);
10904
11467
  const check = checkScriptHash2(hook, sha);
10905
11468
  if (!check.ok) {
@@ -11005,7 +11568,7 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
11005
11568
  process.stderr.write(stderr);
11006
11569
  process.exit(exitCode);
11007
11570
  });
11008
- program2.command("install").alias("add").argument("[hooks...]", "Hooks to install").option("-o, --overwrite", "Overwrite existing hooks", false).option("-a, --all", "Install all available hooks", false).option("-c, --category <category>", "Install all hooks in a category").option("-g, --global", "Install globally (~/.claude/settings.json)", false).option("-p, --project", "Install for current project (.claude/settings.json)", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith, all (default: claude)", "claude").option("--profile <id>", "Agent profile ID to scope hooks to").option("--dry-run", "Preview what would be installed without writing to settings", false).option("--apply-codewith", "Explicitly append Codewith TOML to a config file (prefer open-configs for managed configs)", false).option("--codewith-config <path>", "Explicit Codewith config path required with --apply-codewith").option("-j, --json", "Output as JSON", false).description("Install one or more hooks (registry names, local paths, git URLs, manifest URLs, or <name>@<version> pinned installs)").action(async (hooks, options) => {
11571
+ program2.command("install").alias("add").argument("[hooks...]", "Hooks to install").option("-o, --overwrite", "Overwrite existing hooks", false).option("-a, --all", "Install all available hooks", false).option("-c, --category <category>", "Install all hooks in a category").option("-g, --global", "Install globally (~/.claude/settings.json)", false).option("-p, --project", "Install for current project (.claude/settings.json)", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith, all (default: claude)", "claude").option("--profile <id>", "Agent profile ID to scope hooks to").option("--dry-run", "Preview what would be installed without writing to settings", false).option("--apply-codewith", "Explicitly append Codewith TOML to a config file (prefer configs for managed configs)", false).option("--codewith-config <path>", "Explicit Codewith config path required with --apply-codewith").option("-j, --json", "Output as JSON", false).description("Install one or more hooks (registry names, local paths, git URLs, manifest URLs, or <name>@<version> pinned installs)").action(async (hooks, options) => {
11009
11572
  const scope = resolveScope(options);
11010
11573
  const target = resolveTarget(options);
11011
11574
  let toInstall = hooks;
@@ -11026,7 +11589,7 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
11026
11589
  toInstall = getHooksByCategory(category).map((h) => h.name);
11027
11590
  }
11028
11591
  if (toInstall.length === 0) {
11029
- render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
11592
+ render(/* @__PURE__ */ jsx8(App, {}));
11030
11593
  return;
11031
11594
  }
11032
11595
  if (options.applyCodewith && (target === "codewith" || target === "all") && !options.codewithConfig) {
@@ -11193,7 +11756,7 @@ Dry run \u2014 would install (${scope}, ${target}):
11193
11756
  return;
11194
11757
  }
11195
11758
  const successCount = results.filter((r) => r.success).length;
11196
- const settingsFile = target === "codewith" ? options.applyCodewith ? options.codewithConfig : "TOML fragment only (open-configs should apply)" : scope === "project" ? ".claude/settings.json" : "~/.claude/settings.json";
11759
+ const settingsFile = target === "codewith" ? options.applyCodewith ? options.codewithConfig : "TOML fragment only (configs should apply)" : scope === "project" ? ".claude/settings.json" : "~/.claude/settings.json";
11197
11760
  console.log(chalk2.bold(`
11198
11761
  Installing hooks (${scope}, ${target})...
11199
11762
  `));
@@ -11467,7 +12030,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
11467
12030
  const settingsPath = getSettingsPath(scope);
11468
12031
  const issues = [];
11469
12032
  const healthy = [];
11470
- const settingsExist = existsSync12(settingsPath);
12033
+ const settingsExist = existsSync13(settingsPath);
11471
12034
  if (!settingsExist) {
11472
12035
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
11473
12036
  }
@@ -11475,7 +12038,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
11475
12038
  let wiringCount = 0;
11476
12039
  if (settingsExist) {
11477
12040
  try {
11478
- const settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
12041
+ const settings = JSON.parse(readFileSync12(settingsPath, "utf-8"));
11479
12042
  wiringCount = countSettingsWiring(settings);
11480
12043
  } catch {}
11481
12044
  }
@@ -11484,7 +12047,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
11484
12047
  const meta = custom ? resolveHookMeta(name) : getHook(name);
11485
12048
  let hookHealthy = true;
11486
12049
  if (custom) {
11487
- if (!existsSync12(custom.scriptPath)) {
12050
+ if (!existsSync13(custom.scriptPath)) {
11488
12051
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
11489
12052
  hookHealthy = false;
11490
12053
  }
@@ -11495,15 +12058,15 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
11495
12058
  continue;
11496
12059
  }
11497
12060
  const hookDir = getHookPath(name);
11498
- const hookScript = join12(hookDir, "src", "hook.ts");
11499
- if (!existsSync12(hookScript)) {
12061
+ const hookScript = join14(hookDir, "src", "hook.ts");
12062
+ if (!existsSync13(hookScript)) {
11500
12063
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
11501
12064
  hookHealthy = false;
11502
12065
  }
11503
12066
  }
11504
12067
  if (meta && settingsExist) {
11505
12068
  try {
11506
- const settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
12069
+ const settings = JSON.parse(readFileSync12(settingsPath, "utf-8"));
11507
12070
  if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
11508
12071
  const eventName = meta.event.split(":")[0];
11509
12072
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
@@ -11670,10 +12233,10 @@ program2.command("docs").argument("[hook]", "Hook name (shows general docs if om
11670
12233
  return;
11671
12234
  }
11672
12235
  const hookPath = getHookPath(hook);
11673
- const readmePath = join12(hookPath, "README.md");
12236
+ const readmePath = join14(hookPath, "README.md");
11674
12237
  let readme = "";
11675
- if (existsSync12(readmePath)) {
11676
- readme = readFileSync10(readmePath, "utf-8");
12238
+ if (existsSync13(readmePath)) {
12239
+ readme = readFileSync12(readmePath, "utf-8");
11677
12240
  }
11678
12241
  if (options.json) {
11679
12242
  console.log(JSON.stringify({ ...meta, readme }));
@@ -11727,7 +12290,7 @@ ${meta.displayName} v${meta.version}
11727
12290
  installation: {
11728
12291
  global: "hooks install gitguard",
11729
12292
  project: "hooks install gitguard --project",
11730
- codewith: "hooks install session-start --target codewith # emits TOML for open-configs to apply",
12293
+ codewith: "hooks install session-start --target codewith # emits TOML for configs to apply",
11731
12294
  category: 'hooks install --category "Git Safety"',
11732
12295
  all: "hooks install --all"
11733
12296
  },
@@ -11870,9 +12433,9 @@ program2.command("profile-import").argument("<file>", "JSON file to import profi
11870
12433
  if (file === "-") {
11871
12434
  raw = await new Response(Bun.stdin.stream()).text();
11872
12435
  } else {
11873
- const { readFileSync: readFileSync11 } = await import("fs");
12436
+ const { readFileSync: readFileSync13 } = await import("fs");
11874
12437
  try {
11875
- raw = readFileSync11(file, "utf-8");
12438
+ raw = readFileSync13(file, "utf-8");
11876
12439
  } catch {
11877
12440
  if (options.json) {
11878
12441
  console.log(JSON.stringify({ error: `Cannot read file: ${file}` }));