@hasna/hooks 0.11.7 → 0.12.1

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
@@ -165,6 +165,9 @@ var init_app_home = __esm(() => {
165
165
  });
166
166
 
167
167
  // src/lib/registry.ts
168
+ function matcherFor(meta, target) {
169
+ return meta.targetMatchers?.[target] ?? meta.matcher;
170
+ }
168
171
  function getHooksByCategory(category) {
169
172
  return HOOKS.filter((h) => h.category === category);
170
173
  }
@@ -178,7 +181,7 @@ function searchHooks(query) {
178
181
  function getHook(name) {
179
182
  return HOOKS.find((h) => h.name === name);
180
183
  }
181
- var CATEGORIES, HOOKS;
184
+ var CLAUDE_TRASH_GUARD_MATCHER = "^(Bash|Monitor|apply_patch|ApplyPatch|functions\\.apply_patch)$", CATEGORIES, HOOKS;
182
185
  var init_registry = __esm(() => {
183
186
  init_app_home();
184
187
  CATEGORIES = [
@@ -243,6 +246,8 @@ var init_registry = __esm(() => {
243
246
  category: "Git Safety",
244
247
  event: "PreToolUse",
245
248
  matcher: "^(Bash|Write|Edit|MultiEdit|NotebookEdit|apply_patch|ApplyPatch|functions\\.apply_patch)$",
249
+ targetMatchers: { claude: "^(Bash|Monitor|Write|Edit|MultiEdit|NotebookEdit|apply_patch|ApplyPatch|functions\\.apply_patch)$" },
250
+ legacyMatchers: ["^(Bash|Write|Edit|MultiEdit|NotebookEdit|apply_patch|ApplyPatch|functions\\.apply_patch)$"],
246
251
  tags: ["workspace", "repos", "structure", "guard", "safety", "orgs", "multi-agent"]
247
252
  },
248
253
  {
@@ -253,6 +258,8 @@ var init_registry = __esm(() => {
253
258
  category: "Git Safety",
254
259
  event: "PreToolUse",
255
260
  matcher: "^(Bash|apply_patch|ApplyPatch|functions\\.apply_patch)$",
261
+ targetMatchers: { claude: CLAUDE_TRASH_GUARD_MATCHER },
262
+ legacyMatchers: ["^(Bash|apply_patch|ApplyPatch|functions\\.apply_patch)$"],
256
263
  tags: ["rm", "delete", "trash", "recoverable", "guard", "safety", "bash"],
257
264
  rewritesInput: true,
258
265
  timeoutSeconds: 10
@@ -357,6 +364,28 @@ var init_registry = __esm(() => {
357
364
  matcher: "Bash",
358
365
  tags: ["security", "credentials", "secrets", "detection", "audit"]
359
366
  },
367
+ {
368
+ name: "signed-link-guard",
369
+ displayName: "Signed Link Guard",
370
+ description: "Refuses gh reads that would print composite GitHub content (bodies, comments, reviews, commit messages, check links and details, raw issue/PR/check API objects) where signed action links appear; bounded scalar projections, diffs, lists and writes pass",
371
+ version: "0.1.0",
372
+ category: "Security",
373
+ event: "PreToolUse",
374
+ matcher: "^(Bash|Monitor)$",
375
+ tags: ["github", "gh", "signed-links", "capabilities", "transcript", "guard", "security"],
376
+ timeoutSeconds: 10
377
+ },
378
+ {
379
+ name: "signed-link-output",
380
+ displayName: "Signed Link Output",
381
+ description: "Claude PostToolUse backstop: replaces signed-link URLs in tool output with a redaction marker (updatedToolOutput, where the running Claude Code supports it) and tells the model not to repeat or store the link. It runs after the tool, so it cannot undo display or other persistence",
382
+ version: "0.1.0",
383
+ category: "Security",
384
+ event: "PostToolUse",
385
+ matcher: "^(Bash|WebFetch|mcp__.*)$",
386
+ tags: ["github", "signed-links", "capabilities", "redaction", "transcript", "security"],
387
+ timeoutSeconds: 10
388
+ },
360
389
  {
361
390
  name: "phonenotify",
362
391
  displayName: "Phone Notify",
@@ -2520,7 +2549,7 @@ var init_codex_settings = __esm(() => {
2520
2549
  snapshots = new WeakMap;
2521
2550
  });
2522
2551
 
2523
- // ../../../../validation/build/apps/hooks/node_modules/@hasna/skills/dist/index.js
2552
+ // ../../node_modules/.bun/@hasna+skills@0.9.17+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
2524
2553
  import { existsSync as existsSync10 } from "fs";
2525
2554
  import { homedir as homedir6 } from "os";
2526
2555
  import { join as join10, resolve as resolve4 } from "path";
@@ -33989,6 +34018,7 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
33989
34018
  if (uniqueEventKeys.length === 0) {
33990
34019
  throw new Error(`Hook '${name}' has no installable events for target '${target}'`);
33991
34020
  }
34021
+ const matcher = matcherFor(meta3, target);
33992
34022
  const settings = readSettings2(scope, target);
33993
34023
  if (!settings.hooks)
33994
34024
  settings.hooks = {};
@@ -34023,10 +34053,12 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
34023
34053
  }
34024
34054
  }
34025
34055
  if (positions.length) {
34026
- if (positions.length !== uniqueEventKeys.length || uniqueEventKeys.some((event) => positions.filter((position) => position.event === event).length !== 1) || positions.some((position) => (position.entry.matcher ?? "") !== (meta3.matcher ?? ""))) {
34056
+ if (positions.length !== uniqueEventKeys.length || uniqueEventKeys.some((event) => positions.filter((position) => position.event === event).length !== 1) || positions.some((position) => (position.entry.matcher ?? "") !== matcher && !(meta3.legacyMatchers ?? []).includes(position.entry.matcher ?? ""))) {
34027
34057
  throw new Error("Codex hook events, matchers or duplicate registrations require explicit reconciliation; no settings were changed.");
34028
34058
  }
34029
34059
  for (const { entry, index } of positions) {
34060
+ if (matcher)
34061
+ entry.matcher = matcher;
34030
34062
  entry.hooks[index] = { ...entry.hooks[index], type: "command", command: hookCommand };
34031
34063
  if (typeof meta3.timeoutSeconds === "number" && meta3.timeoutSeconds > 0)
34032
34064
  entry.hooks[index].timeout = meta3.timeoutSeconds;
@@ -34046,8 +34078,8 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
34046
34078
  const entry = {
34047
34079
  hooks: [hookEntry]
34048
34080
  };
34049
- if (meta3.matcher) {
34050
- entry.matcher = meta3.matcher;
34081
+ if (matcher) {
34082
+ entry.matcher = matcher;
34051
34083
  }
34052
34084
  settings.hooks[eventKey].push(entry);
34053
34085
  }
@@ -34466,11 +34498,16 @@ function syncDirectory(path) {
34466
34498
  closeSync6(fd);
34467
34499
  }
34468
34500
  }
34501
+ function nativeSafetyRecordText(command) {
34502
+ const record3 = { schema: "hasna.hooks.native-safety.v1", harness: "sumi", command };
34503
+ return JSON.stringify(record3) + `
34504
+ `;
34505
+ }
34469
34506
  function readNativeSafetyRegistration(home = homedir8()) {
34470
34507
  try {
34471
34508
  home = canonicalHome(home);
34472
34509
  const path = join12(home, ".hasna/hooks/native", filename), text3 = readOwned(path);
34473
- return { record: parseRecord(text3, home), sha256: hash(text3), path };
34510
+ return { record: parseRecord(text3, home), sha256: hash(text3), path, text: text3 };
34474
34511
  } catch (error61) {
34475
34512
  if (error61 instanceof NativeSafetyError)
34476
34513
  throw error61;
@@ -34496,9 +34533,7 @@ function nativeSafetyBindingIntegrityReason(home = homedir8()) {
34496
34533
  }
34497
34534
  function registerNativeSafety(options = {}) {
34498
34535
  const home = canonicalHome(options.home ?? homedir8());
34499
- const record3 = { schema: "hasna.hooks.native-safety.v1", harness: "sumi", command: options.command ?? installedNativeSafetyCommand("trash-guard") };
34500
- const text3 = JSON.stringify(record3) + `
34501
- `;
34536
+ const text3 = nativeSafetyRecordText(options.command ?? installedNativeSafetyCommand("trash-guard"));
34502
34537
  parseRecord(text3, home);
34503
34538
  for (const dir of [join12(home, ".hasna"), join12(home, ".hasna/hooks"), join12(home, ".hasna/hooks/native")]) {
34504
34539
  try {
@@ -34954,13 +34989,115 @@ async function applyRegistration(options, dependencies = {}) {
34954
34989
  throw new NativeRegistrationError("apply_outcome_uncertain", operationId);
34955
34990
  }
34956
34991
  }
34992
+ function inspectSumi(dependencies) {
34993
+ let home, command;
34994
+ try {
34995
+ home = realpathSync7(dependencies.home ?? homedir9());
34996
+ } catch {
34997
+ throw new NativeRegistrationError("settings_unsafe");
34998
+ }
34999
+ try {
35000
+ command = dependencies.command ?? installedNativeSafetyCommand("trash-guard");
35001
+ } catch {
35002
+ throw new NativeRegistrationError("guard_failed");
35003
+ }
35004
+ const binding = nativeSafetyCommandBinding(command);
35005
+ need5(binding?.name === "trash-guard" && binding.home === home, "input_invalid");
35006
+ const directory2 = join13(home, ".hasna", "hooks", "native"), path = join13(directory2, "sumi-trash-guard.json");
35007
+ let existing = directory2;
35008
+ while (!lstatSync9(existing, { throwIfNoEntry: false }))
35009
+ existing = dirname11(existing);
35010
+ safeDirectory2(existing);
35011
+ let before;
35012
+ try {
35013
+ before = readNativeSafetyRegistration(home);
35014
+ } catch (error61) {
35015
+ if (!(error61 instanceof NativeSafetyError) || error61.code !== "binding_missing")
35016
+ throw new NativeRegistrationError(nativeSafetyBindingIntegrityReason(home) ? "settings_unsafe" : "settings_invalid");
35017
+ }
35018
+ const beforeSHA256 = before?.sha256 ?? "absent", desiredSHA256 = sha4(nativeSafetyRecordText(command));
35019
+ const base = {
35020
+ schema: "hasna.hooks.native-readiness-registration/v1",
35021
+ target: "sumi",
35022
+ settingsPath: path,
35023
+ resolvedPath: path,
35024
+ beforeSHA256,
35025
+ desiredSHA256,
35026
+ commandSHA256: sha4(command),
35027
+ action: beforeSHA256 === desiredSHA256 ? "unchanged" : "register",
35028
+ guardVerified: true,
35029
+ nativeAdoptionVerified: false
35030
+ };
35031
+ const plan = { ...base, planDigest: sha4(canonical4(base)) };
35032
+ return { home, command, before, plan };
35033
+ }
35034
+ async function planSumiRegistration(dependencies) {
35035
+ const before = inspectSumi(dependencies);
35036
+ try {
35037
+ await (dependencies.verify ?? verifyNativeSafetyCommand)(before.command, before.home);
35038
+ } catch {
35039
+ throw new NativeRegistrationError("guard_failed");
35040
+ }
35041
+ need5(inspectSumi(dependencies).plan.planDigest === before.plan.planDigest, "settings_changed");
35042
+ return before.plan;
35043
+ }
35044
+ async function applySumiRegistration(expectedPlanDigest, dependencies) {
35045
+ need5(typeof expectedPlanDigest === "string" && /^[a-f0-9]{64}$/.test(expectedPlanDigest), "input_invalid");
35046
+ const plan = await planSumiRegistration(dependencies);
35047
+ need5(plan.planDigest === expectedPlanDigest, "settings_changed");
35048
+ if (plan.action === "unchanged")
35049
+ return { ok: true, changed: false, ...plan };
35050
+ const current2 = inspectSumi(dependencies);
35051
+ need5(current2.plan.planDigest === plan.planDigest, "settings_changed");
35052
+ const operationId = randomUUID5();
35053
+ let directory2 = current2.home;
35054
+ for (const part of [".hasna", "hooks", "native", "trash-readiness-registration", operationId]) {
35055
+ directory2 = join13(directory2, part);
35056
+ try {
35057
+ mkdirSync11(directory2, { mode: 448 });
35058
+ syncDirectory2(dirname11(directory2));
35059
+ } catch (error61) {
35060
+ if (error61.code !== "EEXIST")
35061
+ throw new NativeRegistrationError("settings_unsafe");
35062
+ }
35063
+ safeDirectory2(directory2);
35064
+ }
35065
+ if (current2.before)
35066
+ exclusive(join13(directory2, "settings.before"), current2.before.text);
35067
+ exclusive(join13(directory2, "intent.json"), JSON.stringify({ operationId, plan }) + `
35068
+ `);
35069
+ try {
35070
+ need5(inspectSumi(dependencies).plan.planDigest === plan.planDigest, "settings_changed");
35071
+ const registered = registerNativeSafety({ home: current2.home, command: current2.command, expectedSHA256: plan.beforeSHA256 });
35072
+ const after = readNativeSafetyRegistration(current2.home);
35073
+ need5(registered.changed && registered.sha256 === plan.desiredSHA256 && after.sha256 === plan.desiredSHA256 && after.record.command === current2.command, "apply_outcome_uncertain");
35074
+ await (dependencies.verify ?? verifyNativeSafetyCommand)(after.record.command, current2.home);
35075
+ need5(readNativeSafetyRegistration(current2.home).sha256 === plan.desiredSHA256, "apply_outcome_uncertain");
35076
+ const receipt = {
35077
+ ok: true,
35078
+ changed: true,
35079
+ operationId,
35080
+ planDigest: plan.planDigest,
35081
+ settingsSHA256: after.sha256,
35082
+ guardVerified: true,
35083
+ ...current2.before ? { backup: plan.settingsPath + ".before-" + plan.beforeSHA256 } : {},
35084
+ nativeAdoptionVerified: false
35085
+ };
35086
+ exclusive(join13(directory2, "receipt.json"), JSON.stringify(receipt) + `
35087
+ `);
35088
+ return receipt;
35089
+ } catch {
35090
+ throw new NativeRegistrationError("apply_outcome_uncertain", operationId);
35091
+ }
35092
+ }
34957
35093
  var NativeRegistrationError, need5 = (value, code2) => {
34958
35094
  if (!value)
34959
35095
  throw new NativeRegistrationError(code2);
34960
- }, sha4 = (value) => createHash17("sha256").update(value).digest("hex"), planNativeSafetyRegistration = (options, dependencies = {}) => planRegistration({ target: options.target }, dependencies), applyNativeSafetyRegistration = (options, dependencies = {}) => applyRegistration({ target: options.target, expectedPlanDigest: options.expectedPlanDigest }, dependencies), planNativeReadinessRegistration = (options) => planRegistration({ target: options.target, readiness: true }), applyNativeReadinessRegistration = (options) => applyRegistration({ target: options.target, expectedPlanDigest: options.expectedPlanDigest, readiness: true });
35096
+ }, sha4 = (value) => createHash17("sha256").update(value).digest("hex"), planNativeSafetyRegistration = (options, dependencies = {}) => planRegistration({ target: options.target }, dependencies), applyNativeSafetyRegistration = (options, dependencies = {}) => applyRegistration({ target: options.target, expectedPlanDigest: options.expectedPlanDigest }, dependencies), planNativeReadinessRegistration = (options, sumiDependencies = {}) => options?.target === "sumi" ? planSumiRegistration(sumiDependencies) : planRegistration({ target: options?.target, readiness: true }), applyNativeReadinessRegistration = (options, sumiDependencies = {}) => options?.target === "sumi" ? applySumiRegistration(options.expectedPlanDigest, sumiDependencies) : applyRegistration({ target: options?.target, expectedPlanDigest: options?.expectedPlanDigest, readiness: true });
34961
35097
  var init_native_safety_registration = __esm(() => {
34962
35098
  init_installer();
34963
35099
  init_native_safety2();
35100
+ init_native_safety();
34964
35101
  init_native_readiness_command();
34965
35102
  init_registration();
34966
35103
  NativeRegistrationError = class NativeRegistrationError extends Error {
@@ -35248,13 +35385,14 @@ async function verifyCodexNativeSafety(options = {}, dependencies = {}) {
35248
35385
  await rpc.close();
35249
35386
  }
35250
35387
  }
35251
- var versions, matcher = "^(Bash|apply_patch|ApplyPatch|functions\\.apply_patch)$", CodexSafetyError, digest3 = (bytes) => createHash21("sha256").update(bytes).digest("hex");
35388
+ var SUPPORTED_CODEX_SAFETY_VERSIONS, versions, matcher = "^(Bash|apply_patch|ApplyPatch|functions\\.apply_patch)$", CodexSafetyError, digest3 = (bytes) => createHash21("sha256").update(bytes).digest("hex");
35252
35389
  var init_codex_safety_check = __esm(() => {
35253
35390
  init_native_safety();
35254
35391
  init_native_readiness_command();
35255
35392
  init_native_safety2();
35256
35393
  init_native_readiness();
35257
- versions = new Set(["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.154.0-alpha.6.1", "codex-cli 0.155.0", "codex-cli 0.155.1", "codex-cli 0.156.1", "codex-cli 0.157.0", "codex-cli 0.157.1", "codex-cli 0.158.0", "codex-cli 0.159.0"]);
35394
+ SUPPORTED_CODEX_SAFETY_VERSIONS = ["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.154.0-alpha.6.1", "codex-cli 0.155.0", "codex-cli 0.155.1", "codex-cli 0.156.1", "codex-cli 0.157.0", "codex-cli 0.157.1", "codex-cli 0.158.0", "codex-cli 0.159.0", "codex-cli 0.159.2"];
35395
+ versions = new Set(SUPPORTED_CODEX_SAFETY_VERSIONS);
35258
35396
  CodexSafetyError = class CodexSafetyError extends Error {
35259
35397
  code;
35260
35398
  constructor(code2) {
@@ -35495,7 +35633,7 @@ var init_codex_safety_trust = __esm(() => {
35495
35633
  };
35496
35634
  });
35497
35635
 
35498
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/error.js
35636
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/error.js
35499
35637
  var require_error = __commonJS((exports) => {
35500
35638
  class CommanderError extends Error {
35501
35639
  constructor(exitCode, code2, message) {
@@ -35519,7 +35657,7 @@ var require_error = __commonJS((exports) => {
35519
35657
  exports.InvalidArgumentError = InvalidArgumentError;
35520
35658
  });
35521
35659
 
35522
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/argument.js
35660
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/argument.js
35523
35661
  var require_argument = __commonJS((exports) => {
35524
35662
  var { InvalidArgumentError } = require_error();
35525
35663
 
@@ -35598,7 +35736,7 @@ var require_argument = __commonJS((exports) => {
35598
35736
  exports.humanReadableArgName = humanReadableArgName;
35599
35737
  });
35600
35738
 
35601
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/help.js
35739
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/help.js
35602
35740
  var require_help = __commonJS((exports) => {
35603
35741
  var { humanReadableArgName } = require_argument();
35604
35742
 
@@ -35847,7 +35985,7 @@ var require_help = __commonJS((exports) => {
35847
35985
  exports.Help = Help;
35848
35986
  });
35849
35987
 
35850
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/option.js
35988
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/option.js
35851
35989
  var require_option = __commonJS((exports) => {
35852
35990
  var { InvalidArgumentError } = require_error();
35853
35991
 
@@ -35998,7 +36136,7 @@ var require_option = __commonJS((exports) => {
35998
36136
  exports.DualOptions = DualOptions;
35999
36137
  });
36000
36138
 
36001
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/suggestSimilar.js
36139
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/suggestSimilar.js
36002
36140
  var require_suggestSimilar = __commonJS((exports) => {
36003
36141
  var maxDistance = 3;
36004
36142
  function editDistance(a, b) {
@@ -36071,7 +36209,7 @@ var require_suggestSimilar = __commonJS((exports) => {
36071
36209
  exports.suggestSimilar = suggestSimilar;
36072
36210
  });
36073
36211
 
36074
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/command.js
36212
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/command.js
36075
36213
  var require_command = __commonJS((exports) => {
36076
36214
  var EventEmitter = __require("events").EventEmitter;
36077
36215
  var childProcess = __require("child_process");
@@ -37314,7 +37452,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
37314
37452
  exports.Command = Command;
37315
37453
  });
37316
37454
 
37317
- // ../../../../validation/build/apps/hooks/node_modules/commander/index.js
37455
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/index.js
37318
37456
  var require_commander = __commonJS((exports) => {
37319
37457
  var { Argument } = require_argument();
37320
37458
  var { Command } = require_command();
@@ -37334,7 +37472,7 @@ var require_commander = __commonJS((exports) => {
37334
37472
  exports.InvalidOptionArgumentError = InvalidArgumentError;
37335
37473
  });
37336
37474
 
37337
- // ../../../../validation/build/apps/hooks/node_modules/cli-spinners/spinners.json
37475
+ // ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/spinners.json
37338
37476
  var require_spinners = __commonJS((exports, module) => {
37339
37477
  module.exports = {
37340
37478
  dots: {
@@ -38960,7 +39098,7 @@ var require_spinners = __commonJS((exports, module) => {
38960
39098
  };
38961
39099
  });
38962
39100
 
38963
- // ../../../../validation/build/apps/hooks/node_modules/cli-spinners/index.js
39101
+ // ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/index.js
38964
39102
  var require_cli_spinners = __commonJS((exports, module) => {
38965
39103
  var spinners = Object.assign({}, require_spinners());
38966
39104
  var spinnersList = Object.keys(spinners);
@@ -39041,10 +39179,10 @@ var init_redact = __esm(() => {
39041
39179
 
39042
39180
  // src/lib/profiles.ts
39043
39181
  import { existsSync as existsSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync19, writeFileSync as writeFileSync12, readdirSync as readdirSync8, rmSync, cpSync as cpSync2 } from "fs";
39044
- import { join as join27 } from "path";
39182
+ import { join as join29 } from "path";
39045
39183
  function getProfilesDir() {
39046
- const newDir = join27(getEffectiveDataRoot(), "profiles");
39047
- const oldDir = join27(getHomeDir(), ".hooks", "profiles");
39184
+ const newDir = join29(getEffectiveDataRoot(), "profiles");
39185
+ const oldDir = join29(getHomeDir(), ".hooks", "profiles");
39048
39186
  if (!existsSync14(newDir) && existsSync14(oldDir)) {
39049
39187
  mkdirSync13(newDir, { recursive: true });
39050
39188
  cpSync2(oldDir, newDir, { recursive: true });
@@ -39058,7 +39196,7 @@ function ensureProfilesDir() {
39058
39196
  }
39059
39197
  }
39060
39198
  function profilePath(id) {
39061
- return join27(getProfilesDir(), `${id}.json`);
39199
+ return join29(getProfilesDir(), `${id}.json`);
39062
39200
  }
39063
39201
  function shortUuid() {
39064
39202
  return crypto.randomUUID().slice(0, 8);
@@ -39100,7 +39238,7 @@ function listProfiles() {
39100
39238
  const profiles = [];
39101
39239
  for (const file2 of files) {
39102
39240
  try {
39103
- const content = readFileSync19(join27(dir, file2), "utf-8");
39241
+ const content = readFileSync19(join29(dir, file2), "utf-8");
39104
39242
  profiles.push(JSON.parse(content));
39105
39243
  } catch {}
39106
39244
  }
@@ -39144,18 +39282,18 @@ var init_profiles = __esm(() => {
39144
39282
  init_app_home();
39145
39283
  });
39146
39284
 
39147
- // ../../../../validation/build/apps/hooks/node_modules/@hasna/contracts/dist/client/transport.js
39148
- import { isIP } from "net";
39285
+ // ../../node_modules/.bun/@hasna+contracts@1.2.2+944b86d8024e79fa/node_modules/@hasna/contracts/dist/client/transport.js
39286
+ import { isIP as isIP2 } from "net";
39149
39287
  import { spawnSync as spawnSync2 } from "child_process";
39150
39288
  import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
39151
39289
  import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
39152
39290
  import { hostname as osHostname } from "os";
39153
39291
  import { isAbsolute as isAbsolute21, join as join28 } from "path";
39154
- import { createRequire as createRequire2 } from "module";
39292
+ import { createRequire as createRequire3 } from "module";
39155
39293
  import { fileURLToPath as fileURLToPath22 } from "url";
39156
39294
  import { closeSync as closeSync20, fstatSync as fstatSync19, lstatSync as lstatSync24, openSync as openSync20, readFileSync as readFileSync20, realpathSync as realpathSync12, statSync as statSync2 } from "fs";
39157
39295
  import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
39158
- import { basename as basename9, dirname as dirname22, join as join29, relative as relative5, sep as sep6 } from "path";
39296
+ import { basename as basename9, dirname as dirname22, join as join30, relative as relative5, sep as sep6 } from "path";
39159
39297
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
39160
39298
  function envToken2(name) {
39161
39299
  return name.toUpperCase().replace(/-/g, "_");
@@ -39230,11 +39368,11 @@ function o(e2, r2, t2) {
39230
39368
  }
39231
39369
  function entryPoint(directory2) {
39232
39370
  const root = realpathSync12(directory2);
39233
- const fd = openSync20(join29(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
39371
+ const fd = openSync20(join30(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
39234
39372
  let pkg;
39235
39373
  try {
39236
39374
  const stat2 = fstatSync19(fd);
39237
- if (!stat2.isFile() || stat2.size > MAX_PACKAGE_BYTES2)
39375
+ if (!stat2.isFile() || stat2.size > MAX_PACKAGE_BYTES3)
39238
39376
  throw new Error("Invalid Secrets package metadata");
39239
39377
  pkg = JSON.parse(readFileSync20(fd, "utf8"));
39240
39378
  } finally {
@@ -39250,7 +39388,7 @@ function entryPoint(directory2) {
39250
39388
  if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
39251
39389
  throw new Error("Invalid Secrets import export");
39252
39390
  }
39253
- const file2 = realpathSync12(join29(root, ...parts));
39391
+ const file2 = realpathSync12(join30(root, ...parts));
39254
39392
  const within = relative5(root, file2);
39255
39393
  if (!within || within === ".." || within.startsWith(`..${sep6}`) || !statSync2(file2).isFile()) {
39256
39394
  throw new Error("Secrets import export escapes its package or is not a file");
@@ -39261,7 +39399,7 @@ function resolveInstalledSecrets(parentUrl) {
39261
39399
  let directory2 = dirname22(fileURLToPath3(parentUrl));
39262
39400
  for (;; ) {
39263
39401
  if (basename9(directory2) !== "node_modules") {
39264
- const candidate = join29(directory2, "node_modules", PACKAGE);
39402
+ const candidate = join30(directory2, "node_modules", PACKAGE);
39265
39403
  let absent = false;
39266
39404
  try {
39267
39405
  lstatSync24(candidate);
@@ -39381,7 +39519,7 @@ function readAppConfigFile(path) {
39381
39519
  const uid2 = process.getuid?.() ?? process.geteuid?.();
39382
39520
  if (uid2 !== undefined && before.uid !== uid2)
39383
39521
  unsafe("the file is not owned by the current user");
39384
- if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
39522
+ if (before.size > MAX_CREDENTIAL_FILE_BYTES3)
39385
39523
  unsafe("the file exceeds the size limit");
39386
39524
  const bytes = readFileSync22(fd);
39387
39525
  const after = fstatSync22(fd);
@@ -39473,13 +39611,13 @@ function sealCredential(fields) {
39473
39611
  configurable: false
39474
39612
  });
39475
39613
  }
39476
- Object.defineProperty(sealed, INSPECT_CUSTOM2, {
39614
+ Object.defineProperty(sealed, INSPECT_CUSTOM3, {
39477
39615
  value: () => ({ ...visible, apiKey: "[redacted]" }),
39478
39616
  enumerable: false,
39479
39617
  writable: false,
39480
39618
  configurable: false
39481
39619
  });
39482
- Object.defineProperty(sealed, CREDENTIAL_SEAL2, {
39620
+ Object.defineProperty(sealed, CREDENTIAL_SEAL3, {
39483
39621
  value: true,
39484
39622
  enumerable: false,
39485
39623
  writable: false,
@@ -39510,7 +39648,7 @@ function firstEnvValue(env, keys2) {
39510
39648
  return null;
39511
39649
  }
39512
39650
  function isAmbientEnvironment(env) {
39513
- return env === process.env || env[AMBIENT_ENVIRONMENT2] === true;
39651
+ return env === process.env || env[AMBIENT_ENVIRONMENT3] === true;
39514
39652
  }
39515
39653
  function defaultKeychainRunner(argv) {
39516
39654
  const result = spawnSync2(KEYCHAIN_SECURITY_BIN, [...argv], {
@@ -39604,7 +39742,7 @@ function snapshotClientEnvironment(name, env) {
39604
39742
  snapshot2[key] = descriptor.value;
39605
39743
  }
39606
39744
  if (ambient) {
39607
- Object.defineProperty(snapshot2, AMBIENT_ENVIRONMENT2, {
39745
+ Object.defineProperty(snapshot2, AMBIENT_ENVIRONMENT3, {
39608
39746
  value: true,
39609
39747
  enumerable: false,
39610
39748
  writable: false,
@@ -39777,7 +39915,7 @@ async function completePointerCredential(name, pointerResolution, env = process.
39777
39915
  let secretsSdk;
39778
39916
  try {
39779
39917
  const sdkUrl = resolveInstalledSecrets(import.meta.url);
39780
- secretsSdk = requireSecretsSdk2(fileURLToPath22(sdkUrl));
39918
+ secretsSdk = requireSecretsSdk3(fileURLToPath22(sdkUrl));
39781
39919
  } catch {
39782
39920
  throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
39783
39921
  }
@@ -39861,7 +39999,7 @@ function canonicalAuthorityHostname(authority) {
39861
39999
  }
39862
40000
  assertCanonicalPort(portSuffix.slice(1));
39863
40001
  }
39864
- if (isIP(rawHostname.slice(1, -1)) !== 6) {
40002
+ if (isIP2(rawHostname.slice(1, -1)) !== 6) {
39865
40003
  throw new Error("API URL authority must contain a canonical IPv6 literal.");
39866
40004
  }
39867
40005
  } else {
@@ -39877,7 +40015,7 @@ function canonicalAuthorityHostname(authority) {
39877
40015
  } else {
39878
40016
  rawHostname = authority;
39879
40017
  }
39880
- const ipVersion = isIP(rawHostname);
40018
+ const ipVersion = isIP2(rawHostname);
39881
40019
  const numericAddressParts = rawHostname.split(".");
39882
40020
  const looksLikeNonCanonicalIpv4 = numericAddressParts.every((part) => /^(?:0x[0-9a-f]+|[0-9]+)$/i.test(part));
39883
40021
  if (ipVersion !== 4 && looksLikeNonCanonicalIpv4 || ipVersion !== 4 && !isValidDnsDomain(rawHostname.toLowerCase())) {
@@ -40013,9 +40151,9 @@ function credentialDiskSourcesForMessage(name, env) {
40013
40151
  const paths = credentialDiskSources(name, env);
40014
40152
  return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
40015
40153
  }
40016
- var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", PACKAGE = "@hasna/secrets", MAX_PACKAGE_BYTES2, CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES2, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, AMBIENT_ENVIRONMENT2, requireSecretsSdk2, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2;
40154
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", PACKAGE = "@hasna/secrets", MAX_PACKAGE_BYTES3, CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES3, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM3, CREDENTIAL_SEAL3, AMBIENT_ENVIRONMENT3, requireSecretsSdk3, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS3, AUTHORITY_OVERRIDE_HEADERS3;
40017
40155
  var init_transport = __esm(() => {
40018
- MAX_PACKAGE_BYTES2 = 1024 * 1024;
40156
+ MAX_PACKAGE_BYTES3 = 1024 * 1024;
40019
40157
  CredentialResolutionError = class CredentialResolutionError extends Error {
40020
40158
  appName;
40021
40159
  attempted;
@@ -40034,16 +40172,16 @@ var init_transport = __esm(() => {
40034
40172
  this.path = path;
40035
40173
  }
40036
40174
  };
40037
- MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
40175
+ MAX_CREDENTIAL_FILE_BYTES3 = 64 * 1024;
40038
40176
  SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
40039
40177
  SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
40040
40178
  ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
40041
40179
  VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
40042
40180
  CREDENTIAL_SHAPED_KEY = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
40043
- INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
40044
- CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
40045
- AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
40046
- requireSecretsSdk2 = createRequire2(import.meta.url);
40181
+ INSPECT_CUSTOM3 = Symbol.for("nodejs.util.inspect.custom");
40182
+ CREDENTIAL_SEAL3 = Symbol.for("hasna:contracts:sealedCredential");
40183
+ AMBIENT_ENVIRONMENT3 = Symbol.for("hasna:contracts:ambientClientEnvironment");
40184
+ requireSecretsSdk3 = createRequire3(import.meta.url);
40047
40185
  ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
40048
40186
  DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
40049
40187
  ClientTransportConfigurationError = class ClientTransportConfigurationError extends Error {
@@ -40056,8 +40194,8 @@ var init_transport = __esm(() => {
40056
40194
  this.sources = Object.freeze([...sources]);
40057
40195
  }
40058
40196
  };
40059
- IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
40060
- AUTHORITY_OVERRIDE_HEADERS2 = new Set([
40197
+ IDEMPOTENT_METHODS3 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
40198
+ AUTHORITY_OVERRIDE_HEADERS3 = new Set([
40061
40199
  "host",
40062
40200
  ":authority",
40063
40201
  "forwarded",
@@ -40568,7 +40706,7 @@ __export(exports_run, {
40568
40706
  });
40569
40707
  import { randomBytes as randomBytes2 } from "crypto";
40570
40708
  import { rmSync as rmSync2, writeFileSync as writeFileSync13 } from "fs";
40571
- import { basename as basename11, dirname as dirname24, isAbsolute as isAbsolute25, join as join30 } from "path";
40709
+ import { basename as basename11, dirname as dirname24, isAbsolute as isAbsolute25, join as join31 } from "path";
40572
40710
  function extensionOf(scriptPath) {
40573
40711
  const base = basename11(scriptPath);
40574
40712
  const dot = base.lastIndexOf(".");
@@ -40639,7 +40777,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
40639
40777
  async function executeVerifiedScript(options) {
40640
40778
  const scriptDir = dirname24(options.scriptPath);
40641
40779
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
40642
- const tempPath = join30(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
40780
+ const tempPath = join31(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
40643
40781
  let proc = null;
40644
40782
  let timedOut = false;
40645
40783
  const timeoutTimer = options.timeout ? setTimeout(() => {
@@ -40731,10 +40869,10 @@ __export(exports_custom_install, {
40731
40869
  ensureCustomDir: () => ensureCustomDir
40732
40870
  });
40733
40871
  import { existsSync as existsSync16, mkdirSync as mkdirSync14, mkdtempSync, readFileSync as readFileSync21, rmSync as rmSync4 } from "fs";
40734
- import { join as join31 } from "path";
40872
+ import { join as join32 } from "path";
40735
40873
  import { tmpdir as tmpdir2 } from "os";
40736
40874
  function manifestAt(dir) {
40737
- const path = join31(dir, "manifest.json");
40875
+ const path = join32(dir, "manifest.json");
40738
40876
  if (!existsSync16(path))
40739
40877
  return;
40740
40878
  try {
@@ -40757,8 +40895,8 @@ function writeFromManifest(manifest, srcDir, source, kind) {
40757
40895
  return { name, version: manifest.version, kind, source, dir, scriptPath };
40758
40896
  }
40759
40897
  async function cloneToTemp(gitUrl) {
40760
- const tmp = mkdtempSync(join31(tmpdir2(), "hooks-install-"));
40761
- const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join31(tmp, "repo")], {
40898
+ const tmp = mkdtempSync(join32(tmpdir2(), "hooks-install-"));
40899
+ const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join32(tmp, "repo")], {
40762
40900
  stdout: "pipe",
40763
40901
  stderr: "pipe"
40764
40902
  });
@@ -40771,7 +40909,7 @@ async function cloneToTemp(gitUrl) {
40771
40909
  rmSync4(tmp, { recursive: true, force: true });
40772
40910
  throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
40773
40911
  }
40774
- return join31(tmp, "repo");
40912
+ return join32(tmp, "repo");
40775
40913
  }
40776
40914
  function isGitUrl(value) {
40777
40915
  return value.startsWith("git@") || value.startsWith("ssh://") || value.startsWith("file://") || value.endsWith(".git");
@@ -42010,11 +42148,11 @@ __export(exports_serve, {
42010
42148
  DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
42011
42149
  });
42012
42150
  import { readFileSync as readFileSync25 } from "fs";
42013
- import { dirname as dirname25, join as join32 } from "path";
42151
+ import { dirname as dirname25, join as join33 } from "path";
42014
42152
  import { fileURLToPath as fileURLToPath4 } from "url";
42015
42153
  function packageVersion() {
42016
42154
  try {
42017
- const pkgPath = join32(dirname25(fileURLToPath4(import.meta.url)), "..", "package.json");
42155
+ const pkgPath = join33(dirname25(fileURLToPath4(import.meta.url)), "..", "package.json");
42018
42156
  return JSON.parse(readFileSync25(pkgPath, "utf-8")).version || "0.0.0";
42019
42157
  } catch {
42020
42158
  return "0.0.0";
@@ -42316,7 +42454,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
42316
42454
  import { z as z3 } from "zod";
42317
42455
  import { createServer } from "http";
42318
42456
  import { existsSync as existsSync17, readFileSync as readFileSync26 } from "fs";
42319
- import { join as join33, dirname as dirname26 } from "path";
42457
+ import { join as join34, dirname as dirname26 } from "path";
42320
42458
  import { fileURLToPath as fileURLToPath5 } from "url";
42321
42459
  async function runVerifiedHook(name, stdin, timeoutMs) {
42322
42460
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
@@ -42550,7 +42688,7 @@ function createHooksServer() {
42550
42688
  continue;
42551
42689
  }
42552
42690
  const hookDir = getHookPath(name);
42553
- if (!existsSync17(join33(hookDir, "src", "hook.ts"))) {
42691
+ if (!existsSync17(join34(hookDir, "src", "hook.ts"))) {
42554
42692
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
42555
42693
  hookHealthy = false;
42556
42694
  }
@@ -42558,7 +42696,7 @@ function createHooksServer() {
42558
42696
  if (meta3 && settingsExist) {
42559
42697
  try {
42560
42698
  const settings = JSON.parse(readFileSync26(settingsPath, "utf-8"));
42561
- if (!hookRegisteredInSettings(settings, name, meta3.event, meta3.matcher)) {
42699
+ if (!hookRegisteredInSettings(settings, name, meta3.event, matcherFor(meta3, "claude"))) {
42562
42700
  const eventName = meta3.event.split(":")[0];
42563
42701
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
42564
42702
  hookHealthy = false;
@@ -42594,7 +42732,7 @@ function createHooksServer() {
42594
42732
  return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
42595
42733
  }
42596
42734
  const hookPath = getHookPath(name);
42597
- const readmePath = join33(hookPath, "README.md");
42735
+ const readmePath = join34(hookPath, "README.md");
42598
42736
  let readme = "";
42599
42737
  if (existsSync17(readmePath)) {
42600
42738
  readme = readFileSync26(readmePath, "utf-8");
@@ -43324,7 +43462,7 @@ var init_server = __esm(() => {
43324
43462
  pkg = { name: "@hasna/hooks", version: "0.0.0" };
43325
43463
  try {
43326
43464
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
43327
- const p = join33(__dirname3, rel);
43465
+ const p = join34(__dirname3, rel);
43328
43466
  if (existsSync17(p)) {
43329
43467
  pkg = JSON.parse(readFileSync26(p, "utf-8"));
43330
43468
  break;
@@ -43403,7 +43541,7 @@ __export(exports_native_readiness_binding, {
43403
43541
  import { createHash as createHash25, randomUUID as randomUUID8 } from "crypto";
43404
43542
  import { closeSync as closeSync21, constants as constants19, fsyncSync as fsyncSync8, lstatSync as lstatSync25, mkdirSync as mkdirSync15, openSync as openSync21, realpathSync as realpathSync14, writeFileSync as writeFileSync14 } from "fs";
43405
43543
  import { homedir as homedir15 } from "os";
43406
- import { dirname as dirname27, join as join34 } from "path";
43544
+ import { dirname as dirname27, join as join35 } from "path";
43407
43545
  function need10(value, code2) {
43408
43546
  if (!value)
43409
43547
  throw new ReadinessBindingError(code2);
@@ -43461,7 +43599,7 @@ async function registerTrashReadinessBinding(input2, expectedSHA256) {
43461
43599
  const operationId = randomUUID8();
43462
43600
  let directory2 = home;
43463
43601
  for (const part of [".hasna", "hooks", "native", "trash-readiness"]) {
43464
- directory2 = join34(directory2, part);
43602
+ directory2 = join35(directory2, part);
43465
43603
  try {
43466
43604
  mkdirSync15(directory2, { mode: 448 });
43467
43605
  syncDirectory4(dirname27(directory2));
@@ -43471,12 +43609,12 @@ async function registerTrashReadinessBinding(input2, expectedSHA256) {
43471
43609
  }
43472
43610
  safeDirectory4(directory2);
43473
43611
  }
43474
- const operation = join34(directory2, operationId);
43612
+ const operation = join35(directory2, operationId);
43475
43613
  mkdirSync15(operation, { mode: 448 });
43476
43614
  syncDirectory4(directory2);
43477
43615
  if (before)
43478
- exclusive3(join34(operation, "binding.before"), before.raw);
43479
- exclusive3(join34(operation, "intent.json"), JSON.stringify({ operationId, target, expectedSHA256, desiredSHA256: digest4 }) + `
43616
+ exclusive3(join35(operation, "binding.before"), before.raw);
43617
+ exclusive3(join35(operation, "intent.json"), JSON.stringify({ operationId, target, expectedSHA256, desiredSHA256: digest4 }) + `
43480
43618
  `);
43481
43619
  try {
43482
43620
  need10((read4()?.sha256 ?? "absent") === expectedSHA256, "binding_changed");
@@ -43490,7 +43628,7 @@ async function registerTrashReadinessBinding(input2, expectedSHA256) {
43490
43628
  syncDirectory4(directory2);
43491
43629
  need10(read4()?.sha256 === digest4, "binding_changed");
43492
43630
  const receipt = { ok: true, changed: true, operationId, path: target, sha256: digest4, configurationReady: true, nativeAdoptionVerified: false };
43493
- exclusive3(join34(operation, "receipt.json"), JSON.stringify(receipt) + `
43631
+ exclusive3(join35(operation, "receipt.json"), JSON.stringify(receipt) + `
43494
43632
  `);
43495
43633
  return receipt;
43496
43634
  } catch {
@@ -43514,11 +43652,12 @@ var init_native_readiness_binding = __esm(() => {
43514
43652
 
43515
43653
  // src/cli/index.tsx
43516
43654
  init_native_safety_registration();
43517
- import { resolve as resolve21 } from "path";
43655
+ import { resolve as resolve23 } from "path";
43518
43656
 
43519
43657
  // src/lib/claude-safety-check.ts
43520
43658
  init_native_safety();
43521
43659
  init_native_safety2();
43660
+ init_registry();
43522
43661
  import { constants as constants15, closeSync as closeSync15, fstatSync as fstatSync7, lstatSync as lstatSync10, openSync as openSync15, readFileSync as readFileSync12, realpathSync as realpathSync8 } from "fs";
43523
43662
  import { createHash as createHash19 } from "crypto";
43524
43663
  import { dirname as dirname17, isAbsolute as isAbsolute17, join as join17, resolve as resolve18 } from "path";
@@ -43602,7 +43741,7 @@ async function verifyClaudeSafetyConfiguration(options = {}, dependencies = {})
43602
43741
  need7(!/^(?:hooks run trash-guard|hook-trash-guard)(?:\s|$)/.test(handler.command.trim()), "guard_definition_invalid");
43603
43742
  if (binding?.name !== "trash-guard")
43604
43743
  continue;
43605
- need7(binding.home === home && event === "PreToolUse" && handler.type === "command" && group.matcher === "^(Bash|apply_patch|ApplyPatch|functions\\.apply_patch)$" && handler.timeout === 10 && (handler.async === undefined || handler.async === false) && handler.asyncRewake !== true, "guard_definition_invalid");
43744
+ need7(binding.home === home && event === "PreToolUse" && handler.type === "command" && group.matcher === CLAUDE_TRASH_GUARD_MATCHER && handler.timeout === 10 && (handler.async === undefined || handler.async === false) && handler.asyncRewake !== true, "guard_definition_invalid");
43606
43745
  guards.push({ command: handler.command, path: file2.path });
43607
43746
  }
43608
43747
  }
@@ -43633,16 +43772,25 @@ async function verifyClaudeSafetyConfiguration(options = {}, dependencies = {})
43633
43772
  // src/cli/index.tsx
43634
43773
  init_codex_safety_trust();
43635
43774
 
43636
- // ../../../../validation/build/apps/hooks/node_modules/@hasna/events/dist/commander.js
43775
+ // ../events/dist/commander.js
43637
43776
  import { chmod, mkdir, readFile as readFile2, rename, writeFile } from "fs/promises";
43638
43777
  import { Buffer as Buffer2 } from "buffer";
43778
+ import { existsSync as existsSync22 } from "fs";
43779
+ import { join as join22 } from "path";
43639
43780
  import { existsSync as existsSync13 } from "fs";
43640
43781
  import { homedir as homedir13 } from "os";
43641
- import { join as join20 } from "path";
43782
+ import { join as join20, resolve as resolve20 } from "path";
43783
+ import { homedir as pathsResolverHomedir3 } from "os";
43784
+ import { join as pathsResolverJoin3 } from "path";
43642
43785
  import { createHmac, timingSafeEqual } from "crypto";
43786
+ import { lookup as dnsLookup } from "dns/promises";
43787
+ import { isIP } from "net";
43643
43788
  import { randomUUID as randomUUID7 } from "crypto";
43644
43789
  import { spawn as spawn3 } from "child_process";
43790
+ import { request as nodeHttpRequest } from "http";
43791
+ import { request as nodeHttpsRequest } from "https";
43645
43792
  import { randomUUID as randomUUID22 } from "crypto";
43793
+ import { createRequire as createRequire2 } from "module";
43646
43794
  function getPathValue(input2, path) {
43647
43795
  return path.split(".").reduce((value, part) => {
43648
43796
  if (value && typeof value === "object" && part in value) {
@@ -43738,13 +43886,103 @@ function channelMatchesEvent(channel, event) {
43738
43886
  return true;
43739
43887
  return channel.filters.some((filter) => eventMatchesFilter(event, filter));
43740
43888
  }
43889
+ var PATHS_RESOLVER_KIND_ENV2 = {
43890
+ config: "HASNA_CONFIG_HOME",
43891
+ data: "HASNA_DATA_HOME",
43892
+ state: "HASNA_STATE_HOME",
43893
+ cache: "HASNA_CACHE_HOME"
43894
+ };
43895
+ var PATHS_RESOLVER_APP_SLUG_RE3 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
43896
+ function pathsResolverAssertApp3(app) {
43897
+ if (typeof app !== "string" || app.length === 0) {
43898
+ throw new TypeError("paths: app must be a non-empty string");
43899
+ }
43900
+ if (!PATHS_RESOLVER_APP_SLUG_RE3.test(app)) {
43901
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
43902
+ }
43903
+ }
43904
+ function pathsResolverAssertKind2(kind) {
43905
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV2).includes(kind)) {
43906
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV2).join(", ")}`);
43907
+ }
43908
+ }
43909
+ function pathsResolverBaseDir2(kind, options) {
43910
+ pathsResolverAssertKind2(kind);
43911
+ const env = options.env ?? process.env;
43912
+ const override = env[PATHS_RESOLVER_KIND_ENV2[kind]];
43913
+ if (typeof override === "string" && override.length > 0)
43914
+ return override;
43915
+ const home = options.home ?? pathsResolverHomedir3();
43916
+ const platform = options.platform ?? process.platform;
43917
+ if (platform === "darwin") {
43918
+ switch (kind) {
43919
+ case "config":
43920
+ case "data":
43921
+ return pathsResolverJoin3(home, "Library", "Application Support", "Hasna");
43922
+ case "cache":
43923
+ return pathsResolverJoin3(home, "Library", "Caches", "Hasna");
43924
+ case "state":
43925
+ return pathsResolverJoin3(home, "Library", "Logs", "Hasna");
43926
+ }
43927
+ }
43928
+ switch (kind) {
43929
+ case "config":
43930
+ return pathsResolverJoin3(home, ".config", "hasna");
43931
+ case "data":
43932
+ return pathsResolverJoin3(home, ".local", "share", "hasna");
43933
+ case "state":
43934
+ return pathsResolverJoin3(home, ".local", "state", "hasna");
43935
+ case "cache":
43936
+ return pathsResolverJoin3(home, ".cache", "hasna");
43937
+ }
43938
+ }
43939
+ function pathsResolverResolve2(kind, options) {
43940
+ pathsResolverAssertApp3(options.app);
43941
+ const appSegment = options.internal === true ? pathsResolverJoin3("internal", options.app) : options.app;
43942
+ return pathsResolverJoin3(pathsResolverBaseDir2(kind, options), appSegment);
43943
+ }
43944
+ function dataDir3(options) {
43945
+ return pathsResolverResolve2("data", options);
43946
+ }
43741
43947
  var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
43742
43948
  var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
43949
+ var EVENTS_STORE_SENTINEL_FILE = "events.json";
43950
+ function effectiveHome2() {
43951
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir13();
43952
+ }
43953
+ function legacyHomeDir() {
43954
+ return join20(effectiveHome2(), ".hasna", "events");
43955
+ }
43956
+ function resolverHome() {
43957
+ return dataDir3({ app: "events", home: effectiveHome2() || undefined });
43958
+ }
43959
+ function adoptResolverHome(resolved, env = process.env) {
43960
+ const dataOverride = env.HASNA_DATA_HOME;
43961
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
43962
+ return true;
43963
+ return existsSync13(join20(resolved, EVENTS_STORE_SENTINEL_FILE));
43964
+ }
43965
+ function exactEventsHome() {
43966
+ const dir = process.env[HASNA_EVENTS_DIR_ENV];
43967
+ if (dir && dir.trim())
43968
+ return dir.trim();
43969
+ const home = process.env[HASNA_EVENTS_HOME_ENV];
43970
+ if (home && home.trim())
43971
+ return home.trim();
43972
+ return;
43973
+ }
43974
+ function getEventsHome() {
43975
+ const exact2 = exactEventsHome();
43976
+ if (exact2)
43977
+ return resolve20(exact2);
43978
+ const resolved = resolverHome();
43979
+ return adoptResolverHome(resolved) ? resolve20(resolved) : resolve20(legacyHomeDir());
43980
+ }
43743
43981
  var LOCAL_JSON_EVENT_CURSOR_PREFIX = "local-json-v1:";
43744
43982
  var DEFAULT_EVENT_PAGE_LIMIT = 100;
43745
43983
  var MAX_EVENT_PAGE_LIMIT = 1000;
43746
43984
  function getEventsDataDir(override) {
43747
- return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join20(homedir13(), ".hasna", "events");
43985
+ return override || getEventsHome();
43748
43986
  }
43749
43987
  function getActiveEventsDirEnv() {
43750
43988
  if (process.env[HASNA_EVENTS_DIR_ENV])
@@ -43760,12 +43998,12 @@ class JsonEventsStore {
43760
43998
  channelsPath;
43761
43999
  eventsPath;
43762
44000
  deliveriesPath;
43763
- constructor(dataDir3 = getEventsDataDir()) {
43764
- this.dataDir = dataDir3;
43765
- this.runtime = localJsonRuntime(dataDir3);
43766
- this.channelsPath = join20(dataDir3, "channels.json");
43767
- this.eventsPath = join20(dataDir3, "events.json");
43768
- this.deliveriesPath = join20(dataDir3, "deliveries.json");
44001
+ constructor(dataDir22 = getEventsDataDir()) {
44002
+ this.dataDir = dataDir22;
44003
+ this.runtime = localJsonRuntime(dataDir22);
44004
+ this.channelsPath = join22(dataDir22, "channels.json");
44005
+ this.eventsPath = join22(dataDir22, "events.json");
44006
+ this.deliveriesPath = join22(dataDir22, "deliveries.json");
43769
44007
  }
43770
44008
  async init() {
43771
44009
  await mkdir(this.dataDir, { recursive: true, mode: 448 });
@@ -43882,7 +44120,7 @@ class JsonEventsStore {
43882
44120
  };
43883
44121
  }
43884
44122
  async ensureArrayFile(path) {
43885
- if (!existsSync13(path)) {
44123
+ if (!existsSync22(path)) {
43886
44124
  await writeFile(path, `[]
43887
44125
  `, { encoding: "utf-8", mode: 384 });
43888
44126
  }
@@ -43912,7 +44150,7 @@ class JsonEventsStore {
43912
44150
  });
43913
44151
  }
43914
44152
  }
43915
- function localJsonRuntime(dataDir3 = getEventsDataDir()) {
44153
+ function localJsonRuntime(dataDir22 = getEventsDataDir()) {
43916
44154
  return {
43917
44155
  mode: "local-files",
43918
44156
  name: "json-events-store",
@@ -43925,7 +44163,7 @@ function localJsonRuntime(dataDir3 = getEventsDataDir()) {
43925
44163
  durable: true,
43926
44164
  idempotency: "best-effort-local",
43927
44165
  replayCursors: true,
43928
- description: `Local JSON files in ${dataDir3}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
44166
+ description: `Local JSON files in ${dataDir22}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
43929
44167
  };
43930
44168
  }
43931
44169
  function encodeLocalJsonEventCursor(offset, options = {}) {
@@ -43989,8 +44227,8 @@ function assertCursorFilter(name, cursorValue, optionValue) {
43989
44227
  function findEventByIdentity(events2, identity3) {
43990
44228
  return events2.find((event) => identity3.id !== undefined && event.id === identity3.id || identity3.dedupeKey !== undefined && event.dedupeKey === identity3.dedupeKey);
43991
44229
  }
43992
- async function getEventsStatus(dataDir3) {
43993
- const store = new JsonEventsStore(dataDir3);
44230
+ async function getEventsStatus(dataDir22) {
44231
+ const store = new JsonEventsStore(dataDir22);
43994
44232
  await store.init();
43995
44233
  const [channels, events2, deliveries] = await Promise.all([
43996
44234
  store.listChannels(),
@@ -44032,9 +44270,9 @@ async function getEventsStatus(dataDir3) {
44032
44270
  }
44033
44271
  };
44034
44272
  }
44035
- function statusFile(dataDir3, fileName, records) {
44036
- const path = join20(dataDir3, fileName);
44037
- return { path, exists: existsSync13(path), records };
44273
+ function statusFile(dataDir22, fileName, records) {
44274
+ const path = join22(dataDir22, fileName);
44275
+ return { path, exists: existsSync22(path), records };
44038
44276
  }
44039
44277
  var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
44040
44278
  function buildSignatureBase(timestamp, body) {
@@ -44044,6 +44282,214 @@ function signPayload(secret, timestamp, body) {
44044
44282
  const digest4 = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
44045
44283
  return `sha256=${digest4}`;
44046
44284
  }
44285
+ var DEFAULT_MAX_REDIRECTS = 5;
44286
+ var IPV4_PRIVATE_RANGES = [
44287
+ [0, 16777215],
44288
+ [167772160, 184549375],
44289
+ [1681915904, 1686110207],
44290
+ [2130706432, 2147483647],
44291
+ [2851995648, 2852061183],
44292
+ [2886729728, 2887778303],
44293
+ [3221225472, 3221225727],
44294
+ [3221225984, 3221226239],
44295
+ [3227017984, 3227018239],
44296
+ [3232235520, 3232301055],
44297
+ [3323068416, 3323199487],
44298
+ [3325256704, 3325256959],
44299
+ [3405803776, 3405804031],
44300
+ [3758096384, 4294967295]
44301
+ ];
44302
+ var IPV6_SPECIAL_PREFIXES = [
44303
+ { groups: [0, 0, 0, 0, 0, 0, 0, 0], bits: 128 },
44304
+ { groups: [0, 0, 0, 0, 0, 0, 0, 1], bits: 128 },
44305
+ { groups: [0, 0, 0, 0, 0, 65535, 0, 0], bits: 96 },
44306
+ { groups: [100, 65435, 0, 0, 0, 0, 0, 0], bits: 96 },
44307
+ { groups: [256, 0, 0, 0, 0, 0, 0, 0], bits: 64 },
44308
+ { groups: [8193, 0, 0, 0, 0, 0, 0, 0], bits: 32 },
44309
+ { groups: [8193, 2, 0, 0, 0, 0, 0, 0], bits: 48 },
44310
+ { groups: [8193, 16, 0, 0, 0, 0, 0, 0], bits: 28 },
44311
+ { groups: [8193, 3512, 0, 0, 0, 0, 0, 0], bits: 32 },
44312
+ { groups: [8194, 0, 0, 0, 0, 0, 0, 0], bits: 16 },
44313
+ { groups: [16383, 0, 0, 0, 0, 0, 0, 0], bits: 20 },
44314
+ { groups: [64512, 0, 0, 0, 0, 0, 0, 0], bits: 7 },
44315
+ { groups: [65152, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
44316
+ { groups: [65216, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
44317
+ { groups: [65280, 0, 0, 0, 0, 0, 0, 0], bits: 8 }
44318
+ ];
44319
+ function isPrivateAddress(address) {
44320
+ const normalized = stripZoneId(address);
44321
+ const version2 = isIP(normalized);
44322
+ if (version2 === 4) {
44323
+ const integer3 = ipv4ToInt(normalized);
44324
+ if (integer3 === undefined)
44325
+ return true;
44326
+ return IPV4_PRIVATE_RANGES.some(([low, high]) => integer3 >= low && integer3 <= high);
44327
+ }
44328
+ if (version2 === 6) {
44329
+ const groups = ipv6Groups(normalized);
44330
+ if (!groups)
44331
+ return true;
44332
+ for (const prefix3 of IPV6_SPECIAL_PREFIXES) {
44333
+ if (!ipv6MatchesPrefix(groups, prefix3.groups, prefix3.bits))
44334
+ continue;
44335
+ if (prefix3.bits === 96 && groups[5] === 65535) {
44336
+ return isPrivateAddress(ipv4IntToString(groups[6] << 16 | groups[7]));
44337
+ }
44338
+ if (prefix3.bits === 16 && groups[0] === 8194) {
44339
+ return isPrivateAddress(ipv4IntToString(groups[1] << 16 | groups[2]));
44340
+ }
44341
+ return true;
44342
+ }
44343
+ return false;
44344
+ }
44345
+ return true;
44346
+ }
44347
+ async function resolveWebhookTarget(url2, policy = {}) {
44348
+ const hostname3 = normalizeHostname(url2.hostname);
44349
+ const allowlist = (policy.allowPrivateHosts ?? []).map((entry) => normalizeHostname(entry.toLowerCase()));
44350
+ if (allowlist.includes(hostname3)) {
44351
+ const version22 = isIP(hostname3);
44352
+ if (version22 === 4 || version22 === 6) {
44353
+ return { hostname: hostname3, addresses: [hostname3] };
44354
+ }
44355
+ const lookup2 = policy.lookup ?? defaultTargetLookup;
44356
+ let resolved2;
44357
+ try {
44358
+ resolved2 = await lookup2(hostname3);
44359
+ } catch {
44360
+ throw new Error(`Webhook target ${hostname3} could not be resolved`);
44361
+ }
44362
+ if (!Array.isArray(resolved2) || resolved2.length === 0) {
44363
+ throw new Error(`Webhook target ${hostname3} resolved to no addresses`);
44364
+ }
44365
+ const addresses = resolved2.map((entry) => normalizeHostname(entry.address));
44366
+ return { hostname: hostname3, addresses };
44367
+ }
44368
+ const version2 = isIP(hostname3);
44369
+ if (version2 === 4 || version2 === 6) {
44370
+ if (isPrivateAddress(hostname3)) {
44371
+ throw new Error(`Webhook target ${hostname3} is a private or special-use address`);
44372
+ }
44373
+ return { hostname: hostname3, addresses: [hostname3] };
44374
+ }
44375
+ const lookup = policy.lookup ?? defaultTargetLookup;
44376
+ let resolved;
44377
+ try {
44378
+ resolved = await lookup(hostname3);
44379
+ } catch {
44380
+ throw new Error(`Webhook target ${hostname3} could not be resolved`);
44381
+ }
44382
+ if (!Array.isArray(resolved) || resolved.length === 0) {
44383
+ throw new Error(`Webhook target ${hostname3} resolved to no addresses`);
44384
+ }
44385
+ const allowed = [];
44386
+ for (const entry of resolved) {
44387
+ const address = normalizeHostname(entry.address);
44388
+ if (isPrivateAddress(address)) {
44389
+ if (allowlist.includes(address)) {
44390
+ allowed.push(address);
44391
+ continue;
44392
+ }
44393
+ throw new Error(`Webhook target ${hostname3} resolves to private or special-use address ${address}`);
44394
+ }
44395
+ allowed.push(address);
44396
+ }
44397
+ if (allowed.length === 0) {
44398
+ throw new Error(`Webhook target ${hostname3} resolved to no public addresses`);
44399
+ }
44400
+ return { hostname: hostname3, addresses: allowed };
44401
+ }
44402
+ function normalizeMaxRedirects(value) {
44403
+ if (value === undefined)
44404
+ return DEFAULT_MAX_REDIRECTS;
44405
+ if (!Number.isInteger(value) || value < 0)
44406
+ throw new Error("webhookTargetPolicy.maxRedirects must be a non-negative integer");
44407
+ return value;
44408
+ }
44409
+ var defaultTargetLookup = async (hostname3) => {
44410
+ return dnsLookup(hostname3, { all: true, verbatim: false });
44411
+ };
44412
+ function normalizeHostname(hostname3) {
44413
+ const lower = hostname3.toLowerCase();
44414
+ if (lower.startsWith("[") && lower.endsWith("]"))
44415
+ return lower.slice(1, -1);
44416
+ return lower;
44417
+ }
44418
+ function stripZoneId(address) {
44419
+ const percent = address.indexOf("%");
44420
+ return percent === -1 ? address : address.slice(0, percent);
44421
+ }
44422
+ function ipv4ToInt(address) {
44423
+ const parts = address.split(".");
44424
+ if (parts.length !== 4)
44425
+ return;
44426
+ let value = 0;
44427
+ for (const part of parts) {
44428
+ if (!/^\d{1,3}$/.test(part))
44429
+ return;
44430
+ const octet = Number(part);
44431
+ if (octet > 255)
44432
+ return;
44433
+ value = value << 8 | octet;
44434
+ }
44435
+ return value >>> 0;
44436
+ }
44437
+ function ipv4IntToString(integer3) {
44438
+ return [
44439
+ integer3 >>> 24 & 255,
44440
+ integer3 >>> 16 & 255,
44441
+ integer3 >>> 8 & 255,
44442
+ integer3 & 255
44443
+ ].join(".");
44444
+ }
44445
+ function ipv6Groups(address) {
44446
+ const raw = stripZoneId(address);
44447
+ const doubleColon = raw.indexOf("::");
44448
+ const headText = doubleColon === -1 ? raw : raw.slice(0, doubleColon);
44449
+ const tailText = doubleColon === -1 ? "" : raw.slice(doubleColon + 2);
44450
+ const parseGroups = (text3) => {
44451
+ if (text3 === "")
44452
+ return [];
44453
+ const out = [];
44454
+ for (const part of text3.split(":")) {
44455
+ if (part.includes(".")) {
44456
+ const v4 = ipv4ToInt(part);
44457
+ if (v4 === undefined)
44458
+ return;
44459
+ out.push(v4 >>> 16 & 65535, v4 & 65535);
44460
+ } else {
44461
+ if (!/^[0-9a-fA-F]{1,4}$/.test(part))
44462
+ return;
44463
+ out.push(parseInt(part, 16));
44464
+ }
44465
+ }
44466
+ return out;
44467
+ };
44468
+ const head = parseGroups(headText);
44469
+ if (!head)
44470
+ return;
44471
+ const tail = parseGroups(tailText);
44472
+ if (!tail)
44473
+ return;
44474
+ const total = head.length + tail.length;
44475
+ if (doubleColon === -1) {
44476
+ return total === 8 ? head : undefined;
44477
+ }
44478
+ if (total >= 8)
44479
+ return;
44480
+ return [...head, ...new Array(8 - total).fill(0), ...tail];
44481
+ }
44482
+ function ipv6MatchesPrefix(groups, prefixGroups, prefixBits) {
44483
+ let remaining = prefixBits;
44484
+ for (let index = 0;index < prefixGroups.length && remaining > 0; index += 1) {
44485
+ const take = Math.min(16, remaining);
44486
+ const mask = 65535 << 16 - take & 65535;
44487
+ if ((groups[index] & mask) !== (prefixGroups[index] & mask))
44488
+ return false;
44489
+ remaining -= take;
44490
+ }
44491
+ return true;
44492
+ }
44047
44493
  function now() {
44048
44494
  return new Date().toISOString();
44049
44495
  }
@@ -44074,9 +44520,18 @@ function buildWebhookRequest(event, channel, options = {}) {
44074
44520
  }
44075
44521
  return { body, headers };
44076
44522
  }
44523
+ function normalizeWebhookUrl(raw) {
44524
+ const url2 = new URL(raw);
44525
+ if (url2.username !== "" || url2.password !== "") {
44526
+ url2.username = "";
44527
+ url2.password = "";
44528
+ }
44529
+ return url2.toString();
44530
+ }
44077
44531
  async function dispatchWebhook(event, channel, options = {}) {
44078
44532
  if (!channel.webhook)
44079
44533
  throw new Error(`Channel ${channel.id} has no webhook config`);
44534
+ const webhookUrl = normalizeWebhookUrl(channel.webhook.url);
44080
44535
  const startedAt = now();
44081
44536
  let secret = channel.webhook.secret;
44082
44537
  if (channel.webhook.secretRef) {
@@ -44093,10 +44548,14 @@ async function dispatchWebhook(event, channel, options = {}) {
44093
44548
  }
44094
44549
  const timestamp = (options.now?.() ?? new Date).toISOString();
44095
44550
  const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
44551
+ const validateTargets = options.webhookTargetPolicy !== undefined || options.fetchImpl === undefined;
44552
+ if (validateTargets) {
44553
+ return dispatchValidatedWebhook(event, channel, { body, headers, startedAt, options });
44554
+ }
44096
44555
  const controller = new AbortController;
44097
44556
  const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
44098
44557
  try {
44099
- const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
44558
+ const response = await (options.fetchImpl ?? fetch)(webhookUrl, {
44100
44559
  method: "POST",
44101
44560
  headers,
44102
44561
  body,
@@ -44124,6 +44583,130 @@ async function dispatchWebhook(event, channel, options = {}) {
44124
44583
  clearTimeout(timeout);
44125
44584
  }
44126
44585
  }
44586
+ function isRedirectStatus(status) {
44587
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
44588
+ }
44589
+ function redirectKeepsBody(status) {
44590
+ return status === 307 || status === 308;
44591
+ }
44592
+ async function pinnedNativeRequest(target, addresses, method, headers, body, signal, tls) {
44593
+ const isHttps = target.protocol === "https:";
44594
+ if (!isHttps && target.protocol !== "http:") {
44595
+ throw new Error(`Webhook target uses unsupported protocol ${target.protocol}`);
44596
+ }
44597
+ const defaultPort = isHttps ? 443 : 80;
44598
+ const port = target.port ? Number(target.port) : defaultPort;
44599
+ const requestOptions = {
44600
+ hostname: target.hostname,
44601
+ port,
44602
+ path: `${target.pathname}${target.search}`,
44603
+ method,
44604
+ headers,
44605
+ ...tls?.ca ? { ca: tls.ca } : {},
44606
+ lookup: (hostname3, _options, callback) => {
44607
+ const entries = addresses.map((address) => ({
44608
+ address,
44609
+ family: address.includes(":") ? 6 : 4
44610
+ }));
44611
+ callback(null, entries);
44612
+ }
44613
+ };
44614
+ return new Promise((resolve23, reject) => {
44615
+ const request = isHttps ? nodeHttpsRequest(requestOptions, onResponse) : nodeHttpRequest(requestOptions, onResponse);
44616
+ const onAbort = () => {
44617
+ const error61 = new Error("The operation was aborted.");
44618
+ error61.name = "AbortError";
44619
+ request.destroy(error61);
44620
+ };
44621
+ if (signal.aborted)
44622
+ onAbort();
44623
+ else
44624
+ signal.addEventListener("abort", onAbort, { once: true });
44625
+ request.on("error", reject);
44626
+ if (body !== undefined)
44627
+ request.write(body);
44628
+ request.end();
44629
+ function onResponse(response) {
44630
+ const chunks = [];
44631
+ response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
44632
+ response.on("error", reject);
44633
+ response.on("end", () => {
44634
+ const headersRecord = {};
44635
+ for (const [name, value] of Object.entries(response.headers)) {
44636
+ if (typeof value === "string")
44637
+ headersRecord[name] = value;
44638
+ else if (Array.isArray(value))
44639
+ headersRecord[name] = value.join(", ");
44640
+ }
44641
+ resolve23(new Response(Buffer.concat(chunks), { status: response.statusCode ?? 200, headers: headersRecord }));
44642
+ });
44643
+ }
44644
+ });
44645
+ }
44646
+ async function dispatchValidatedWebhook(event, channel, input2) {
44647
+ const { body, headers, startedAt, options } = input2;
44648
+ const webhook = channel.webhook;
44649
+ if (!webhook)
44650
+ throw new Error(`Channel ${channel.id} has no webhook config`);
44651
+ const policy = options.webhookTargetPolicy ?? {};
44652
+ const maxRedirects = normalizeMaxRedirects(policy.maxRedirects);
44653
+ const controller = new AbortController;
44654
+ const timeout = setTimeout(() => controller.abort(), webhook.timeoutMs ?? 15000);
44655
+ try {
44656
+ let target = new URL(normalizeWebhookUrl(webhook.url));
44657
+ let requestHeaders = headers;
44658
+ let method = "POST";
44659
+ let requestBody = body;
44660
+ let redirectsFollowed = 0;
44661
+ for (;; ) {
44662
+ const resolved = await resolveWebhookTarget(target, policy).catch((error61) => {
44663
+ throw new Error(`Webhook target rejected by SSRF guard: ${error61.message}`);
44664
+ });
44665
+ const response = options.fetchImpl ? await options.fetchImpl(target, {
44666
+ method,
44667
+ headers: requestHeaders,
44668
+ body: requestBody,
44669
+ signal: controller.signal,
44670
+ redirect: "manual"
44671
+ }) : await pinnedNativeRequest(target, resolved.addresses, method, requestHeaders, requestBody, controller.signal, options.tls);
44672
+ const location = response.headers.get("location");
44673
+ if (isRedirectStatus(response.status) && location) {
44674
+ if (redirectsFollowed >= maxRedirects) {
44675
+ return failedAttempt(startedAt, `Webhook target exceeded ${maxRedirects} redirects`);
44676
+ }
44677
+ redirectsFollowed += 1;
44678
+ const next = new URL(location, target);
44679
+ target = next;
44680
+ if (!redirectKeepsBody(response.status)) {
44681
+ method = "GET";
44682
+ requestBody = undefined;
44683
+ requestHeaders = Object.fromEntries(Object.entries(requestHeaders).filter(([name]) => name.toLowerCase() !== "content-type" && name.toLowerCase() !== "content-length"));
44684
+ }
44685
+ continue;
44686
+ }
44687
+ const responseBody = truncate(await response.text());
44688
+ return {
44689
+ attempt: 1,
44690
+ status: response.ok ? "success" : "failed",
44691
+ startedAt,
44692
+ completedAt: now(),
44693
+ responseStatus: response.status,
44694
+ responseBody,
44695
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
44696
+ };
44697
+ }
44698
+ } catch (error61) {
44699
+ return {
44700
+ attempt: 1,
44701
+ status: "failed",
44702
+ startedAt,
44703
+ completedAt: now(),
44704
+ error: error61 instanceof Error ? error61.message : String(error61)
44705
+ };
44706
+ } finally {
44707
+ clearTimeout(timeout);
44708
+ }
44709
+ }
44127
44710
  function failedAttempt(startedAt, error61) {
44128
44711
  return {
44129
44712
  attempt: 1,
@@ -44152,7 +44735,7 @@ async function dispatchCommand(event, channel) {
44152
44735
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
44153
44736
  HASNA_EVENT_JSON: eventJson
44154
44737
  };
44155
- return new Promise((resolve16) => {
44738
+ return new Promise((resolve23) => {
44156
44739
  const child = spawn3(channel.command.command, channel.command.args ?? [], {
44157
44740
  cwd: channel.command.cwd,
44158
44741
  env,
@@ -44170,7 +44753,7 @@ async function dispatchCommand(event, channel) {
44170
44753
  });
44171
44754
  child.on("error", (error61) => {
44172
44755
  clearTimeout(timeout);
44173
- resolve16({
44756
+ resolve23({
44174
44757
  attempt: 1,
44175
44758
  status: "failed",
44176
44759
  startedAt,
@@ -44183,7 +44766,7 @@ async function dispatchCommand(event, channel) {
44183
44766
  child.on("close", (code2, signal) => {
44184
44767
  clearTimeout(timeout);
44185
44768
  const success2 = code2 === 0;
44186
- resolve16({
44769
+ resolve23({
44187
44770
  attempt: 1,
44188
44771
  status: success2 ? "success" : "failed",
44189
44772
  startedAt,
@@ -44305,6 +44888,22 @@ function setPath(input2, path, replacement) {
44305
44888
  if (last && last in cursor)
44306
44889
  cursor[last] = replacement;
44307
44890
  }
44891
+ var MAX_PACKAGE_BYTES2 = 1024 * 1024;
44892
+ var MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
44893
+ var INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
44894
+ var CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
44895
+ var AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
44896
+ var requireSecretsSdk2 = createRequire2(import.meta.url);
44897
+ var IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
44898
+ var AUTHORITY_OVERRIDE_HEADERS2 = new Set([
44899
+ "host",
44900
+ ":authority",
44901
+ "forwarded",
44902
+ "x-forwarded-host",
44903
+ "x-original-host"
44904
+ ]);
44905
+ var MAX_ENVELOPE_BYTES = 256 * 1024;
44906
+ var MAX_REQUEST_BYTES = MAX_ENVELOPE_BYTES * 2 + 8192;
44308
44907
  function createEvent(input2) {
44309
44908
  return {
44310
44909
  id: input2.id ?? randomUUID22(),
@@ -44333,7 +44932,9 @@ class EventsClient {
44333
44932
  this.transportOptions = {
44334
44933
  fetchImpl: options.fetchImpl,
44335
44934
  secretResolver: options.secretResolver,
44336
- now: options.now
44935
+ now: options.now,
44936
+ tls: options.tls,
44937
+ webhookTargetPolicy: options.webhookTargetPolicy
44337
44938
  };
44338
44939
  this.catalog = options.catalog ?? defaultEventTypeCatalog;
44339
44940
  this.validateCatalogTypes = options.validateCatalogTypes ?? false;
@@ -44620,6 +45221,13 @@ function parseMatcherExpression(value, label) {
44620
45221
  negated: false
44621
45222
  };
44622
45223
  }
45224
+ function webhookTargetPolicyFromEnv() {
45225
+ const value = process.env.HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS;
45226
+ if (!value)
45227
+ return;
45228
+ const hosts = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
45229
+ return hosts.length > 0 ? { allowPrivateHosts: hosts } : undefined;
45230
+ }
44623
45231
  var DEFAULT_EVENT_LIST_LIMIT = 100;
44624
45232
  function parseJsonObject(value, fallback) {
44625
45233
  if (!value)
@@ -44645,7 +45253,7 @@ function parseHeaders(values) {
44645
45253
  function createClient(options) {
44646
45254
  if (options.createClient)
44647
45255
  return options.createClient();
44648
- return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
45256
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir), webhookTargetPolicy: webhookTargetPolicyFromEnv() });
44649
45257
  }
44650
45258
  function print(value, json2, text3) {
44651
45259
  if (json2)
@@ -44726,6 +45334,8 @@ function registerChannelCommands(program2, options) {
44726
45334
  metadata: parseJsonObject(actionOptions.metadata, {})
44727
45335
  }, { honorFilters: actionOptions.honorFilters });
44728
45336
  print(result, json2, `${result.status}: ${result.channelId}`);
45337
+ if (result.status === "failed")
45338
+ process.exitCode = 1;
44729
45339
  } catch (error61) {
44730
45340
  fail(error61, json2);
44731
45341
  }
@@ -44818,7 +45428,7 @@ function replaySummary(events2, deliveries, nextCursor) {
44818
45428
  // src/cli/index.tsx
44819
45429
  import { render } from "ink";
44820
45430
 
44821
- // ../../../../validation/build/apps/hooks/node_modules/commander/esm.mjs
45431
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/esm.mjs
44822
45432
  var import__ = __toESM(require_commander(), 1);
44823
45433
  var {
44824
45434
  program: program2,
@@ -44837,18 +45447,18 @@ var {
44837
45447
  // src/cli/index.tsx
44838
45448
  import chalk2 from "chalk";
44839
45449
  import { existsSync as existsSync18, readFileSync as readFileSync27, rmSync as rmSync6 } from "fs";
44840
- import { join as join35, dirname as dirname28 } from "path";
45450
+ import { join as join36, dirname as dirname28 } from "path";
44841
45451
  import { fileURLToPath as fileURLToPath6 } from "url";
44842
45452
 
44843
45453
  // src/cli/components/App.tsx
44844
45454
  import { useState as useState7 } from "react";
44845
45455
  import { Box as Box8, Text as Text10, useApp, useInput as useInput5 } from "ink";
44846
45456
 
44847
- // ../../../../validation/build/apps/hooks/node_modules/ink-select-input/build/Indicator.js
45457
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
44848
45458
  import React from "react";
44849
45459
  import { Box, Text } from "ink";
44850
45460
 
44851
- // ../../../../validation/build/apps/hooks/node_modules/is-unicode-supported/index.js
45461
+ // ../../node_modules/.bun/is-unicode-supported@2.1.0/node_modules/is-unicode-supported/index.js
44852
45462
  import process3 from "process";
44853
45463
  function isUnicodeSupported() {
44854
45464
  const { env } = process3;
@@ -44859,7 +45469,7 @@ function isUnicodeSupported() {
44859
45469
  return Boolean(env.WT_SESSION) || Boolean(env.TERMINUS_SUBLIME) || env.ConEmuTask === "{cmd::Cmder}" || TERM_PROGRAM === "Terminus-Sublime" || TERM_PROGRAM === "vscode" || TERM === "xterm-256color" || TERM === "alacritty" || TERM === "rxvt-unicode" || TERM === "rxvt-unicode-256color" || env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
44860
45470
  }
44861
45471
 
44862
- // ../../../../validation/build/apps/hooks/node_modules/figures/index.js
45472
+ // ../../node_modules/.bun/figures@6.1.0/node_modules/figures/index.js
44863
45473
  var common2 = {
44864
45474
  circleQuestionMark: "(?)",
44865
45475
  questionMarkPrefix: "(?)",
@@ -45135,23 +45745,23 @@ var figures = shouldUseMain ? mainSymbols : fallbackSymbols;
45135
45745
  var figures_default = figures;
45136
45746
  var replacements = Object.entries(specialMainSymbols);
45137
45747
 
45138
- // ../../../../validation/build/apps/hooks/node_modules/ink-select-input/build/Indicator.js
45748
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
45139
45749
  function Indicator({ isSelected = false }) {
45140
45750
  return React.createElement(Box, { marginRight: 1 }, isSelected ? React.createElement(Text, { color: "blue" }, figures_default.pointer) : React.createElement(Text, null, " "));
45141
45751
  }
45142
45752
  var Indicator_default = Indicator;
45143
- // ../../../../validation/build/apps/hooks/node_modules/ink-select-input/build/Item.js
45753
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Item.js
45144
45754
  import * as React2 from "react";
45145
45755
  import { Text as Text2 } from "ink";
45146
45756
  function Item({ isSelected = false, label }) {
45147
45757
  return React2.createElement(Text2, { color: isSelected ? "blue" : undefined }, label);
45148
45758
  }
45149
45759
  var Item_default = Item;
45150
- // ../../../../validation/build/apps/hooks/node_modules/ink-select-input/build/SelectInput.js
45760
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
45151
45761
  import { isDeepStrictEqual } from "util";
45152
45762
  import React3, { useState, useEffect, useRef, useCallback } from "react";
45153
45763
 
45154
- // ../../../../validation/build/apps/hooks/node_modules/to-rotated/index.js
45764
+ // ../../node_modules/.bun/to-rotated@1.0.0/node_modules/to-rotated/index.js
45155
45765
  function toRotated(array3, steps) {
45156
45766
  if (!Array.isArray(array3)) {
45157
45767
  throw new TypeError(`Expected an array, got \`${typeof array3}\`.`);
@@ -45173,7 +45783,7 @@ function toRotated(array3, steps) {
45173
45783
  ];
45174
45784
  }
45175
45785
 
45176
- // ../../../../validation/build/apps/hooks/node_modules/ink-select-input/build/SelectInput.js
45786
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
45177
45787
  import { Box as Box2, useInput } from "ink";
45178
45788
  function SelectInput({ items = [], isFocused = true, initialIndex = 0, indicatorComponent = Indicator_default, itemComponent = Item_default, limit: customLimit, onSelect, onHighlight }) {
45179
45789
  const hasLimit = typeof customLimit === "number" && items.length > customLimit;
@@ -45469,7 +46079,7 @@ function HookSelect({
45469
46079
  import { useState as useState4, useEffect as useEffect3 } from "react";
45470
46080
  import { Box as Box6, Text as Text7, useInput as useInput4 } from "ink";
45471
46081
 
45472
- // ../../../../validation/build/apps/hooks/node_modules/ink-text-input/build/index.js
46082
+ // ../../node_modules/.bun/ink-text-input@6.0.0+fa875fef9f949ebd/node_modules/ink-text-input/build/index.js
45473
46083
  import React5, { useState as useState3, useEffect as useEffect2 } from "react";
45474
46084
  import { Text as Text6, useInput as useInput3 } from "ink";
45475
46085
  import chalk from "chalk";
@@ -45647,7 +46257,7 @@ function SearchView({
45647
46257
  import { useState as useState6, useEffect as useEffect5 } from "react";
45648
46258
  import { Box as Box7, Text as Text9 } from "ink";
45649
46259
 
45650
- // ../../../../validation/build/apps/hooks/node_modules/ink-spinner/build/index.js
46260
+ // ../../node_modules/.bun/ink-spinner@5.0.0+fa875fef9f949ebd/node_modules/ink-spinner/build/index.js
45651
46261
  var import_cli_spinners = __toESM(require_cli_spinners(), 1);
45652
46262
  import React7, { useState as useState5, useEffect as useEffect4 } from "react";
45653
46263
  import { Text as Text8 } from "ink";
@@ -46014,10 +46624,10 @@ init_native_safety();
46014
46624
 
46015
46625
  // src/lib/sumi-binding.ts
46016
46626
  import { homedir as homedir14 } from "os";
46017
- import { join as join24, sep as sep3 } from "path";
46627
+ import { join as join27, sep as sep3 } from "path";
46018
46628
 
46019
46629
  // hooks/hook-trash-guard/src/binary.ts
46020
- import { dirname as dirname21, isAbsolute as isAbsolute20, join as join22, resolve as resolve20 } from "path";
46630
+ import { dirname as dirname21, isAbsolute as isAbsolute20, join as join24, resolve as resolve21 } from "path";
46021
46631
  import { accessSync, closeSync as closeSync19, constants as constants18, fstatSync as fstatSync18, lstatSync as lstatSync23, openSync as openSync19, readFileSync as readFileSync17, readlinkSync as readlinkSync3, readSync as readSync3, realpathSync as realpathSync11, statSync } from "fs";
46022
46632
  import { spawnSync } from "child_process";
46023
46633
  function trustedOwner(uid2) {
@@ -46039,7 +46649,7 @@ function safeExecutableFile(path, systemBinAlias = false) {
46039
46649
  parent = dirname21(parent);
46040
46650
  continue;
46041
46651
  }
46042
- const next = join22(parent, part), info2 = lstatSync23(next);
46652
+ const next = join24(parent, part), info2 = lstatSync23(next);
46043
46653
  if (systemBinAlias && next === "/bin" && info2.isSymbolicLink() && info2.uid === 0) {
46044
46654
  const target = realpathSync11(next);
46045
46655
  if (!safeBinaryDirectory(target))
@@ -46051,7 +46661,7 @@ function safeExecutableFile(path, systemBinAlias = false) {
46051
46661
  return false;
46052
46662
  parent = next;
46053
46663
  }
46054
- const candidate = join22(parent, name), info = lstatSync23(candidate);
46664
+ const candidate = join24(parent, name), info = lstatSync23(candidate);
46055
46665
  if (!trustedOwner(info.uid))
46056
46666
  return false;
46057
46667
  if (info.isSymbolicLink()) {
@@ -46064,9 +46674,9 @@ function safeExecutableFile(path, systemBinAlias = false) {
46064
46674
  return false;
46065
46675
  }
46066
46676
  function safeInterpreterSearchDirectory(path) {
46067
- if (!isAbsolute20(path) || resolve20(path) !== (path.replace(/\/+$/, "") || "/"))
46677
+ if (!isAbsolute20(path) || resolve21(path) !== (path.replace(/\/+$/, "") || "/"))
46068
46678
  return false;
46069
- for (let cursor = resolve20(path);; cursor = dirname21(cursor)) {
46679
+ for (let cursor = resolve21(path);; cursor = dirname21(cursor)) {
46070
46680
  try {
46071
46681
  lstatSync23(cursor);
46072
46682
  return safeBinaryDirectory(cursor);
@@ -46098,7 +46708,7 @@ function safeScriptInterpreter(executable2, pathValue) {
46098
46708
  for (const dir of pathValue.split(":").slice(0, 64)) {
46099
46709
  if (!safeInterpreterSearchDirectory(dir))
46100
46710
  return false;
46101
- const interpreter = resolve20(dir, "bun");
46711
+ const interpreter = resolve21(dir, "bun");
46102
46712
  try {
46103
46713
  if (!statSync(interpreter).isFile())
46104
46714
  continue;
@@ -46133,7 +46743,7 @@ function inspectTrashBinary(env = process.env) {
46133
46743
  for (const dir of pathValue.split(":").slice(0, 64)) {
46134
46744
  if (!isAbsolute20(dir) || Date.now() >= deadline)
46135
46745
  continue;
46136
- const candidate = resolve20(dir, "trash");
46746
+ const candidate = resolve21(dir, "trash");
46137
46747
  try {
46138
46748
  const stat2 = statSync(candidate);
46139
46749
  if (!stat2.isFile() || !trustedOwner(stat2.uid) || (stat2.mode & 73) === 0 || (stat2.mode & 18) !== 0) {
@@ -46141,7 +46751,7 @@ function inspectTrashBinary(env = process.env) {
46141
46751
  continue;
46142
46752
  }
46143
46753
  const executable2 = realpathSync11(candidate);
46144
- if (!safeBinaryDirectory(resolve20(dir)) || !safeBinaryDirectory(dirname21(executable2))) {
46754
+ if (!safeBinaryDirectory(resolve21(dir)) || !safeBinaryDirectory(dirname21(executable2))) {
46145
46755
  reject("unsafe_directory");
46146
46756
  continue;
46147
46757
  }
@@ -46149,15 +46759,15 @@ function inspectTrashBinary(env = process.env) {
46149
46759
  reject("unsafe_directory");
46150
46760
  continue;
46151
46761
  }
46152
- const packageRoot = resolve20(dirname21(executable2), "../..");
46153
- const manifestPath = join22(packageRoot, "package.json");
46762
+ const packageRoot = resolve21(dirname21(executable2), "../..");
46763
+ const manifestPath = join24(packageRoot, "package.json");
46154
46764
  const manifestStat = statSync(manifestPath);
46155
46765
  if (!manifestStat.isFile() || !trustedOwner(manifestStat.uid) || manifestStat.size > 16384 || (manifestStat.mode & 18) !== 0 || lstatSync23(manifestPath).isSymbolicLink()) {
46156
46766
  reject("unsafe_manifest");
46157
46767
  continue;
46158
46768
  }
46159
46769
  const manifest = JSON.parse(readFileSync17(manifestPath, "utf8"));
46160
- if (manifest.name !== "@hasna/trash" || typeof manifest.version !== "string" || typeof manifest.bin?.trash !== "string" || realpathSync11(resolve20(packageRoot, manifest.bin.trash)) !== executable2) {
46770
+ if (manifest.name !== "@hasna/trash" || typeof manifest.version !== "string" || typeof manifest.bin?.trash !== "string" || realpathSync11(resolve21(packageRoot, manifest.bin.trash)) !== executable2) {
46161
46771
  reject("unrecognized_package");
46162
46772
  continue;
46163
46773
  }
@@ -46202,7 +46812,7 @@ function failure(code2, reason) {
46202
46812
  return { ok: false, target: "sumi", code: code2, ...reason ? { reason: reason.slice(0, 300) } : {} };
46203
46813
  }
46204
46814
  function nativeSafetyBindingDurable(worker, home) {
46205
- return !(worker.startsWith(join24(home, ".bun") + sep3) || worker.includes(`${sep3}node_modules${sep3}.bun${sep3}`));
46815
+ return !(worker.startsWith(join27(home, ".bun") + sep3) || worker.includes(`${sep3}node_modules${sep3}.bun${sep3}`));
46206
46816
  }
46207
46817
  async function provisionSumiSafetyBinding(options = {}) {
46208
46818
  const home = options.home ?? homedir14();
@@ -46277,7 +46887,7 @@ init_transport2();
46277
46887
  init_db();
46278
46888
  import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
46279
46889
  var __dirname4 = dirname28(fileURLToPath6(import.meta.url));
46280
- var pkgPath = existsSync18(join35(__dirname4, "..", "package.json")) ? join35(__dirname4, "..", "package.json") : join35(__dirname4, "..", "..", "package.json");
46890
+ var pkgPath = existsSync18(join36(__dirname4, "..", "package.json")) ? join36(__dirname4, "..", "package.json") : join36(__dirname4, "..", "..", "package.json");
46281
46891
  var pkg2 = JSON.parse(readFileSync27(pkgPath, "utf-8"));
46282
46892
  var program3 = new Command;
46283
46893
  function resolveScope(options) {
@@ -46510,7 +47120,7 @@ program3.command("init").description("Register a new agent profile with a unique
46510
47120
  if (profile.name) {
46511
47121
  console.log(` ${chalk2.dim("Name:")} ${profile.name}`);
46512
47122
  }
46513
- console.log(` ${chalk2.dim("Profile:")} ${join35(getProfilesDir(), `${profile.agent_id}.json`)}`);
47123
+ console.log(` ${chalk2.dim("Profile:")} ${join36(getProfilesDir(), `${profile.agent_id}.json`)}`);
46514
47124
  console.log();
46515
47125
  console.log(chalk2.dim(" Install hooks with this profile:"));
46516
47126
  console.log(` hooks install gitguard --profile ${profile.agent_id}`);
@@ -47165,7 +47775,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
47165
47775
  continue;
47166
47776
  }
47167
47777
  const hookDir = getHookPath(name);
47168
- const hookScript = join35(hookDir, "src", "hook.ts");
47778
+ const hookScript = join36(hookDir, "src", "hook.ts");
47169
47779
  if (!existsSync18(hookScript)) {
47170
47780
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
47171
47781
  hookHealthy = false;
@@ -47174,7 +47784,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
47174
47784
  if (meta3 && settingsExist) {
47175
47785
  try {
47176
47786
  const settings = JSON.parse(readFileSync27(settingsPath, "utf-8"));
47177
- if (!hookRegisteredInSettings(settings, name, meta3.event, meta3.matcher)) {
47787
+ if (!hookRegisteredInSettings(settings, name, meta3.event, matcherFor(meta3, "claude"))) {
47178
47788
  const eventName = meta3.event.split(":")[0];
47179
47789
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
47180
47790
  hookHealthy = false;
@@ -47376,7 +47986,7 @@ program3.command("docs").argument("[hook]", "Hook name (shows general docs if om
47376
47986
  return;
47377
47987
  }
47378
47988
  const hookPath = getHookPath(hook);
47379
- const readmePath = join35(hookPath, "README.md");
47989
+ const readmePath = join36(hookPath, "README.md");
47380
47990
  let readme = "";
47381
47991
  if (existsSync18(readmePath)) {
47382
47992
  readme = readFileSync27(readmePath, "utf-8");
@@ -48042,13 +48652,13 @@ startup.command("check").requiredOption("--target <target>", "codex, claude or s
48042
48652
  process.exitCode = 1;
48043
48653
  });
48044
48654
  for (const action of ["registration-plan", "registration-apply"]) {
48045
- const command = startup.command(action).requiredOption("--target <target>", "codex or claude");
48655
+ const command = startup.command(action).requiredOption("--target <target>", "codex, claude or sumi (Sumi: its native guard record)");
48046
48656
  if (action === "registration-apply")
48047
48657
  command.requiredOption("--expected-plan-digest <sha256>", "Exact registration-plan digest");
48048
48658
  command.action(async (options) => {
48049
48659
  try {
48050
48660
  const { planNativeReadinessRegistration: planNativeReadinessRegistration2, applyNativeReadinessRegistration: applyNativeReadinessRegistration2 } = await Promise.resolve().then(() => (init_native_safety_registration(), exports_native_safety_registration));
48051
- if (options.target !== "codex" && options.target !== "claude")
48661
+ if (options.target !== "codex" && options.target !== "claude" && options.target !== "sumi")
48052
48662
  throw new NativeRegistrationError("input_invalid");
48053
48663
  console.log(JSON.stringify(action === "registration-plan" ? await planNativeReadinessRegistration2({ target: options.target }) : await applyNativeReadinessRegistration2({ target: options.target, expectedPlanDigest: options.expectedPlanDigest })));
48054
48664
  } catch (error61) {
@@ -48174,7 +48784,7 @@ safety.command("verify").requiredOption("--target <target>", "sumi, codex or cla
48174
48784
  throw new ClaudeSafetyError("settings_invalid");
48175
48785
  console.log(JSON.stringify(await verifyClaudeSafetyConfiguration({
48176
48786
  cwd: options.cwd,
48177
- ...process.env.CLAUDE_CONFIG_DIR ? { userSettingsPath: resolve21(process.env.CLAUDE_CONFIG_DIR, "settings.json") } : {}
48787
+ ...process.env.CLAUDE_CONFIG_DIR ? { userSettingsPath: resolve23(process.env.CLAUDE_CONFIG_DIR, "settings.json") } : {}
48178
48788
  })));
48179
48789
  } catch (error61) {
48180
48790
  console.log(JSON.stringify({ ok: false, target: "claude", code: error61 instanceof ClaudeSafetyError ? error61.code : "settings_invalid", nativeAdoptionVerified: false }));