@hasna/hooks 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/index.js CHANGED
@@ -2246,6 +2246,17 @@ var init_registry = __esm(() => {
2246
2246
  matcher: "",
2247
2247
  tags: ["knowledge", "context", "codewith", "deterministic", "session-start", "prompt", "subagent"]
2248
2248
  },
2249
+ {
2250
+ name: "mementos-context",
2251
+ displayName: "Mementos Prompt Context",
2252
+ description: "Retrieves scoped memories for a user prompt, with optional relevance judgments",
2253
+ version: "0.1.0",
2254
+ category: "Context Management",
2255
+ event: "UserPromptSubmit",
2256
+ matcher: "",
2257
+ timeoutSeconds: 12,
2258
+ tags: ["mementos", "memory", "prompt", "codex", "claude", "codewith", "opt-in"]
2259
+ },
2249
2260
  {
2250
2261
  name: "autoformat",
2251
2262
  displayName: "Auto Format",
@@ -4489,10 +4500,84 @@ var init_resolve = __esm(() => {
4489
4500
  init_manifest();
4490
4501
  });
4491
4502
 
4503
+ // src/lib/mementos-options.ts
4504
+ function validateMementosRegistration(name, value) {
4505
+ if (value === undefined)
4506
+ return;
4507
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value)) || !Object.hasOwn(value, "enabled") || Object.keys(value).some((key) => key !== "enabled" && key !== "secretRef") || name.replace(/^hook-/, "") !== "mementos-context" || typeof value.enabled !== "boolean") {
4508
+ throw new Error("Mementos options require the mementos-context hook and an explicit enable/disable choice.");
4509
+ }
4510
+ if (value.secretRef !== undefined && (!value.enabled || typeof value.secretRef !== "string" || value.secretRef.length > 256 || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*(?:\/[a-zA-Z0-9][a-zA-Z0-9_.-]*){1,12}$/.test(value.secretRef))) {
4511
+ throw new Error("OpenRouter requires an enabled Mementos hook and a valid Secrets reference, never a key value.");
4512
+ }
4513
+ }
4514
+ function mementosRegistrationFromFlags(name, flags) {
4515
+ if (!flags.enableMementos && !flags.disableMementos && flags.openrouterSecretRef === undefined)
4516
+ return;
4517
+ if (Boolean(flags.enableMementos) === Boolean(flags.disableMementos)) {
4518
+ throw new Error("Choose exactly one of --enable-mementos or --disable-mementos.");
4519
+ }
4520
+ const value = { enabled: flags.enableMementos === true, ...flags.openrouterSecretRef !== undefined ? { secretRef: flags.openrouterSecretRef } : {} };
4521
+ validateMementosRegistration(name, value);
4522
+ return value;
4523
+ }
4524
+ function mementosCommandSuffix(value) {
4525
+ validateMementosRegistration("mementos-context", value);
4526
+ if (!value)
4527
+ return "";
4528
+ return value.enabled ? ` --enable-mementos${value.secretRef ? ` --openrouter-secret-ref ${value.secretRef}` : ""}` : " --disable-mementos";
4529
+ }
4530
+ function retainedMementosRegistration(commands) {
4531
+ const values = commands.filter((command) => /^hooks run mementos-context(?:\s|$)/.test(command)).map((command) => {
4532
+ const args = command.trim().split(/\s+/).slice(3);
4533
+ const flags = {};
4534
+ const seen = new Set;
4535
+ while (args.length) {
4536
+ const key = args.shift();
4537
+ if (seen.has(key))
4538
+ throw new Error("Ambiguous Mementos registration; specify its enablement explicitly.");
4539
+ seen.add(key);
4540
+ if (key === "--profile" && /^[\w-]+$/.test(args[0] ?? ""))
4541
+ args.shift();
4542
+ else if (key === "--enable-mementos")
4543
+ flags.enableMementos = true;
4544
+ else if (key === "--disable-mementos")
4545
+ flags.disableMementos = true;
4546
+ else if (key === "--openrouter-secret-ref" && args.length)
4547
+ flags.openrouterSecretRef = args.shift();
4548
+ else
4549
+ throw new Error("Unrecognized Mementos registration options; specify its enablement explicitly.");
4550
+ }
4551
+ return mementosRegistrationFromFlags("mementos-context", flags);
4552
+ });
4553
+ if (new Set(values.map((value) => JSON.stringify(value))).size > 1) {
4554
+ throw new Error("Conflicting Mementos registrations; specify its enablement explicitly.");
4555
+ }
4556
+ return values[0];
4557
+ }
4558
+ function mementosRunEnvironment(env, value) {
4559
+ validateMementosRegistration("mementos-context", value);
4560
+ if (!value)
4561
+ return env;
4562
+ return {
4563
+ ...env,
4564
+ HOOKS_MEMENTOS_ENABLED: value.enabled && env.HOOKS_MEMENTOS_ENABLED !== "0" ? "1" : "0",
4565
+ ...value.secretRef ? { HOOKS_MEMENTOS_OPENROUTER_SECRET_REF: value.secretRef } : {}
4566
+ };
4567
+ }
4568
+
4492
4569
  // src/lib/registration.ts
4493
4570
  function registrationName(command) {
4494
4571
  if (typeof command !== "string")
4495
4572
  return;
4573
+ if (/^hooks run mementos-context(?:\s|$)/.test(command.trim())) {
4574
+ try {
4575
+ retainedMementosRegistration([command.trim()]);
4576
+ return "mementos-context";
4577
+ } catch {
4578
+ return;
4579
+ }
4580
+ }
4496
4581
  const match = command.trim().match(REGISTRATION_COMMAND_RE);
4497
4582
  return match ? match[1] ?? match[2] : undefined;
4498
4583
  }
@@ -5232,6 +5317,78 @@ var init_store = __esm(() => {
5232
5317
  };
5233
5318
  });
5234
5319
 
5320
+ // src/lib/codex-settings.ts
5321
+ import { closeSync, existsSync as existsSync10, lstatSync, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
5322
+ import { dirname as dirname3 } from "path";
5323
+ import { randomUUID as randomUUID3 } from "crypto";
5324
+ function current(path) {
5325
+ try {
5326
+ const stat = lstatSync(path);
5327
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576)
5328
+ throw new Error("invalid_file");
5329
+ return readFileSync4(path, "utf8");
5330
+ } catch (error) {
5331
+ if (error.code === "ENOENT")
5332
+ return null;
5333
+ throw new Error("Cannot safely read Codex hooks.json; no settings were changed.");
5334
+ }
5335
+ }
5336
+ function readCodexSettings(path) {
5337
+ const raw = current(path);
5338
+ let settings;
5339
+ try {
5340
+ settings = raw === null ? {} : JSON.parse(raw);
5341
+ if (!settings || typeof settings !== "object" || Array.isArray(settings))
5342
+ throw new Error;
5343
+ if (settings.hooks !== undefined) {
5344
+ if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks))
5345
+ throw new Error;
5346
+ for (const entries of Object.values(settings.hooks)) {
5347
+ if (!Array.isArray(entries) || entries.some((entry) => !entry || !Array.isArray(entry.hooks) || entry.hooks.some((hook) => !hook || typeof hook !== "object")))
5348
+ throw new Error;
5349
+ }
5350
+ }
5351
+ } catch {
5352
+ throw new Error("Invalid Codex hooks.json; repair it before installing or removing hooks.");
5353
+ }
5354
+ snapshots.set(settings, { path, raw });
5355
+ return settings;
5356
+ }
5357
+ function writeCodexSettings(path, settings) {
5358
+ const snapshot = snapshots.get(settings);
5359
+ if (!snapshot || snapshot.path !== path)
5360
+ throw new Error("Codex settings require a complete current read before writing.");
5361
+ mkdirSync4(dirname3(path), { recursive: true });
5362
+ const lock = `${path}.hooks-write.lock`;
5363
+ let fd;
5364
+ try {
5365
+ fd = openSync(lock, "wx", 384);
5366
+ } catch {
5367
+ throw new Error("Codex settings are being edited; retry after the active writer finishes.");
5368
+ }
5369
+ const temp = `${path}.${randomUUID3()}.tmp`;
5370
+ try {
5371
+ if (current(path) !== snapshot.raw)
5372
+ throw new Error("Codex settings changed since reading; re-read before retrying.");
5373
+ const next = JSON.stringify(settings, null, 2) + `
5374
+ `;
5375
+ writeFileSync3(temp, next, { flag: "wx", mode: 384 });
5376
+ if (current(path) !== snapshot.raw)
5377
+ throw new Error("Codex settings changed since reading; re-read before retrying.");
5378
+ renameSync2(temp, path);
5379
+ snapshots.set(settings, { path, raw: next });
5380
+ } finally {
5381
+ if (existsSync10(temp))
5382
+ unlinkSync(temp);
5383
+ closeSync(fd);
5384
+ unlinkSync(lock);
5385
+ }
5386
+ }
5387
+ var snapshots;
5388
+ var init_codex_settings = __esm(() => {
5389
+ snapshots = new WeakMap;
5390
+ });
5391
+
5235
5392
  // src/lib/installer.ts
5236
5393
  var exports_installer = {};
5237
5394
  __export(exports_installer, {
@@ -5252,8 +5409,8 @@ __export(exports_installer, {
5252
5409
  detectRewriteConflict: () => detectRewriteConflict,
5253
5410
  buildCodewithTomlFragment: () => buildCodewithTomlFragment
5254
5411
  });
5255
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, rmdirSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
5256
- import { join as join10, dirname as dirname3 } from "path";
5412
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
5413
+ import { join as join10, dirname as dirname4 } from "path";
5257
5414
  import { homedir as homedir4 } from "os";
5258
5415
  import { fileURLToPath as fileURLToPath2 } from "url";
5259
5416
  function normalizeHookName(name) {
@@ -5263,12 +5420,19 @@ function shortHookName(name) {
5263
5420
  return name.startsWith("hook-") ? name.slice("hook-".length) : name;
5264
5421
  }
5265
5422
  function removeHookEntriesByName(entries, hookName) {
5266
- return entries.filter((entry) => !entry.hooks?.some((h) => {
5267
- const match = h.command?.match(/^hooks run ([\w-]+)/);
5268
- return match && match[1] === hookName;
5269
- }));
5423
+ return entries.flatMap((entry) => {
5424
+ if (!Array.isArray(entry.hooks))
5425
+ return [entry];
5426
+ const kept = entry.hooks.filter((h) => {
5427
+ const match = h.command?.match(/^hooks run ([\w-]+)/);
5428
+ return !(match && match[1] === hookName);
5429
+ });
5430
+ return kept.length === entry.hooks.length ? [entry] : kept.length ? [{ ...entry, hooks: kept }] : [];
5431
+ });
5270
5432
  }
5271
5433
  function getTargetSettingsDir(target) {
5434
+ if (target === "codex")
5435
+ return ".codex";
5272
5436
  if (target === "codewith")
5273
5437
  return ".codewith";
5274
5438
  if (target === "gemini")
@@ -5276,6 +5440,8 @@ function getTargetSettingsDir(target) {
5276
5440
  return ".claude";
5277
5441
  }
5278
5442
  function getGlobalSettingsPathOverride(target) {
5443
+ if (target === "codex")
5444
+ return process.env.HASNA_HOOKS_CODEX_SETTINGS_PATH;
5279
5445
  if (target === "claude")
5280
5446
  return process.env.HASNA_HOOKS_CLAUDE_SETTINGS_PATH;
5281
5447
  if (target === "gemini")
@@ -5292,6 +5458,8 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
5292
5458
  if (globalOverride)
5293
5459
  return globalOverride;
5294
5460
  const dir = getTargetSettingsDir(target);
5461
+ if (target === "codex")
5462
+ return join10(scope === "project" ? process.cwd() : homedir4(), dir, "hooks.json");
5295
5463
  if (scope === "project") {
5296
5464
  return target === "codewith" ? join10(process.cwd(), dir, "config.toml") : join10(process.cwd(), dir, "settings.json");
5297
5465
  }
@@ -5305,13 +5473,15 @@ function getHookPath(name) {
5305
5473
  return join10(HOOKS_DIR, normalizeHookName(shortName));
5306
5474
  }
5307
5475
  function hookExists(name) {
5308
- return existsSync10(getHookPath(name));
5476
+ return existsSync11(getHookPath(name));
5309
5477
  }
5310
5478
  function readSettings(scope = "global", target = "claude") {
5311
5479
  const path = getSettingsPath(scope, target);
5480
+ if (target === "codex")
5481
+ return readCodexSettings(path);
5312
5482
  try {
5313
- if (existsSync10(path)) {
5314
- return JSON.parse(readFileSync4(path, "utf-8"));
5483
+ if (existsSync11(path)) {
5484
+ return JSON.parse(readFileSync5(path, "utf-8"));
5315
5485
  }
5316
5486
  } catch (error) {
5317
5487
  console.warn(`[hooks] Failed to read settings at ${path}: ${error instanceof Error ? error.message : String(error)}`);
@@ -5320,11 +5490,13 @@ function readSettings(scope = "global", target = "claude") {
5320
5490
  }
5321
5491
  function writeSettings(settings, scope = "global", target = "claude") {
5322
5492
  const path = getSettingsPath(scope, target);
5323
- const dir = dirname3(path);
5324
- if (!existsSync10(dir)) {
5325
- mkdirSync4(dir, { recursive: true });
5493
+ if (target === "codex")
5494
+ return writeCodexSettings(path, settings);
5495
+ const dir = dirname4(path);
5496
+ if (!existsSync11(dir)) {
5497
+ mkdirSync5(dir, { recursive: true });
5326
5498
  }
5327
- writeFileSync3(path, JSON.stringify(settings, null, 2) + `
5499
+ writeFileSync4(path, JSON.stringify(settings, null, 2) + `
5328
5500
  `);
5329
5501
  }
5330
5502
  function getTargetEventName(internalEvent, target) {
@@ -5347,6 +5519,8 @@ function codewithMatcher(matcher) {
5347
5519
  }
5348
5520
  function codewithTimeout(name) {
5349
5521
  switch (name) {
5522
+ case "mementos-context":
5523
+ return 12;
5350
5524
  case "knowledge-context":
5351
5525
  return 6;
5352
5526
  case "session-start":
@@ -5383,12 +5557,13 @@ function codewithStatusMessage(name) {
5383
5557
  return `Running ${name}`;
5384
5558
  }
5385
5559
  }
5386
- function buildCodewithTomlFragment(name, profile) {
5560
+ function buildCodewithTomlFragment(name, profile, mementos) {
5387
5561
  const shortName = shortHookName(name);
5388
5562
  const meta = resolveHookMeta(shortName);
5389
5563
  if (!meta)
5390
5564
  throw new Error(`Hook '${shortName}' not found`);
5391
- const command = profile ? `hooks run ${shortName} --profile ${profile}` : `hooks run ${shortName}`;
5565
+ validateMementosRegistration(shortName, mementos);
5566
+ const command = (profile ? `hooks run ${shortName} --profile ${profile}` : `hooks run ${shortName}`) + mementosCommandSuffix(mementos);
5392
5567
  const matcher = codewithMatcher(meta.matcher);
5393
5568
  const fragments = [];
5394
5569
  for (const event of getHookEvents(meta)) {
@@ -5413,21 +5588,21 @@ function buildCodewithTomlFragment(name, profile) {
5413
5588
  function readCodewithConfig(scope, configPath) {
5414
5589
  const path = getSettingsPath(scope, "codewith", configPath);
5415
5590
  try {
5416
- return existsSync10(path) ? readFileSync4(path, "utf-8") : "";
5591
+ return existsSync11(path) ? readFileSync5(path, "utf-8") : "";
5417
5592
  } catch {
5418
5593
  return "";
5419
5594
  }
5420
5595
  }
5421
5596
  function appendCodewithFragment(fragment, scope, configPath) {
5422
5597
  const path = getSettingsPath(scope, "codewith", configPath);
5423
- const dir = dirname3(path);
5424
- if (!existsSync10(dir))
5425
- mkdirSync4(dir, { recursive: true });
5426
- const existing = existsSync10(path) ? readFileSync4(path, "utf-8") : "";
5598
+ const dir = dirname4(path);
5599
+ if (!existsSync11(dir))
5600
+ mkdirSync5(dir, { recursive: true });
5601
+ const existing = existsSync11(path) ? readFileSync5(path, "utf-8") : "";
5427
5602
  const sep2 = existing.trim() ? `
5428
5603
 
5429
5604
  ` : "";
5430
- writeFileSync3(path, `${existing.replace(/\s*$/, "")}${sep2}${fragment}`);
5605
+ writeFileSync4(path, `${existing.replace(/\s*$/, "")}${sep2}${fragment}`);
5431
5606
  return path;
5432
5607
  }
5433
5608
  function detectConflict(name, scope, target) {
@@ -5470,14 +5645,14 @@ function detectRewriteConflict(name, scope, target, lookup = getHook) {
5470
5645
  }
5471
5646
  return;
5472
5647
  }
5473
- function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath) {
5648
+ function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath, mementos) {
5474
5649
  const shortName = shortHookName(name);
5475
5650
  if (!hookExists(shortName)) {
5476
5651
  return { hook: shortName, success: false, error: `Hook '${shortName}' not found`, target };
5477
5652
  }
5478
5653
  if (target === "codewith") {
5479
5654
  try {
5480
- const fragment = buildCodewithTomlFragment(shortName, profile);
5655
+ const fragment = buildCodewithTomlFragment(shortName, profile, mementos);
5481
5656
  if (codewithMode !== "write") {
5482
5657
  return {
5483
5658
  hook: shortName,
@@ -5524,18 +5699,25 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
5524
5699
  };
5525
5700
  }
5526
5701
  }
5527
- const registered = getRegisteredHooksForTarget(scope, target);
5528
- if (registered.includes(shortName) && !overwrite) {
5529
- return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
5530
- }
5531
- const conflict = detectConflict(shortName, scope, target);
5532
- const rewriteConflict = detectRewriteConflict(shortName, scope, target);
5533
- if (rewriteConflict) {
5534
- return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
5535
- }
5536
5702
  try {
5537
- registerHook(shortName, scope, target, profile);
5538
- return { hook: shortName, success: true, scope, target, ...conflict ? { conflict } : {} };
5703
+ const registered = getRegisteredHooksForTarget(scope, target);
5704
+ if (registered.includes(shortName) && !overwrite) {
5705
+ return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
5706
+ }
5707
+ const conflict = detectConflict(shortName, scope, target);
5708
+ const rewriteConflict = detectRewriteConflict(shortName, scope, target);
5709
+ if (rewriteConflict) {
5710
+ return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
5711
+ }
5712
+ registerHook(shortName, scope, target, profile, mementos);
5713
+ return {
5714
+ hook: shortName,
5715
+ success: true,
5716
+ scope,
5717
+ target,
5718
+ ...conflict ? { conflict } : {},
5719
+ ...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." } : {}
5720
+ };
5539
5721
  } catch (error) {
5540
5722
  return {
5541
5723
  hook: shortName,
@@ -5546,7 +5728,15 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
5546
5728
  }
5547
5729
  }
5548
5730
  function installHook(name, options = {}) {
5549
- const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath } = options;
5731
+ const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath, mementos } = options;
5732
+ try {
5733
+ validateMementosRegistration(name, mementos);
5734
+ } catch (error) {
5735
+ return { hook: name, success: false, target, error: error instanceof Error ? error.message : "Invalid Mementos options" };
5736
+ }
5737
+ if (!/^[\w-]+$/.test(name) || profile !== undefined && !/^[\w-]+$/.test(profile)) {
5738
+ return { hook: name, success: false, error: "Hook and profile names must contain only letters, digits, underscores or hyphens.", target };
5739
+ }
5550
5740
  if (target === "all") {
5551
5741
  const shortName = shortHookName(name);
5552
5742
  const meta = resolveHookMeta(shortName);
@@ -5561,7 +5751,7 @@ function installHook(name, options = {}) {
5561
5751
  };
5562
5752
  }
5563
5753
  }
5564
- const claudeResult = installForTarget(name, scope, overwrite, "claude", profile);
5754
+ const claudeResult = installForTarget(name, scope, overwrite, "claude", profile, "fragment", undefined, mementos);
5565
5755
  if (!claudeResult.success) {
5566
5756
  return {
5567
5757
  ...claudeResult,
@@ -5569,7 +5759,7 @@ function installHook(name, options = {}) {
5569
5759
  target: "all"
5570
5760
  };
5571
5761
  }
5572
- const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath);
5762
+ const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath, mementos);
5573
5763
  if (!codewithResult.success) {
5574
5764
  return {
5575
5765
  ...codewithResult,
@@ -5579,9 +5769,9 @@ function installHook(name, options = {}) {
5579
5769
  }
5580
5770
  return { ...claudeResult, target: "all" };
5581
5771
  }
5582
- return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath);
5772
+ return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath, mementos);
5583
5773
  }
5584
- function registerHook(name, scope = "global", target = "claude", profile) {
5774
+ function registerHook(name, scope = "global", target = "claude", profile, mementos) {
5585
5775
  const meta = resolveHookMeta(name);
5586
5776
  if (!meta)
5587
5777
  return;
@@ -5599,8 +5789,9 @@ function registerHook(name, scope = "global", target = "claude", profile) {
5599
5789
  const settings = readSettings(scope, target);
5600
5790
  if (!settings.hooks)
5601
5791
  settings.hooks = {};
5792
+ const retained = name === "mementos-context" && mementos === undefined ? retainedMementosRegistration(Object.values(settings.hooks).flatMap((groups) => Array.isArray(groups) ? groups.flatMap((group) => Array.isArray(group.hooks) ? group.hooks.map((hook) => hook.command).filter((command) => typeof command === "string") : []) : [])) : undefined;
5602
5793
  removeHookFromAllEvents(settings, name);
5603
- const hookCommand = profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`;
5794
+ const hookCommand = (profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`) + mementosCommandSuffix(mementos ?? retained);
5604
5795
  for (const eventKey of uniqueEventKeys) {
5605
5796
  if (!settings.hooks[eventKey])
5606
5797
  settings.hooks[eventKey] = [];
@@ -5645,10 +5836,12 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
5645
5836
  if (target === "codewith") {
5646
5837
  const config = readCodewithConfig(scope);
5647
5838
  const registered2 = [];
5648
- const re = /command\s*=\s*["']hooks run ([\w-]+)(?:\s+--profile\s+[\w-]+)?["']/g;
5839
+ const re = /^\s*command\s*=\s*["']([^"'\n]+)["']\s*$/gm;
5649
5840
  let match;
5650
5841
  while (match = re.exec(config)) {
5651
- registered2.push(match[1]);
5842
+ const name = registrationName(match[1]);
5843
+ if (name)
5844
+ registered2.push(name);
5652
5845
  }
5653
5846
  return [...new Set(registered2)];
5654
5847
  }
@@ -5659,12 +5852,9 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
5659
5852
  for (const eventKey of Object.keys(settings.hooks)) {
5660
5853
  for (const entry of settings.hooks[eventKey]) {
5661
5854
  for (const hook of entry.hooks || []) {
5662
- const newMatch = hook.command?.match(/^hooks run ([\w-]+)(?:\s+--profile\s+[\w-]+)?$/);
5663
- const oldMatch = hook.command?.match(/^hook-([\w-]+)$/);
5664
- const match = newMatch || oldMatch;
5665
- if (match) {
5666
- registered.push(match[1]);
5667
- }
5855
+ const name = registrationName(hook.command);
5856
+ if (name)
5857
+ registered.push(name);
5668
5858
  }
5669
5859
  }
5670
5860
  }
@@ -5678,7 +5868,8 @@ function removeHook(name, scope = "global", target = "claude") {
5678
5868
  if (target === "all") {
5679
5869
  const claudeRemoved = removeHookForTarget(shortName, scope, "claude");
5680
5870
  const geminiRemoved = removeHookForTarget(shortName, scope, "gemini");
5681
- return claudeRemoved || geminiRemoved;
5871
+ const codexRemoved = removeHookForTarget(shortName, scope, "codex");
5872
+ return claudeRemoved || geminiRemoved || codexRemoved;
5682
5873
  }
5683
5874
  if (target === "codewith") {
5684
5875
  return false;
@@ -5695,9 +5886,9 @@ function removeHookForTarget(name, scope, target) {
5695
5886
  }
5696
5887
  function readSettingsAt(path) {
5697
5888
  try {
5698
- if (!existsSync10(path))
5889
+ if (!existsSync11(path))
5699
5890
  return;
5700
- const raw = readFileSync4(path, "utf-8");
5891
+ const raw = readFileSync5(path, "utf-8");
5701
5892
  return { raw, settings: JSON.parse(raw) };
5702
5893
  } catch {
5703
5894
  return;
@@ -5730,7 +5921,9 @@ function pruneStaleRegistrations(scope = "global") {
5730
5921
  commands.add(finding.command);
5731
5922
  staleCommandsByEvent.set(finding.event, commands);
5732
5923
  }
5733
- const settings = parsed.settings;
5924
+ const settings = target === "codex" ? readCodexSettings(path) : parsed.settings;
5925
+ if (target === "codex" && readFileSync5(path, "utf8") !== parsed.raw)
5926
+ throw new Error("Codex settings changed before pruning; retry.");
5734
5927
  for (const [eventKey, commands] of staleCommandsByEvent) {
5735
5928
  const entries = settings.hooks?.[eventKey];
5736
5929
  if (!Array.isArray(entries))
@@ -5755,7 +5948,7 @@ function pruneStaleRegistrations(scope = "global") {
5755
5948
  if (settings.hooks && Object.keys(settings.hooks).length === 0)
5756
5949
  delete settings.hooks;
5757
5950
  const backupPath = `${path}.bak`;
5758
- writeFileSync3(backupPath, parsed.raw);
5951
+ writeFileSync4(backupPath, parsed.raw);
5759
5952
  writeSettings(settings, scope, target);
5760
5953
  results.push({ file: path, removed: stale, backupPath });
5761
5954
  }
@@ -5763,7 +5956,7 @@ function pruneStaleRegistrations(scope = "global") {
5763
5956
  }
5764
5957
  function removeCodewithHookEntry(configText, name) {
5765
5958
  const sections = configText.split(/\n\s*\n/);
5766
- const nameRe = new RegExp(`^command\\s*=\\s*"hooks run ${escapeRegExp(name)}(?:\\s+--profile\\s+[\\w-]+)?"$`);
5959
+ const commandRe = /^command\s*=\s*"([^"\n]+)"$/;
5767
5960
  const entryHeaderRe = /^\[\[hooks\.([\w]+)\.hooks\]\]$/;
5768
5961
  const eventHeaderRe = /^\[\[hooks\.([\w]+)\]\]$/;
5769
5962
  const eventsWithEntries = new Set;
@@ -5776,7 +5969,7 @@ function removeCodewithHookEntry(configText, name) {
5776
5969
  if (entryMatch) {
5777
5970
  const event = entryMatch[1];
5778
5971
  eventsWithEntries.add(event);
5779
- if (lines.some((line) => nameRe.test(line.trim()))) {
5972
+ if (lines.some((line) => registrationName(commandRe.exec(line.trim())?.[1]) === name)) {
5780
5973
  removed = true;
5781
5974
  continue;
5782
5975
  }
@@ -5817,10 +6010,10 @@ function codewithHasHookEntryInText(configText, name) {
5817
6010
  }
5818
6011
  function codewithHasHookEntry(name, scope = "global") {
5819
6012
  const path = getSettingsPath(scope, "codewith");
5820
- if (!existsSync10(path))
6013
+ if (!existsSync11(path))
5821
6014
  return false;
5822
6015
  try {
5823
- return codewithHasHookEntryInText(readFileSync4(path, "utf-8"), name);
6016
+ return codewithHasHookEntryInText(readFileSync5(path, "utf-8"), name);
5824
6017
  } catch {
5825
6018
  return false;
5826
6019
  }
@@ -5834,7 +6027,7 @@ function removeStoreDirFailClosed(dir) {
5834
6027
  }
5835
6028
  for (const entry of entries) {
5836
6029
  if (!entry.isDirectory()) {
5837
- unlinkSync(join10(dir, entry.name));
6030
+ unlinkSync2(join10(dir, entry.name));
5838
6031
  }
5839
6032
  }
5840
6033
  rmdirSync(dir);
@@ -5844,14 +6037,14 @@ function uninstallHook(name, scope = "global", target = "claude") {
5844
6037
  const custom = readCustomManifest(shortName);
5845
6038
  const bundledMeta = custom ? undefined : getHook(shortName);
5846
6039
  const existsInTarget = (t) => getRegisteredHooksForTarget("global", t).includes(shortName) || getRegisteredHooksForTarget("project", t).includes(shortName);
5847
- const registeredClaudeOrGemini = target === "all" ? existsInTarget("claude") || existsInTarget("gemini") : false;
6040
+ const registeredClaudeOrGemini = target === "all" ? JSON_SETTINGS_TARGETS.some(existsInTarget) : false;
5848
6041
  const registeredGlobal = registeredClaudeOrGemini || getRegisteredHooksForTarget("global", target === "all" ? "claude" : target).includes(shortName);
5849
6042
  const registeredProject = registeredClaudeOrGemini || getRegisteredHooksForTarget("project", target === "all" ? "claude" : target).includes(shortName);
5850
6043
  const codewithOnly = (target === "codewith" || target === "all") && codewithHasHookEntry(shortName, scope);
5851
6044
  if (!custom && !bundledMeta && !registeredGlobal && !registeredProject && !codewithOnly) {
5852
6045
  return { name: shortName, removed: false, source: null, settingsScopes: [], storeDirRemoved: false, pinRemoved: false, dbRecordRemoved: false, registrationsRemaining: [], error: `Hook '${shortName}' not found` };
5853
6046
  }
5854
- const writableTargets = target === "all" ? ["claude", "gemini"] : target === "codewith" ? [] : [target];
6047
+ const writableTargets = target === "all" ? JSON_SETTINGS_TARGETS : target === "codewith" ? [] : [target];
5855
6048
  const scopes = [];
5856
6049
  for (const s of ["global", "project"]) {
5857
6050
  for (const t of writableTargets) {
@@ -5865,11 +6058,11 @@ function uninstallHook(name, scope = "global", target = "claude") {
5865
6058
  const registrationsRemaining = [];
5866
6059
  if (target === "codewith" || target === "all") {
5867
6060
  const codewithPath = getSettingsPath(scope, "codewith");
5868
- if (existsSync10(codewithPath)) {
5869
- const before = readFileSync4(codewithPath, "utf-8");
6061
+ if (existsSync11(codewithPath)) {
6062
+ const before = readFileSync5(codewithPath, "utf-8");
5870
6063
  const after = removeCodewithHookEntry(before, shortName);
5871
6064
  if (after.removed) {
5872
- writeFileSync3(codewithPath, after.text, "utf-8");
6065
+ writeFileSync4(codewithPath, after.text, "utf-8");
5873
6066
  }
5874
6067
  if (codewithHasHookEntryInText(after.removed ? after.text : before, shortName)) {
5875
6068
  registrationsRemaining.push("codewith");
@@ -5912,8 +6105,9 @@ var init_installer = __esm(() => {
5912
6105
  init_manifest();
5913
6106
  init_registration();
5914
6107
  init_store();
5915
- __dirname2 = dirname3(fileURLToPath2(import.meta.url));
5916
- HOOKS_DIR = existsSync10(join10(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join10(__dirname2, "..", "..", "hooks") : join10(__dirname2, "..", "hooks");
6108
+ init_codex_settings();
6109
+ __dirname2 = dirname4(fileURLToPath2(import.meta.url));
6110
+ HOOKS_DIR = existsSync11(join10(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join10(__dirname2, "..", "..", "hooks") : join10(__dirname2, "..", "hooks");
5917
6111
  EVENT_MAP = {
5918
6112
  claude: {
5919
6113
  PreToolUse: "PreToolUse",
@@ -5922,7 +6116,7 @@ var init_installer = __esm(() => {
5922
6116
  Notification: "Notification",
5923
6117
  SessionStart: "SessionStart",
5924
6118
  SessionEnd: "SessionEnd",
5925
- UserPromptSubmit: null,
6119
+ UserPromptSubmit: "UserPromptSubmit",
5926
6120
  SubagentStart: null
5927
6121
  },
5928
6122
  gemini: {
@@ -5944,10 +6138,20 @@ var init_installer = __esm(() => {
5944
6138
  SessionEnd: null,
5945
6139
  UserPromptSubmit: "UserPromptSubmit",
5946
6140
  SubagentStart: "SubagentStart"
6141
+ },
6142
+ codex: {
6143
+ PreToolUse: "PreToolUse",
6144
+ PostToolUse: "PostToolUse",
6145
+ Stop: "Stop",
6146
+ Notification: null,
6147
+ SessionStart: "SessionStart",
6148
+ SessionEnd: "SessionEnd",
6149
+ UserPromptSubmit: "UserPromptSubmit",
6150
+ SubagentStart: "SubagentStart"
5947
6151
  }
5948
6152
  };
5949
6153
  getInstalledHooks = getRegisteredHooks;
5950
- JSON_SETTINGS_TARGETS = ["claude", "gemini"];
6154
+ JSON_SETTINGS_TARGETS = ["claude", "gemini", "codex"];
5951
6155
  });
5952
6156
 
5953
6157
  // src/lib/redact.ts
@@ -6016,21 +6220,21 @@ var init_redact = __esm(() => {
6016
6220
  });
6017
6221
 
6018
6222
  // src/lib/profiles.ts
6019
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4, readdirSync as readdirSync4, rmSync, cpSync as cpSync2 } from "fs";
6223
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync4, rmSync, cpSync as cpSync2 } from "fs";
6020
6224
  import { join as join11 } from "path";
6021
6225
  function getProfilesDir() {
6022
6226
  const newDir = join11(getEffectiveDataRoot(), "profiles");
6023
6227
  const oldDir = join11(getHomeDir(), ".hooks", "profiles");
6024
- if (!existsSync11(newDir) && existsSync11(oldDir)) {
6025
- mkdirSync5(newDir, { recursive: true });
6228
+ if (!existsSync12(newDir) && existsSync12(oldDir)) {
6229
+ mkdirSync6(newDir, { recursive: true });
6026
6230
  cpSync2(oldDir, newDir, { recursive: true });
6027
6231
  }
6028
6232
  return newDir;
6029
6233
  }
6030
6234
  function ensureProfilesDir() {
6031
6235
  const dir = getProfilesDir();
6032
- if (!existsSync11(dir)) {
6033
- mkdirSync5(dir, { recursive: true });
6236
+ if (!existsSync12(dir)) {
6237
+ mkdirSync6(dir, { recursive: true });
6034
6238
  }
6035
6239
  }
6036
6240
  function profilePath(id) {
@@ -6053,30 +6257,30 @@ function createProfile(input) {
6053
6257
  if (input.name) {
6054
6258
  profile.name = input.name;
6055
6259
  }
6056
- writeFileSync4(profilePath(id), JSON.stringify(profile, null, 2) + `
6260
+ writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
6057
6261
  `);
6058
6262
  return profile;
6059
6263
  }
6060
6264
  function getProfile(id) {
6061
6265
  const path = profilePath(id);
6062
6266
  try {
6063
- if (!existsSync11(path))
6267
+ if (!existsSync12(path))
6064
6268
  return null;
6065
- return JSON.parse(readFileSync5(path, "utf-8"));
6269
+ return JSON.parse(readFileSync6(path, "utf-8"));
6066
6270
  } catch {
6067
6271
  return null;
6068
6272
  }
6069
6273
  }
6070
6274
  function listProfiles() {
6071
6275
  const dir = getProfilesDir();
6072
- if (!existsSync11(dir))
6276
+ if (!existsSync12(dir))
6073
6277
  return [];
6074
6278
  try {
6075
6279
  const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
6076
6280
  const profiles = [];
6077
6281
  for (const file of files) {
6078
6282
  try {
6079
- const content = readFileSync5(join11(dir, file), "utf-8");
6283
+ const content = readFileSync6(join11(dir, file), "utf-8");
6080
6284
  profiles.push(JSON.parse(content));
6081
6285
  } catch {}
6082
6286
  }
@@ -6090,7 +6294,7 @@ function touchProfile(id) {
6090
6294
  if (!profile)
6091
6295
  return;
6092
6296
  profile.last_seen_at = new Date().toISOString();
6093
- writeFileSync4(profilePath(id), JSON.stringify(profile, null, 2) + `
6297
+ writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
6094
6298
  `);
6095
6299
  }
6096
6300
  function exportProfiles() {
@@ -6106,11 +6310,11 @@ function importProfiles(profiles) {
6106
6310
  continue;
6107
6311
  }
6108
6312
  const path = profilePath(profile.agent_id);
6109
- if (existsSync11(path)) {
6313
+ if (existsSync12(path)) {
6110
6314
  skipped++;
6111
6315
  continue;
6112
6316
  }
6113
- writeFileSync4(path, JSON.stringify(profile, null, 2) + `
6317
+ writeFileSync5(path, JSON.stringify(profile, null, 2) + `
6114
6318
  `);
6115
6319
  imported++;
6116
6320
  }
@@ -6123,7 +6327,7 @@ var init_profiles = __esm(() => {
6123
6327
  // ../../node_modules/.bun/@hasna+contracts@1.0.2+e0db24ae03127387/node_modules/@hasna/contracts/dist/client/transport.js
6124
6328
  import { isIP as isIP2 } from "net";
6125
6329
  import { spawnSync } from "child_process";
6126
- import { closeSync, fstatSync, openSync, readFileSync as readFileSync6 } from "fs";
6330
+ import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as readFileSync7 } from "fs";
6127
6331
  import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
6128
6332
  import { createRequire as createRequire2 } from "module";
6129
6333
  import { hostname as osHostname } from "os";
@@ -6227,7 +6431,7 @@ function readAppConfigFile(path) {
6227
6431
  };
6228
6432
  let fd = -1;
6229
6433
  try {
6230
- fd = openSync(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
6434
+ fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
6231
6435
  } catch (error) {
6232
6436
  const code = error.code;
6233
6437
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -6248,7 +6452,7 @@ function readAppConfigFile(path) {
6248
6452
  unsafe("the file is not owned by the current user");
6249
6453
  if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
6250
6454
  unsafe("the file exceeds the size limit");
6251
- const bytes = readFileSync6(fd);
6455
+ const bytes = readFileSync7(fd);
6252
6456
  const after = fstatSync(fd);
6253
6457
  if (!configFileReadsCoherent(before, after)) {
6254
6458
  unsafe("the file changed while being read");
@@ -6256,7 +6460,7 @@ function readAppConfigFile(path) {
6256
6460
  return parseEnvFile(bytes.toString("utf8"));
6257
6461
  } finally {
6258
6462
  if (fd !== -1)
6259
- closeSync(fd);
6463
+ closeSync2(fd);
6260
6464
  }
6261
6465
  }
6262
6466
  function readCredentialFile(path, apiKeyKeys) {
@@ -7124,8 +7328,8 @@ var init_db_writer = __esm(() => {
7124
7328
  });
7125
7329
 
7126
7330
  // src/lib/hook-env.ts
7127
- import { dirname as dirname4, isAbsolute as isAbsolute3 } from "path";
7128
- import { existsSync as existsSync12, realpathSync as realpathSync2, statSync } from "fs";
7331
+ import { dirname as dirname5, isAbsolute as isAbsolute3 } from "path";
7332
+ import { existsSync as existsSync13, realpathSync as realpathSync2, statSync } from "fs";
7129
7333
  import { tmpdir } from "os";
7130
7334
  function isDeniedEnvName(name) {
7131
7335
  const upper = name.toUpperCase();
@@ -7173,9 +7377,9 @@ function isUnsafePathEntry(entry, home) {
7173
7377
  }
7174
7378
  function sanitizeHookPath(pathValue, home) {
7175
7379
  const baseline = PATH_BASELINE.slice();
7176
- if (process.platform === "darwin" && existsSync12("/opt/homebrew/bin"))
7380
+ if (process.platform === "darwin" && existsSync13("/opt/homebrew/bin"))
7177
7381
  baseline.push("/opt/homebrew/bin");
7178
- const bunDir = dirname4(process.execPath);
7382
+ const bunDir = dirname5(process.execPath);
7179
7383
  const seen = new Set;
7180
7384
  const out = [];
7181
7385
  for (const dir of baseline) {
@@ -7299,8 +7503,8 @@ __export(exports_run, {
7299
7503
  HookTimeoutError: () => HookTimeoutError
7300
7504
  });
7301
7505
  import { randomBytes as randomBytes2 } from "crypto";
7302
- import { rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
7303
- import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join13 } from "path";
7506
+ import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
7507
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute4, join as join13 } from "path";
7304
7508
  function extensionOf(scriptPath) {
7305
7509
  const base = basename2(scriptPath);
7306
7510
  const dot = base.lastIndexOf(".");
@@ -7369,7 +7573,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
7369
7573
  return chunks.join("");
7370
7574
  }
7371
7575
  async function executeVerifiedScript(options) {
7372
- const scriptDir = dirname5(options.scriptPath);
7576
+ const scriptDir = dirname6(options.scriptPath);
7373
7577
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
7374
7578
  const tempPath = join13(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
7375
7579
  let proc = null;
@@ -7379,7 +7583,7 @@ async function executeVerifiedScript(options) {
7379
7583
  killGroup(proc, { leaderAlive: proc !== null && proc.exitCode === null });
7380
7584
  }, options.timeout) : null;
7381
7585
  try {
7382
- writeFileSync5(tempPath, options.content, { flag: "wx", mode: 384 });
7586
+ writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
7383
7587
  proc = Bun.spawn([...interpreter.command, isAbsolute4(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
7384
7588
  stdin: new Response(options.stdin),
7385
7589
  stdout: "pipe",
@@ -7462,15 +7666,15 @@ __export(exports_custom_install, {
7462
7666
  installCustomSource: () => installCustomSource,
7463
7667
  ensureCustomDir: () => ensureCustomDir
7464
7668
  });
7465
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
7669
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
7466
7670
  import { join as join14 } from "path";
7467
7671
  import { tmpdir as tmpdir2 } from "os";
7468
7672
  function manifestAt(dir) {
7469
7673
  const path = join14(dir, "manifest.json");
7470
- if (!existsSync13(path))
7674
+ if (!existsSync14(path))
7471
7675
  return;
7472
7676
  try {
7473
- return parseManifest(readFileSync7(path, "utf-8"));
7677
+ return parseManifest(readFileSync8(path, "utf-8"));
7474
7678
  } catch {
7475
7679
  return;
7476
7680
  }
@@ -7513,7 +7717,7 @@ function isCustomSource(value) {
7513
7717
  return true;
7514
7718
  if (value.startsWith("http://") || value.startsWith("https://"))
7515
7719
  return true;
7516
- return existsSync13(value);
7720
+ return existsSync14(value);
7517
7721
  }
7518
7722
  async function installCustomSource(source) {
7519
7723
  if (isGitUrl(source)) {
@@ -7527,11 +7731,11 @@ async function installCustomSource(source) {
7527
7731
  if (source.startsWith("http://") || source.startsWith("https://")) {
7528
7732
  return installManifestUrl(source);
7529
7733
  }
7530
- if (!existsSync13(source)) {
7734
+ if (!existsSync14(source)) {
7531
7735
  throw new Error(`No such local path: ${source}`);
7532
7736
  }
7533
7737
  if (source.endsWith("manifest.json")) {
7534
- const manifest = parseManifest(readFileSync7(source, "utf-8"));
7738
+ const manifest = parseManifest(readFileSync8(source, "utf-8"));
7535
7739
  return writeFromManifest(manifest, source.substring(0, source.lastIndexOf("/")), source, "local");
7536
7740
  }
7537
7741
  return copyManifestDir(source, source, "local");
@@ -7562,7 +7766,7 @@ async function installManifestUrl(url) {
7562
7766
  }
7563
7767
  function ensureCustomDir() {
7564
7768
  const dir = getCustomHooksDir();
7565
- mkdirSync6(dir, { recursive: true });
7769
+ mkdirSync7(dir, { recursive: true });
7566
7770
  return dir;
7567
7771
  }
7568
7772
  var init_custom_install = __esm(() => {
@@ -7581,12 +7785,12 @@ __export(exports_sync, {
7581
7785
  commitSyncArtifacts: () => commitSyncArtifacts
7582
7786
  });
7583
7787
  import { createHash as createHash2 } from "crypto";
7584
- import { readFileSync as readFileSync8 } from "fs";
7788
+ import { readFileSync as readFileSync9 } from "fs";
7585
7789
  function sha256OfText(text) {
7586
7790
  return createHash2("sha256").update(text).digest("hex");
7587
7791
  }
7588
7792
  function sha256OfPath(path) {
7589
- return createHash2("sha256").update(readFileSync8(path)).digest("hex");
7793
+ return createHash2("sha256").update(readFileSync9(path)).digest("hex");
7590
7794
  }
7591
7795
  function collectBundledCatalog() {
7592
7796
  const out = [];
@@ -8536,13 +8740,13 @@ __export(exports_serve, {
8536
8740
  SERVE_HOST: () => SERVE_HOST,
8537
8741
  DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
8538
8742
  });
8539
- import { readFileSync as readFileSync9 } from "fs";
8540
- import { dirname as dirname6, join as join15 } from "path";
8743
+ import { readFileSync as readFileSync10 } from "fs";
8744
+ import { dirname as dirname7, join as join15 } from "path";
8541
8745
  import { fileURLToPath as fileURLToPath3 } from "url";
8542
8746
  function packageVersion() {
8543
8747
  try {
8544
- const pkgPath = join15(dirname6(fileURLToPath3(import.meta.url)), "..", "package.json");
8545
- return JSON.parse(readFileSync9(pkgPath, "utf-8")).version || "0.0.0";
8748
+ const pkgPath = join15(dirname7(fileURLToPath3(import.meta.url)), "..", "package.json");
8749
+ return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
8546
8750
  } catch {
8547
8751
  return "0.0.0";
8548
8752
  }
@@ -8843,17 +9047,17 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
8843
9047
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8844
9048
  import { z as z2 } from "zod";
8845
9049
  import { createServer } from "http";
8846
- import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
8847
- import { join as join16, dirname as dirname7 } from "path";
9050
+ import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
9051
+ import { join as join16, dirname as dirname8 } from "path";
8848
9052
  import { fileURLToPath as fileURLToPath4 } from "url";
8849
9053
  async function runVerifiedHook(name, stdin, timeoutMs) {
8850
9054
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
8851
9055
  const resolved = resolveHook2(name);
8852
9056
  const hookScript = resolveScriptPath2(name);
8853
- if (!resolved || !hookScript || !existsSync14(hookScript)) {
9057
+ if (!resolved || !hookScript || !existsSync15(hookScript)) {
8854
9058
  throw new Error(`Hook '${name}' not found`);
8855
9059
  }
8856
- const content = readFileSync10(hookScript);
9060
+ const content = readFileSync11(hookScript);
8857
9061
  const sha = sha256Of(content);
8858
9062
  const check = checkScriptHash(name, sha);
8859
9063
  if (!check.ok) {
@@ -9058,7 +9262,7 @@ function createHooksServer() {
9058
9262
  const settingsPath = getSettingsPath(scope);
9059
9263
  const issues = [];
9060
9264
  const healthy = [];
9061
- const settingsExist = existsSync14(settingsPath);
9265
+ const settingsExist = existsSync15(settingsPath);
9062
9266
  if (!settingsExist) {
9063
9267
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
9064
9268
  }
@@ -9068,7 +9272,7 @@ function createHooksServer() {
9068
9272
  const meta = custom ? resolveHookMeta(name) : getHook(name);
9069
9273
  let hookHealthy = true;
9070
9274
  if (custom) {
9071
- if (!existsSync14(custom.scriptPath)) {
9275
+ if (!existsSync15(custom.scriptPath)) {
9072
9276
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
9073
9277
  hookHealthy = false;
9074
9278
  }
@@ -9078,14 +9282,14 @@ function createHooksServer() {
9078
9282
  continue;
9079
9283
  }
9080
9284
  const hookDir = getHookPath(name);
9081
- if (!existsSync14(join16(hookDir, "src", "hook.ts"))) {
9285
+ if (!existsSync15(join16(hookDir, "src", "hook.ts"))) {
9082
9286
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
9083
9287
  hookHealthy = false;
9084
9288
  }
9085
9289
  }
9086
9290
  if (meta && settingsExist) {
9087
9291
  try {
9088
- const settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
9292
+ const settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
9089
9293
  if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
9090
9294
  const eventName = meta.event.split(":")[0];
9091
9295
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
@@ -9124,8 +9328,8 @@ function createHooksServer() {
9124
9328
  const hookPath = getHookPath(name);
9125
9329
  const readmePath = join16(hookPath, "README.md");
9126
9330
  let readme = "";
9127
- if (existsSync14(readmePath)) {
9128
- readme = readFileSync10(readmePath, "utf-8");
9331
+ if (existsSync15(readmePath)) {
9332
+ readme = readFileSync11(readmePath, "utf-8");
9129
9333
  }
9130
9334
  if (verbose) {
9131
9335
  return { content: [{ type: "text", text: JSON.stringify({ ...meta, readme }) }] };
@@ -9308,7 +9512,7 @@ function createHooksServer() {
9308
9512
  const ctx = {
9309
9513
  scope,
9310
9514
  settings_path: settingsPath,
9311
- settings_exists: existsSync14(settingsPath),
9515
+ settings_exists: existsSync15(settingsPath),
9312
9516
  registered_hooks: hooks,
9313
9517
  hook_count: hooks.length,
9314
9518
  healthy,
@@ -9454,8 +9658,8 @@ function createHooksServer() {
9454
9658
  const settingsPath = getSettingsPath(scope);
9455
9659
  let settings = {};
9456
9660
  try {
9457
- if (existsSync14(settingsPath))
9458
- settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
9661
+ if (existsSync15(settingsPath))
9662
+ settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
9459
9663
  } catch {}
9460
9664
  if (!settings.hooks)
9461
9665
  settings.hooks = {};
@@ -9463,10 +9667,10 @@ function createHooksServer() {
9463
9667
  if (!disabled.includes(name))
9464
9668
  disabled.push(name);
9465
9669
  settings.hooks.__disabled = disabled;
9466
- const { writeFileSync: writeFileSync6, mkdirSync: mkdirSync7 } = await import("fs");
9467
- const { dirname: dirname8 } = await import("path");
9468
- mkdirSync7(dirname8(settingsPath), { recursive: true });
9469
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + `
9670
+ const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync8 } = await import("fs");
9671
+ const { dirname: dirname9 } = await import("path");
9672
+ mkdirSync8(dirname9(settingsPath), { recursive: true });
9673
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
9470
9674
  `);
9471
9675
  return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
9472
9676
  });
@@ -9477,15 +9681,15 @@ function createHooksServer() {
9477
9681
  const settingsPath = getSettingsPath(scope);
9478
9682
  let settings = {};
9479
9683
  try {
9480
- if (existsSync14(settingsPath))
9481
- settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
9684
+ if (existsSync15(settingsPath))
9685
+ settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
9482
9686
  } catch {}
9483
9687
  if (settings.hooks?.__disabled) {
9484
9688
  settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
9485
9689
  if (settings.hooks.__disabled.length === 0)
9486
9690
  delete settings.hooks.__disabled;
9487
- const { writeFileSync: writeFileSync6 } = await import("fs");
9488
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + `
9691
+ const { writeFileSync: writeFileSync7 } = await import("fs");
9692
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
9489
9693
  `);
9490
9694
  }
9491
9695
  return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, scope }) }] };
@@ -9848,13 +10052,13 @@ var init_server = __esm(() => {
9848
10052
  init_redact();
9849
10053
  init_authority();
9850
10054
  init_storage();
9851
- __dirname3 = dirname7(fileURLToPath4(import.meta.url));
10055
+ __dirname3 = dirname8(fileURLToPath4(import.meta.url));
9852
10056
  pkg = { name: "@hasna/hooks", version: "0.0.0" };
9853
10057
  try {
9854
10058
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
9855
10059
  const p = join16(__dirname3, rel);
9856
- if (existsSync14(p)) {
9857
- pkg = JSON.parse(readFileSync10(p, "utf-8"));
10060
+ if (existsSync15(p)) {
10061
+ pkg = JSON.parse(readFileSync11(p, "utf-8"));
9858
10062
  break;
9859
10063
  }
9860
10064
  }
@@ -11596,8 +11800,8 @@ var {
11596
11800
 
11597
11801
  // src/cli/index.tsx
11598
11802
  import chalk2 from "chalk";
11599
- import { existsSync as existsSync15, readFileSync as readFileSync11, rmSync as rmSync4 } from "fs";
11600
- import { join as join17, dirname as dirname8 } from "path";
11803
+ import { existsSync as existsSync16, readFileSync as readFileSync12, rmSync as rmSync4 } from "fs";
11804
+ import { join as join17, dirname as dirname9 } from "path";
11601
11805
  import { fileURLToPath as fileURLToPath5 } from "url";
11602
11806
 
11603
11807
  // src/cli/components/App.tsx
@@ -12439,7 +12643,7 @@ function InstallProgress({
12439
12643
  onComplete
12440
12644
  }) {
12441
12645
  const [results, setResults] = useState6([]);
12442
- const [current, setCurrent] = useState6(0);
12646
+ const [current2, setCurrent] = useState6(0);
12443
12647
  const [installing, setInstalling] = useState6(true);
12444
12648
  useEffect5(() => {
12445
12649
  const install = async () => {
@@ -12463,12 +12667,12 @@ function InstallProgress({
12463
12667
  marginBottom: 1,
12464
12668
  children: /* @__PURE__ */ jsxDEV6(Text9, {
12465
12669
  bold: true,
12466
- children: installing ? `Installing hooks (${current + 1}/${hooks.length})...` : "Installation complete!"
12670
+ children: installing ? `Installing hooks (${current2 + 1}/${hooks.length})...` : "Installation complete!"
12467
12671
  }, undefined, false, undefined, this)
12468
12672
  }, undefined, false, undefined, this),
12469
12673
  hooks.map((name, i) => {
12470
12674
  const result = results[i];
12471
- const isCurrent = i === current && installing;
12675
+ const isCurrent = i === current2 && installing;
12472
12676
  const meta = getHook(name);
12473
12677
  return /* @__PURE__ */ jsxDEV6(Box7, {
12474
12678
  flexDirection: "column",
@@ -12785,9 +12989,9 @@ init_local_opt_in();
12785
12989
  init_transport2();
12786
12990
  init_db();
12787
12991
  import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
12788
- var __dirname4 = dirname8(fileURLToPath5(import.meta.url));
12789
- var pkgPath = existsSync15(join17(__dirname4, "..", "package.json")) ? join17(__dirname4, "..", "package.json") : join17(__dirname4, "..", "..", "package.json");
12790
- var pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
12992
+ var __dirname4 = dirname9(fileURLToPath5(import.meta.url));
12993
+ var pkgPath = existsSync16(join17(__dirname4, "..", "package.json")) ? join17(__dirname4, "..", "package.json") : join17(__dirname4, "..", "..", "package.json");
12994
+ var pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
12791
12995
  var program2 = new Command;
12792
12996
  function resolveScope(options) {
12793
12997
  if (options.project)
@@ -12797,12 +13001,45 @@ function resolveScope(options) {
12797
13001
  function resolveTarget(options) {
12798
13002
  if (options.target === "gemini")
12799
13003
  return "gemini";
13004
+ if (options.target === "codex")
13005
+ return "codex";
12800
13006
  if (options.target === "codewith")
12801
13007
  return "codewith";
12802
13008
  if (options.target === "all")
12803
13009
  return "all";
12804
13010
  return "claude";
12805
13011
  }
13012
+ function resolveConcreteTarget(options) {
13013
+ if (options.target === "gemini")
13014
+ return "gemini";
13015
+ if (options.target === "codex")
13016
+ return "codex";
13017
+ if (options.target === "codewith")
13018
+ return "codewith";
13019
+ return "claude";
13020
+ }
13021
+ function formatSettingsPath(scope, target) {
13022
+ if (target === "all")
13023
+ return "target-specific settings";
13024
+ const actual = getSettingsPath(scope, target);
13025
+ if (scope === "project") {
13026
+ if (target === "codewith")
13027
+ return ".codewith/config.toml";
13028
+ if (target === "gemini")
13029
+ return ".gemini/settings.json";
13030
+ if (target === "codex")
13031
+ return ".codex/hooks.json";
13032
+ return ".claude/settings.json";
13033
+ }
13034
+ if (target === "codewith") {
13035
+ return process.env.HASNA_HOOKS_CODEWITH_CONFIG_PATH ? "$HASNA_HOOKS_CODEWITH_CONFIG_PATH" : "~/.codewith/config.toml";
13036
+ }
13037
+ if (target === "gemini")
13038
+ return "~/.gemini/settings.json";
13039
+ if (target === "codex")
13040
+ return "~/.codex/hooks.json";
13041
+ return actual === getSettingsPath("global", "claude") ? "~/.claude/settings.json" : actual;
13042
+ }
12806
13043
  function parseLimit(value, fallback, max) {
12807
13044
  const parsed = value ? parseInt(value, 10) : fallback;
12808
13045
  if (!Number.isFinite(parsed) || parsed <= 0)
@@ -12834,7 +13071,7 @@ async function openLocalStoreOrExit(json2) {
12834
13071
  function readToken(tokenFile) {
12835
13072
  if (tokenFile) {
12836
13073
  try {
12837
- return readFileSync11(tokenFile, "utf-8").trim();
13074
+ return readFileSync12(tokenFile, "utf-8").trim();
12838
13075
  } catch {
12839
13076
  return;
12840
13077
  }
@@ -12978,7 +13215,15 @@ program2.command("init").description("Register a new agent profile with a unique
12978
13215
  console.log(` hooks install gitguard --profile ${profile.agent_id}`);
12979
13216
  console.log();
12980
13217
  });
12981
- program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>", "Agent profile ID").description("Execute a hook (called by AI coding agents)").action(async (hook, options) => {
13218
+ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>", "Agent profile ID").option("--enable-mementos", "Enable mementos-context for this invocation (provider assistance remains separately opt-in)").option("--disable-mementos", "Disable mementos-context for this invocation").option("--openrouter-secret-ref <reference>", "Secrets reference for the enabled Mementos hook; never a key value").description("Execute a hook (called by AI coding agents)").action(async (hook, options) => {
13219
+ let mementos;
13220
+ try {
13221
+ mementos = mementosRegistrationFromFlags(hook, options);
13222
+ } catch (error) {
13223
+ console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Invalid Mementos options" }));
13224
+ process.exitCode = 1;
13225
+ return;
13226
+ }
12982
13227
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
12983
13228
  const { sha256Of: sha256Of2, checkScriptHash: checkScriptHash2 } = await Promise.resolve().then(() => (init_store(), exports_store));
12984
13229
  const { recordHookRun: recordHookRun2, resolveEventType: resolveEventType2 } = await Promise.resolve().then(() => (init_db_writer(), exports_db_writer));
@@ -12988,11 +13233,11 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
12988
13233
  process.exit(1);
12989
13234
  }
12990
13235
  const hookScript = resolveScriptPath2(hook);
12991
- if (!hookScript || !existsSync15(hookScript)) {
13236
+ if (!hookScript || !existsSync16(hookScript)) {
12992
13237
  console.error(JSON.stringify({ error: `Hook script not found: ${hookScript ?? hook}` }));
12993
13238
  process.exit(1);
12994
13239
  }
12995
- const content = readFileSync11(hookScript);
13240
+ const content = readFileSync12(hookScript);
12996
13241
  const sha = sha256Of2(content);
12997
13242
  const check = checkScriptHash2(hook, sha);
12998
13243
  if (!check.ok) {
@@ -13038,7 +13283,7 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
13038
13283
  content,
13039
13284
  args,
13040
13285
  stdin: hookStdin,
13041
- env: process.env,
13286
+ env: mementosRunEnvironment(process.env, mementos),
13042
13287
  timeout
13043
13288
  }));
13044
13289
  } catch (err) {
@@ -13098,7 +13343,7 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
13098
13343
  process.stderr.write(stderr);
13099
13344
  process.exit(exitCode);
13100
13345
  });
13101
- 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) => {
13346
+ 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, codex, all (default: claude)", "claude").option("--profile <id>", "Agent profile ID to scope hooks to").option("--enable-mementos", "Persistently enable mementos-context (provider assistance remains separately opt-in)").option("--disable-mementos", "Persistently disable mementos-context").option("--openrouter-secret-ref <reference>", "Secrets reference for the enabled Mementos hook; never a key value").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) => {
13102
13347
  const scope = resolveScope(options);
13103
13348
  const target = resolveTarget(options);
13104
13349
  let toInstall = hooks;
@@ -13122,6 +13367,18 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
13122
13367
  render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
13123
13368
  return;
13124
13369
  }
13370
+ let mementos;
13371
+ try {
13372
+ mementos = mementosRegistrationFromFlags(toInstall.length === 1 ? toInstall[0] : "", options);
13373
+ } catch (error) {
13374
+ const message = error instanceof Error ? error.message : "Invalid Mementos options";
13375
+ if (options.json)
13376
+ console.log(JSON.stringify({ error: message, applied: false }));
13377
+ else
13378
+ console.error(message);
13379
+ process.exitCode = 1;
13380
+ return;
13381
+ }
13125
13382
  if (options.applyCodewith && (target === "codewith" || target === "all") && !options.codewithConfig) {
13126
13383
  const message = "--apply-codewith requires --codewith-config <path>; refusing to write default ~/.codewith/config.toml.";
13127
13384
  if (options.json) {
@@ -13160,6 +13417,7 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
13160
13417
  console.log(JSON.stringify({
13161
13418
  dryRun: true,
13162
13419
  would_install: known,
13420
+ ...mementos ? { mementos } : {},
13163
13421
  custom_sources: customSources,
13164
13422
  pinned: pinnedRequests.map((p) => `${p.pinned.name}@${p.pinned.version}`),
13165
13423
  unknown,
@@ -13264,7 +13522,8 @@ Dry run \u2014 would install (${scope}, ${target}):
13264
13522
  target,
13265
13523
  profile: options.profile,
13266
13524
  codewithMode: options.applyCodewith ? "write" : "fragment",
13267
- codewithConfigPath: options.codewithConfig
13525
+ codewithConfigPath: options.codewithConfig,
13526
+ mementos
13268
13527
  });
13269
13528
  results.push(result);
13270
13529
  }
@@ -13277,7 +13536,8 @@ Dry run \u2014 would install (${scope}, ${target}):
13277
13536
  success: results.filter((r) => r.success).length,
13278
13537
  scope,
13279
13538
  target,
13280
- applied: results.some((r) => r.applied)
13539
+ applied: results.some((r) => r.applied),
13540
+ ...target === "codex" ? { notes: results.filter((r) => r.success && r.note).map((r) => r.note) } : {}
13281
13541
  }));
13282
13542
  if (results.some((r) => !r.success)) {
13283
13543
  process.exitCode = 1;
@@ -13285,7 +13545,7 @@ Dry run \u2014 would install (${scope}, ${target}):
13285
13545
  return;
13286
13546
  }
13287
13547
  const successCount = results.filter((r) => r.success).length;
13288
- const settingsFile = target === "codewith" ? options.applyCodewith ? options.codewithConfig : "TOML fragment only (configs should apply)" : scope === "project" ? ".claude/settings.json" : "~/.claude/settings.json";
13548
+ const settingsFile = target === "codewith" ? options.applyCodewith ? options.codewithConfig : "TOML fragment only (configs should apply)" : formatSettingsPath(scope, target);
13289
13549
  console.log(chalk2.bold(`
13290
13550
  Installing hooks (${scope}, ${target})...
13291
13551
  `));
@@ -13299,6 +13559,8 @@ Installing hooks (${scope}, ${target})...
13299
13559
  if (result.conflict) {
13300
13560
  console.log(chalk2.yellow(` \u26A0 Warning: ${result.conflict}`));
13301
13561
  }
13562
+ if (result.note && target === "codex")
13563
+ console.log(chalk2.dim(` ${result.note}`));
13302
13564
  if (result.fragment && target === "codewith") {
13303
13565
  console.log(chalk2.dim(" Codewith TOML fragment:"));
13304
13566
  console.log(chalk2.cyan(result.fragment.trimEnd().split(`
@@ -13325,12 +13587,12 @@ Installing hooks (${scope}, ${target})...
13325
13587
  console.log(chalk2.dim(`
13326
13588
  Registered in ${settingsFile}`));
13327
13589
  });
13328
- program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-a, --all", "Show all available hooks", false).option("-i, --installed", "Show only installed hooks", false).option("-r, --registered", "Show registered hooks", false).option("-g, --global", "Check global settings", false).option("-p, --project", "Check project settings", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith (default: claude)", "claude").option("-n, --limit <n>", "Max rows to show in compact output", "20").option("--verbose", "Show descriptions and full detail columns", false).option("-j, --json", "Output as JSON", false).description("List available or installed hooks (bundled + custom/registry)").action((options) => {
13590
+ program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-a, --all", "Show all available hooks", false).option("-i, --installed", "Show only installed hooks", false).option("-r, --registered", "Show registered hooks", false).option("-g, --global", "Check global settings", false).option("-p, --project", "Check project settings", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith, codex (default: claude)", "claude").option("-n, --limit <n>", "Max rows to show in compact output", "20").option("--verbose", "Show descriptions and full detail columns", false).option("-j, --json", "Output as JSON", false).description("List available or installed hooks (bundled + custom/registry)").action((options) => {
13329
13591
  const scope = resolveScope(options);
13330
13592
  const limit = options.all ? Number.MAX_SAFE_INTEGER : parseLimit(options.limit, 20, 200);
13331
13593
  const storeHooks = listStoreHooks();
13332
13594
  if (options.registered || options.installed) {
13333
- const target = options.target === "gemini" ? "gemini" : options.target === "codewith" ? "codewith" : "claude";
13595
+ const target = resolveConcreteTarget(options);
13334
13596
  const registered = getRegisteredHooksForTarget(scope, target);
13335
13597
  const installed = [...new Set([...registered, ...storeHooks.map((s) => s.meta.name)])];
13336
13598
  const shown = options.registered ? registered : installed;
@@ -13453,7 +13715,7 @@ Found ${results.length} hook(s), showing ${visible.length}:
13453
13715
  console.log(hookSummaryLine(h, { verbose: options.verbose }));
13454
13716
  printDisclosureHint(results.length - visible.length, "hooks info <name>");
13455
13717
  });
13456
- program2.command("remove").alias("rm").argument("<hook>", "Hook to remove").option("-g, --global", "Remove from global settings", false).option("-p, --project", "Remove from project settings", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith, all (default: claude)", "claude").option("-j, --json", "Output as JSON", false).description("Remove an installed hook (settings registration + store + lock + DB)").action(async (hook, options) => {
13718
+ program2.command("remove").alias("rm").argument("<hook>", "Hook to remove").option("-g, --global", "Remove from global settings", false).option("-p, --project", "Remove from project settings", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith, codex, all (default: claude)", "claude").option("-j, --json", "Output as JSON", false).description("Remove an installed hook (settings registration + store + lock + DB)").action(async (hook, options) => {
13457
13719
  const scope = resolveScope(options);
13458
13720
  const target = resolveTarget(options);
13459
13721
  const { uninstallHook: uninstallHook2 } = await Promise.resolve().then(() => (init_installer(), exports_installer));
@@ -13569,7 +13831,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
13569
13831
  });
13570
13832
  }
13571
13833
  const staleNames = new Set(stale.map((finding) => finding.hook));
13572
- const settingsExist = existsSync15(settingsPath);
13834
+ const settingsExist = existsSync16(settingsPath);
13573
13835
  if (!settingsExist) {
13574
13836
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
13575
13837
  }
@@ -13577,7 +13839,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
13577
13839
  let wiringCount = 0;
13578
13840
  if (settingsExist) {
13579
13841
  try {
13580
- const settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
13842
+ const settings = JSON.parse(readFileSync12(settingsPath, "utf-8"));
13581
13843
  wiringCount = countSettingsWiring(settings);
13582
13844
  } catch {}
13583
13845
  }
@@ -13588,7 +13850,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
13588
13850
  const meta = custom ? resolveHookMeta(name) : getHook(name);
13589
13851
  let hookHealthy = true;
13590
13852
  if (custom) {
13591
- if (!existsSync15(custom.scriptPath)) {
13853
+ if (!existsSync16(custom.scriptPath)) {
13592
13854
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
13593
13855
  hookHealthy = false;
13594
13856
  }
@@ -13600,14 +13862,14 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
13600
13862
  }
13601
13863
  const hookDir = getHookPath(name);
13602
13864
  const hookScript = join17(hookDir, "src", "hook.ts");
13603
- if (!existsSync15(hookScript)) {
13865
+ if (!existsSync16(hookScript)) {
13604
13866
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
13605
13867
  hookHealthy = false;
13606
13868
  }
13607
13869
  }
13608
13870
  if (meta && settingsExist) {
13609
13871
  try {
13610
- const settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
13872
+ const settings = JSON.parse(readFileSync12(settingsPath, "utf-8"));
13611
13873
  if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
13612
13874
  const eventName = meta.event.split(":")[0];
13613
13875
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
@@ -13812,8 +14074,8 @@ program2.command("docs").argument("[hook]", "Hook name (shows general docs if om
13812
14074
  const hookPath = getHookPath(hook);
13813
14075
  const readmePath = join17(hookPath, "README.md");
13814
14076
  let readme = "";
13815
- if (existsSync15(readmePath)) {
13816
- readme = readFileSync11(readmePath, "utf-8");
14077
+ if (existsSync16(readmePath)) {
14078
+ readme = readFileSync12(readmePath, "utf-8");
13817
14079
  }
13818
14080
  if (options.json) {
13819
14081
  console.log(JSON.stringify({ ...meta, readme }));
@@ -13932,7 +14194,7 @@ ${meta.displayName} v${meta.version}
13932
14194
  console.log();
13933
14195
  });
13934
14196
  program2.command("upgrade").option("-c, --check", "Check for updates without installing", false).option("-j, --json", "Output as JSON", false).description("Update the @hasna/hooks package to the latest version").action(async (options) => {
13935
- const current = pkg2.version;
14197
+ const current2 = pkg2.version;
13936
14198
  let pm = "npm";
13937
14199
  try {
13938
14200
  const which = Bun.spawnSync(["which", "bun"]);
@@ -13950,13 +14212,13 @@ program2.command("upgrade").option("-c, --check", "Check for updates without ins
13950
14212
  }
13951
14213
  process.exit(1);
13952
14214
  }
13953
- const upToDate = current === latest2;
14215
+ const upToDate = current2 === latest2;
13954
14216
  if (options.json) {
13955
- console.log(JSON.stringify({ current, latest: latest2, upToDate }));
14217
+ console.log(JSON.stringify({ current: current2, latest: latest2, upToDate }));
13956
14218
  } else if (upToDate) {
13957
- console.log(chalk2.green(`\u2713 Already on latest version (${current})`));
14219
+ console.log(chalk2.green(`\u2713 Already on latest version (${current2})`));
13958
14220
  } else {
13959
- console.log(chalk2.yellow(`Update available: ${current} \u2192 ${latest2}`));
14221
+ console.log(chalk2.yellow(`Update available: ${current2} \u2192 ${latest2}`));
13960
14222
  console.log(chalk2.dim(` Run: hooks upgrade`));
13961
14223
  }
13962
14224
  return;
@@ -13977,7 +14239,7 @@ Upgrading @hasna/hooks (${pm})...
13977
14239
  const exitCode = await proc.exited;
13978
14240
  if (exitCode !== 0) {
13979
14241
  if (options.json) {
13980
- console.log(JSON.stringify({ current, updated: false, error: `${pm} exited with code ${exitCode}` }));
14242
+ console.log(JSON.stringify({ current: current2, updated: false, error: `${pm} exited with code ${exitCode}` }));
13981
14243
  } else {
13982
14244
  console.log(chalk2.red(`
13983
14245
  \u2717 Upgrade failed (exit code ${exitCode})`));
@@ -13987,18 +14249,18 @@ Upgrading @hasna/hooks (${pm})...
13987
14249
  const versionProc = Bun.spawnSync(["npm", "view", "@hasna/hooks", "version"]);
13988
14250
  const latest = new TextDecoder().decode(versionProc.stdout).trim() || "unknown";
13989
14251
  if (options.json) {
13990
- console.log(JSON.stringify({ current, latest, updated: true }));
14252
+ console.log(JSON.stringify({ current: current2, latest, updated: true }));
13991
14253
  } else {
13992
14254
  console.log(chalk2.green(`
13993
- \u2713 Upgraded: ${current} \u2192 ${latest}`));
14255
+ \u2713 Upgraded: ${current2} \u2192 ${latest}`));
13994
14256
  }
13995
14257
  });
13996
14258
  program2.command("profile-export").description("Export all agent profiles as JSON (for backup/cross-machine setup)").option("-o, --output <file>", "Write to file instead of stdout").option("-j, --json", "Output as JSON (default: true)", false).action(async (options) => {
13997
14259
  const profiles = exportProfiles();
13998
14260
  const json2 = JSON.stringify(profiles, null, 2);
13999
14261
  if (options.output) {
14000
- const { writeFileSync: writeFileSync6 } = await import("fs");
14001
- writeFileSync6(options.output, json2 + `
14262
+ const { writeFileSync: writeFileSync7 } = await import("fs");
14263
+ writeFileSync7(options.output, json2 + `
14002
14264
  `);
14003
14265
  console.log(chalk2.green(`\u2713 Exported ${profiles.length} profile(s) to ${options.output}`));
14004
14266
  } else {
@@ -14010,9 +14272,9 @@ program2.command("profile-import").argument("<file>", "JSON file to import profi
14010
14272
  if (file === "-") {
14011
14273
  raw = await new Response(Bun.stdin.stream()).text();
14012
14274
  } else {
14013
- const { readFileSync: readFileSync12 } = await import("fs");
14275
+ const { readFileSync: readFileSync13 } = await import("fs");
14014
14276
  try {
14015
- raw = readFileSync12(file, "utf-8");
14277
+ raw = readFileSync13(file, "utf-8");
14016
14278
  } catch {
14017
14279
  if (options.json) {
14018
14280
  console.log(JSON.stringify({ error: `Cannot read file: ${file}` }));