@hasna/hooks 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,6 +48,16 @@ tools return compact summaries by default, while explicit flags such as
48
48
  `compact:false`, `verbose:true`, or a detail tool like `hooks_info` return full
49
49
  records.
50
50
 
51
+ ## Optional Mementos prompt context
52
+
53
+ `hooks install mementos-context --target codex` registers a native prompt hook.
54
+ Enable it in the calling shell with `HOOKS_MEMENTOS_ENABLED=1`; set
55
+ `HOOKS_MEMENTOS_PROJECT` to an existing Mementos project. Provider judgments are
56
+ separately opt-in. The hook returns bounded, quoted memory data and continues
57
+ on errors. See [setup, scope and provider options](hooks/hook-mementos-context/README.md).
58
+ Codex requires review and trust of the exact registration through `/hooks`.
59
+ Claude Code and Codewith also support this hook through their explicit targets.
60
+
51
61
  ## Codewith-native hooks
52
62
 
53
63
  @hasna/hooks includes unprefixed Codewith-native hook names:
package/bin/hooks-mcp.js CHANGED
@@ -377,6 +377,17 @@ var init_registry = __esm(() => {
377
377
  matcher: "",
378
378
  tags: ["knowledge", "context", "codewith", "deterministic", "session-start", "prompt", "subagent"]
379
379
  },
380
+ {
381
+ name: "mementos-context",
382
+ displayName: "Mementos Prompt Context",
383
+ description: "Retrieves scoped memories for a user prompt, with optional relevance judgments",
384
+ version: "0.1.0",
385
+ category: "Context Management",
386
+ event: "UserPromptSubmit",
387
+ matcher: "",
388
+ timeoutSeconds: 12,
389
+ tags: ["mementos", "memory", "prompt", "codex", "claude", "codewith", "opt-in"]
390
+ },
380
391
  {
381
392
  name: "autoformat",
382
393
  displayName: "Auto Format",
@@ -1410,7 +1421,7 @@ var init_redact = __esm(() => {
1410
1421
  // ../../node_modules/.bun/@hasna+contracts@1.0.2+e0db24ae03127387/node_modules/@hasna/contracts/dist/client/transport.js
1411
1422
  import { isIP } from "net";
1412
1423
  import { spawnSync } from "child_process";
1413
- import { closeSync, fstatSync, openSync, readFileSync as readFileSync6 } from "fs";
1424
+ import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as readFileSync7 } from "fs";
1414
1425
  import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
1415
1426
  import { createRequire } from "module";
1416
1427
  import { hostname as osHostname } from "os";
@@ -1514,7 +1525,7 @@ function readAppConfigFile(path) {
1514
1525
  };
1515
1526
  let fd = -1;
1516
1527
  try {
1517
- fd = openSync(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
1528
+ fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
1518
1529
  } catch (error) {
1519
1530
  const code = error.code;
1520
1531
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -1535,7 +1546,7 @@ function readAppConfigFile(path) {
1535
1546
  unsafe("the file is not owned by the current user");
1536
1547
  if (before.size > MAX_CREDENTIAL_FILE_BYTES)
1537
1548
  unsafe("the file exceeds the size limit");
1538
- const bytes = readFileSync6(fd);
1549
+ const bytes = readFileSync7(fd);
1539
1550
  const after = fstatSync(fd);
1540
1551
  if (!configFileReadsCoherent(before, after)) {
1541
1552
  unsafe("the file changed while being read");
@@ -1543,7 +1554,7 @@ function readAppConfigFile(path) {
1543
1554
  return parseEnvFile(bytes.toString("utf8"));
1544
1555
  } finally {
1545
1556
  if (fd !== -1)
1546
- closeSync(fd);
1557
+ closeSync2(fd);
1547
1558
  }
1548
1559
  }
1549
1560
  function readCredentialFile(path, apiKeyKeys) {
@@ -2198,8 +2209,8 @@ var init_local_opt_in = __esm(() => {
2198
2209
  });
2199
2210
 
2200
2211
  // src/lib/hook-env.ts
2201
- import { dirname as dirname4, isAbsolute as isAbsolute3 } from "path";
2202
- import { existsSync as existsSync9, realpathSync as realpathSync2, statSync } from "fs";
2212
+ import { dirname as dirname5, isAbsolute as isAbsolute3 } from "path";
2213
+ import { existsSync as existsSync10, realpathSync as realpathSync2, statSync } from "fs";
2203
2214
  import { tmpdir } from "os";
2204
2215
  function isDeniedEnvName(name) {
2205
2216
  const upper = name.toUpperCase();
@@ -2247,9 +2258,9 @@ function isUnsafePathEntry(entry, home) {
2247
2258
  }
2248
2259
  function sanitizeHookPath(pathValue, home) {
2249
2260
  const baseline = PATH_BASELINE.slice();
2250
- if (process.platform === "darwin" && existsSync9("/opt/homebrew/bin"))
2261
+ if (process.platform === "darwin" && existsSync10("/opt/homebrew/bin"))
2251
2262
  baseline.push("/opt/homebrew/bin");
2252
- const bunDir = dirname4(process.execPath);
2263
+ const bunDir = dirname5(process.execPath);
2253
2264
  const seen = new Set;
2254
2265
  const out = [];
2255
2266
  for (const dir of baseline) {
@@ -2373,8 +2384,8 @@ __export(exports_run, {
2373
2384
  HookTimeoutError: () => HookTimeoutError
2374
2385
  });
2375
2386
  import { randomBytes as randomBytes2 } from "crypto";
2376
- import { rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
2377
- import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join10 } from "path";
2387
+ import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
2388
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute4, join as join10 } from "path";
2378
2389
  function extensionOf(scriptPath) {
2379
2390
  const base = basename2(scriptPath);
2380
2391
  const dot = base.lastIndexOf(".");
@@ -2443,7 +2454,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
2443
2454
  return chunks.join("");
2444
2455
  }
2445
2456
  async function executeVerifiedScript(options) {
2446
- const scriptDir = dirname5(options.scriptPath);
2457
+ const scriptDir = dirname6(options.scriptPath);
2447
2458
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
2448
2459
  const tempPath = join10(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
2449
2460
  let proc = null;
@@ -2453,7 +2464,7 @@ async function executeVerifiedScript(options) {
2453
2464
  killGroup(proc, { leaderAlive: proc !== null && proc.exitCode === null });
2454
2465
  }, options.timeout) : null;
2455
2466
  try {
2456
- writeFileSync5(tempPath, options.content, { flag: "wx", mode: 384 });
2467
+ writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
2457
2468
  proc = Bun.spawn([...interpreter.command, isAbsolute4(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
2458
2469
  stdin: new Response(options.stdin),
2459
2470
  stdout: "pipe",
@@ -2653,16 +2664,16 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
2653
2664
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2654
2665
  import { z as z2 } from "zod";
2655
2666
  init_registry();
2656
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
2657
- import { join as join11, dirname as dirname6 } from "path";
2667
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
2668
+ import { join as join11, dirname as dirname7 } from "path";
2658
2669
  import { fileURLToPath as fileURLToPath3 } from "url";
2659
2670
 
2660
2671
  // src/lib/installer.ts
2661
2672
  init_registry();
2662
2673
  init_resolve();
2663
2674
  init_manifest();
2664
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, rmdirSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
2665
- import { join as join7, dirname as dirname3 } from "path";
2675
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
2676
+ import { join as join7, dirname as dirname4 } from "path";
2666
2677
  import { homedir as homedir3 } from "os";
2667
2678
  import { fileURLToPath as fileURLToPath2 } from "url";
2668
2679
 
@@ -2861,9 +2872,78 @@ function checkScriptHash(name, actual) {
2861
2872
  return { ok: true, pinned: true, expected, actual, name };
2862
2873
  }
2863
2874
 
2875
+ // src/lib/codex-settings.ts
2876
+ import { closeSync, existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
2877
+ import { dirname as dirname3 } from "path";
2878
+ import { randomUUID } from "crypto";
2879
+ var snapshots = new WeakMap;
2880
+ function current(path) {
2881
+ try {
2882
+ const stat = lstatSync(path);
2883
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576)
2884
+ throw new Error("invalid_file");
2885
+ return readFileSync4(path, "utf8");
2886
+ } catch (error) {
2887
+ if (error.code === "ENOENT")
2888
+ return null;
2889
+ throw new Error("Cannot safely read Codex hooks.json; no settings were changed.");
2890
+ }
2891
+ }
2892
+ function readCodexSettings(path) {
2893
+ const raw = current(path);
2894
+ let settings;
2895
+ try {
2896
+ settings = raw === null ? {} : JSON.parse(raw);
2897
+ if (!settings || typeof settings !== "object" || Array.isArray(settings))
2898
+ throw new Error;
2899
+ if (settings.hooks !== undefined) {
2900
+ if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks))
2901
+ throw new Error;
2902
+ for (const entries of Object.values(settings.hooks)) {
2903
+ if (!Array.isArray(entries) || entries.some((entry) => !entry || !Array.isArray(entry.hooks) || entry.hooks.some((hook) => !hook || typeof hook !== "object")))
2904
+ throw new Error;
2905
+ }
2906
+ }
2907
+ } catch {
2908
+ throw new Error("Invalid Codex hooks.json; repair it before installing or removing hooks.");
2909
+ }
2910
+ snapshots.set(settings, { path, raw });
2911
+ return settings;
2912
+ }
2913
+ function writeCodexSettings(path, settings) {
2914
+ const snapshot = snapshots.get(settings);
2915
+ if (!snapshot || snapshot.path !== path)
2916
+ throw new Error("Codex settings require a complete current read before writing.");
2917
+ mkdirSync4(dirname3(path), { recursive: true });
2918
+ const lock = `${path}.hooks-write.lock`;
2919
+ let fd;
2920
+ try {
2921
+ fd = openSync(lock, "wx", 384);
2922
+ } catch {
2923
+ throw new Error("Codex settings are being edited; retry after the active writer finishes.");
2924
+ }
2925
+ const temp = `${path}.${randomUUID()}.tmp`;
2926
+ try {
2927
+ if (current(path) !== snapshot.raw)
2928
+ throw new Error("Codex settings changed since reading; re-read before retrying.");
2929
+ const next = JSON.stringify(settings, null, 2) + `
2930
+ `;
2931
+ writeFileSync3(temp, next, { flag: "wx", mode: 384 });
2932
+ if (current(path) !== snapshot.raw)
2933
+ throw new Error("Codex settings changed since reading; re-read before retrying.");
2934
+ renameSync2(temp, path);
2935
+ snapshots.set(settings, { path, raw: next });
2936
+ } finally {
2937
+ if (existsSync7(temp))
2938
+ unlinkSync(temp);
2939
+ closeSync(fd);
2940
+ unlinkSync(lock);
2941
+ }
2942
+ }
2943
+
2864
2944
  // src/lib/installer.ts
2865
- var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
2866
- var HOOKS_DIR = existsSync7(join7(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join7(__dirname2, "..", "..", "hooks") : join7(__dirname2, "..", "hooks");
2945
+ var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
2946
+ var HOOKS_DIR = existsSync8(join7(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join7(__dirname2, "..", "..", "hooks") : join7(__dirname2, "..", "hooks");
2867
2947
  function normalizeHookName(name) {
2868
2948
  return name.startsWith("hook-") ? name : `hook-${name}`;
2869
2949
  }
@@ -2871,10 +2951,15 @@ function shortHookName(name) {
2871
2951
  return name.startsWith("hook-") ? name.slice("hook-".length) : name;
2872
2952
  }
2873
2953
  function removeHookEntriesByName(entries, hookName) {
2874
- return entries.filter((entry) => !entry.hooks?.some((h) => {
2875
- const match = h.command?.match(/^hooks run ([\w-]+)/);
2876
- return match && match[1] === hookName;
2877
- }));
2954
+ return entries.flatMap((entry) => {
2955
+ if (!Array.isArray(entry.hooks))
2956
+ return [entry];
2957
+ const kept = entry.hooks.filter((h) => {
2958
+ const match = h.command?.match(/^hooks run ([\w-]+)/);
2959
+ return !(match && match[1] === hookName);
2960
+ });
2961
+ return kept.length === entry.hooks.length ? [entry] : kept.length ? [{ ...entry, hooks: kept }] : [];
2962
+ });
2878
2963
  }
2879
2964
  var EVENT_MAP = {
2880
2965
  claude: {
@@ -2884,7 +2969,7 @@ var EVENT_MAP = {
2884
2969
  Notification: "Notification",
2885
2970
  SessionStart: "SessionStart",
2886
2971
  SessionEnd: "SessionEnd",
2887
- UserPromptSubmit: null,
2972
+ UserPromptSubmit: "UserPromptSubmit",
2888
2973
  SubagentStart: null
2889
2974
  },
2890
2975
  gemini: {
@@ -2906,9 +2991,21 @@ var EVENT_MAP = {
2906
2991
  SessionEnd: null,
2907
2992
  UserPromptSubmit: "UserPromptSubmit",
2908
2993
  SubagentStart: "SubagentStart"
2994
+ },
2995
+ codex: {
2996
+ PreToolUse: "PreToolUse",
2997
+ PostToolUse: "PostToolUse",
2998
+ Stop: "Stop",
2999
+ Notification: null,
3000
+ SessionStart: "SessionStart",
3001
+ SessionEnd: "SessionEnd",
3002
+ UserPromptSubmit: "UserPromptSubmit",
3003
+ SubagentStart: "SubagentStart"
2909
3004
  }
2910
3005
  };
2911
3006
  function getTargetSettingsDir(target) {
3007
+ if (target === "codex")
3008
+ return ".codex";
2912
3009
  if (target === "codewith")
2913
3010
  return ".codewith";
2914
3011
  if (target === "gemini")
@@ -2916,6 +3013,8 @@ function getTargetSettingsDir(target) {
2916
3013
  return ".claude";
2917
3014
  }
2918
3015
  function getGlobalSettingsPathOverride(target) {
3016
+ if (target === "codex")
3017
+ return process.env.HASNA_HOOKS_CODEX_SETTINGS_PATH;
2919
3018
  if (target === "claude")
2920
3019
  return process.env.HASNA_HOOKS_CLAUDE_SETTINGS_PATH;
2921
3020
  if (target === "gemini")
@@ -2932,6 +3031,8 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
2932
3031
  if (globalOverride)
2933
3032
  return globalOverride;
2934
3033
  const dir = getTargetSettingsDir(target);
3034
+ if (target === "codex")
3035
+ return join7(scope === "project" ? process.cwd() : homedir3(), dir, "hooks.json");
2935
3036
  if (scope === "project") {
2936
3037
  return target === "codewith" ? join7(process.cwd(), dir, "config.toml") : join7(process.cwd(), dir, "settings.json");
2937
3038
  }
@@ -2945,13 +3046,15 @@ function getHookPath(name) {
2945
3046
  return join7(HOOKS_DIR, normalizeHookName(shortName));
2946
3047
  }
2947
3048
  function hookExists(name) {
2948
- return existsSync7(getHookPath(name));
3049
+ return existsSync8(getHookPath(name));
2949
3050
  }
2950
3051
  function readSettings(scope = "global", target = "claude") {
2951
3052
  const path = getSettingsPath(scope, target);
3053
+ if (target === "codex")
3054
+ return readCodexSettings(path);
2952
3055
  try {
2953
- if (existsSync7(path)) {
2954
- return JSON.parse(readFileSync4(path, "utf-8"));
3056
+ if (existsSync8(path)) {
3057
+ return JSON.parse(readFileSync5(path, "utf-8"));
2955
3058
  }
2956
3059
  } catch (error) {
2957
3060
  console.warn(`[hooks] Failed to read settings at ${path}: ${error instanceof Error ? error.message : String(error)}`);
@@ -2960,11 +3063,13 @@ function readSettings(scope = "global", target = "claude") {
2960
3063
  }
2961
3064
  function writeSettings(settings, scope = "global", target = "claude") {
2962
3065
  const path = getSettingsPath(scope, target);
2963
- const dir = dirname3(path);
2964
- if (!existsSync7(dir)) {
2965
- mkdirSync4(dir, { recursive: true });
3066
+ if (target === "codex")
3067
+ return writeCodexSettings(path, settings);
3068
+ const dir = dirname4(path);
3069
+ if (!existsSync8(dir)) {
3070
+ mkdirSync5(dir, { recursive: true });
2966
3071
  }
2967
- writeFileSync3(path, JSON.stringify(settings, null, 2) + `
3072
+ writeFileSync4(path, JSON.stringify(settings, null, 2) + `
2968
3073
  `);
2969
3074
  }
2970
3075
  function getTargetEventName(internalEvent, target) {
@@ -2987,6 +3092,8 @@ function codewithMatcher(matcher) {
2987
3092
  }
2988
3093
  function codewithTimeout(name) {
2989
3094
  switch (name) {
3095
+ case "mementos-context":
3096
+ return 12;
2990
3097
  case "knowledge-context":
2991
3098
  return 6;
2992
3099
  case "session-start":
@@ -3053,21 +3160,21 @@ function buildCodewithTomlFragment(name, profile) {
3053
3160
  function readCodewithConfig(scope, configPath) {
3054
3161
  const path = getSettingsPath(scope, "codewith", configPath);
3055
3162
  try {
3056
- return existsSync7(path) ? readFileSync4(path, "utf-8") : "";
3163
+ return existsSync8(path) ? readFileSync5(path, "utf-8") : "";
3057
3164
  } catch {
3058
3165
  return "";
3059
3166
  }
3060
3167
  }
3061
3168
  function appendCodewithFragment(fragment, scope, configPath) {
3062
3169
  const path = getSettingsPath(scope, "codewith", configPath);
3063
- const dir = dirname3(path);
3064
- if (!existsSync7(dir))
3065
- mkdirSync4(dir, { recursive: true });
3066
- const existing = existsSync7(path) ? readFileSync4(path, "utf-8") : "";
3170
+ const dir = dirname4(path);
3171
+ if (!existsSync8(dir))
3172
+ mkdirSync5(dir, { recursive: true });
3173
+ const existing = existsSync8(path) ? readFileSync5(path, "utf-8") : "";
3067
3174
  const sep2 = existing.trim() ? `
3068
3175
 
3069
3176
  ` : "";
3070
- writeFileSync3(path, `${existing.replace(/\s*$/, "")}${sep2}${fragment}`);
3177
+ writeFileSync4(path, `${existing.replace(/\s*$/, "")}${sep2}${fragment}`);
3071
3178
  return path;
3072
3179
  }
3073
3180
  function detectConflict(name, scope, target) {
@@ -3164,18 +3271,25 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
3164
3271
  };
3165
3272
  }
3166
3273
  }
3167
- const registered = getRegisteredHooksForTarget(scope, target);
3168
- if (registered.includes(shortName) && !overwrite) {
3169
- return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
3170
- }
3171
- const conflict = detectConflict(shortName, scope, target);
3172
- const rewriteConflict = detectRewriteConflict(shortName, scope, target);
3173
- if (rewriteConflict) {
3174
- return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
3175
- }
3176
3274
  try {
3275
+ const registered = getRegisteredHooksForTarget(scope, target);
3276
+ if (registered.includes(shortName) && !overwrite) {
3277
+ return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
3278
+ }
3279
+ const conflict = detectConflict(shortName, scope, target);
3280
+ const rewriteConflict = detectRewriteConflict(shortName, scope, target);
3281
+ if (rewriteConflict) {
3282
+ return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
3283
+ }
3177
3284
  registerHook(shortName, scope, target, profile);
3178
- return { hook: shortName, success: true, scope, target, ...conflict ? { conflict } : {} };
3285
+ return {
3286
+ hook: shortName,
3287
+ success: true,
3288
+ scope,
3289
+ target,
3290
+ ...conflict ? { conflict } : {},
3291
+ ...target === "codex" ? { applied: true, configPath: getSettingsPath(scope, target), note: "Registered in Codex hooks.json. Review and trust the exact definition in Codex /hooks; project hooks also require a trusted project. Registration alone does not prove execution." } : {}
3292
+ };
3179
3293
  } catch (error) {
3180
3294
  return {
3181
3295
  hook: shortName,
@@ -3187,6 +3301,9 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
3187
3301
  }
3188
3302
  function installHook(name, options = {}) {
3189
3303
  const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath } = options;
3304
+ if (!/^[\w-]+$/.test(name) || profile !== undefined && !/^[\w-]+$/.test(profile)) {
3305
+ return { hook: name, success: false, error: "Hook and profile names must contain only letters, digits, underscores or hyphens.", target };
3306
+ }
3190
3307
  if (target === "all") {
3191
3308
  const shortName = shortHookName(name);
3192
3309
  const meta = resolveHookMeta(shortName);
@@ -3315,7 +3432,8 @@ function removeHook(name, scope = "global", target = "claude") {
3315
3432
  if (target === "all") {
3316
3433
  const claudeRemoved = removeHookForTarget(shortName, scope, "claude");
3317
3434
  const geminiRemoved = removeHookForTarget(shortName, scope, "gemini");
3318
- return claudeRemoved || geminiRemoved;
3435
+ const codexRemoved = removeHookForTarget(shortName, scope, "codex");
3436
+ return claudeRemoved || geminiRemoved || codexRemoved;
3319
3437
  }
3320
3438
  if (target === "codewith") {
3321
3439
  return false;
@@ -3333,21 +3451,21 @@ function removeHookForTarget(name, scope, target) {
3333
3451
 
3334
3452
  // src/lib/profiles.ts
3335
3453
  init_app_home();
3336
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4, readdirSync as readdirSync4, rmSync, cpSync as cpSync2 } from "fs";
3454
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync4, rmSync, cpSync as cpSync2 } from "fs";
3337
3455
  import { join as join8 } from "path";
3338
3456
  function getProfilesDir() {
3339
3457
  const newDir = join8(getEffectiveDataRoot(), "profiles");
3340
3458
  const oldDir = join8(getHomeDir(), ".hooks", "profiles");
3341
- if (!existsSync8(newDir) && existsSync8(oldDir)) {
3342
- mkdirSync5(newDir, { recursive: true });
3459
+ if (!existsSync9(newDir) && existsSync9(oldDir)) {
3460
+ mkdirSync6(newDir, { recursive: true });
3343
3461
  cpSync2(oldDir, newDir, { recursive: true });
3344
3462
  }
3345
3463
  return newDir;
3346
3464
  }
3347
3465
  function ensureProfilesDir() {
3348
3466
  const dir = getProfilesDir();
3349
- if (!existsSync8(dir)) {
3350
- mkdirSync5(dir, { recursive: true });
3467
+ if (!existsSync9(dir)) {
3468
+ mkdirSync6(dir, { recursive: true });
3351
3469
  }
3352
3470
  }
3353
3471
  function profilePath(id) {
@@ -3370,30 +3488,30 @@ function createProfile(input) {
3370
3488
  if (input.name) {
3371
3489
  profile.name = input.name;
3372
3490
  }
3373
- writeFileSync4(profilePath(id), JSON.stringify(profile, null, 2) + `
3491
+ writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
3374
3492
  `);
3375
3493
  return profile;
3376
3494
  }
3377
3495
  function getProfile(id) {
3378
3496
  const path = profilePath(id);
3379
3497
  try {
3380
- if (!existsSync8(path))
3498
+ if (!existsSync9(path))
3381
3499
  return null;
3382
- return JSON.parse(readFileSync5(path, "utf-8"));
3500
+ return JSON.parse(readFileSync6(path, "utf-8"));
3383
3501
  } catch {
3384
3502
  return null;
3385
3503
  }
3386
3504
  }
3387
3505
  function listProfiles() {
3388
3506
  const dir = getProfilesDir();
3389
- if (!existsSync8(dir))
3507
+ if (!existsSync9(dir))
3390
3508
  return [];
3391
3509
  try {
3392
3510
  const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
3393
3511
  const profiles = [];
3394
3512
  for (const file of files) {
3395
3513
  try {
3396
- const content = readFileSync5(join8(dir, file), "utf-8");
3514
+ const content = readFileSync6(join8(dir, file), "utf-8");
3397
3515
  profiles.push(JSON.parse(content));
3398
3516
  } catch {}
3399
3517
  }
@@ -3925,13 +4043,13 @@ function coerceForSqlite(value) {
3925
4043
  return String(value);
3926
4044
  }
3927
4045
  // src/mcp/server.ts
3928
- var __dirname3 = dirname6(fileURLToPath3(import.meta.url));
4046
+ var __dirname3 = dirname7(fileURLToPath3(import.meta.url));
3929
4047
  var pkg = { name: "@hasna/hooks", version: "0.0.0" };
3930
4048
  try {
3931
4049
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
3932
4050
  const p = join11(__dirname3, rel);
3933
- if (existsSync10(p)) {
3934
- pkg = JSON.parse(readFileSync7(p, "utf-8"));
4051
+ if (existsSync11(p)) {
4052
+ pkg = JSON.parse(readFileSync8(p, "utf-8"));
3935
4053
  break;
3936
4054
  }
3937
4055
  }
@@ -3940,10 +4058,10 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
3940
4058
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
3941
4059
  const resolved = resolveHook2(name);
3942
4060
  const hookScript = resolveScriptPath2(name);
3943
- if (!resolved || !hookScript || !existsSync10(hookScript)) {
4061
+ if (!resolved || !hookScript || !existsSync11(hookScript)) {
3944
4062
  throw new Error(`Hook '${name}' not found`);
3945
4063
  }
3946
- const content = readFileSync7(hookScript);
4064
+ const content = readFileSync8(hookScript);
3947
4065
  const sha = sha256Of(content);
3948
4066
  const check = checkScriptHash(name, sha);
3949
4067
  if (!check.ok) {
@@ -4149,7 +4267,7 @@ function createHooksServer() {
4149
4267
  const settingsPath = getSettingsPath(scope);
4150
4268
  const issues = [];
4151
4269
  const healthy = [];
4152
- const settingsExist = existsSync10(settingsPath);
4270
+ const settingsExist = existsSync11(settingsPath);
4153
4271
  if (!settingsExist) {
4154
4272
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
4155
4273
  }
@@ -4159,7 +4277,7 @@ function createHooksServer() {
4159
4277
  const meta = custom ? resolveHookMeta(name) : getHook(name);
4160
4278
  let hookHealthy = true;
4161
4279
  if (custom) {
4162
- if (!existsSync10(custom.scriptPath)) {
4280
+ if (!existsSync11(custom.scriptPath)) {
4163
4281
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
4164
4282
  hookHealthy = false;
4165
4283
  }
@@ -4169,14 +4287,14 @@ function createHooksServer() {
4169
4287
  continue;
4170
4288
  }
4171
4289
  const hookDir = getHookPath(name);
4172
- if (!existsSync10(join11(hookDir, "src", "hook.ts"))) {
4290
+ if (!existsSync11(join11(hookDir, "src", "hook.ts"))) {
4173
4291
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
4174
4292
  hookHealthy = false;
4175
4293
  }
4176
4294
  }
4177
4295
  if (meta && settingsExist) {
4178
4296
  try {
4179
- const settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
4297
+ const settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
4180
4298
  if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
4181
4299
  const eventName = meta.event.split(":")[0];
4182
4300
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
@@ -4215,8 +4333,8 @@ function createHooksServer() {
4215
4333
  const hookPath = getHookPath(name);
4216
4334
  const readmePath = join11(hookPath, "README.md");
4217
4335
  let readme = "";
4218
- if (existsSync10(readmePath)) {
4219
- readme = readFileSync7(readmePath, "utf-8");
4336
+ if (existsSync11(readmePath)) {
4337
+ readme = readFileSync8(readmePath, "utf-8");
4220
4338
  }
4221
4339
  if (verbose) {
4222
4340
  return { content: [{ type: "text", text: JSON.stringify({ ...meta, readme }) }] };
@@ -4399,7 +4517,7 @@ function createHooksServer() {
4399
4517
  const ctx = {
4400
4518
  scope,
4401
4519
  settings_path: settingsPath,
4402
- settings_exists: existsSync10(settingsPath),
4520
+ settings_exists: existsSync11(settingsPath),
4403
4521
  registered_hooks: hooks,
4404
4522
  hook_count: hooks.length,
4405
4523
  healthy,
@@ -4545,8 +4663,8 @@ function createHooksServer() {
4545
4663
  const settingsPath = getSettingsPath(scope);
4546
4664
  let settings = {};
4547
4665
  try {
4548
- if (existsSync10(settingsPath))
4549
- settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
4666
+ if (existsSync11(settingsPath))
4667
+ settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
4550
4668
  } catch {}
4551
4669
  if (!settings.hooks)
4552
4670
  settings.hooks = {};
@@ -4554,10 +4672,10 @@ function createHooksServer() {
4554
4672
  if (!disabled.includes(name))
4555
4673
  disabled.push(name);
4556
4674
  settings.hooks.__disabled = disabled;
4557
- const { writeFileSync: writeFileSync6, mkdirSync: mkdirSync6 } = await import("fs");
4558
- const { dirname: dirname7 } = await import("path");
4559
- mkdirSync6(dirname7(settingsPath), { recursive: true });
4560
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + `
4675
+ const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync7 } = await import("fs");
4676
+ const { dirname: dirname8 } = await import("path");
4677
+ mkdirSync7(dirname8(settingsPath), { recursive: true });
4678
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
4561
4679
  `);
4562
4680
  return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
4563
4681
  });
@@ -4568,15 +4686,15 @@ function createHooksServer() {
4568
4686
  const settingsPath = getSettingsPath(scope);
4569
4687
  let settings = {};
4570
4688
  try {
4571
- if (existsSync10(settingsPath))
4572
- settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
4689
+ if (existsSync11(settingsPath))
4690
+ settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
4573
4691
  } catch {}
4574
4692
  if (settings.hooks?.__disabled) {
4575
4693
  settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
4576
4694
  if (settings.hooks.__disabled.length === 0)
4577
4695
  delete settings.hooks.__disabled;
4578
- const { writeFileSync: writeFileSync6 } = await import("fs");
4579
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + `
4696
+ const { writeFileSync: writeFileSync7 } = await import("fs");
4697
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
4580
4698
  `);
4581
4699
  }
4582
4700
  return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, scope }) }] };