@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/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",
@@ -5232,6 +5243,78 @@ var init_store = __esm(() => {
5232
5243
  };
5233
5244
  });
5234
5245
 
5246
+ // src/lib/codex-settings.ts
5247
+ import { closeSync, existsSync as existsSync10, lstatSync, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
5248
+ import { dirname as dirname3 } from "path";
5249
+ import { randomUUID as randomUUID3 } from "crypto";
5250
+ function current(path) {
5251
+ try {
5252
+ const stat = lstatSync(path);
5253
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576)
5254
+ throw new Error("invalid_file");
5255
+ return readFileSync4(path, "utf8");
5256
+ } catch (error) {
5257
+ if (error.code === "ENOENT")
5258
+ return null;
5259
+ throw new Error("Cannot safely read Codex hooks.json; no settings were changed.");
5260
+ }
5261
+ }
5262
+ function readCodexSettings(path) {
5263
+ const raw = current(path);
5264
+ let settings;
5265
+ try {
5266
+ settings = raw === null ? {} : JSON.parse(raw);
5267
+ if (!settings || typeof settings !== "object" || Array.isArray(settings))
5268
+ throw new Error;
5269
+ if (settings.hooks !== undefined) {
5270
+ if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks))
5271
+ throw new Error;
5272
+ for (const entries of Object.values(settings.hooks)) {
5273
+ if (!Array.isArray(entries) || entries.some((entry) => !entry || !Array.isArray(entry.hooks) || entry.hooks.some((hook) => !hook || typeof hook !== "object")))
5274
+ throw new Error;
5275
+ }
5276
+ }
5277
+ } catch {
5278
+ throw new Error("Invalid Codex hooks.json; repair it before installing or removing hooks.");
5279
+ }
5280
+ snapshots.set(settings, { path, raw });
5281
+ return settings;
5282
+ }
5283
+ function writeCodexSettings(path, settings) {
5284
+ const snapshot = snapshots.get(settings);
5285
+ if (!snapshot || snapshot.path !== path)
5286
+ throw new Error("Codex settings require a complete current read before writing.");
5287
+ mkdirSync4(dirname3(path), { recursive: true });
5288
+ const lock = `${path}.hooks-write.lock`;
5289
+ let fd;
5290
+ try {
5291
+ fd = openSync(lock, "wx", 384);
5292
+ } catch {
5293
+ throw new Error("Codex settings are being edited; retry after the active writer finishes.");
5294
+ }
5295
+ const temp = `${path}.${randomUUID3()}.tmp`;
5296
+ try {
5297
+ if (current(path) !== snapshot.raw)
5298
+ throw new Error("Codex settings changed since reading; re-read before retrying.");
5299
+ const next = JSON.stringify(settings, null, 2) + `
5300
+ `;
5301
+ writeFileSync3(temp, next, { flag: "wx", mode: 384 });
5302
+ if (current(path) !== snapshot.raw)
5303
+ throw new Error("Codex settings changed since reading; re-read before retrying.");
5304
+ renameSync2(temp, path);
5305
+ snapshots.set(settings, { path, raw: next });
5306
+ } finally {
5307
+ if (existsSync10(temp))
5308
+ unlinkSync(temp);
5309
+ closeSync(fd);
5310
+ unlinkSync(lock);
5311
+ }
5312
+ }
5313
+ var snapshots;
5314
+ var init_codex_settings = __esm(() => {
5315
+ snapshots = new WeakMap;
5316
+ });
5317
+
5235
5318
  // src/lib/installer.ts
5236
5319
  var exports_installer = {};
5237
5320
  __export(exports_installer, {
@@ -5252,8 +5335,8 @@ __export(exports_installer, {
5252
5335
  detectRewriteConflict: () => detectRewriteConflict,
5253
5336
  buildCodewithTomlFragment: () => buildCodewithTomlFragment
5254
5337
  });
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";
5338
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
5339
+ import { join as join10, dirname as dirname4 } from "path";
5257
5340
  import { homedir as homedir4 } from "os";
5258
5341
  import { fileURLToPath as fileURLToPath2 } from "url";
5259
5342
  function normalizeHookName(name) {
@@ -5263,12 +5346,19 @@ function shortHookName(name) {
5263
5346
  return name.startsWith("hook-") ? name.slice("hook-".length) : name;
5264
5347
  }
5265
5348
  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
- }));
5349
+ return entries.flatMap((entry) => {
5350
+ if (!Array.isArray(entry.hooks))
5351
+ return [entry];
5352
+ const kept = entry.hooks.filter((h) => {
5353
+ const match = h.command?.match(/^hooks run ([\w-]+)/);
5354
+ return !(match && match[1] === hookName);
5355
+ });
5356
+ return kept.length === entry.hooks.length ? [entry] : kept.length ? [{ ...entry, hooks: kept }] : [];
5357
+ });
5270
5358
  }
5271
5359
  function getTargetSettingsDir(target) {
5360
+ if (target === "codex")
5361
+ return ".codex";
5272
5362
  if (target === "codewith")
5273
5363
  return ".codewith";
5274
5364
  if (target === "gemini")
@@ -5276,6 +5366,8 @@ function getTargetSettingsDir(target) {
5276
5366
  return ".claude";
5277
5367
  }
5278
5368
  function getGlobalSettingsPathOverride(target) {
5369
+ if (target === "codex")
5370
+ return process.env.HASNA_HOOKS_CODEX_SETTINGS_PATH;
5279
5371
  if (target === "claude")
5280
5372
  return process.env.HASNA_HOOKS_CLAUDE_SETTINGS_PATH;
5281
5373
  if (target === "gemini")
@@ -5292,6 +5384,8 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
5292
5384
  if (globalOverride)
5293
5385
  return globalOverride;
5294
5386
  const dir = getTargetSettingsDir(target);
5387
+ if (target === "codex")
5388
+ return join10(scope === "project" ? process.cwd() : homedir4(), dir, "hooks.json");
5295
5389
  if (scope === "project") {
5296
5390
  return target === "codewith" ? join10(process.cwd(), dir, "config.toml") : join10(process.cwd(), dir, "settings.json");
5297
5391
  }
@@ -5305,13 +5399,15 @@ function getHookPath(name) {
5305
5399
  return join10(HOOKS_DIR, normalizeHookName(shortName));
5306
5400
  }
5307
5401
  function hookExists(name) {
5308
- return existsSync10(getHookPath(name));
5402
+ return existsSync11(getHookPath(name));
5309
5403
  }
5310
5404
  function readSettings(scope = "global", target = "claude") {
5311
5405
  const path = getSettingsPath(scope, target);
5406
+ if (target === "codex")
5407
+ return readCodexSettings(path);
5312
5408
  try {
5313
- if (existsSync10(path)) {
5314
- return JSON.parse(readFileSync4(path, "utf-8"));
5409
+ if (existsSync11(path)) {
5410
+ return JSON.parse(readFileSync5(path, "utf-8"));
5315
5411
  }
5316
5412
  } catch (error) {
5317
5413
  console.warn(`[hooks] Failed to read settings at ${path}: ${error instanceof Error ? error.message : String(error)}`);
@@ -5320,11 +5416,13 @@ function readSettings(scope = "global", target = "claude") {
5320
5416
  }
5321
5417
  function writeSettings(settings, scope = "global", target = "claude") {
5322
5418
  const path = getSettingsPath(scope, target);
5323
- const dir = dirname3(path);
5324
- if (!existsSync10(dir)) {
5325
- mkdirSync4(dir, { recursive: true });
5419
+ if (target === "codex")
5420
+ return writeCodexSettings(path, settings);
5421
+ const dir = dirname4(path);
5422
+ if (!existsSync11(dir)) {
5423
+ mkdirSync5(dir, { recursive: true });
5326
5424
  }
5327
- writeFileSync3(path, JSON.stringify(settings, null, 2) + `
5425
+ writeFileSync4(path, JSON.stringify(settings, null, 2) + `
5328
5426
  `);
5329
5427
  }
5330
5428
  function getTargetEventName(internalEvent, target) {
@@ -5347,6 +5445,8 @@ function codewithMatcher(matcher) {
5347
5445
  }
5348
5446
  function codewithTimeout(name) {
5349
5447
  switch (name) {
5448
+ case "mementos-context":
5449
+ return 12;
5350
5450
  case "knowledge-context":
5351
5451
  return 6;
5352
5452
  case "session-start":
@@ -5413,21 +5513,21 @@ function buildCodewithTomlFragment(name, profile) {
5413
5513
  function readCodewithConfig(scope, configPath) {
5414
5514
  const path = getSettingsPath(scope, "codewith", configPath);
5415
5515
  try {
5416
- return existsSync10(path) ? readFileSync4(path, "utf-8") : "";
5516
+ return existsSync11(path) ? readFileSync5(path, "utf-8") : "";
5417
5517
  } catch {
5418
5518
  return "";
5419
5519
  }
5420
5520
  }
5421
5521
  function appendCodewithFragment(fragment, scope, configPath) {
5422
5522
  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") : "";
5523
+ const dir = dirname4(path);
5524
+ if (!existsSync11(dir))
5525
+ mkdirSync5(dir, { recursive: true });
5526
+ const existing = existsSync11(path) ? readFileSync5(path, "utf-8") : "";
5427
5527
  const sep2 = existing.trim() ? `
5428
5528
 
5429
5529
  ` : "";
5430
- writeFileSync3(path, `${existing.replace(/\s*$/, "")}${sep2}${fragment}`);
5530
+ writeFileSync4(path, `${existing.replace(/\s*$/, "")}${sep2}${fragment}`);
5431
5531
  return path;
5432
5532
  }
5433
5533
  function detectConflict(name, scope, target) {
@@ -5524,18 +5624,25 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
5524
5624
  };
5525
5625
  }
5526
5626
  }
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
5627
  try {
5628
+ const registered = getRegisteredHooksForTarget(scope, target);
5629
+ if (registered.includes(shortName) && !overwrite) {
5630
+ return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
5631
+ }
5632
+ const conflict = detectConflict(shortName, scope, target);
5633
+ const rewriteConflict = detectRewriteConflict(shortName, scope, target);
5634
+ if (rewriteConflict) {
5635
+ return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
5636
+ }
5537
5637
  registerHook(shortName, scope, target, profile);
5538
- return { hook: shortName, success: true, scope, target, ...conflict ? { conflict } : {} };
5638
+ return {
5639
+ hook: shortName,
5640
+ success: true,
5641
+ scope,
5642
+ target,
5643
+ ...conflict ? { conflict } : {},
5644
+ ...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." } : {}
5645
+ };
5539
5646
  } catch (error) {
5540
5647
  return {
5541
5648
  hook: shortName,
@@ -5547,6 +5654,9 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
5547
5654
  }
5548
5655
  function installHook(name, options = {}) {
5549
5656
  const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath } = options;
5657
+ if (!/^[\w-]+$/.test(name) || profile !== undefined && !/^[\w-]+$/.test(profile)) {
5658
+ return { hook: name, success: false, error: "Hook and profile names must contain only letters, digits, underscores or hyphens.", target };
5659
+ }
5550
5660
  if (target === "all") {
5551
5661
  const shortName = shortHookName(name);
5552
5662
  const meta = resolveHookMeta(shortName);
@@ -5678,7 +5788,8 @@ function removeHook(name, scope = "global", target = "claude") {
5678
5788
  if (target === "all") {
5679
5789
  const claudeRemoved = removeHookForTarget(shortName, scope, "claude");
5680
5790
  const geminiRemoved = removeHookForTarget(shortName, scope, "gemini");
5681
- return claudeRemoved || geminiRemoved;
5791
+ const codexRemoved = removeHookForTarget(shortName, scope, "codex");
5792
+ return claudeRemoved || geminiRemoved || codexRemoved;
5682
5793
  }
5683
5794
  if (target === "codewith") {
5684
5795
  return false;
@@ -5695,9 +5806,9 @@ function removeHookForTarget(name, scope, target) {
5695
5806
  }
5696
5807
  function readSettingsAt(path) {
5697
5808
  try {
5698
- if (!existsSync10(path))
5809
+ if (!existsSync11(path))
5699
5810
  return;
5700
- const raw = readFileSync4(path, "utf-8");
5811
+ const raw = readFileSync5(path, "utf-8");
5701
5812
  return { raw, settings: JSON.parse(raw) };
5702
5813
  } catch {
5703
5814
  return;
@@ -5730,7 +5841,9 @@ function pruneStaleRegistrations(scope = "global") {
5730
5841
  commands.add(finding.command);
5731
5842
  staleCommandsByEvent.set(finding.event, commands);
5732
5843
  }
5733
- const settings = parsed.settings;
5844
+ const settings = target === "codex" ? readCodexSettings(path) : parsed.settings;
5845
+ if (target === "codex" && readFileSync5(path, "utf8") !== parsed.raw)
5846
+ throw new Error("Codex settings changed before pruning; retry.");
5734
5847
  for (const [eventKey, commands] of staleCommandsByEvent) {
5735
5848
  const entries = settings.hooks?.[eventKey];
5736
5849
  if (!Array.isArray(entries))
@@ -5755,7 +5868,7 @@ function pruneStaleRegistrations(scope = "global") {
5755
5868
  if (settings.hooks && Object.keys(settings.hooks).length === 0)
5756
5869
  delete settings.hooks;
5757
5870
  const backupPath = `${path}.bak`;
5758
- writeFileSync3(backupPath, parsed.raw);
5871
+ writeFileSync4(backupPath, parsed.raw);
5759
5872
  writeSettings(settings, scope, target);
5760
5873
  results.push({ file: path, removed: stale, backupPath });
5761
5874
  }
@@ -5817,10 +5930,10 @@ function codewithHasHookEntryInText(configText, name) {
5817
5930
  }
5818
5931
  function codewithHasHookEntry(name, scope = "global") {
5819
5932
  const path = getSettingsPath(scope, "codewith");
5820
- if (!existsSync10(path))
5933
+ if (!existsSync11(path))
5821
5934
  return false;
5822
5935
  try {
5823
- return codewithHasHookEntryInText(readFileSync4(path, "utf-8"), name);
5936
+ return codewithHasHookEntryInText(readFileSync5(path, "utf-8"), name);
5824
5937
  } catch {
5825
5938
  return false;
5826
5939
  }
@@ -5834,7 +5947,7 @@ function removeStoreDirFailClosed(dir) {
5834
5947
  }
5835
5948
  for (const entry of entries) {
5836
5949
  if (!entry.isDirectory()) {
5837
- unlinkSync(join10(dir, entry.name));
5950
+ unlinkSync2(join10(dir, entry.name));
5838
5951
  }
5839
5952
  }
5840
5953
  rmdirSync(dir);
@@ -5844,14 +5957,14 @@ function uninstallHook(name, scope = "global", target = "claude") {
5844
5957
  const custom = readCustomManifest(shortName);
5845
5958
  const bundledMeta = custom ? undefined : getHook(shortName);
5846
5959
  const existsInTarget = (t) => getRegisteredHooksForTarget("global", t).includes(shortName) || getRegisteredHooksForTarget("project", t).includes(shortName);
5847
- const registeredClaudeOrGemini = target === "all" ? existsInTarget("claude") || existsInTarget("gemini") : false;
5960
+ const registeredClaudeOrGemini = target === "all" ? JSON_SETTINGS_TARGETS.some(existsInTarget) : false;
5848
5961
  const registeredGlobal = registeredClaudeOrGemini || getRegisteredHooksForTarget("global", target === "all" ? "claude" : target).includes(shortName);
5849
5962
  const registeredProject = registeredClaudeOrGemini || getRegisteredHooksForTarget("project", target === "all" ? "claude" : target).includes(shortName);
5850
5963
  const codewithOnly = (target === "codewith" || target === "all") && codewithHasHookEntry(shortName, scope);
5851
5964
  if (!custom && !bundledMeta && !registeredGlobal && !registeredProject && !codewithOnly) {
5852
5965
  return { name: shortName, removed: false, source: null, settingsScopes: [], storeDirRemoved: false, pinRemoved: false, dbRecordRemoved: false, registrationsRemaining: [], error: `Hook '${shortName}' not found` };
5853
5966
  }
5854
- const writableTargets = target === "all" ? ["claude", "gemini"] : target === "codewith" ? [] : [target];
5967
+ const writableTargets = target === "all" ? JSON_SETTINGS_TARGETS : target === "codewith" ? [] : [target];
5855
5968
  const scopes = [];
5856
5969
  for (const s of ["global", "project"]) {
5857
5970
  for (const t of writableTargets) {
@@ -5865,11 +5978,11 @@ function uninstallHook(name, scope = "global", target = "claude") {
5865
5978
  const registrationsRemaining = [];
5866
5979
  if (target === "codewith" || target === "all") {
5867
5980
  const codewithPath = getSettingsPath(scope, "codewith");
5868
- if (existsSync10(codewithPath)) {
5869
- const before = readFileSync4(codewithPath, "utf-8");
5981
+ if (existsSync11(codewithPath)) {
5982
+ const before = readFileSync5(codewithPath, "utf-8");
5870
5983
  const after = removeCodewithHookEntry(before, shortName);
5871
5984
  if (after.removed) {
5872
- writeFileSync3(codewithPath, after.text, "utf-8");
5985
+ writeFileSync4(codewithPath, after.text, "utf-8");
5873
5986
  }
5874
5987
  if (codewithHasHookEntryInText(after.removed ? after.text : before, shortName)) {
5875
5988
  registrationsRemaining.push("codewith");
@@ -5912,8 +6025,9 @@ var init_installer = __esm(() => {
5912
6025
  init_manifest();
5913
6026
  init_registration();
5914
6027
  init_store();
5915
- __dirname2 = dirname3(fileURLToPath2(import.meta.url));
5916
- HOOKS_DIR = existsSync10(join10(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join10(__dirname2, "..", "..", "hooks") : join10(__dirname2, "..", "hooks");
6028
+ init_codex_settings();
6029
+ __dirname2 = dirname4(fileURLToPath2(import.meta.url));
6030
+ HOOKS_DIR = existsSync11(join10(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join10(__dirname2, "..", "..", "hooks") : join10(__dirname2, "..", "hooks");
5917
6031
  EVENT_MAP = {
5918
6032
  claude: {
5919
6033
  PreToolUse: "PreToolUse",
@@ -5922,7 +6036,7 @@ var init_installer = __esm(() => {
5922
6036
  Notification: "Notification",
5923
6037
  SessionStart: "SessionStart",
5924
6038
  SessionEnd: "SessionEnd",
5925
- UserPromptSubmit: null,
6039
+ UserPromptSubmit: "UserPromptSubmit",
5926
6040
  SubagentStart: null
5927
6041
  },
5928
6042
  gemini: {
@@ -5944,10 +6058,20 @@ var init_installer = __esm(() => {
5944
6058
  SessionEnd: null,
5945
6059
  UserPromptSubmit: "UserPromptSubmit",
5946
6060
  SubagentStart: "SubagentStart"
6061
+ },
6062
+ codex: {
6063
+ PreToolUse: "PreToolUse",
6064
+ PostToolUse: "PostToolUse",
6065
+ Stop: "Stop",
6066
+ Notification: null,
6067
+ SessionStart: "SessionStart",
6068
+ SessionEnd: "SessionEnd",
6069
+ UserPromptSubmit: "UserPromptSubmit",
6070
+ SubagentStart: "SubagentStart"
5947
6071
  }
5948
6072
  };
5949
6073
  getInstalledHooks = getRegisteredHooks;
5950
- JSON_SETTINGS_TARGETS = ["claude", "gemini"];
6074
+ JSON_SETTINGS_TARGETS = ["claude", "gemini", "codex"];
5951
6075
  });
5952
6076
 
5953
6077
  // src/lib/redact.ts
@@ -6016,21 +6140,21 @@ var init_redact = __esm(() => {
6016
6140
  });
6017
6141
 
6018
6142
  // 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";
6143
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync4, rmSync, cpSync as cpSync2 } from "fs";
6020
6144
  import { join as join11 } from "path";
6021
6145
  function getProfilesDir() {
6022
6146
  const newDir = join11(getEffectiveDataRoot(), "profiles");
6023
6147
  const oldDir = join11(getHomeDir(), ".hooks", "profiles");
6024
- if (!existsSync11(newDir) && existsSync11(oldDir)) {
6025
- mkdirSync5(newDir, { recursive: true });
6148
+ if (!existsSync12(newDir) && existsSync12(oldDir)) {
6149
+ mkdirSync6(newDir, { recursive: true });
6026
6150
  cpSync2(oldDir, newDir, { recursive: true });
6027
6151
  }
6028
6152
  return newDir;
6029
6153
  }
6030
6154
  function ensureProfilesDir() {
6031
6155
  const dir = getProfilesDir();
6032
- if (!existsSync11(dir)) {
6033
- mkdirSync5(dir, { recursive: true });
6156
+ if (!existsSync12(dir)) {
6157
+ mkdirSync6(dir, { recursive: true });
6034
6158
  }
6035
6159
  }
6036
6160
  function profilePath(id) {
@@ -6053,30 +6177,30 @@ function createProfile(input) {
6053
6177
  if (input.name) {
6054
6178
  profile.name = input.name;
6055
6179
  }
6056
- writeFileSync4(profilePath(id), JSON.stringify(profile, null, 2) + `
6180
+ writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
6057
6181
  `);
6058
6182
  return profile;
6059
6183
  }
6060
6184
  function getProfile(id) {
6061
6185
  const path = profilePath(id);
6062
6186
  try {
6063
- if (!existsSync11(path))
6187
+ if (!existsSync12(path))
6064
6188
  return null;
6065
- return JSON.parse(readFileSync5(path, "utf-8"));
6189
+ return JSON.parse(readFileSync6(path, "utf-8"));
6066
6190
  } catch {
6067
6191
  return null;
6068
6192
  }
6069
6193
  }
6070
6194
  function listProfiles() {
6071
6195
  const dir = getProfilesDir();
6072
- if (!existsSync11(dir))
6196
+ if (!existsSync12(dir))
6073
6197
  return [];
6074
6198
  try {
6075
6199
  const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
6076
6200
  const profiles = [];
6077
6201
  for (const file of files) {
6078
6202
  try {
6079
- const content = readFileSync5(join11(dir, file), "utf-8");
6203
+ const content = readFileSync6(join11(dir, file), "utf-8");
6080
6204
  profiles.push(JSON.parse(content));
6081
6205
  } catch {}
6082
6206
  }
@@ -6090,7 +6214,7 @@ function touchProfile(id) {
6090
6214
  if (!profile)
6091
6215
  return;
6092
6216
  profile.last_seen_at = new Date().toISOString();
6093
- writeFileSync4(profilePath(id), JSON.stringify(profile, null, 2) + `
6217
+ writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
6094
6218
  `);
6095
6219
  }
6096
6220
  function exportProfiles() {
@@ -6106,11 +6230,11 @@ function importProfiles(profiles) {
6106
6230
  continue;
6107
6231
  }
6108
6232
  const path = profilePath(profile.agent_id);
6109
- if (existsSync11(path)) {
6233
+ if (existsSync12(path)) {
6110
6234
  skipped++;
6111
6235
  continue;
6112
6236
  }
6113
- writeFileSync4(path, JSON.stringify(profile, null, 2) + `
6237
+ writeFileSync5(path, JSON.stringify(profile, null, 2) + `
6114
6238
  `);
6115
6239
  imported++;
6116
6240
  }
@@ -6123,7 +6247,7 @@ var init_profiles = __esm(() => {
6123
6247
  // ../../node_modules/.bun/@hasna+contracts@1.0.2+e0db24ae03127387/node_modules/@hasna/contracts/dist/client/transport.js
6124
6248
  import { isIP as isIP2 } from "net";
6125
6249
  import { spawnSync } from "child_process";
6126
- import { closeSync, fstatSync, openSync, readFileSync as readFileSync6 } from "fs";
6250
+ import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as readFileSync7 } from "fs";
6127
6251
  import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
6128
6252
  import { createRequire as createRequire2 } from "module";
6129
6253
  import { hostname as osHostname } from "os";
@@ -6227,7 +6351,7 @@ function readAppConfigFile(path) {
6227
6351
  };
6228
6352
  let fd = -1;
6229
6353
  try {
6230
- fd = openSync(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
6354
+ fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
6231
6355
  } catch (error) {
6232
6356
  const code = error.code;
6233
6357
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -6248,7 +6372,7 @@ function readAppConfigFile(path) {
6248
6372
  unsafe("the file is not owned by the current user");
6249
6373
  if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
6250
6374
  unsafe("the file exceeds the size limit");
6251
- const bytes = readFileSync6(fd);
6375
+ const bytes = readFileSync7(fd);
6252
6376
  const after = fstatSync(fd);
6253
6377
  if (!configFileReadsCoherent(before, after)) {
6254
6378
  unsafe("the file changed while being read");
@@ -6256,7 +6380,7 @@ function readAppConfigFile(path) {
6256
6380
  return parseEnvFile(bytes.toString("utf8"));
6257
6381
  } finally {
6258
6382
  if (fd !== -1)
6259
- closeSync(fd);
6383
+ closeSync2(fd);
6260
6384
  }
6261
6385
  }
6262
6386
  function readCredentialFile(path, apiKeyKeys) {
@@ -7124,8 +7248,8 @@ var init_db_writer = __esm(() => {
7124
7248
  });
7125
7249
 
7126
7250
  // 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";
7251
+ import { dirname as dirname5, isAbsolute as isAbsolute3 } from "path";
7252
+ import { existsSync as existsSync13, realpathSync as realpathSync2, statSync } from "fs";
7129
7253
  import { tmpdir } from "os";
7130
7254
  function isDeniedEnvName(name) {
7131
7255
  const upper = name.toUpperCase();
@@ -7173,9 +7297,9 @@ function isUnsafePathEntry(entry, home) {
7173
7297
  }
7174
7298
  function sanitizeHookPath(pathValue, home) {
7175
7299
  const baseline = PATH_BASELINE.slice();
7176
- if (process.platform === "darwin" && existsSync12("/opt/homebrew/bin"))
7300
+ if (process.platform === "darwin" && existsSync13("/opt/homebrew/bin"))
7177
7301
  baseline.push("/opt/homebrew/bin");
7178
- const bunDir = dirname4(process.execPath);
7302
+ const bunDir = dirname5(process.execPath);
7179
7303
  const seen = new Set;
7180
7304
  const out = [];
7181
7305
  for (const dir of baseline) {
@@ -7299,8 +7423,8 @@ __export(exports_run, {
7299
7423
  HookTimeoutError: () => HookTimeoutError
7300
7424
  });
7301
7425
  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";
7426
+ import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
7427
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute4, join as join13 } from "path";
7304
7428
  function extensionOf(scriptPath) {
7305
7429
  const base = basename2(scriptPath);
7306
7430
  const dot = base.lastIndexOf(".");
@@ -7369,7 +7493,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
7369
7493
  return chunks.join("");
7370
7494
  }
7371
7495
  async function executeVerifiedScript(options) {
7372
- const scriptDir = dirname5(options.scriptPath);
7496
+ const scriptDir = dirname6(options.scriptPath);
7373
7497
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
7374
7498
  const tempPath = join13(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
7375
7499
  let proc = null;
@@ -7379,7 +7503,7 @@ async function executeVerifiedScript(options) {
7379
7503
  killGroup(proc, { leaderAlive: proc !== null && proc.exitCode === null });
7380
7504
  }, options.timeout) : null;
7381
7505
  try {
7382
- writeFileSync5(tempPath, options.content, { flag: "wx", mode: 384 });
7506
+ writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
7383
7507
  proc = Bun.spawn([...interpreter.command, isAbsolute4(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
7384
7508
  stdin: new Response(options.stdin),
7385
7509
  stdout: "pipe",
@@ -7462,15 +7586,15 @@ __export(exports_custom_install, {
7462
7586
  installCustomSource: () => installCustomSource,
7463
7587
  ensureCustomDir: () => ensureCustomDir
7464
7588
  });
7465
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
7589
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
7466
7590
  import { join as join14 } from "path";
7467
7591
  import { tmpdir as tmpdir2 } from "os";
7468
7592
  function manifestAt(dir) {
7469
7593
  const path = join14(dir, "manifest.json");
7470
- if (!existsSync13(path))
7594
+ if (!existsSync14(path))
7471
7595
  return;
7472
7596
  try {
7473
- return parseManifest(readFileSync7(path, "utf-8"));
7597
+ return parseManifest(readFileSync8(path, "utf-8"));
7474
7598
  } catch {
7475
7599
  return;
7476
7600
  }
@@ -7513,7 +7637,7 @@ function isCustomSource(value) {
7513
7637
  return true;
7514
7638
  if (value.startsWith("http://") || value.startsWith("https://"))
7515
7639
  return true;
7516
- return existsSync13(value);
7640
+ return existsSync14(value);
7517
7641
  }
7518
7642
  async function installCustomSource(source) {
7519
7643
  if (isGitUrl(source)) {
@@ -7527,11 +7651,11 @@ async function installCustomSource(source) {
7527
7651
  if (source.startsWith("http://") || source.startsWith("https://")) {
7528
7652
  return installManifestUrl(source);
7529
7653
  }
7530
- if (!existsSync13(source)) {
7654
+ if (!existsSync14(source)) {
7531
7655
  throw new Error(`No such local path: ${source}`);
7532
7656
  }
7533
7657
  if (source.endsWith("manifest.json")) {
7534
- const manifest = parseManifest(readFileSync7(source, "utf-8"));
7658
+ const manifest = parseManifest(readFileSync8(source, "utf-8"));
7535
7659
  return writeFromManifest(manifest, source.substring(0, source.lastIndexOf("/")), source, "local");
7536
7660
  }
7537
7661
  return copyManifestDir(source, source, "local");
@@ -7562,7 +7686,7 @@ async function installManifestUrl(url) {
7562
7686
  }
7563
7687
  function ensureCustomDir() {
7564
7688
  const dir = getCustomHooksDir();
7565
- mkdirSync6(dir, { recursive: true });
7689
+ mkdirSync7(dir, { recursive: true });
7566
7690
  return dir;
7567
7691
  }
7568
7692
  var init_custom_install = __esm(() => {
@@ -7581,12 +7705,12 @@ __export(exports_sync, {
7581
7705
  commitSyncArtifacts: () => commitSyncArtifacts
7582
7706
  });
7583
7707
  import { createHash as createHash2 } from "crypto";
7584
- import { readFileSync as readFileSync8 } from "fs";
7708
+ import { readFileSync as readFileSync9 } from "fs";
7585
7709
  function sha256OfText(text) {
7586
7710
  return createHash2("sha256").update(text).digest("hex");
7587
7711
  }
7588
7712
  function sha256OfPath(path) {
7589
- return createHash2("sha256").update(readFileSync8(path)).digest("hex");
7713
+ return createHash2("sha256").update(readFileSync9(path)).digest("hex");
7590
7714
  }
7591
7715
  function collectBundledCatalog() {
7592
7716
  const out = [];
@@ -8536,13 +8660,13 @@ __export(exports_serve, {
8536
8660
  SERVE_HOST: () => SERVE_HOST,
8537
8661
  DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
8538
8662
  });
8539
- import { readFileSync as readFileSync9 } from "fs";
8540
- import { dirname as dirname6, join as join15 } from "path";
8663
+ import { readFileSync as readFileSync10 } from "fs";
8664
+ import { dirname as dirname7, join as join15 } from "path";
8541
8665
  import { fileURLToPath as fileURLToPath3 } from "url";
8542
8666
  function packageVersion() {
8543
8667
  try {
8544
- const pkgPath = join15(dirname6(fileURLToPath3(import.meta.url)), "..", "package.json");
8545
- return JSON.parse(readFileSync9(pkgPath, "utf-8")).version || "0.0.0";
8668
+ const pkgPath = join15(dirname7(fileURLToPath3(import.meta.url)), "..", "package.json");
8669
+ return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
8546
8670
  } catch {
8547
8671
  return "0.0.0";
8548
8672
  }
@@ -8843,17 +8967,17 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
8843
8967
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8844
8968
  import { z as z2 } from "zod";
8845
8969
  import { createServer } from "http";
8846
- import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
8847
- import { join as join16, dirname as dirname7 } from "path";
8970
+ import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
8971
+ import { join as join16, dirname as dirname8 } from "path";
8848
8972
  import { fileURLToPath as fileURLToPath4 } from "url";
8849
8973
  async function runVerifiedHook(name, stdin, timeoutMs) {
8850
8974
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
8851
8975
  const resolved = resolveHook2(name);
8852
8976
  const hookScript = resolveScriptPath2(name);
8853
- if (!resolved || !hookScript || !existsSync14(hookScript)) {
8977
+ if (!resolved || !hookScript || !existsSync15(hookScript)) {
8854
8978
  throw new Error(`Hook '${name}' not found`);
8855
8979
  }
8856
- const content = readFileSync10(hookScript);
8980
+ const content = readFileSync11(hookScript);
8857
8981
  const sha = sha256Of(content);
8858
8982
  const check = checkScriptHash(name, sha);
8859
8983
  if (!check.ok) {
@@ -9058,7 +9182,7 @@ function createHooksServer() {
9058
9182
  const settingsPath = getSettingsPath(scope);
9059
9183
  const issues = [];
9060
9184
  const healthy = [];
9061
- const settingsExist = existsSync14(settingsPath);
9185
+ const settingsExist = existsSync15(settingsPath);
9062
9186
  if (!settingsExist) {
9063
9187
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
9064
9188
  }
@@ -9068,7 +9192,7 @@ function createHooksServer() {
9068
9192
  const meta = custom ? resolveHookMeta(name) : getHook(name);
9069
9193
  let hookHealthy = true;
9070
9194
  if (custom) {
9071
- if (!existsSync14(custom.scriptPath)) {
9195
+ if (!existsSync15(custom.scriptPath)) {
9072
9196
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
9073
9197
  hookHealthy = false;
9074
9198
  }
@@ -9078,14 +9202,14 @@ function createHooksServer() {
9078
9202
  continue;
9079
9203
  }
9080
9204
  const hookDir = getHookPath(name);
9081
- if (!existsSync14(join16(hookDir, "src", "hook.ts"))) {
9205
+ if (!existsSync15(join16(hookDir, "src", "hook.ts"))) {
9082
9206
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
9083
9207
  hookHealthy = false;
9084
9208
  }
9085
9209
  }
9086
9210
  if (meta && settingsExist) {
9087
9211
  try {
9088
- const settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
9212
+ const settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
9089
9213
  if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
9090
9214
  const eventName = meta.event.split(":")[0];
9091
9215
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
@@ -9124,8 +9248,8 @@ function createHooksServer() {
9124
9248
  const hookPath = getHookPath(name);
9125
9249
  const readmePath = join16(hookPath, "README.md");
9126
9250
  let readme = "";
9127
- if (existsSync14(readmePath)) {
9128
- readme = readFileSync10(readmePath, "utf-8");
9251
+ if (existsSync15(readmePath)) {
9252
+ readme = readFileSync11(readmePath, "utf-8");
9129
9253
  }
9130
9254
  if (verbose) {
9131
9255
  return { content: [{ type: "text", text: JSON.stringify({ ...meta, readme }) }] };
@@ -9308,7 +9432,7 @@ function createHooksServer() {
9308
9432
  const ctx = {
9309
9433
  scope,
9310
9434
  settings_path: settingsPath,
9311
- settings_exists: existsSync14(settingsPath),
9435
+ settings_exists: existsSync15(settingsPath),
9312
9436
  registered_hooks: hooks,
9313
9437
  hook_count: hooks.length,
9314
9438
  healthy,
@@ -9454,8 +9578,8 @@ function createHooksServer() {
9454
9578
  const settingsPath = getSettingsPath(scope);
9455
9579
  let settings = {};
9456
9580
  try {
9457
- if (existsSync14(settingsPath))
9458
- settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
9581
+ if (existsSync15(settingsPath))
9582
+ settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
9459
9583
  } catch {}
9460
9584
  if (!settings.hooks)
9461
9585
  settings.hooks = {};
@@ -9463,10 +9587,10 @@ function createHooksServer() {
9463
9587
  if (!disabled.includes(name))
9464
9588
  disabled.push(name);
9465
9589
  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) + `
9590
+ const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync8 } = await import("fs");
9591
+ const { dirname: dirname9 } = await import("path");
9592
+ mkdirSync8(dirname9(settingsPath), { recursive: true });
9593
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
9470
9594
  `);
9471
9595
  return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
9472
9596
  });
@@ -9477,15 +9601,15 @@ function createHooksServer() {
9477
9601
  const settingsPath = getSettingsPath(scope);
9478
9602
  let settings = {};
9479
9603
  try {
9480
- if (existsSync14(settingsPath))
9481
- settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
9604
+ if (existsSync15(settingsPath))
9605
+ settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
9482
9606
  } catch {}
9483
9607
  if (settings.hooks?.__disabled) {
9484
9608
  settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
9485
9609
  if (settings.hooks.__disabled.length === 0)
9486
9610
  delete settings.hooks.__disabled;
9487
- const { writeFileSync: writeFileSync6 } = await import("fs");
9488
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + `
9611
+ const { writeFileSync: writeFileSync7 } = await import("fs");
9612
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
9489
9613
  `);
9490
9614
  }
9491
9615
  return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, scope }) }] };
@@ -9848,13 +9972,13 @@ var init_server = __esm(() => {
9848
9972
  init_redact();
9849
9973
  init_authority();
9850
9974
  init_storage();
9851
- __dirname3 = dirname7(fileURLToPath4(import.meta.url));
9975
+ __dirname3 = dirname8(fileURLToPath4(import.meta.url));
9852
9976
  pkg = { name: "@hasna/hooks", version: "0.0.0" };
9853
9977
  try {
9854
9978
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
9855
9979
  const p = join16(__dirname3, rel);
9856
- if (existsSync14(p)) {
9857
- pkg = JSON.parse(readFileSync10(p, "utf-8"));
9980
+ if (existsSync15(p)) {
9981
+ pkg = JSON.parse(readFileSync11(p, "utf-8"));
9858
9982
  break;
9859
9983
  }
9860
9984
  }
@@ -11596,8 +11720,8 @@ var {
11596
11720
 
11597
11721
  // src/cli/index.tsx
11598
11722
  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";
11723
+ import { existsSync as existsSync16, readFileSync as readFileSync12, rmSync as rmSync4 } from "fs";
11724
+ import { join as join17, dirname as dirname9 } from "path";
11601
11725
  import { fileURLToPath as fileURLToPath5 } from "url";
11602
11726
 
11603
11727
  // src/cli/components/App.tsx
@@ -12439,7 +12563,7 @@ function InstallProgress({
12439
12563
  onComplete
12440
12564
  }) {
12441
12565
  const [results, setResults] = useState6([]);
12442
- const [current, setCurrent] = useState6(0);
12566
+ const [current2, setCurrent] = useState6(0);
12443
12567
  const [installing, setInstalling] = useState6(true);
12444
12568
  useEffect5(() => {
12445
12569
  const install = async () => {
@@ -12463,12 +12587,12 @@ function InstallProgress({
12463
12587
  marginBottom: 1,
12464
12588
  children: /* @__PURE__ */ jsxDEV6(Text9, {
12465
12589
  bold: true,
12466
- children: installing ? `Installing hooks (${current + 1}/${hooks.length})...` : "Installation complete!"
12590
+ children: installing ? `Installing hooks (${current2 + 1}/${hooks.length})...` : "Installation complete!"
12467
12591
  }, undefined, false, undefined, this)
12468
12592
  }, undefined, false, undefined, this),
12469
12593
  hooks.map((name, i) => {
12470
12594
  const result = results[i];
12471
- const isCurrent = i === current && installing;
12595
+ const isCurrent = i === current2 && installing;
12472
12596
  const meta = getHook(name);
12473
12597
  return /* @__PURE__ */ jsxDEV6(Box7, {
12474
12598
  flexDirection: "column",
@@ -12785,9 +12909,9 @@ init_local_opt_in();
12785
12909
  init_transport2();
12786
12910
  init_db();
12787
12911
  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"));
12912
+ var __dirname4 = dirname9(fileURLToPath5(import.meta.url));
12913
+ var pkgPath = existsSync16(join17(__dirname4, "..", "package.json")) ? join17(__dirname4, "..", "package.json") : join17(__dirname4, "..", "..", "package.json");
12914
+ var pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
12791
12915
  var program2 = new Command;
12792
12916
  function resolveScope(options) {
12793
12917
  if (options.project)
@@ -12797,12 +12921,45 @@ function resolveScope(options) {
12797
12921
  function resolveTarget(options) {
12798
12922
  if (options.target === "gemini")
12799
12923
  return "gemini";
12924
+ if (options.target === "codex")
12925
+ return "codex";
12800
12926
  if (options.target === "codewith")
12801
12927
  return "codewith";
12802
12928
  if (options.target === "all")
12803
12929
  return "all";
12804
12930
  return "claude";
12805
12931
  }
12932
+ function resolveConcreteTarget(options) {
12933
+ if (options.target === "gemini")
12934
+ return "gemini";
12935
+ if (options.target === "codex")
12936
+ return "codex";
12937
+ if (options.target === "codewith")
12938
+ return "codewith";
12939
+ return "claude";
12940
+ }
12941
+ function formatSettingsPath(scope, target) {
12942
+ if (target === "all")
12943
+ return "target-specific settings";
12944
+ const actual = getSettingsPath(scope, target);
12945
+ if (scope === "project") {
12946
+ if (target === "codewith")
12947
+ return ".codewith/config.toml";
12948
+ if (target === "gemini")
12949
+ return ".gemini/settings.json";
12950
+ if (target === "codex")
12951
+ return ".codex/hooks.json";
12952
+ return ".claude/settings.json";
12953
+ }
12954
+ if (target === "codewith") {
12955
+ return process.env.HASNA_HOOKS_CODEWITH_CONFIG_PATH ? "$HASNA_HOOKS_CODEWITH_CONFIG_PATH" : "~/.codewith/config.toml";
12956
+ }
12957
+ if (target === "gemini")
12958
+ return "~/.gemini/settings.json";
12959
+ if (target === "codex")
12960
+ return "~/.codex/hooks.json";
12961
+ return actual === getSettingsPath("global", "claude") ? "~/.claude/settings.json" : actual;
12962
+ }
12806
12963
  function parseLimit(value, fallback, max) {
12807
12964
  const parsed = value ? parseInt(value, 10) : fallback;
12808
12965
  if (!Number.isFinite(parsed) || parsed <= 0)
@@ -12834,7 +12991,7 @@ async function openLocalStoreOrExit(json2) {
12834
12991
  function readToken(tokenFile) {
12835
12992
  if (tokenFile) {
12836
12993
  try {
12837
- return readFileSync11(tokenFile, "utf-8").trim();
12994
+ return readFileSync12(tokenFile, "utf-8").trim();
12838
12995
  } catch {
12839
12996
  return;
12840
12997
  }
@@ -12988,11 +13145,11 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
12988
13145
  process.exit(1);
12989
13146
  }
12990
13147
  const hookScript = resolveScriptPath2(hook);
12991
- if (!hookScript || !existsSync15(hookScript)) {
13148
+ if (!hookScript || !existsSync16(hookScript)) {
12992
13149
  console.error(JSON.stringify({ error: `Hook script not found: ${hookScript ?? hook}` }));
12993
13150
  process.exit(1);
12994
13151
  }
12995
- const content = readFileSync11(hookScript);
13152
+ const content = readFileSync12(hookScript);
12996
13153
  const sha = sha256Of2(content);
12997
13154
  const check = checkScriptHash2(hook, sha);
12998
13155
  if (!check.ok) {
@@ -13098,7 +13255,7 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
13098
13255
  process.stderr.write(stderr);
13099
13256
  process.exit(exitCode);
13100
13257
  });
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) => {
13258
+ 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("--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
13259
  const scope = resolveScope(options);
13103
13260
  const target = resolveTarget(options);
13104
13261
  let toInstall = hooks;
@@ -13277,7 +13434,8 @@ Dry run \u2014 would install (${scope}, ${target}):
13277
13434
  success: results.filter((r) => r.success).length,
13278
13435
  scope,
13279
13436
  target,
13280
- applied: results.some((r) => r.applied)
13437
+ applied: results.some((r) => r.applied),
13438
+ ...target === "codex" ? { notes: results.filter((r) => r.success && r.note).map((r) => r.note) } : {}
13281
13439
  }));
13282
13440
  if (results.some((r) => !r.success)) {
13283
13441
  process.exitCode = 1;
@@ -13285,7 +13443,7 @@ Dry run \u2014 would install (${scope}, ${target}):
13285
13443
  return;
13286
13444
  }
13287
13445
  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";
13446
+ const settingsFile = target === "codewith" ? options.applyCodewith ? options.codewithConfig : "TOML fragment only (configs should apply)" : formatSettingsPath(scope, target);
13289
13447
  console.log(chalk2.bold(`
13290
13448
  Installing hooks (${scope}, ${target})...
13291
13449
  `));
@@ -13299,6 +13457,8 @@ Installing hooks (${scope}, ${target})...
13299
13457
  if (result.conflict) {
13300
13458
  console.log(chalk2.yellow(` \u26A0 Warning: ${result.conflict}`));
13301
13459
  }
13460
+ if (result.note && target === "codex")
13461
+ console.log(chalk2.dim(` ${result.note}`));
13302
13462
  if (result.fragment && target === "codewith") {
13303
13463
  console.log(chalk2.dim(" Codewith TOML fragment:"));
13304
13464
  console.log(chalk2.cyan(result.fragment.trimEnd().split(`
@@ -13325,12 +13485,12 @@ Installing hooks (${scope}, ${target})...
13325
13485
  console.log(chalk2.dim(`
13326
13486
  Registered in ${settingsFile}`));
13327
13487
  });
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) => {
13488
+ 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
13489
  const scope = resolveScope(options);
13330
13490
  const limit = options.all ? Number.MAX_SAFE_INTEGER : parseLimit(options.limit, 20, 200);
13331
13491
  const storeHooks = listStoreHooks();
13332
13492
  if (options.registered || options.installed) {
13333
- const target = options.target === "gemini" ? "gemini" : options.target === "codewith" ? "codewith" : "claude";
13493
+ const target = resolveConcreteTarget(options);
13334
13494
  const registered = getRegisteredHooksForTarget(scope, target);
13335
13495
  const installed = [...new Set([...registered, ...storeHooks.map((s) => s.meta.name)])];
13336
13496
  const shown = options.registered ? registered : installed;
@@ -13453,7 +13613,7 @@ Found ${results.length} hook(s), showing ${visible.length}:
13453
13613
  console.log(hookSummaryLine(h, { verbose: options.verbose }));
13454
13614
  printDisclosureHint(results.length - visible.length, "hooks info <name>");
13455
13615
  });
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) => {
13616
+ 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
13617
  const scope = resolveScope(options);
13458
13618
  const target = resolveTarget(options);
13459
13619
  const { uninstallHook: uninstallHook2 } = await Promise.resolve().then(() => (init_installer(), exports_installer));
@@ -13569,7 +13729,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
13569
13729
  });
13570
13730
  }
13571
13731
  const staleNames = new Set(stale.map((finding) => finding.hook));
13572
- const settingsExist = existsSync15(settingsPath);
13732
+ const settingsExist = existsSync16(settingsPath);
13573
13733
  if (!settingsExist) {
13574
13734
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
13575
13735
  }
@@ -13577,7 +13737,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
13577
13737
  let wiringCount = 0;
13578
13738
  if (settingsExist) {
13579
13739
  try {
13580
- const settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
13740
+ const settings = JSON.parse(readFileSync12(settingsPath, "utf-8"));
13581
13741
  wiringCount = countSettingsWiring(settings);
13582
13742
  } catch {}
13583
13743
  }
@@ -13588,7 +13748,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
13588
13748
  const meta = custom ? resolveHookMeta(name) : getHook(name);
13589
13749
  let hookHealthy = true;
13590
13750
  if (custom) {
13591
- if (!existsSync15(custom.scriptPath)) {
13751
+ if (!existsSync16(custom.scriptPath)) {
13592
13752
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
13593
13753
  hookHealthy = false;
13594
13754
  }
@@ -13600,14 +13760,14 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
13600
13760
  }
13601
13761
  const hookDir = getHookPath(name);
13602
13762
  const hookScript = join17(hookDir, "src", "hook.ts");
13603
- if (!existsSync15(hookScript)) {
13763
+ if (!existsSync16(hookScript)) {
13604
13764
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
13605
13765
  hookHealthy = false;
13606
13766
  }
13607
13767
  }
13608
13768
  if (meta && settingsExist) {
13609
13769
  try {
13610
- const settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
13770
+ const settings = JSON.parse(readFileSync12(settingsPath, "utf-8"));
13611
13771
  if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
13612
13772
  const eventName = meta.event.split(":")[0];
13613
13773
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
@@ -13812,8 +13972,8 @@ program2.command("docs").argument("[hook]", "Hook name (shows general docs if om
13812
13972
  const hookPath = getHookPath(hook);
13813
13973
  const readmePath = join17(hookPath, "README.md");
13814
13974
  let readme = "";
13815
- if (existsSync15(readmePath)) {
13816
- readme = readFileSync11(readmePath, "utf-8");
13975
+ if (existsSync16(readmePath)) {
13976
+ readme = readFileSync12(readmePath, "utf-8");
13817
13977
  }
13818
13978
  if (options.json) {
13819
13979
  console.log(JSON.stringify({ ...meta, readme }));
@@ -13932,7 +14092,7 @@ ${meta.displayName} v${meta.version}
13932
14092
  console.log();
13933
14093
  });
13934
14094
  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;
14095
+ const current2 = pkg2.version;
13936
14096
  let pm = "npm";
13937
14097
  try {
13938
14098
  const which = Bun.spawnSync(["which", "bun"]);
@@ -13950,13 +14110,13 @@ program2.command("upgrade").option("-c, --check", "Check for updates without ins
13950
14110
  }
13951
14111
  process.exit(1);
13952
14112
  }
13953
- const upToDate = current === latest2;
14113
+ const upToDate = current2 === latest2;
13954
14114
  if (options.json) {
13955
- console.log(JSON.stringify({ current, latest: latest2, upToDate }));
14115
+ console.log(JSON.stringify({ current: current2, latest: latest2, upToDate }));
13956
14116
  } else if (upToDate) {
13957
- console.log(chalk2.green(`\u2713 Already on latest version (${current})`));
14117
+ console.log(chalk2.green(`\u2713 Already on latest version (${current2})`));
13958
14118
  } else {
13959
- console.log(chalk2.yellow(`Update available: ${current} \u2192 ${latest2}`));
14119
+ console.log(chalk2.yellow(`Update available: ${current2} \u2192 ${latest2}`));
13960
14120
  console.log(chalk2.dim(` Run: hooks upgrade`));
13961
14121
  }
13962
14122
  return;
@@ -13977,7 +14137,7 @@ Upgrading @hasna/hooks (${pm})...
13977
14137
  const exitCode = await proc.exited;
13978
14138
  if (exitCode !== 0) {
13979
14139
  if (options.json) {
13980
- console.log(JSON.stringify({ current, updated: false, error: `${pm} exited with code ${exitCode}` }));
14140
+ console.log(JSON.stringify({ current: current2, updated: false, error: `${pm} exited with code ${exitCode}` }));
13981
14141
  } else {
13982
14142
  console.log(chalk2.red(`
13983
14143
  \u2717 Upgrade failed (exit code ${exitCode})`));
@@ -13987,18 +14147,18 @@ Upgrading @hasna/hooks (${pm})...
13987
14147
  const versionProc = Bun.spawnSync(["npm", "view", "@hasna/hooks", "version"]);
13988
14148
  const latest = new TextDecoder().decode(versionProc.stdout).trim() || "unknown";
13989
14149
  if (options.json) {
13990
- console.log(JSON.stringify({ current, latest, updated: true }));
14150
+ console.log(JSON.stringify({ current: current2, latest, updated: true }));
13991
14151
  } else {
13992
14152
  console.log(chalk2.green(`
13993
- \u2713 Upgraded: ${current} \u2192 ${latest}`));
14153
+ \u2713 Upgraded: ${current2} \u2192 ${latest}`));
13994
14154
  }
13995
14155
  });
13996
14156
  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
14157
  const profiles = exportProfiles();
13998
14158
  const json2 = JSON.stringify(profiles, null, 2);
13999
14159
  if (options.output) {
14000
- const { writeFileSync: writeFileSync6 } = await import("fs");
14001
- writeFileSync6(options.output, json2 + `
14160
+ const { writeFileSync: writeFileSync7 } = await import("fs");
14161
+ writeFileSync7(options.output, json2 + `
14002
14162
  `);
14003
14163
  console.log(chalk2.green(`\u2713 Exported ${profiles.length} profile(s) to ${options.output}`));
14004
14164
  } else {
@@ -14010,9 +14170,9 @@ program2.command("profile-import").argument("<file>", "JSON file to import profi
14010
14170
  if (file === "-") {
14011
14171
  raw = await new Response(Bun.stdin.stream()).text();
14012
14172
  } else {
14013
- const { readFileSync: readFileSync12 } = await import("fs");
14173
+ const { readFileSync: readFileSync13 } = await import("fs");
14014
14174
  try {
14015
- raw = readFileSync12(file, "utf-8");
14175
+ raw = readFileSync13(file, "utf-8");
14016
14176
  } catch {
14017
14177
  if (options.json) {
14018
14178
  console.log(JSON.stringify({ error: `Cannot read file: ${file}` }));