@hasna/hooks 0.11.7 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 {
@@ -35495,7 +35632,7 @@ var init_codex_safety_trust = __esm(() => {
35495
35632
  };
35496
35633
  });
35497
35634
 
35498
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/error.js
35635
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/error.js
35499
35636
  var require_error = __commonJS((exports) => {
35500
35637
  class CommanderError extends Error {
35501
35638
  constructor(exitCode, code2, message) {
@@ -35519,7 +35656,7 @@ var require_error = __commonJS((exports) => {
35519
35656
  exports.InvalidArgumentError = InvalidArgumentError;
35520
35657
  });
35521
35658
 
35522
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/argument.js
35659
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/argument.js
35523
35660
  var require_argument = __commonJS((exports) => {
35524
35661
  var { InvalidArgumentError } = require_error();
35525
35662
 
@@ -35598,7 +35735,7 @@ var require_argument = __commonJS((exports) => {
35598
35735
  exports.humanReadableArgName = humanReadableArgName;
35599
35736
  });
35600
35737
 
35601
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/help.js
35738
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/help.js
35602
35739
  var require_help = __commonJS((exports) => {
35603
35740
  var { humanReadableArgName } = require_argument();
35604
35741
 
@@ -35847,7 +35984,7 @@ var require_help = __commonJS((exports) => {
35847
35984
  exports.Help = Help;
35848
35985
  });
35849
35986
 
35850
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/option.js
35987
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/option.js
35851
35988
  var require_option = __commonJS((exports) => {
35852
35989
  var { InvalidArgumentError } = require_error();
35853
35990
 
@@ -35998,7 +36135,7 @@ var require_option = __commonJS((exports) => {
35998
36135
  exports.DualOptions = DualOptions;
35999
36136
  });
36000
36137
 
36001
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/suggestSimilar.js
36138
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/suggestSimilar.js
36002
36139
  var require_suggestSimilar = __commonJS((exports) => {
36003
36140
  var maxDistance = 3;
36004
36141
  function editDistance(a, b) {
@@ -36071,7 +36208,7 @@ var require_suggestSimilar = __commonJS((exports) => {
36071
36208
  exports.suggestSimilar = suggestSimilar;
36072
36209
  });
36073
36210
 
36074
- // ../../../../validation/build/apps/hooks/node_modules/commander/lib/command.js
36211
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/command.js
36075
36212
  var require_command = __commonJS((exports) => {
36076
36213
  var EventEmitter = __require("events").EventEmitter;
36077
36214
  var childProcess = __require("child_process");
@@ -37314,7 +37451,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
37314
37451
  exports.Command = Command;
37315
37452
  });
37316
37453
 
37317
- // ../../../../validation/build/apps/hooks/node_modules/commander/index.js
37454
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/index.js
37318
37455
  var require_commander = __commonJS((exports) => {
37319
37456
  var { Argument } = require_argument();
37320
37457
  var { Command } = require_command();
@@ -37334,7 +37471,7 @@ var require_commander = __commonJS((exports) => {
37334
37471
  exports.InvalidOptionArgumentError = InvalidArgumentError;
37335
37472
  });
37336
37473
 
37337
- // ../../../../validation/build/apps/hooks/node_modules/cli-spinners/spinners.json
37474
+ // ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/spinners.json
37338
37475
  var require_spinners = __commonJS((exports, module) => {
37339
37476
  module.exports = {
37340
37477
  dots: {
@@ -38960,7 +39097,7 @@ var require_spinners = __commonJS((exports, module) => {
38960
39097
  };
38961
39098
  });
38962
39099
 
38963
- // ../../../../validation/build/apps/hooks/node_modules/cli-spinners/index.js
39100
+ // ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/index.js
38964
39101
  var require_cli_spinners = __commonJS((exports, module) => {
38965
39102
  var spinners = Object.assign({}, require_spinners());
38966
39103
  var spinnersList = Object.keys(spinners);
@@ -39041,10 +39178,10 @@ var init_redact = __esm(() => {
39041
39178
 
39042
39179
  // src/lib/profiles.ts
39043
39180
  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";
39181
+ import { join as join29 } from "path";
39045
39182
  function getProfilesDir() {
39046
- const newDir = join27(getEffectiveDataRoot(), "profiles");
39047
- const oldDir = join27(getHomeDir(), ".hooks", "profiles");
39183
+ const newDir = join29(getEffectiveDataRoot(), "profiles");
39184
+ const oldDir = join29(getHomeDir(), ".hooks", "profiles");
39048
39185
  if (!existsSync14(newDir) && existsSync14(oldDir)) {
39049
39186
  mkdirSync13(newDir, { recursive: true });
39050
39187
  cpSync2(oldDir, newDir, { recursive: true });
@@ -39058,7 +39195,7 @@ function ensureProfilesDir() {
39058
39195
  }
39059
39196
  }
39060
39197
  function profilePath(id) {
39061
- return join27(getProfilesDir(), `${id}.json`);
39198
+ return join29(getProfilesDir(), `${id}.json`);
39062
39199
  }
39063
39200
  function shortUuid() {
39064
39201
  return crypto.randomUUID().slice(0, 8);
@@ -39100,7 +39237,7 @@ function listProfiles() {
39100
39237
  const profiles = [];
39101
39238
  for (const file2 of files) {
39102
39239
  try {
39103
- const content = readFileSync19(join27(dir, file2), "utf-8");
39240
+ const content = readFileSync19(join29(dir, file2), "utf-8");
39104
39241
  profiles.push(JSON.parse(content));
39105
39242
  } catch {}
39106
39243
  }
@@ -39144,18 +39281,18 @@ var init_profiles = __esm(() => {
39144
39281
  init_app_home();
39145
39282
  });
39146
39283
 
39147
- // ../../../../validation/build/apps/hooks/node_modules/@hasna/contracts/dist/client/transport.js
39148
- import { isIP } from "net";
39284
+ // ../../node_modules/.bun/@hasna+contracts@1.2.2+944b86d8024e79fa/node_modules/@hasna/contracts/dist/client/transport.js
39285
+ import { isIP as isIP2 } from "net";
39149
39286
  import { spawnSync as spawnSync2 } from "child_process";
39150
39287
  import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
39151
39288
  import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
39152
39289
  import { hostname as osHostname } from "os";
39153
39290
  import { isAbsolute as isAbsolute21, join as join28 } from "path";
39154
- import { createRequire as createRequire2 } from "module";
39291
+ import { createRequire as createRequire3 } from "module";
39155
39292
  import { fileURLToPath as fileURLToPath22 } from "url";
39156
39293
  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
39294
  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";
39295
+ import { basename as basename9, dirname as dirname22, join as join30, relative as relative5, sep as sep6 } from "path";
39159
39296
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
39160
39297
  function envToken2(name) {
39161
39298
  return name.toUpperCase().replace(/-/g, "_");
@@ -39230,11 +39367,11 @@ function o(e2, r2, t2) {
39230
39367
  }
39231
39368
  function entryPoint(directory2) {
39232
39369
  const root = realpathSync12(directory2);
39233
- const fd = openSync20(join29(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
39370
+ const fd = openSync20(join30(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
39234
39371
  let pkg;
39235
39372
  try {
39236
39373
  const stat2 = fstatSync19(fd);
39237
- if (!stat2.isFile() || stat2.size > MAX_PACKAGE_BYTES2)
39374
+ if (!stat2.isFile() || stat2.size > MAX_PACKAGE_BYTES3)
39238
39375
  throw new Error("Invalid Secrets package metadata");
39239
39376
  pkg = JSON.parse(readFileSync20(fd, "utf8"));
39240
39377
  } finally {
@@ -39250,7 +39387,7 @@ function entryPoint(directory2) {
39250
39387
  if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
39251
39388
  throw new Error("Invalid Secrets import export");
39252
39389
  }
39253
- const file2 = realpathSync12(join29(root, ...parts));
39390
+ const file2 = realpathSync12(join30(root, ...parts));
39254
39391
  const within = relative5(root, file2);
39255
39392
  if (!within || within === ".." || within.startsWith(`..${sep6}`) || !statSync2(file2).isFile()) {
39256
39393
  throw new Error("Secrets import export escapes its package or is not a file");
@@ -39261,7 +39398,7 @@ function resolveInstalledSecrets(parentUrl) {
39261
39398
  let directory2 = dirname22(fileURLToPath3(parentUrl));
39262
39399
  for (;; ) {
39263
39400
  if (basename9(directory2) !== "node_modules") {
39264
- const candidate = join29(directory2, "node_modules", PACKAGE);
39401
+ const candidate = join30(directory2, "node_modules", PACKAGE);
39265
39402
  let absent = false;
39266
39403
  try {
39267
39404
  lstatSync24(candidate);
@@ -39381,7 +39518,7 @@ function readAppConfigFile(path) {
39381
39518
  const uid2 = process.getuid?.() ?? process.geteuid?.();
39382
39519
  if (uid2 !== undefined && before.uid !== uid2)
39383
39520
  unsafe("the file is not owned by the current user");
39384
- if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
39521
+ if (before.size > MAX_CREDENTIAL_FILE_BYTES3)
39385
39522
  unsafe("the file exceeds the size limit");
39386
39523
  const bytes = readFileSync22(fd);
39387
39524
  const after = fstatSync22(fd);
@@ -39473,13 +39610,13 @@ function sealCredential(fields) {
39473
39610
  configurable: false
39474
39611
  });
39475
39612
  }
39476
- Object.defineProperty(sealed, INSPECT_CUSTOM2, {
39613
+ Object.defineProperty(sealed, INSPECT_CUSTOM3, {
39477
39614
  value: () => ({ ...visible, apiKey: "[redacted]" }),
39478
39615
  enumerable: false,
39479
39616
  writable: false,
39480
39617
  configurable: false
39481
39618
  });
39482
- Object.defineProperty(sealed, CREDENTIAL_SEAL2, {
39619
+ Object.defineProperty(sealed, CREDENTIAL_SEAL3, {
39483
39620
  value: true,
39484
39621
  enumerable: false,
39485
39622
  writable: false,
@@ -39510,7 +39647,7 @@ function firstEnvValue(env, keys2) {
39510
39647
  return null;
39511
39648
  }
39512
39649
  function isAmbientEnvironment(env) {
39513
- return env === process.env || env[AMBIENT_ENVIRONMENT2] === true;
39650
+ return env === process.env || env[AMBIENT_ENVIRONMENT3] === true;
39514
39651
  }
39515
39652
  function defaultKeychainRunner(argv) {
39516
39653
  const result = spawnSync2(KEYCHAIN_SECURITY_BIN, [...argv], {
@@ -39604,7 +39741,7 @@ function snapshotClientEnvironment(name, env) {
39604
39741
  snapshot2[key] = descriptor.value;
39605
39742
  }
39606
39743
  if (ambient) {
39607
- Object.defineProperty(snapshot2, AMBIENT_ENVIRONMENT2, {
39744
+ Object.defineProperty(snapshot2, AMBIENT_ENVIRONMENT3, {
39608
39745
  value: true,
39609
39746
  enumerable: false,
39610
39747
  writable: false,
@@ -39777,7 +39914,7 @@ async function completePointerCredential(name, pointerResolution, env = process.
39777
39914
  let secretsSdk;
39778
39915
  try {
39779
39916
  const sdkUrl = resolveInstalledSecrets(import.meta.url);
39780
- secretsSdk = requireSecretsSdk2(fileURLToPath22(sdkUrl));
39917
+ secretsSdk = requireSecretsSdk3(fileURLToPath22(sdkUrl));
39781
39918
  } catch {
39782
39919
  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
39920
  }
@@ -39861,7 +39998,7 @@ function canonicalAuthorityHostname(authority) {
39861
39998
  }
39862
39999
  assertCanonicalPort(portSuffix.slice(1));
39863
40000
  }
39864
- if (isIP(rawHostname.slice(1, -1)) !== 6) {
40001
+ if (isIP2(rawHostname.slice(1, -1)) !== 6) {
39865
40002
  throw new Error("API URL authority must contain a canonical IPv6 literal.");
39866
40003
  }
39867
40004
  } else {
@@ -39877,7 +40014,7 @@ function canonicalAuthorityHostname(authority) {
39877
40014
  } else {
39878
40015
  rawHostname = authority;
39879
40016
  }
39880
- const ipVersion = isIP(rawHostname);
40017
+ const ipVersion = isIP2(rawHostname);
39881
40018
  const numericAddressParts = rawHostname.split(".");
39882
40019
  const looksLikeNonCanonicalIpv4 = numericAddressParts.every((part) => /^(?:0x[0-9a-f]+|[0-9]+)$/i.test(part));
39883
40020
  if (ipVersion !== 4 && looksLikeNonCanonicalIpv4 || ipVersion !== 4 && !isValidDnsDomain(rawHostname.toLowerCase())) {
@@ -40013,9 +40150,9 @@ function credentialDiskSourcesForMessage(name, env) {
40013
40150
  const paths = credentialDiskSources(name, env);
40014
40151
  return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
40015
40152
  }
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;
40153
+ 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
40154
  var init_transport = __esm(() => {
40018
- MAX_PACKAGE_BYTES2 = 1024 * 1024;
40155
+ MAX_PACKAGE_BYTES3 = 1024 * 1024;
40019
40156
  CredentialResolutionError = class CredentialResolutionError extends Error {
40020
40157
  appName;
40021
40158
  attempted;
@@ -40034,16 +40171,16 @@ var init_transport = __esm(() => {
40034
40171
  this.path = path;
40035
40172
  }
40036
40173
  };
40037
- MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
40174
+ MAX_CREDENTIAL_FILE_BYTES3 = 64 * 1024;
40038
40175
  SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
40039
40176
  SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
40040
40177
  ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
40041
40178
  VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
40042
40179
  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);
40180
+ INSPECT_CUSTOM3 = Symbol.for("nodejs.util.inspect.custom");
40181
+ CREDENTIAL_SEAL3 = Symbol.for("hasna:contracts:sealedCredential");
40182
+ AMBIENT_ENVIRONMENT3 = Symbol.for("hasna:contracts:ambientClientEnvironment");
40183
+ requireSecretsSdk3 = createRequire3(import.meta.url);
40047
40184
  ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
40048
40185
  DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
40049
40186
  ClientTransportConfigurationError = class ClientTransportConfigurationError extends Error {
@@ -40056,8 +40193,8 @@ var init_transport = __esm(() => {
40056
40193
  this.sources = Object.freeze([...sources]);
40057
40194
  }
40058
40195
  };
40059
- IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
40060
- AUTHORITY_OVERRIDE_HEADERS2 = new Set([
40196
+ IDEMPOTENT_METHODS3 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
40197
+ AUTHORITY_OVERRIDE_HEADERS3 = new Set([
40061
40198
  "host",
40062
40199
  ":authority",
40063
40200
  "forwarded",
@@ -40568,7 +40705,7 @@ __export(exports_run, {
40568
40705
  });
40569
40706
  import { randomBytes as randomBytes2 } from "crypto";
40570
40707
  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";
40708
+ import { basename as basename11, dirname as dirname24, isAbsolute as isAbsolute25, join as join31 } from "path";
40572
40709
  function extensionOf(scriptPath) {
40573
40710
  const base = basename11(scriptPath);
40574
40711
  const dot = base.lastIndexOf(".");
@@ -40639,7 +40776,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
40639
40776
  async function executeVerifiedScript(options) {
40640
40777
  const scriptDir = dirname24(options.scriptPath);
40641
40778
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
40642
- const tempPath = join30(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
40779
+ const tempPath = join31(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
40643
40780
  let proc = null;
40644
40781
  let timedOut = false;
40645
40782
  const timeoutTimer = options.timeout ? setTimeout(() => {
@@ -40731,10 +40868,10 @@ __export(exports_custom_install, {
40731
40868
  ensureCustomDir: () => ensureCustomDir
40732
40869
  });
40733
40870
  import { existsSync as existsSync16, mkdirSync as mkdirSync14, mkdtempSync, readFileSync as readFileSync21, rmSync as rmSync4 } from "fs";
40734
- import { join as join31 } from "path";
40871
+ import { join as join32 } from "path";
40735
40872
  import { tmpdir as tmpdir2 } from "os";
40736
40873
  function manifestAt(dir) {
40737
- const path = join31(dir, "manifest.json");
40874
+ const path = join32(dir, "manifest.json");
40738
40875
  if (!existsSync16(path))
40739
40876
  return;
40740
40877
  try {
@@ -40757,8 +40894,8 @@ function writeFromManifest(manifest, srcDir, source, kind) {
40757
40894
  return { name, version: manifest.version, kind, source, dir, scriptPath };
40758
40895
  }
40759
40896
  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")], {
40897
+ const tmp = mkdtempSync(join32(tmpdir2(), "hooks-install-"));
40898
+ const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join32(tmp, "repo")], {
40762
40899
  stdout: "pipe",
40763
40900
  stderr: "pipe"
40764
40901
  });
@@ -40771,7 +40908,7 @@ async function cloneToTemp(gitUrl) {
40771
40908
  rmSync4(tmp, { recursive: true, force: true });
40772
40909
  throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
40773
40910
  }
40774
- return join31(tmp, "repo");
40911
+ return join32(tmp, "repo");
40775
40912
  }
40776
40913
  function isGitUrl(value) {
40777
40914
  return value.startsWith("git@") || value.startsWith("ssh://") || value.startsWith("file://") || value.endsWith(".git");
@@ -42010,11 +42147,11 @@ __export(exports_serve, {
42010
42147
  DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
42011
42148
  });
42012
42149
  import { readFileSync as readFileSync25 } from "fs";
42013
- import { dirname as dirname25, join as join32 } from "path";
42150
+ import { dirname as dirname25, join as join33 } from "path";
42014
42151
  import { fileURLToPath as fileURLToPath4 } from "url";
42015
42152
  function packageVersion() {
42016
42153
  try {
42017
- const pkgPath = join32(dirname25(fileURLToPath4(import.meta.url)), "..", "package.json");
42154
+ const pkgPath = join33(dirname25(fileURLToPath4(import.meta.url)), "..", "package.json");
42018
42155
  return JSON.parse(readFileSync25(pkgPath, "utf-8")).version || "0.0.0";
42019
42156
  } catch {
42020
42157
  return "0.0.0";
@@ -42316,7 +42453,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
42316
42453
  import { z as z3 } from "zod";
42317
42454
  import { createServer } from "http";
42318
42455
  import { existsSync as existsSync17, readFileSync as readFileSync26 } from "fs";
42319
- import { join as join33, dirname as dirname26 } from "path";
42456
+ import { join as join34, dirname as dirname26 } from "path";
42320
42457
  import { fileURLToPath as fileURLToPath5 } from "url";
42321
42458
  async function runVerifiedHook(name, stdin, timeoutMs) {
42322
42459
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
@@ -42550,7 +42687,7 @@ function createHooksServer() {
42550
42687
  continue;
42551
42688
  }
42552
42689
  const hookDir = getHookPath(name);
42553
- if (!existsSync17(join33(hookDir, "src", "hook.ts"))) {
42690
+ if (!existsSync17(join34(hookDir, "src", "hook.ts"))) {
42554
42691
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
42555
42692
  hookHealthy = false;
42556
42693
  }
@@ -42558,7 +42695,7 @@ function createHooksServer() {
42558
42695
  if (meta3 && settingsExist) {
42559
42696
  try {
42560
42697
  const settings = JSON.parse(readFileSync26(settingsPath, "utf-8"));
42561
- if (!hookRegisteredInSettings(settings, name, meta3.event, meta3.matcher)) {
42698
+ if (!hookRegisteredInSettings(settings, name, meta3.event, matcherFor(meta3, "claude"))) {
42562
42699
  const eventName = meta3.event.split(":")[0];
42563
42700
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
42564
42701
  hookHealthy = false;
@@ -42594,7 +42731,7 @@ function createHooksServer() {
42594
42731
  return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
42595
42732
  }
42596
42733
  const hookPath = getHookPath(name);
42597
- const readmePath = join33(hookPath, "README.md");
42734
+ const readmePath = join34(hookPath, "README.md");
42598
42735
  let readme = "";
42599
42736
  if (existsSync17(readmePath)) {
42600
42737
  readme = readFileSync26(readmePath, "utf-8");
@@ -43324,7 +43461,7 @@ var init_server = __esm(() => {
43324
43461
  pkg = { name: "@hasna/hooks", version: "0.0.0" };
43325
43462
  try {
43326
43463
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
43327
- const p = join33(__dirname3, rel);
43464
+ const p = join34(__dirname3, rel);
43328
43465
  if (existsSync17(p)) {
43329
43466
  pkg = JSON.parse(readFileSync26(p, "utf-8"));
43330
43467
  break;
@@ -43403,7 +43540,7 @@ __export(exports_native_readiness_binding, {
43403
43540
  import { createHash as createHash25, randomUUID as randomUUID8 } from "crypto";
43404
43541
  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
43542
  import { homedir as homedir15 } from "os";
43406
- import { dirname as dirname27, join as join34 } from "path";
43543
+ import { dirname as dirname27, join as join35 } from "path";
43407
43544
  function need10(value, code2) {
43408
43545
  if (!value)
43409
43546
  throw new ReadinessBindingError(code2);
@@ -43461,7 +43598,7 @@ async function registerTrashReadinessBinding(input2, expectedSHA256) {
43461
43598
  const operationId = randomUUID8();
43462
43599
  let directory2 = home;
43463
43600
  for (const part of [".hasna", "hooks", "native", "trash-readiness"]) {
43464
- directory2 = join34(directory2, part);
43601
+ directory2 = join35(directory2, part);
43465
43602
  try {
43466
43603
  mkdirSync15(directory2, { mode: 448 });
43467
43604
  syncDirectory4(dirname27(directory2));
@@ -43471,12 +43608,12 @@ async function registerTrashReadinessBinding(input2, expectedSHA256) {
43471
43608
  }
43472
43609
  safeDirectory4(directory2);
43473
43610
  }
43474
- const operation = join34(directory2, operationId);
43611
+ const operation = join35(directory2, operationId);
43475
43612
  mkdirSync15(operation, { mode: 448 });
43476
43613
  syncDirectory4(directory2);
43477
43614
  if (before)
43478
- exclusive3(join34(operation, "binding.before"), before.raw);
43479
- exclusive3(join34(operation, "intent.json"), JSON.stringify({ operationId, target, expectedSHA256, desiredSHA256: digest4 }) + `
43615
+ exclusive3(join35(operation, "binding.before"), before.raw);
43616
+ exclusive3(join35(operation, "intent.json"), JSON.stringify({ operationId, target, expectedSHA256, desiredSHA256: digest4 }) + `
43480
43617
  `);
43481
43618
  try {
43482
43619
  need10((read4()?.sha256 ?? "absent") === expectedSHA256, "binding_changed");
@@ -43490,7 +43627,7 @@ async function registerTrashReadinessBinding(input2, expectedSHA256) {
43490
43627
  syncDirectory4(directory2);
43491
43628
  need10(read4()?.sha256 === digest4, "binding_changed");
43492
43629
  const receipt = { ok: true, changed: true, operationId, path: target, sha256: digest4, configurationReady: true, nativeAdoptionVerified: false };
43493
- exclusive3(join34(operation, "receipt.json"), JSON.stringify(receipt) + `
43630
+ exclusive3(join35(operation, "receipt.json"), JSON.stringify(receipt) + `
43494
43631
  `);
43495
43632
  return receipt;
43496
43633
  } catch {
@@ -43514,11 +43651,12 @@ var init_native_readiness_binding = __esm(() => {
43514
43651
 
43515
43652
  // src/cli/index.tsx
43516
43653
  init_native_safety_registration();
43517
- import { resolve as resolve21 } from "path";
43654
+ import { resolve as resolve23 } from "path";
43518
43655
 
43519
43656
  // src/lib/claude-safety-check.ts
43520
43657
  init_native_safety();
43521
43658
  init_native_safety2();
43659
+ init_registry();
43522
43660
  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
43661
  import { createHash as createHash19 } from "crypto";
43524
43662
  import { dirname as dirname17, isAbsolute as isAbsolute17, join as join17, resolve as resolve18 } from "path";
@@ -43602,7 +43740,7 @@ async function verifyClaudeSafetyConfiguration(options = {}, dependencies = {})
43602
43740
  need7(!/^(?:hooks run trash-guard|hook-trash-guard)(?:\s|$)/.test(handler.command.trim()), "guard_definition_invalid");
43603
43741
  if (binding?.name !== "trash-guard")
43604
43742
  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");
43743
+ 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
43744
  guards.push({ command: handler.command, path: file2.path });
43607
43745
  }
43608
43746
  }
@@ -43633,16 +43771,25 @@ async function verifyClaudeSafetyConfiguration(options = {}, dependencies = {})
43633
43771
  // src/cli/index.tsx
43634
43772
  init_codex_safety_trust();
43635
43773
 
43636
- // ../../../../validation/build/apps/hooks/node_modules/@hasna/events/dist/commander.js
43774
+ // ../events/dist/commander.js
43637
43775
  import { chmod, mkdir, readFile as readFile2, rename, writeFile } from "fs/promises";
43638
43776
  import { Buffer as Buffer2 } from "buffer";
43777
+ import { existsSync as existsSync22 } from "fs";
43778
+ import { join as join22 } from "path";
43639
43779
  import { existsSync as existsSync13 } from "fs";
43640
43780
  import { homedir as homedir13 } from "os";
43641
- import { join as join20 } from "path";
43781
+ import { join as join20, resolve as resolve20 } from "path";
43782
+ import { homedir as pathsResolverHomedir3 } from "os";
43783
+ import { join as pathsResolverJoin3 } from "path";
43642
43784
  import { createHmac, timingSafeEqual } from "crypto";
43785
+ import { lookup as dnsLookup } from "dns/promises";
43786
+ import { isIP } from "net";
43643
43787
  import { randomUUID as randomUUID7 } from "crypto";
43644
43788
  import { spawn as spawn3 } from "child_process";
43789
+ import { request as nodeHttpRequest } from "http";
43790
+ import { request as nodeHttpsRequest } from "https";
43645
43791
  import { randomUUID as randomUUID22 } from "crypto";
43792
+ import { createRequire as createRequire2 } from "module";
43646
43793
  function getPathValue(input2, path) {
43647
43794
  return path.split(".").reduce((value, part) => {
43648
43795
  if (value && typeof value === "object" && part in value) {
@@ -43738,13 +43885,103 @@ function channelMatchesEvent(channel, event) {
43738
43885
  return true;
43739
43886
  return channel.filters.some((filter) => eventMatchesFilter(event, filter));
43740
43887
  }
43888
+ var PATHS_RESOLVER_KIND_ENV2 = {
43889
+ config: "HASNA_CONFIG_HOME",
43890
+ data: "HASNA_DATA_HOME",
43891
+ state: "HASNA_STATE_HOME",
43892
+ cache: "HASNA_CACHE_HOME"
43893
+ };
43894
+ var PATHS_RESOLVER_APP_SLUG_RE3 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
43895
+ function pathsResolverAssertApp3(app) {
43896
+ if (typeof app !== "string" || app.length === 0) {
43897
+ throw new TypeError("paths: app must be a non-empty string");
43898
+ }
43899
+ if (!PATHS_RESOLVER_APP_SLUG_RE3.test(app)) {
43900
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
43901
+ }
43902
+ }
43903
+ function pathsResolverAssertKind2(kind) {
43904
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV2).includes(kind)) {
43905
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV2).join(", ")}`);
43906
+ }
43907
+ }
43908
+ function pathsResolverBaseDir2(kind, options) {
43909
+ pathsResolverAssertKind2(kind);
43910
+ const env = options.env ?? process.env;
43911
+ const override = env[PATHS_RESOLVER_KIND_ENV2[kind]];
43912
+ if (typeof override === "string" && override.length > 0)
43913
+ return override;
43914
+ const home = options.home ?? pathsResolverHomedir3();
43915
+ const platform = options.platform ?? process.platform;
43916
+ if (platform === "darwin") {
43917
+ switch (kind) {
43918
+ case "config":
43919
+ case "data":
43920
+ return pathsResolverJoin3(home, "Library", "Application Support", "Hasna");
43921
+ case "cache":
43922
+ return pathsResolverJoin3(home, "Library", "Caches", "Hasna");
43923
+ case "state":
43924
+ return pathsResolverJoin3(home, "Library", "Logs", "Hasna");
43925
+ }
43926
+ }
43927
+ switch (kind) {
43928
+ case "config":
43929
+ return pathsResolverJoin3(home, ".config", "hasna");
43930
+ case "data":
43931
+ return pathsResolverJoin3(home, ".local", "share", "hasna");
43932
+ case "state":
43933
+ return pathsResolverJoin3(home, ".local", "state", "hasna");
43934
+ case "cache":
43935
+ return pathsResolverJoin3(home, ".cache", "hasna");
43936
+ }
43937
+ }
43938
+ function pathsResolverResolve2(kind, options) {
43939
+ pathsResolverAssertApp3(options.app);
43940
+ const appSegment = options.internal === true ? pathsResolverJoin3("internal", options.app) : options.app;
43941
+ return pathsResolverJoin3(pathsResolverBaseDir2(kind, options), appSegment);
43942
+ }
43943
+ function dataDir3(options) {
43944
+ return pathsResolverResolve2("data", options);
43945
+ }
43741
43946
  var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
43742
43947
  var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
43948
+ var EVENTS_STORE_SENTINEL_FILE = "events.json";
43949
+ function effectiveHome2() {
43950
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir13();
43951
+ }
43952
+ function legacyHomeDir() {
43953
+ return join20(effectiveHome2(), ".hasna", "events");
43954
+ }
43955
+ function resolverHome() {
43956
+ return dataDir3({ app: "events", home: effectiveHome2() || undefined });
43957
+ }
43958
+ function adoptResolverHome(resolved, env = process.env) {
43959
+ const dataOverride = env.HASNA_DATA_HOME;
43960
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
43961
+ return true;
43962
+ return existsSync13(join20(resolved, EVENTS_STORE_SENTINEL_FILE));
43963
+ }
43964
+ function exactEventsHome() {
43965
+ const dir = process.env[HASNA_EVENTS_DIR_ENV];
43966
+ if (dir && dir.trim())
43967
+ return dir.trim();
43968
+ const home = process.env[HASNA_EVENTS_HOME_ENV];
43969
+ if (home && home.trim())
43970
+ return home.trim();
43971
+ return;
43972
+ }
43973
+ function getEventsHome() {
43974
+ const exact2 = exactEventsHome();
43975
+ if (exact2)
43976
+ return resolve20(exact2);
43977
+ const resolved = resolverHome();
43978
+ return adoptResolverHome(resolved) ? resolve20(resolved) : resolve20(legacyHomeDir());
43979
+ }
43743
43980
  var LOCAL_JSON_EVENT_CURSOR_PREFIX = "local-json-v1:";
43744
43981
  var DEFAULT_EVENT_PAGE_LIMIT = 100;
43745
43982
  var MAX_EVENT_PAGE_LIMIT = 1000;
43746
43983
  function getEventsDataDir(override) {
43747
- return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join20(homedir13(), ".hasna", "events");
43984
+ return override || getEventsHome();
43748
43985
  }
43749
43986
  function getActiveEventsDirEnv() {
43750
43987
  if (process.env[HASNA_EVENTS_DIR_ENV])
@@ -43760,12 +43997,12 @@ class JsonEventsStore {
43760
43997
  channelsPath;
43761
43998
  eventsPath;
43762
43999
  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");
44000
+ constructor(dataDir22 = getEventsDataDir()) {
44001
+ this.dataDir = dataDir22;
44002
+ this.runtime = localJsonRuntime(dataDir22);
44003
+ this.channelsPath = join22(dataDir22, "channels.json");
44004
+ this.eventsPath = join22(dataDir22, "events.json");
44005
+ this.deliveriesPath = join22(dataDir22, "deliveries.json");
43769
44006
  }
43770
44007
  async init() {
43771
44008
  await mkdir(this.dataDir, { recursive: true, mode: 448 });
@@ -43882,7 +44119,7 @@ class JsonEventsStore {
43882
44119
  };
43883
44120
  }
43884
44121
  async ensureArrayFile(path) {
43885
- if (!existsSync13(path)) {
44122
+ if (!existsSync22(path)) {
43886
44123
  await writeFile(path, `[]
43887
44124
  `, { encoding: "utf-8", mode: 384 });
43888
44125
  }
@@ -43912,7 +44149,7 @@ class JsonEventsStore {
43912
44149
  });
43913
44150
  }
43914
44151
  }
43915
- function localJsonRuntime(dataDir3 = getEventsDataDir()) {
44152
+ function localJsonRuntime(dataDir22 = getEventsDataDir()) {
43916
44153
  return {
43917
44154
  mode: "local-files",
43918
44155
  name: "json-events-store",
@@ -43925,7 +44162,7 @@ function localJsonRuntime(dataDir3 = getEventsDataDir()) {
43925
44162
  durable: true,
43926
44163
  idempotency: "best-effort-local",
43927
44164
  replayCursors: true,
43928
- description: `Local JSON files in ${dataDir3}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
44165
+ description: `Local JSON files in ${dataDir22}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
43929
44166
  };
43930
44167
  }
43931
44168
  function encodeLocalJsonEventCursor(offset, options = {}) {
@@ -43989,8 +44226,8 @@ function assertCursorFilter(name, cursorValue, optionValue) {
43989
44226
  function findEventByIdentity(events2, identity3) {
43990
44227
  return events2.find((event) => identity3.id !== undefined && event.id === identity3.id || identity3.dedupeKey !== undefined && event.dedupeKey === identity3.dedupeKey);
43991
44228
  }
43992
- async function getEventsStatus(dataDir3) {
43993
- const store = new JsonEventsStore(dataDir3);
44229
+ async function getEventsStatus(dataDir22) {
44230
+ const store = new JsonEventsStore(dataDir22);
43994
44231
  await store.init();
43995
44232
  const [channels, events2, deliveries] = await Promise.all([
43996
44233
  store.listChannels(),
@@ -44032,9 +44269,9 @@ async function getEventsStatus(dataDir3) {
44032
44269
  }
44033
44270
  };
44034
44271
  }
44035
- function statusFile(dataDir3, fileName, records) {
44036
- const path = join20(dataDir3, fileName);
44037
- return { path, exists: existsSync13(path), records };
44272
+ function statusFile(dataDir22, fileName, records) {
44273
+ const path = join22(dataDir22, fileName);
44274
+ return { path, exists: existsSync22(path), records };
44038
44275
  }
44039
44276
  var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
44040
44277
  function buildSignatureBase(timestamp, body) {
@@ -44044,6 +44281,214 @@ function signPayload(secret, timestamp, body) {
44044
44281
  const digest4 = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
44045
44282
  return `sha256=${digest4}`;
44046
44283
  }
44284
+ var DEFAULT_MAX_REDIRECTS = 5;
44285
+ var IPV4_PRIVATE_RANGES = [
44286
+ [0, 16777215],
44287
+ [167772160, 184549375],
44288
+ [1681915904, 1686110207],
44289
+ [2130706432, 2147483647],
44290
+ [2851995648, 2852061183],
44291
+ [2886729728, 2887778303],
44292
+ [3221225472, 3221225727],
44293
+ [3221225984, 3221226239],
44294
+ [3227017984, 3227018239],
44295
+ [3232235520, 3232301055],
44296
+ [3323068416, 3323199487],
44297
+ [3325256704, 3325256959],
44298
+ [3405803776, 3405804031],
44299
+ [3758096384, 4294967295]
44300
+ ];
44301
+ var IPV6_SPECIAL_PREFIXES = [
44302
+ { groups: [0, 0, 0, 0, 0, 0, 0, 0], bits: 128 },
44303
+ { groups: [0, 0, 0, 0, 0, 0, 0, 1], bits: 128 },
44304
+ { groups: [0, 0, 0, 0, 0, 65535, 0, 0], bits: 96 },
44305
+ { groups: [100, 65435, 0, 0, 0, 0, 0, 0], bits: 96 },
44306
+ { groups: [256, 0, 0, 0, 0, 0, 0, 0], bits: 64 },
44307
+ { groups: [8193, 0, 0, 0, 0, 0, 0, 0], bits: 32 },
44308
+ { groups: [8193, 2, 0, 0, 0, 0, 0, 0], bits: 48 },
44309
+ { groups: [8193, 16, 0, 0, 0, 0, 0, 0], bits: 28 },
44310
+ { groups: [8193, 3512, 0, 0, 0, 0, 0, 0], bits: 32 },
44311
+ { groups: [8194, 0, 0, 0, 0, 0, 0, 0], bits: 16 },
44312
+ { groups: [16383, 0, 0, 0, 0, 0, 0, 0], bits: 20 },
44313
+ { groups: [64512, 0, 0, 0, 0, 0, 0, 0], bits: 7 },
44314
+ { groups: [65152, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
44315
+ { groups: [65216, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
44316
+ { groups: [65280, 0, 0, 0, 0, 0, 0, 0], bits: 8 }
44317
+ ];
44318
+ function isPrivateAddress(address) {
44319
+ const normalized = stripZoneId(address);
44320
+ const version2 = isIP(normalized);
44321
+ if (version2 === 4) {
44322
+ const integer3 = ipv4ToInt(normalized);
44323
+ if (integer3 === undefined)
44324
+ return true;
44325
+ return IPV4_PRIVATE_RANGES.some(([low, high]) => integer3 >= low && integer3 <= high);
44326
+ }
44327
+ if (version2 === 6) {
44328
+ const groups = ipv6Groups(normalized);
44329
+ if (!groups)
44330
+ return true;
44331
+ for (const prefix3 of IPV6_SPECIAL_PREFIXES) {
44332
+ if (!ipv6MatchesPrefix(groups, prefix3.groups, prefix3.bits))
44333
+ continue;
44334
+ if (prefix3.bits === 96 && groups[5] === 65535) {
44335
+ return isPrivateAddress(ipv4IntToString(groups[6] << 16 | groups[7]));
44336
+ }
44337
+ if (prefix3.bits === 16 && groups[0] === 8194) {
44338
+ return isPrivateAddress(ipv4IntToString(groups[1] << 16 | groups[2]));
44339
+ }
44340
+ return true;
44341
+ }
44342
+ return false;
44343
+ }
44344
+ return true;
44345
+ }
44346
+ async function resolveWebhookTarget(url2, policy = {}) {
44347
+ const hostname3 = normalizeHostname(url2.hostname);
44348
+ const allowlist = (policy.allowPrivateHosts ?? []).map((entry) => normalizeHostname(entry.toLowerCase()));
44349
+ if (allowlist.includes(hostname3)) {
44350
+ const version22 = isIP(hostname3);
44351
+ if (version22 === 4 || version22 === 6) {
44352
+ return { hostname: hostname3, addresses: [hostname3] };
44353
+ }
44354
+ const lookup2 = policy.lookup ?? defaultTargetLookup;
44355
+ let resolved2;
44356
+ try {
44357
+ resolved2 = await lookup2(hostname3);
44358
+ } catch {
44359
+ throw new Error(`Webhook target ${hostname3} could not be resolved`);
44360
+ }
44361
+ if (!Array.isArray(resolved2) || resolved2.length === 0) {
44362
+ throw new Error(`Webhook target ${hostname3} resolved to no addresses`);
44363
+ }
44364
+ const addresses = resolved2.map((entry) => normalizeHostname(entry.address));
44365
+ return { hostname: hostname3, addresses };
44366
+ }
44367
+ const version2 = isIP(hostname3);
44368
+ if (version2 === 4 || version2 === 6) {
44369
+ if (isPrivateAddress(hostname3)) {
44370
+ throw new Error(`Webhook target ${hostname3} is a private or special-use address`);
44371
+ }
44372
+ return { hostname: hostname3, addresses: [hostname3] };
44373
+ }
44374
+ const lookup = policy.lookup ?? defaultTargetLookup;
44375
+ let resolved;
44376
+ try {
44377
+ resolved = await lookup(hostname3);
44378
+ } catch {
44379
+ throw new Error(`Webhook target ${hostname3} could not be resolved`);
44380
+ }
44381
+ if (!Array.isArray(resolved) || resolved.length === 0) {
44382
+ throw new Error(`Webhook target ${hostname3} resolved to no addresses`);
44383
+ }
44384
+ const allowed = [];
44385
+ for (const entry of resolved) {
44386
+ const address = normalizeHostname(entry.address);
44387
+ if (isPrivateAddress(address)) {
44388
+ if (allowlist.includes(address)) {
44389
+ allowed.push(address);
44390
+ continue;
44391
+ }
44392
+ throw new Error(`Webhook target ${hostname3} resolves to private or special-use address ${address}`);
44393
+ }
44394
+ allowed.push(address);
44395
+ }
44396
+ if (allowed.length === 0) {
44397
+ throw new Error(`Webhook target ${hostname3} resolved to no public addresses`);
44398
+ }
44399
+ return { hostname: hostname3, addresses: allowed };
44400
+ }
44401
+ function normalizeMaxRedirects(value) {
44402
+ if (value === undefined)
44403
+ return DEFAULT_MAX_REDIRECTS;
44404
+ if (!Number.isInteger(value) || value < 0)
44405
+ throw new Error("webhookTargetPolicy.maxRedirects must be a non-negative integer");
44406
+ return value;
44407
+ }
44408
+ var defaultTargetLookup = async (hostname3) => {
44409
+ return dnsLookup(hostname3, { all: true, verbatim: false });
44410
+ };
44411
+ function normalizeHostname(hostname3) {
44412
+ const lower = hostname3.toLowerCase();
44413
+ if (lower.startsWith("[") && lower.endsWith("]"))
44414
+ return lower.slice(1, -1);
44415
+ return lower;
44416
+ }
44417
+ function stripZoneId(address) {
44418
+ const percent = address.indexOf("%");
44419
+ return percent === -1 ? address : address.slice(0, percent);
44420
+ }
44421
+ function ipv4ToInt(address) {
44422
+ const parts = address.split(".");
44423
+ if (parts.length !== 4)
44424
+ return;
44425
+ let value = 0;
44426
+ for (const part of parts) {
44427
+ if (!/^\d{1,3}$/.test(part))
44428
+ return;
44429
+ const octet = Number(part);
44430
+ if (octet > 255)
44431
+ return;
44432
+ value = value << 8 | octet;
44433
+ }
44434
+ return value >>> 0;
44435
+ }
44436
+ function ipv4IntToString(integer3) {
44437
+ return [
44438
+ integer3 >>> 24 & 255,
44439
+ integer3 >>> 16 & 255,
44440
+ integer3 >>> 8 & 255,
44441
+ integer3 & 255
44442
+ ].join(".");
44443
+ }
44444
+ function ipv6Groups(address) {
44445
+ const raw = stripZoneId(address);
44446
+ const doubleColon = raw.indexOf("::");
44447
+ const headText = doubleColon === -1 ? raw : raw.slice(0, doubleColon);
44448
+ const tailText = doubleColon === -1 ? "" : raw.slice(doubleColon + 2);
44449
+ const parseGroups = (text3) => {
44450
+ if (text3 === "")
44451
+ return [];
44452
+ const out = [];
44453
+ for (const part of text3.split(":")) {
44454
+ if (part.includes(".")) {
44455
+ const v4 = ipv4ToInt(part);
44456
+ if (v4 === undefined)
44457
+ return;
44458
+ out.push(v4 >>> 16 & 65535, v4 & 65535);
44459
+ } else {
44460
+ if (!/^[0-9a-fA-F]{1,4}$/.test(part))
44461
+ return;
44462
+ out.push(parseInt(part, 16));
44463
+ }
44464
+ }
44465
+ return out;
44466
+ };
44467
+ const head = parseGroups(headText);
44468
+ if (!head)
44469
+ return;
44470
+ const tail = parseGroups(tailText);
44471
+ if (!tail)
44472
+ return;
44473
+ const total = head.length + tail.length;
44474
+ if (doubleColon === -1) {
44475
+ return total === 8 ? head : undefined;
44476
+ }
44477
+ if (total >= 8)
44478
+ return;
44479
+ return [...head, ...new Array(8 - total).fill(0), ...tail];
44480
+ }
44481
+ function ipv6MatchesPrefix(groups, prefixGroups, prefixBits) {
44482
+ let remaining = prefixBits;
44483
+ for (let index = 0;index < prefixGroups.length && remaining > 0; index += 1) {
44484
+ const take = Math.min(16, remaining);
44485
+ const mask = 65535 << 16 - take & 65535;
44486
+ if ((groups[index] & mask) !== (prefixGroups[index] & mask))
44487
+ return false;
44488
+ remaining -= take;
44489
+ }
44490
+ return true;
44491
+ }
44047
44492
  function now() {
44048
44493
  return new Date().toISOString();
44049
44494
  }
@@ -44074,9 +44519,18 @@ function buildWebhookRequest(event, channel, options = {}) {
44074
44519
  }
44075
44520
  return { body, headers };
44076
44521
  }
44522
+ function normalizeWebhookUrl(raw) {
44523
+ const url2 = new URL(raw);
44524
+ if (url2.username !== "" || url2.password !== "") {
44525
+ url2.username = "";
44526
+ url2.password = "";
44527
+ }
44528
+ return url2.toString();
44529
+ }
44077
44530
  async function dispatchWebhook(event, channel, options = {}) {
44078
44531
  if (!channel.webhook)
44079
44532
  throw new Error(`Channel ${channel.id} has no webhook config`);
44533
+ const webhookUrl = normalizeWebhookUrl(channel.webhook.url);
44080
44534
  const startedAt = now();
44081
44535
  let secret = channel.webhook.secret;
44082
44536
  if (channel.webhook.secretRef) {
@@ -44093,10 +44547,14 @@ async function dispatchWebhook(event, channel, options = {}) {
44093
44547
  }
44094
44548
  const timestamp = (options.now?.() ?? new Date).toISOString();
44095
44549
  const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
44550
+ const validateTargets = options.webhookTargetPolicy !== undefined || options.fetchImpl === undefined;
44551
+ if (validateTargets) {
44552
+ return dispatchValidatedWebhook(event, channel, { body, headers, startedAt, options });
44553
+ }
44096
44554
  const controller = new AbortController;
44097
44555
  const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
44098
44556
  try {
44099
- const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
44557
+ const response = await (options.fetchImpl ?? fetch)(webhookUrl, {
44100
44558
  method: "POST",
44101
44559
  headers,
44102
44560
  body,
@@ -44124,6 +44582,130 @@ async function dispatchWebhook(event, channel, options = {}) {
44124
44582
  clearTimeout(timeout);
44125
44583
  }
44126
44584
  }
44585
+ function isRedirectStatus(status) {
44586
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
44587
+ }
44588
+ function redirectKeepsBody(status) {
44589
+ return status === 307 || status === 308;
44590
+ }
44591
+ async function pinnedNativeRequest(target, addresses, method, headers, body, signal, tls) {
44592
+ const isHttps = target.protocol === "https:";
44593
+ if (!isHttps && target.protocol !== "http:") {
44594
+ throw new Error(`Webhook target uses unsupported protocol ${target.protocol}`);
44595
+ }
44596
+ const defaultPort = isHttps ? 443 : 80;
44597
+ const port = target.port ? Number(target.port) : defaultPort;
44598
+ const requestOptions = {
44599
+ hostname: target.hostname,
44600
+ port,
44601
+ path: `${target.pathname}${target.search}`,
44602
+ method,
44603
+ headers,
44604
+ ...tls?.ca ? { ca: tls.ca } : {},
44605
+ lookup: (hostname3, _options, callback) => {
44606
+ const entries = addresses.map((address) => ({
44607
+ address,
44608
+ family: address.includes(":") ? 6 : 4
44609
+ }));
44610
+ callback(null, entries);
44611
+ }
44612
+ };
44613
+ return new Promise((resolve23, reject) => {
44614
+ const request = isHttps ? nodeHttpsRequest(requestOptions, onResponse) : nodeHttpRequest(requestOptions, onResponse);
44615
+ const onAbort = () => {
44616
+ const error61 = new Error("The operation was aborted.");
44617
+ error61.name = "AbortError";
44618
+ request.destroy(error61);
44619
+ };
44620
+ if (signal.aborted)
44621
+ onAbort();
44622
+ else
44623
+ signal.addEventListener("abort", onAbort, { once: true });
44624
+ request.on("error", reject);
44625
+ if (body !== undefined)
44626
+ request.write(body);
44627
+ request.end();
44628
+ function onResponse(response) {
44629
+ const chunks = [];
44630
+ response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
44631
+ response.on("error", reject);
44632
+ response.on("end", () => {
44633
+ const headersRecord = {};
44634
+ for (const [name, value] of Object.entries(response.headers)) {
44635
+ if (typeof value === "string")
44636
+ headersRecord[name] = value;
44637
+ else if (Array.isArray(value))
44638
+ headersRecord[name] = value.join(", ");
44639
+ }
44640
+ resolve23(new Response(Buffer.concat(chunks), { status: response.statusCode ?? 200, headers: headersRecord }));
44641
+ });
44642
+ }
44643
+ });
44644
+ }
44645
+ async function dispatchValidatedWebhook(event, channel, input2) {
44646
+ const { body, headers, startedAt, options } = input2;
44647
+ const webhook = channel.webhook;
44648
+ if (!webhook)
44649
+ throw new Error(`Channel ${channel.id} has no webhook config`);
44650
+ const policy = options.webhookTargetPolicy ?? {};
44651
+ const maxRedirects = normalizeMaxRedirects(policy.maxRedirects);
44652
+ const controller = new AbortController;
44653
+ const timeout = setTimeout(() => controller.abort(), webhook.timeoutMs ?? 15000);
44654
+ try {
44655
+ let target = new URL(normalizeWebhookUrl(webhook.url));
44656
+ let requestHeaders = headers;
44657
+ let method = "POST";
44658
+ let requestBody = body;
44659
+ let redirectsFollowed = 0;
44660
+ for (;; ) {
44661
+ const resolved = await resolveWebhookTarget(target, policy).catch((error61) => {
44662
+ throw new Error(`Webhook target rejected by SSRF guard: ${error61.message}`);
44663
+ });
44664
+ const response = options.fetchImpl ? await options.fetchImpl(target, {
44665
+ method,
44666
+ headers: requestHeaders,
44667
+ body: requestBody,
44668
+ signal: controller.signal,
44669
+ redirect: "manual"
44670
+ }) : await pinnedNativeRequest(target, resolved.addresses, method, requestHeaders, requestBody, controller.signal, options.tls);
44671
+ const location = response.headers.get("location");
44672
+ if (isRedirectStatus(response.status) && location) {
44673
+ if (redirectsFollowed >= maxRedirects) {
44674
+ return failedAttempt(startedAt, `Webhook target exceeded ${maxRedirects} redirects`);
44675
+ }
44676
+ redirectsFollowed += 1;
44677
+ const next = new URL(location, target);
44678
+ target = next;
44679
+ if (!redirectKeepsBody(response.status)) {
44680
+ method = "GET";
44681
+ requestBody = undefined;
44682
+ requestHeaders = Object.fromEntries(Object.entries(requestHeaders).filter(([name]) => name.toLowerCase() !== "content-type" && name.toLowerCase() !== "content-length"));
44683
+ }
44684
+ continue;
44685
+ }
44686
+ const responseBody = truncate(await response.text());
44687
+ return {
44688
+ attempt: 1,
44689
+ status: response.ok ? "success" : "failed",
44690
+ startedAt,
44691
+ completedAt: now(),
44692
+ responseStatus: response.status,
44693
+ responseBody,
44694
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
44695
+ };
44696
+ }
44697
+ } catch (error61) {
44698
+ return {
44699
+ attempt: 1,
44700
+ status: "failed",
44701
+ startedAt,
44702
+ completedAt: now(),
44703
+ error: error61 instanceof Error ? error61.message : String(error61)
44704
+ };
44705
+ } finally {
44706
+ clearTimeout(timeout);
44707
+ }
44708
+ }
44127
44709
  function failedAttempt(startedAt, error61) {
44128
44710
  return {
44129
44711
  attempt: 1,
@@ -44152,7 +44734,7 @@ async function dispatchCommand(event, channel) {
44152
44734
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
44153
44735
  HASNA_EVENT_JSON: eventJson
44154
44736
  };
44155
- return new Promise((resolve16) => {
44737
+ return new Promise((resolve23) => {
44156
44738
  const child = spawn3(channel.command.command, channel.command.args ?? [], {
44157
44739
  cwd: channel.command.cwd,
44158
44740
  env,
@@ -44170,7 +44752,7 @@ async function dispatchCommand(event, channel) {
44170
44752
  });
44171
44753
  child.on("error", (error61) => {
44172
44754
  clearTimeout(timeout);
44173
- resolve16({
44755
+ resolve23({
44174
44756
  attempt: 1,
44175
44757
  status: "failed",
44176
44758
  startedAt,
@@ -44183,7 +44765,7 @@ async function dispatchCommand(event, channel) {
44183
44765
  child.on("close", (code2, signal) => {
44184
44766
  clearTimeout(timeout);
44185
44767
  const success2 = code2 === 0;
44186
- resolve16({
44768
+ resolve23({
44187
44769
  attempt: 1,
44188
44770
  status: success2 ? "success" : "failed",
44189
44771
  startedAt,
@@ -44305,6 +44887,22 @@ function setPath(input2, path, replacement) {
44305
44887
  if (last && last in cursor)
44306
44888
  cursor[last] = replacement;
44307
44889
  }
44890
+ var MAX_PACKAGE_BYTES2 = 1024 * 1024;
44891
+ var MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
44892
+ var INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
44893
+ var CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
44894
+ var AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
44895
+ var requireSecretsSdk2 = createRequire2(import.meta.url);
44896
+ var IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
44897
+ var AUTHORITY_OVERRIDE_HEADERS2 = new Set([
44898
+ "host",
44899
+ ":authority",
44900
+ "forwarded",
44901
+ "x-forwarded-host",
44902
+ "x-original-host"
44903
+ ]);
44904
+ var MAX_ENVELOPE_BYTES = 256 * 1024;
44905
+ var MAX_REQUEST_BYTES = MAX_ENVELOPE_BYTES * 2 + 8192;
44308
44906
  function createEvent(input2) {
44309
44907
  return {
44310
44908
  id: input2.id ?? randomUUID22(),
@@ -44333,7 +44931,9 @@ class EventsClient {
44333
44931
  this.transportOptions = {
44334
44932
  fetchImpl: options.fetchImpl,
44335
44933
  secretResolver: options.secretResolver,
44336
- now: options.now
44934
+ now: options.now,
44935
+ tls: options.tls,
44936
+ webhookTargetPolicy: options.webhookTargetPolicy
44337
44937
  };
44338
44938
  this.catalog = options.catalog ?? defaultEventTypeCatalog;
44339
44939
  this.validateCatalogTypes = options.validateCatalogTypes ?? false;
@@ -44620,6 +45220,13 @@ function parseMatcherExpression(value, label) {
44620
45220
  negated: false
44621
45221
  };
44622
45222
  }
45223
+ function webhookTargetPolicyFromEnv() {
45224
+ const value = process.env.HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS;
45225
+ if (!value)
45226
+ return;
45227
+ const hosts = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
45228
+ return hosts.length > 0 ? { allowPrivateHosts: hosts } : undefined;
45229
+ }
44623
45230
  var DEFAULT_EVENT_LIST_LIMIT = 100;
44624
45231
  function parseJsonObject(value, fallback) {
44625
45232
  if (!value)
@@ -44645,7 +45252,7 @@ function parseHeaders(values) {
44645
45252
  function createClient(options) {
44646
45253
  if (options.createClient)
44647
45254
  return options.createClient();
44648
- return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
45255
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir), webhookTargetPolicy: webhookTargetPolicyFromEnv() });
44649
45256
  }
44650
45257
  function print(value, json2, text3) {
44651
45258
  if (json2)
@@ -44726,6 +45333,8 @@ function registerChannelCommands(program2, options) {
44726
45333
  metadata: parseJsonObject(actionOptions.metadata, {})
44727
45334
  }, { honorFilters: actionOptions.honorFilters });
44728
45335
  print(result, json2, `${result.status}: ${result.channelId}`);
45336
+ if (result.status === "failed")
45337
+ process.exitCode = 1;
44729
45338
  } catch (error61) {
44730
45339
  fail(error61, json2);
44731
45340
  }
@@ -44818,7 +45427,7 @@ function replaySummary(events2, deliveries, nextCursor) {
44818
45427
  // src/cli/index.tsx
44819
45428
  import { render } from "ink";
44820
45429
 
44821
- // ../../../../validation/build/apps/hooks/node_modules/commander/esm.mjs
45430
+ // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/esm.mjs
44822
45431
  var import__ = __toESM(require_commander(), 1);
44823
45432
  var {
44824
45433
  program: program2,
@@ -44837,18 +45446,18 @@ var {
44837
45446
  // src/cli/index.tsx
44838
45447
  import chalk2 from "chalk";
44839
45448
  import { existsSync as existsSync18, readFileSync as readFileSync27, rmSync as rmSync6 } from "fs";
44840
- import { join as join35, dirname as dirname28 } from "path";
45449
+ import { join as join36, dirname as dirname28 } from "path";
44841
45450
  import { fileURLToPath as fileURLToPath6 } from "url";
44842
45451
 
44843
45452
  // src/cli/components/App.tsx
44844
45453
  import { useState as useState7 } from "react";
44845
45454
  import { Box as Box8, Text as Text10, useApp, useInput as useInput5 } from "ink";
44846
45455
 
44847
- // ../../../../validation/build/apps/hooks/node_modules/ink-select-input/build/Indicator.js
45456
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
44848
45457
  import React from "react";
44849
45458
  import { Box, Text } from "ink";
44850
45459
 
44851
- // ../../../../validation/build/apps/hooks/node_modules/is-unicode-supported/index.js
45460
+ // ../../node_modules/.bun/is-unicode-supported@2.1.0/node_modules/is-unicode-supported/index.js
44852
45461
  import process3 from "process";
44853
45462
  function isUnicodeSupported() {
44854
45463
  const { env } = process3;
@@ -44859,7 +45468,7 @@ function isUnicodeSupported() {
44859
45468
  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
45469
  }
44861
45470
 
44862
- // ../../../../validation/build/apps/hooks/node_modules/figures/index.js
45471
+ // ../../node_modules/.bun/figures@6.1.0/node_modules/figures/index.js
44863
45472
  var common2 = {
44864
45473
  circleQuestionMark: "(?)",
44865
45474
  questionMarkPrefix: "(?)",
@@ -45135,23 +45744,23 @@ var figures = shouldUseMain ? mainSymbols : fallbackSymbols;
45135
45744
  var figures_default = figures;
45136
45745
  var replacements = Object.entries(specialMainSymbols);
45137
45746
 
45138
- // ../../../../validation/build/apps/hooks/node_modules/ink-select-input/build/Indicator.js
45747
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
45139
45748
  function Indicator({ isSelected = false }) {
45140
45749
  return React.createElement(Box, { marginRight: 1 }, isSelected ? React.createElement(Text, { color: "blue" }, figures_default.pointer) : React.createElement(Text, null, " "));
45141
45750
  }
45142
45751
  var Indicator_default = Indicator;
45143
- // ../../../../validation/build/apps/hooks/node_modules/ink-select-input/build/Item.js
45752
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Item.js
45144
45753
  import * as React2 from "react";
45145
45754
  import { Text as Text2 } from "ink";
45146
45755
  function Item({ isSelected = false, label }) {
45147
45756
  return React2.createElement(Text2, { color: isSelected ? "blue" : undefined }, label);
45148
45757
  }
45149
45758
  var Item_default = Item;
45150
- // ../../../../validation/build/apps/hooks/node_modules/ink-select-input/build/SelectInput.js
45759
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
45151
45760
  import { isDeepStrictEqual } from "util";
45152
45761
  import React3, { useState, useEffect, useRef, useCallback } from "react";
45153
45762
 
45154
- // ../../../../validation/build/apps/hooks/node_modules/to-rotated/index.js
45763
+ // ../../node_modules/.bun/to-rotated@1.0.0/node_modules/to-rotated/index.js
45155
45764
  function toRotated(array3, steps) {
45156
45765
  if (!Array.isArray(array3)) {
45157
45766
  throw new TypeError(`Expected an array, got \`${typeof array3}\`.`);
@@ -45173,7 +45782,7 @@ function toRotated(array3, steps) {
45173
45782
  ];
45174
45783
  }
45175
45784
 
45176
- // ../../../../validation/build/apps/hooks/node_modules/ink-select-input/build/SelectInput.js
45785
+ // ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
45177
45786
  import { Box as Box2, useInput } from "ink";
45178
45787
  function SelectInput({ items = [], isFocused = true, initialIndex = 0, indicatorComponent = Indicator_default, itemComponent = Item_default, limit: customLimit, onSelect, onHighlight }) {
45179
45788
  const hasLimit = typeof customLimit === "number" && items.length > customLimit;
@@ -45469,7 +46078,7 @@ function HookSelect({
45469
46078
  import { useState as useState4, useEffect as useEffect3 } from "react";
45470
46079
  import { Box as Box6, Text as Text7, useInput as useInput4 } from "ink";
45471
46080
 
45472
- // ../../../../validation/build/apps/hooks/node_modules/ink-text-input/build/index.js
46081
+ // ../../node_modules/.bun/ink-text-input@6.0.0+fa875fef9f949ebd/node_modules/ink-text-input/build/index.js
45473
46082
  import React5, { useState as useState3, useEffect as useEffect2 } from "react";
45474
46083
  import { Text as Text6, useInput as useInput3 } from "ink";
45475
46084
  import chalk from "chalk";
@@ -45647,7 +46256,7 @@ function SearchView({
45647
46256
  import { useState as useState6, useEffect as useEffect5 } from "react";
45648
46257
  import { Box as Box7, Text as Text9 } from "ink";
45649
46258
 
45650
- // ../../../../validation/build/apps/hooks/node_modules/ink-spinner/build/index.js
46259
+ // ../../node_modules/.bun/ink-spinner@5.0.0+fa875fef9f949ebd/node_modules/ink-spinner/build/index.js
45651
46260
  var import_cli_spinners = __toESM(require_cli_spinners(), 1);
45652
46261
  import React7, { useState as useState5, useEffect as useEffect4 } from "react";
45653
46262
  import { Text as Text8 } from "ink";
@@ -46014,10 +46623,10 @@ init_native_safety();
46014
46623
 
46015
46624
  // src/lib/sumi-binding.ts
46016
46625
  import { homedir as homedir14 } from "os";
46017
- import { join as join24, sep as sep3 } from "path";
46626
+ import { join as join27, sep as sep3 } from "path";
46018
46627
 
46019
46628
  // hooks/hook-trash-guard/src/binary.ts
46020
- import { dirname as dirname21, isAbsolute as isAbsolute20, join as join22, resolve as resolve20 } from "path";
46629
+ import { dirname as dirname21, isAbsolute as isAbsolute20, join as join24, resolve as resolve21 } from "path";
46021
46630
  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
46631
  import { spawnSync } from "child_process";
46023
46632
  function trustedOwner(uid2) {
@@ -46039,7 +46648,7 @@ function safeExecutableFile(path, systemBinAlias = false) {
46039
46648
  parent = dirname21(parent);
46040
46649
  continue;
46041
46650
  }
46042
- const next = join22(parent, part), info2 = lstatSync23(next);
46651
+ const next = join24(parent, part), info2 = lstatSync23(next);
46043
46652
  if (systemBinAlias && next === "/bin" && info2.isSymbolicLink() && info2.uid === 0) {
46044
46653
  const target = realpathSync11(next);
46045
46654
  if (!safeBinaryDirectory(target))
@@ -46051,7 +46660,7 @@ function safeExecutableFile(path, systemBinAlias = false) {
46051
46660
  return false;
46052
46661
  parent = next;
46053
46662
  }
46054
- const candidate = join22(parent, name), info = lstatSync23(candidate);
46663
+ const candidate = join24(parent, name), info = lstatSync23(candidate);
46055
46664
  if (!trustedOwner(info.uid))
46056
46665
  return false;
46057
46666
  if (info.isSymbolicLink()) {
@@ -46064,9 +46673,9 @@ function safeExecutableFile(path, systemBinAlias = false) {
46064
46673
  return false;
46065
46674
  }
46066
46675
  function safeInterpreterSearchDirectory(path) {
46067
- if (!isAbsolute20(path) || resolve20(path) !== (path.replace(/\/+$/, "") || "/"))
46676
+ if (!isAbsolute20(path) || resolve21(path) !== (path.replace(/\/+$/, "") || "/"))
46068
46677
  return false;
46069
- for (let cursor = resolve20(path);; cursor = dirname21(cursor)) {
46678
+ for (let cursor = resolve21(path);; cursor = dirname21(cursor)) {
46070
46679
  try {
46071
46680
  lstatSync23(cursor);
46072
46681
  return safeBinaryDirectory(cursor);
@@ -46098,7 +46707,7 @@ function safeScriptInterpreter(executable2, pathValue) {
46098
46707
  for (const dir of pathValue.split(":").slice(0, 64)) {
46099
46708
  if (!safeInterpreterSearchDirectory(dir))
46100
46709
  return false;
46101
- const interpreter = resolve20(dir, "bun");
46710
+ const interpreter = resolve21(dir, "bun");
46102
46711
  try {
46103
46712
  if (!statSync(interpreter).isFile())
46104
46713
  continue;
@@ -46133,7 +46742,7 @@ function inspectTrashBinary(env = process.env) {
46133
46742
  for (const dir of pathValue.split(":").slice(0, 64)) {
46134
46743
  if (!isAbsolute20(dir) || Date.now() >= deadline)
46135
46744
  continue;
46136
- const candidate = resolve20(dir, "trash");
46745
+ const candidate = resolve21(dir, "trash");
46137
46746
  try {
46138
46747
  const stat2 = statSync(candidate);
46139
46748
  if (!stat2.isFile() || !trustedOwner(stat2.uid) || (stat2.mode & 73) === 0 || (stat2.mode & 18) !== 0) {
@@ -46141,7 +46750,7 @@ function inspectTrashBinary(env = process.env) {
46141
46750
  continue;
46142
46751
  }
46143
46752
  const executable2 = realpathSync11(candidate);
46144
- if (!safeBinaryDirectory(resolve20(dir)) || !safeBinaryDirectory(dirname21(executable2))) {
46753
+ if (!safeBinaryDirectory(resolve21(dir)) || !safeBinaryDirectory(dirname21(executable2))) {
46145
46754
  reject("unsafe_directory");
46146
46755
  continue;
46147
46756
  }
@@ -46149,15 +46758,15 @@ function inspectTrashBinary(env = process.env) {
46149
46758
  reject("unsafe_directory");
46150
46759
  continue;
46151
46760
  }
46152
- const packageRoot = resolve20(dirname21(executable2), "../..");
46153
- const manifestPath = join22(packageRoot, "package.json");
46761
+ const packageRoot = resolve21(dirname21(executable2), "../..");
46762
+ const manifestPath = join24(packageRoot, "package.json");
46154
46763
  const manifestStat = statSync(manifestPath);
46155
46764
  if (!manifestStat.isFile() || !trustedOwner(manifestStat.uid) || manifestStat.size > 16384 || (manifestStat.mode & 18) !== 0 || lstatSync23(manifestPath).isSymbolicLink()) {
46156
46765
  reject("unsafe_manifest");
46157
46766
  continue;
46158
46767
  }
46159
46768
  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) {
46769
+ if (manifest.name !== "@hasna/trash" || typeof manifest.version !== "string" || typeof manifest.bin?.trash !== "string" || realpathSync11(resolve21(packageRoot, manifest.bin.trash)) !== executable2) {
46161
46770
  reject("unrecognized_package");
46162
46771
  continue;
46163
46772
  }
@@ -46202,7 +46811,7 @@ function failure(code2, reason) {
46202
46811
  return { ok: false, target: "sumi", code: code2, ...reason ? { reason: reason.slice(0, 300) } : {} };
46203
46812
  }
46204
46813
  function nativeSafetyBindingDurable(worker, home) {
46205
- return !(worker.startsWith(join24(home, ".bun") + sep3) || worker.includes(`${sep3}node_modules${sep3}.bun${sep3}`));
46814
+ return !(worker.startsWith(join27(home, ".bun") + sep3) || worker.includes(`${sep3}node_modules${sep3}.bun${sep3}`));
46206
46815
  }
46207
46816
  async function provisionSumiSafetyBinding(options = {}) {
46208
46817
  const home = options.home ?? homedir14();
@@ -46277,7 +46886,7 @@ init_transport2();
46277
46886
  init_db();
46278
46887
  import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
46279
46888
  var __dirname4 = dirname28(fileURLToPath6(import.meta.url));
46280
- var pkgPath = existsSync18(join35(__dirname4, "..", "package.json")) ? join35(__dirname4, "..", "package.json") : join35(__dirname4, "..", "..", "package.json");
46889
+ var pkgPath = existsSync18(join36(__dirname4, "..", "package.json")) ? join36(__dirname4, "..", "package.json") : join36(__dirname4, "..", "..", "package.json");
46281
46890
  var pkg2 = JSON.parse(readFileSync27(pkgPath, "utf-8"));
46282
46891
  var program3 = new Command;
46283
46892
  function resolveScope(options) {
@@ -46510,7 +47119,7 @@ program3.command("init").description("Register a new agent profile with a unique
46510
47119
  if (profile.name) {
46511
47120
  console.log(` ${chalk2.dim("Name:")} ${profile.name}`);
46512
47121
  }
46513
- console.log(` ${chalk2.dim("Profile:")} ${join35(getProfilesDir(), `${profile.agent_id}.json`)}`);
47122
+ console.log(` ${chalk2.dim("Profile:")} ${join36(getProfilesDir(), `${profile.agent_id}.json`)}`);
46514
47123
  console.log();
46515
47124
  console.log(chalk2.dim(" Install hooks with this profile:"));
46516
47125
  console.log(` hooks install gitguard --profile ${profile.agent_id}`);
@@ -47165,7 +47774,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
47165
47774
  continue;
47166
47775
  }
47167
47776
  const hookDir = getHookPath(name);
47168
- const hookScript = join35(hookDir, "src", "hook.ts");
47777
+ const hookScript = join36(hookDir, "src", "hook.ts");
47169
47778
  if (!existsSync18(hookScript)) {
47170
47779
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
47171
47780
  hookHealthy = false;
@@ -47174,7 +47783,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
47174
47783
  if (meta3 && settingsExist) {
47175
47784
  try {
47176
47785
  const settings = JSON.parse(readFileSync27(settingsPath, "utf-8"));
47177
- if (!hookRegisteredInSettings(settings, name, meta3.event, meta3.matcher)) {
47786
+ if (!hookRegisteredInSettings(settings, name, meta3.event, matcherFor(meta3, "claude"))) {
47178
47787
  const eventName = meta3.event.split(":")[0];
47179
47788
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
47180
47789
  hookHealthy = false;
@@ -47376,7 +47985,7 @@ program3.command("docs").argument("[hook]", "Hook name (shows general docs if om
47376
47985
  return;
47377
47986
  }
47378
47987
  const hookPath = getHookPath(hook);
47379
- const readmePath = join35(hookPath, "README.md");
47988
+ const readmePath = join36(hookPath, "README.md");
47380
47989
  let readme = "";
47381
47990
  if (existsSync18(readmePath)) {
47382
47991
  readme = readFileSync27(readmePath, "utf-8");
@@ -48042,13 +48651,13 @@ startup.command("check").requiredOption("--target <target>", "codex, claude or s
48042
48651
  process.exitCode = 1;
48043
48652
  });
48044
48653
  for (const action of ["registration-plan", "registration-apply"]) {
48045
- const command = startup.command(action).requiredOption("--target <target>", "codex or claude");
48654
+ const command = startup.command(action).requiredOption("--target <target>", "codex, claude or sumi (Sumi: its native guard record)");
48046
48655
  if (action === "registration-apply")
48047
48656
  command.requiredOption("--expected-plan-digest <sha256>", "Exact registration-plan digest");
48048
48657
  command.action(async (options) => {
48049
48658
  try {
48050
48659
  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")
48660
+ if (options.target !== "codex" && options.target !== "claude" && options.target !== "sumi")
48052
48661
  throw new NativeRegistrationError("input_invalid");
48053
48662
  console.log(JSON.stringify(action === "registration-plan" ? await planNativeReadinessRegistration2({ target: options.target }) : await applyNativeReadinessRegistration2({ target: options.target, expectedPlanDigest: options.expectedPlanDigest })));
48054
48663
  } catch (error61) {
@@ -48174,7 +48783,7 @@ safety.command("verify").requiredOption("--target <target>", "sumi, codex or cla
48174
48783
  throw new ClaudeSafetyError("settings_invalid");
48175
48784
  console.log(JSON.stringify(await verifyClaudeSafetyConfiguration({
48176
48785
  cwd: options.cwd,
48177
- ...process.env.CLAUDE_CONFIG_DIR ? { userSettingsPath: resolve21(process.env.CLAUDE_CONFIG_DIR, "settings.json") } : {}
48786
+ ...process.env.CLAUDE_CONFIG_DIR ? { userSettingsPath: resolve23(process.env.CLAUDE_CONFIG_DIR, "settings.json") } : {}
48178
48787
  })));
48179
48788
  } catch (error61) {
48180
48789
  console.log(JSON.stringify({ ok: false, target: "claude", code: error61 instanceof ClaudeSafetyError ? error61.code : "settings_invalid", nativeAdoptionVerified: false }));