@hasna/hooks 0.10.3 → 0.10.5

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
@@ -4519,6 +4519,9 @@ function buildNativeSafetyCommand(binding) {
4519
4519
  return prefix + [encoded, binding.runtime, binding.home, binding.path].map(quote).join(" ");
4520
4520
  }
4521
4521
  function nativeSafetyRegistration(command) {
4522
+ return nativeSafetyCommandBinding(command)?.name;
4523
+ }
4524
+ function nativeSafetyCommandBinding(command) {
4522
4525
  if (!command.startsWith(prefix))
4523
4526
  return;
4524
4527
  const encoded = command.slice(prefix.length).match(/^'([A-Za-z0-9_-]+)' /)?.[1];
@@ -4526,7 +4529,7 @@ function nativeSafetyRegistration(command) {
4526
4529
  return;
4527
4530
  try {
4528
4531
  const binding = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
4529
- return buildNativeSafetyCommand(binding) === command ? binding.name : undefined;
4532
+ return buildNativeSafetyCommand(binding) === command ? binding : undefined;
4530
4533
  } catch {
4531
4534
  return;
4532
4535
  }
@@ -37385,25 +37388,25 @@ var init_redact = __esm(() => {
37385
37388
  });
37386
37389
 
37387
37390
  // src/lib/profiles.ts
37388
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync8, readdirSync as readdirSync8, rmSync, cpSync as cpSync2 } from "fs";
37389
- import { join as join13 } from "path";
37391
+ import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync10, readdirSync as readdirSync8, rmSync, cpSync as cpSync2 } from "fs";
37392
+ import { join as join19 } from "path";
37390
37393
  function getProfilesDir() {
37391
- const newDir = join13(getEffectiveDataRoot(), "profiles");
37392
- const oldDir = join13(getHomeDir(), ".hooks", "profiles");
37393
- if (!existsSync14(newDir) && existsSync14(oldDir)) {
37394
- mkdirSync9(newDir, { recursive: true });
37394
+ const newDir = join19(getEffectiveDataRoot(), "profiles");
37395
+ const oldDir = join19(getHomeDir(), ".hooks", "profiles");
37396
+ if (!existsSync15(newDir) && existsSync15(oldDir)) {
37397
+ mkdirSync11(newDir, { recursive: true });
37395
37398
  cpSync2(oldDir, newDir, { recursive: true });
37396
37399
  }
37397
37400
  return newDir;
37398
37401
  }
37399
37402
  function ensureProfilesDir() {
37400
37403
  const dir = getProfilesDir();
37401
- if (!existsSync14(dir)) {
37402
- mkdirSync9(dir, { recursive: true });
37404
+ if (!existsSync15(dir)) {
37405
+ mkdirSync11(dir, { recursive: true });
37403
37406
  }
37404
37407
  }
37405
37408
  function profilePath(id) {
37406
- return join13(getProfilesDir(), `${id}.json`);
37409
+ return join19(getProfilesDir(), `${id}.json`);
37407
37410
  }
37408
37411
  function shortUuid() {
37409
37412
  return crypto.randomUUID().slice(0, 8);
@@ -37422,30 +37425,30 @@ function createProfile(input2) {
37422
37425
  if (input2.name) {
37423
37426
  profile.name = input2.name;
37424
37427
  }
37425
- writeFileSync8(profilePath(id), JSON.stringify(profile, null, 2) + `
37428
+ writeFileSync10(profilePath(id), JSON.stringify(profile, null, 2) + `
37426
37429
  `);
37427
37430
  return profile;
37428
37431
  }
37429
37432
  function getProfile(id) {
37430
37433
  const path = profilePath(id);
37431
37434
  try {
37432
- if (!existsSync14(path))
37435
+ if (!existsSync15(path))
37433
37436
  return null;
37434
- return JSON.parse(readFileSync9(path, "utf-8"));
37437
+ return JSON.parse(readFileSync14(path, "utf-8"));
37435
37438
  } catch {
37436
37439
  return null;
37437
37440
  }
37438
37441
  }
37439
37442
  function listProfiles() {
37440
37443
  const dir = getProfilesDir();
37441
- if (!existsSync14(dir))
37444
+ if (!existsSync15(dir))
37442
37445
  return [];
37443
37446
  try {
37444
37447
  const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
37445
37448
  const profiles = [];
37446
37449
  for (const file2 of files) {
37447
37450
  try {
37448
- const content = readFileSync9(join13(dir, file2), "utf-8");
37451
+ const content = readFileSync14(join19(dir, file2), "utf-8");
37449
37452
  profiles.push(JSON.parse(content));
37450
37453
  } catch {}
37451
37454
  }
@@ -37459,7 +37462,7 @@ function touchProfile(id) {
37459
37462
  if (!profile)
37460
37463
  return;
37461
37464
  profile.last_seen_at = new Date().toISOString();
37462
- writeFileSync8(profilePath(id), JSON.stringify(profile, null, 2) + `
37465
+ writeFileSync10(profilePath(id), JSON.stringify(profile, null, 2) + `
37463
37466
  `);
37464
37467
  }
37465
37468
  function exportProfiles() {
@@ -37475,11 +37478,11 @@ function importProfiles(profiles) {
37475
37478
  continue;
37476
37479
  }
37477
37480
  const path = profilePath(profile.agent_id);
37478
- if (existsSync14(path)) {
37481
+ if (existsSync15(path)) {
37479
37482
  skipped++;
37480
37483
  continue;
37481
37484
  }
37482
- writeFileSync8(path, JSON.stringify(profile, null, 2) + `
37485
+ writeFileSync10(path, JSON.stringify(profile, null, 2) + `
37483
37486
  `);
37484
37487
  imported++;
37485
37488
  }
@@ -37491,16 +37494,16 @@ var init_profiles = __esm(() => {
37491
37494
 
37492
37495
  // ../../node_modules/.bun/@hasna+contracts@1.2.2+944b86d8024e79fa/node_modules/@hasna/contracts/dist/client/transport.js
37493
37496
  import { isIP as isIP2 } from "net";
37494
- import { spawnSync } from "child_process";
37497
+ import { spawnSync as spawnSync2 } from "child_process";
37495
37498
  import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
37496
37499
  import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
37497
37500
  import { hostname as osHostname } from "os";
37498
- import { isAbsolute as isAbsolute7, join as join22 } from "path";
37501
+ import { isAbsolute as isAbsolute17, join as join22 } from "path";
37499
37502
  import { createRequire as createRequire3 } from "module";
37500
37503
  import { fileURLToPath as fileURLToPath22 } from "url";
37501
- import { closeSync as closeSync4, fstatSync as fstatSync3, lstatSync as lstatSync7, openSync as openSync4, readFileSync as readFileSync12, realpathSync as realpathSync7, statSync } from "fs";
37504
+ import { closeSync as closeSync6, fstatSync as fstatSync5, lstatSync as lstatSync10, openSync as openSync6, readFileSync as readFileSync15, realpathSync as realpathSync9, statSync as statSync2 } from "fs";
37502
37505
  import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
37503
- import { basename as basename5, dirname as dirname11, join as join17, relative, sep as sep3 } from "path";
37506
+ import { basename as basename5, dirname as dirname19, join as join20, relative, sep as sep3 } from "path";
37504
37507
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
37505
37508
  function envToken2(name) {
37506
37509
  return name.toUpperCase().replace(/-/g, "_");
@@ -37574,16 +37577,16 @@ function o(e2, r2, t2) {
37574
37577
  }
37575
37578
  }
37576
37579
  function entryPoint(directory2) {
37577
- const root = realpathSync7(directory2);
37578
- const fd = openSync4(join17(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
37580
+ const root = realpathSync9(directory2);
37581
+ const fd = openSync6(join20(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
37579
37582
  let pkg;
37580
37583
  try {
37581
- const stat2 = fstatSync3(fd);
37584
+ const stat2 = fstatSync5(fd);
37582
37585
  if (!stat2.isFile() || stat2.size > MAX_PACKAGE_BYTES3)
37583
37586
  throw new Error("Invalid Secrets package metadata");
37584
- pkg = JSON.parse(readFileSync12(fd, "utf8"));
37587
+ pkg = JSON.parse(readFileSync15(fd, "utf8"));
37585
37588
  } finally {
37586
- closeSync4(fd);
37589
+ closeSync6(fd);
37587
37590
  }
37588
37591
  if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== PACKAGE) {
37589
37592
  throw new Error("Invalid Secrets package identity");
@@ -37595,21 +37598,21 @@ function entryPoint(directory2) {
37595
37598
  if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
37596
37599
  throw new Error("Invalid Secrets import export");
37597
37600
  }
37598
- const file2 = realpathSync7(join17(root, ...parts));
37601
+ const file2 = realpathSync9(join20(root, ...parts));
37599
37602
  const within = relative(root, file2);
37600
- if (!within || within === ".." || within.startsWith(`..${sep3}`) || !statSync(file2).isFile()) {
37603
+ if (!within || within === ".." || within.startsWith(`..${sep3}`) || !statSync2(file2).isFile()) {
37601
37604
  throw new Error("Secrets import export escapes its package or is not a file");
37602
37605
  }
37603
37606
  return pathToFileURL(file2).href;
37604
37607
  }
37605
37608
  function resolveInstalledSecrets(parentUrl) {
37606
- let directory2 = dirname11(fileURLToPath3(parentUrl));
37609
+ let directory2 = dirname19(fileURLToPath3(parentUrl));
37607
37610
  for (;; ) {
37608
37611
  if (basename5(directory2) !== "node_modules") {
37609
- const candidate = join17(directory2, "node_modules", PACKAGE);
37612
+ const candidate = join20(directory2, "node_modules", PACKAGE);
37610
37613
  let absent = false;
37611
37614
  try {
37612
- lstatSync7(candidate);
37615
+ lstatSync10(candidate);
37613
37616
  } catch (error61) {
37614
37617
  if (error61.code !== "ENOENT")
37615
37618
  throw error61;
@@ -37618,7 +37621,7 @@ function resolveInstalledSecrets(parentUrl) {
37618
37621
  if (!absent)
37619
37622
  return entryPoint(candidate);
37620
37623
  }
37621
- const parent = dirname11(directory2);
37624
+ const parent = dirname19(directory2);
37622
37625
  if (parent === directory2)
37623
37626
  throw new Error("Secrets SDK is not installed for this consumer");
37624
37627
  directory2 = parent;
@@ -37630,7 +37633,7 @@ function homeDir(env) {
37630
37633
  }
37631
37634
  function absoluteOverride(env, key) {
37632
37635
  const value = env[key]?.trim();
37633
- return value && isAbsolute7(value) ? value : null;
37636
+ return value && isAbsolute17(value) ? value : null;
37634
37637
  }
37635
37638
  function hasnaHomeDir(env) {
37636
37639
  const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
@@ -37858,7 +37861,7 @@ function isAmbientEnvironment(env) {
37858
37861
  return env === process.env || env[AMBIENT_ENVIRONMENT3] === true;
37859
37862
  }
37860
37863
  function defaultKeychainRunner(argv) {
37861
- const result = spawnSync(KEYCHAIN_SECURITY_BIN, [...argv], {
37864
+ const result = spawnSync2(KEYCHAIN_SECURITY_BIN, [...argv], {
37862
37865
  encoding: "utf8",
37863
37866
  stdio: ["ignore", "pipe", "pipe"],
37864
37867
  timeout: KEYCHAIN_SPAWN_TIMEOUT_MS
@@ -38737,8 +38740,8 @@ var init_db_writer = __esm(() => {
38737
38740
  });
38738
38741
 
38739
38742
  // src/lib/hook-env.ts
38740
- import { dirname as dirname17, isAbsolute as isAbsolute15 } from "path";
38741
- import { existsSync as existsSync15, realpathSync as realpathSync8, statSync as statSync2 } from "fs";
38743
+ import { dirname as dirname20, isAbsolute as isAbsolute18 } from "path";
38744
+ import { existsSync as existsSync16, realpathSync as realpathSync10, statSync as statSync4 } from "fs";
38742
38745
  import { tmpdir } from "os";
38743
38746
  function isDeniedEnvName(name) {
38744
38747
  const upper = name.toUpperCase();
@@ -38757,7 +38760,7 @@ function isBashFunctionEnvName(name) {
38757
38760
  return name.startsWith("BASH_FUNC_");
38758
38761
  }
38759
38762
  function isUnsafePathEntry(entry, home) {
38760
- if (entry === "" || !isAbsolute15(entry))
38763
+ if (entry === "" || !isAbsolute18(entry))
38761
38764
  return true;
38762
38765
  if (home && (entry === home || entry.startsWith(home + "/")))
38763
38766
  return true;
@@ -38767,7 +38770,7 @@ function isUnsafePathEntry(entry, home) {
38767
38770
  return true;
38768
38771
  const canonical4 = (path) => {
38769
38772
  try {
38770
- return realpathSync8(path);
38773
+ return realpathSync10(path);
38771
38774
  } catch {
38772
38775
  return path;
38773
38776
  }
@@ -38779,16 +38782,16 @@ function isUnsafePathEntry(entry, home) {
38779
38782
  return true;
38780
38783
  }
38781
38784
  try {
38782
- if ((statSync2(entry).mode & 2) !== 0)
38785
+ if ((statSync4(entry).mode & 2) !== 0)
38783
38786
  return true;
38784
38787
  } catch {}
38785
38788
  return false;
38786
38789
  }
38787
38790
  function sanitizeHookPath(pathValue, home) {
38788
38791
  const baseline = PATH_BASELINE.slice();
38789
- if (process.platform === "darwin" && existsSync15("/opt/homebrew/bin"))
38792
+ if (process.platform === "darwin" && existsSync16("/opt/homebrew/bin"))
38790
38793
  baseline.push("/opt/homebrew/bin");
38791
- const bunDir = dirname17(process.execPath);
38794
+ const bunDir = dirname20(process.execPath);
38792
38795
  const seen = new Set;
38793
38796
  const out = [];
38794
38797
  for (const dir of baseline) {
@@ -38912,8 +38915,8 @@ __export(exports_run, {
38912
38915
  HookTimeoutError: () => HookTimeoutError
38913
38916
  });
38914
38917
  import { randomBytes as randomBytes2 } from "crypto";
38915
- import { rmSync as rmSync2, writeFileSync as writeFileSync10 } from "fs";
38916
- import { basename as basename9, dirname as dirname19, isAbsolute as isAbsolute17, join as join19 } from "path";
38918
+ import { rmSync as rmSync2, writeFileSync as writeFileSync11 } from "fs";
38919
+ import { basename as basename9, dirname as dirname21, isAbsolute as isAbsolute19, join as join24 } from "path";
38917
38920
  function extensionOf(scriptPath) {
38918
38921
  const base = basename9(scriptPath);
38919
38922
  const dot = base.lastIndexOf(".");
@@ -38982,9 +38985,9 @@ async function readPipeWithDeadline(stream, deadlineMs) {
38982
38985
  return chunks.join("");
38983
38986
  }
38984
38987
  async function executeVerifiedScript(options) {
38985
- const scriptDir = dirname19(options.scriptPath);
38988
+ const scriptDir = dirname21(options.scriptPath);
38986
38989
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
38987
- const tempPath = join19(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
38990
+ const tempPath = join24(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
38988
38991
  let proc = null;
38989
38992
  let timedOut = false;
38990
38993
  const timeoutTimer = options.timeout ? setTimeout(() => {
@@ -38992,8 +38995,8 @@ async function executeVerifiedScript(options) {
38992
38995
  killGroup(proc, { leaderAlive: proc !== null && proc.exitCode === null });
38993
38996
  }, options.timeout) : null;
38994
38997
  try {
38995
- writeFileSync10(tempPath, options.content, { flag: "wx", mode: 384 });
38996
- proc = Bun.spawn([...interpreter.command, isAbsolute17(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
38998
+ writeFileSync11(tempPath, options.content, { flag: "wx", mode: 384 });
38999
+ proc = Bun.spawn([...interpreter.command, isAbsolute19(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
38997
39000
  stdin: new Response(options.stdin),
38998
39001
  stdout: "pipe",
38999
39002
  stderr: "pipe",
@@ -39075,15 +39078,15 @@ __export(exports_custom_install, {
39075
39078
  installCustomSource: () => installCustomSource,
39076
39079
  ensureCustomDir: () => ensureCustomDir
39077
39080
  });
39078
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, mkdtempSync, readFileSync as readFileSync14, rmSync as rmSync4 } from "fs";
39079
- import { join as join20 } from "path";
39081
+ import { existsSync as existsSync17, mkdirSync as mkdirSync12, mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync4 } from "fs";
39082
+ import { join as join27 } from "path";
39080
39083
  import { tmpdir as tmpdir2 } from "os";
39081
39084
  function manifestAt(dir) {
39082
- const path = join20(dir, "manifest.json");
39083
- if (!existsSync16(path))
39085
+ const path = join27(dir, "manifest.json");
39086
+ if (!existsSync17(path))
39084
39087
  return;
39085
39088
  try {
39086
- return parseManifest(readFileSync14(path, "utf-8"));
39089
+ return parseManifest(readFileSync17(path, "utf-8"));
39087
39090
  } catch {
39088
39091
  return;
39089
39092
  }
@@ -39102,8 +39105,8 @@ function writeFromManifest(manifest, srcDir, source, kind) {
39102
39105
  return { name, version: manifest.version, kind, source, dir, scriptPath };
39103
39106
  }
39104
39107
  async function cloneToTemp(gitUrl) {
39105
- const tmp = mkdtempSync(join20(tmpdir2(), "hooks-install-"));
39106
- const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join20(tmp, "repo")], {
39108
+ const tmp = mkdtempSync(join27(tmpdir2(), "hooks-install-"));
39109
+ const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join27(tmp, "repo")], {
39107
39110
  stdout: "pipe",
39108
39111
  stderr: "pipe"
39109
39112
  });
@@ -39116,7 +39119,7 @@ async function cloneToTemp(gitUrl) {
39116
39119
  rmSync4(tmp, { recursive: true, force: true });
39117
39120
  throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
39118
39121
  }
39119
- return join20(tmp, "repo");
39122
+ return join27(tmp, "repo");
39120
39123
  }
39121
39124
  function isGitUrl(value) {
39122
39125
  return value.startsWith("git@") || value.startsWith("ssh://") || value.startsWith("file://") || value.endsWith(".git");
@@ -39126,7 +39129,7 @@ function isCustomSource(value) {
39126
39129
  return true;
39127
39130
  if (value.startsWith("http://") || value.startsWith("https://"))
39128
39131
  return true;
39129
- return existsSync16(value);
39132
+ return existsSync17(value);
39130
39133
  }
39131
39134
  async function installCustomSource(source) {
39132
39135
  if (isGitUrl(source)) {
@@ -39140,11 +39143,11 @@ async function installCustomSource(source) {
39140
39143
  if (source.startsWith("http://") || source.startsWith("https://")) {
39141
39144
  return installManifestUrl(source);
39142
39145
  }
39143
- if (!existsSync16(source)) {
39146
+ if (!existsSync17(source)) {
39144
39147
  throw new Error(`No such local path: ${source}`);
39145
39148
  }
39146
39149
  if (source.endsWith("manifest.json")) {
39147
- const manifest = parseManifest(readFileSync14(source, "utf-8"));
39150
+ const manifest = parseManifest(readFileSync17(source, "utf-8"));
39148
39151
  return writeFromManifest(manifest, source.substring(0, source.lastIndexOf("/")), source, "local");
39149
39152
  }
39150
39153
  return copyManifestDir(source, source, "local");
@@ -39175,7 +39178,7 @@ async function installManifestUrl(url2) {
39175
39178
  }
39176
39179
  function ensureCustomDir() {
39177
39180
  const dir = getCustomHooksDir();
39178
- mkdirSync11(dir, { recursive: true });
39181
+ mkdirSync12(dir, { recursive: true });
39179
39182
  return dir;
39180
39183
  }
39181
39184
  var init_custom_install = __esm(() => {
@@ -39193,13 +39196,13 @@ __export(exports_sync, {
39193
39196
  fetchPinnedHook: () => fetchPinnedHook,
39194
39197
  commitSyncArtifacts: () => commitSyncArtifacts
39195
39198
  });
39196
- import { createHash as createHash7 } from "crypto";
39197
- import { readFileSync as readFileSync15 } from "fs";
39199
+ import { createHash as createHash12 } from "crypto";
39200
+ import { readFileSync as readFileSync19 } from "fs";
39198
39201
  function sha256OfText(text3) {
39199
- return createHash7("sha256").update(text3).digest("hex");
39202
+ return createHash12("sha256").update(text3).digest("hex");
39200
39203
  }
39201
39204
  function sha256OfPath(path) {
39202
- return createHash7("sha256").update(readFileSync15(path)).digest("hex");
39205
+ return createHash12("sha256").update(readFileSync19(path)).digest("hex");
39203
39206
  }
39204
39207
  function collectBundledCatalog() {
39205
39208
  const out = [];
@@ -40354,13 +40357,13 @@ __export(exports_serve, {
40354
40357
  SERVE_HOST: () => SERVE_HOST,
40355
40358
  DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
40356
40359
  });
40357
- import { readFileSync as readFileSync17 } from "fs";
40358
- import { dirname as dirname20, join as join24 } from "path";
40360
+ import { readFileSync as readFileSync20 } from "fs";
40361
+ import { dirname as dirname22, join as join29 } from "path";
40359
40362
  import { fileURLToPath as fileURLToPath4 } from "url";
40360
40363
  function packageVersion() {
40361
40364
  try {
40362
- const pkgPath = join24(dirname20(fileURLToPath4(import.meta.url)), "..", "package.json");
40363
- return JSON.parse(readFileSync17(pkgPath, "utf-8")).version || "0.0.0";
40365
+ const pkgPath = join29(dirname22(fileURLToPath4(import.meta.url)), "..", "package.json");
40366
+ return JSON.parse(readFileSync20(pkgPath, "utf-8")).version || "0.0.0";
40364
40367
  } catch {
40365
40368
  return "0.0.0";
40366
40369
  }
@@ -40660,17 +40663,17 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
40660
40663
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
40661
40664
  import { z as z3 } from "zod";
40662
40665
  import { createServer } from "http";
40663
- import { existsSync as existsSync17, readFileSync as readFileSync19 } from "fs";
40664
- import { join as join27, dirname as dirname21 } from "path";
40666
+ import { existsSync as existsSync18, readFileSync as readFileSync21 } from "fs";
40667
+ import { join as join30, dirname as dirname23 } from "path";
40665
40668
  import { fileURLToPath as fileURLToPath5 } from "url";
40666
40669
  async function runVerifiedHook(name, stdin, timeoutMs) {
40667
40670
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
40668
40671
  const resolved = resolveHook2(name);
40669
40672
  const hookScript = resolveScriptPath2(name);
40670
- if (!resolved || !hookScript || !existsSync17(hookScript)) {
40673
+ if (!resolved || !hookScript || !existsSync18(hookScript)) {
40671
40674
  throw new Error(`Hook '${name}' not found`);
40672
40675
  }
40673
- const content = readFileSync19(hookScript);
40676
+ const content = readFileSync21(hookScript);
40674
40677
  const sha4 = sha256Of(content);
40675
40678
  const check2 = checkScriptHash(name, sha4);
40676
40679
  if (!check2.ok) {
@@ -40875,7 +40878,7 @@ function createHooksServer() {
40875
40878
  const settingsPath = getSettingsPath(scope);
40876
40879
  const issues = [];
40877
40880
  const healthy = [];
40878
- const settingsExist = existsSync17(settingsPath);
40881
+ const settingsExist = existsSync18(settingsPath);
40879
40882
  if (!settingsExist) {
40880
40883
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
40881
40884
  }
@@ -40885,7 +40888,7 @@ function createHooksServer() {
40885
40888
  const meta3 = custom2 ? resolveHookMeta(name) : getHook(name);
40886
40889
  let hookHealthy = true;
40887
40890
  if (custom2) {
40888
- if (!existsSync17(custom2.scriptPath)) {
40891
+ if (!existsSync18(custom2.scriptPath)) {
40889
40892
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom2.scriptPath}`, severity: "error" });
40890
40893
  hookHealthy = false;
40891
40894
  }
@@ -40895,14 +40898,14 @@ function createHooksServer() {
40895
40898
  continue;
40896
40899
  }
40897
40900
  const hookDir = getHookPath(name);
40898
- if (!existsSync17(join27(hookDir, "src", "hook.ts"))) {
40901
+ if (!existsSync18(join30(hookDir, "src", "hook.ts"))) {
40899
40902
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
40900
40903
  hookHealthy = false;
40901
40904
  }
40902
40905
  }
40903
40906
  if (meta3 && settingsExist) {
40904
40907
  try {
40905
- const settings = JSON.parse(readFileSync19(settingsPath, "utf-8"));
40908
+ const settings = JSON.parse(readFileSync21(settingsPath, "utf-8"));
40906
40909
  if (!hookRegisteredInSettings(settings, name, meta3.event, meta3.matcher)) {
40907
40910
  const eventName = meta3.event.split(":")[0];
40908
40911
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
@@ -40939,10 +40942,10 @@ function createHooksServer() {
40939
40942
  return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
40940
40943
  }
40941
40944
  const hookPath = getHookPath(name);
40942
- const readmePath = join27(hookPath, "README.md");
40945
+ const readmePath = join30(hookPath, "README.md");
40943
40946
  let readme = "";
40944
- if (existsSync17(readmePath)) {
40945
- readme = readFileSync19(readmePath, "utf-8");
40947
+ if (existsSync18(readmePath)) {
40948
+ readme = readFileSync21(readmePath, "utf-8");
40946
40949
  }
40947
40950
  if (verbose) {
40948
40951
  return { content: [{ type: "text", text: JSON.stringify({ ...meta3, readme }) }] };
@@ -41125,7 +41128,7 @@ function createHooksServer() {
41125
41128
  const ctx = {
41126
41129
  scope,
41127
41130
  settings_path: settingsPath,
41128
- settings_exists: existsSync17(settingsPath),
41131
+ settings_exists: existsSync18(settingsPath),
41129
41132
  registered_hooks: hooks,
41130
41133
  hook_count: hooks.length,
41131
41134
  healthy,
@@ -41271,8 +41274,8 @@ function createHooksServer() {
41271
41274
  const settingsPath = getSettingsPath(scope);
41272
41275
  let settings = {};
41273
41276
  try {
41274
- if (existsSync17(settingsPath))
41275
- settings = JSON.parse(readFileSync19(settingsPath, "utf-8"));
41277
+ if (existsSync18(settingsPath))
41278
+ settings = JSON.parse(readFileSync21(settingsPath, "utf-8"));
41276
41279
  } catch {}
41277
41280
  if (!settings.hooks)
41278
41281
  settings.hooks = {};
@@ -41294,8 +41297,8 @@ function createHooksServer() {
41294
41297
  const settingsPath = getSettingsPath(scope);
41295
41298
  let settings = {};
41296
41299
  try {
41297
- if (existsSync17(settingsPath))
41298
- settings = JSON.parse(readFileSync19(settingsPath, "utf-8"));
41300
+ if (existsSync18(settingsPath))
41301
+ settings = JSON.parse(readFileSync21(settingsPath, "utf-8"));
41299
41302
  } catch {}
41300
41303
  if (settings.hooks?.__disabled) {
41301
41304
  settings.hooks.__disabled = settings.hooks.__disabled.filter((n2) => n2 !== name);
@@ -41665,13 +41668,13 @@ var init_server = __esm(() => {
41665
41668
  init_redact();
41666
41669
  init_authority();
41667
41670
  init_storage();
41668
- __dirname3 = dirname21(fileURLToPath5(import.meta.url));
41671
+ __dirname3 = dirname23(fileURLToPath5(import.meta.url));
41669
41672
  pkg = { name: "@hasna/hooks", version: "0.0.0" };
41670
41673
  try {
41671
41674
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
41672
- const p = join27(__dirname3, rel);
41673
- if (existsSync17(p)) {
41674
- pkg = JSON.parse(readFileSync19(p, "utf-8"));
41675
+ const p = join30(__dirname3, rel);
41676
+ if (existsSync18(p)) {
41677
+ pkg = JSON.parse(readFileSync21(p, "utf-8"));
41675
41678
  break;
41676
41679
  }
41677
41680
  }
@@ -43413,8 +43416,8 @@ var {
43413
43416
 
43414
43417
  // src/cli/index.tsx
43415
43418
  import chalk2 from "chalk";
43416
- import { existsSync as existsSync18, readFileSync as readFileSync20, rmSync as rmSync6 } from "fs";
43417
- import { join as join29, dirname as dirname22 } from "path";
43419
+ import { existsSync as existsSync19, readFileSync as readFileSync23, rmSync as rmSync6 } from "fs";
43420
+ import { join as join31, dirname as dirname24 } from "path";
43418
43421
  import { fileURLToPath as fileURLToPath6 } from "url";
43419
43422
 
43420
43423
  // src/cli/components/App.tsx
@@ -44588,6 +44591,439 @@ function App({ initialHooks, overwrite = false }) {
44588
44591
 
44589
44592
  // src/cli/index.tsx
44590
44593
  init_native_safety();
44594
+
44595
+ // src/native-safety.ts
44596
+ init_native_safety();
44597
+ import { execFile } from "child_process";
44598
+ import { createHash as createHash7, randomUUID as randomUUID5 } from "crypto";
44599
+ import { closeSync as closeSync4, constants as constants3, existsSync as existsSync14, fstatSync as fstatSync3, fsyncSync, linkSync as linkSync3, lstatSync as lstatSync7, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync9, realpathSync as realpathSync7, renameSync as renameSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync8 } from "fs";
44600
+ import { homedir as homedir7 } from "os";
44601
+ import { dirname as dirname11, isAbsolute as isAbsolute7, join as join13, resolve as resolve15 } from "path";
44602
+
44603
+ class NativeSafetyError extends Error {
44604
+ code;
44605
+ reason;
44606
+ constructor(code2, reason) {
44607
+ super(`Native deletion safety refused (${code2}). Run the reviewed Hooks safety setup before using shell or patch tools.`);
44608
+ this.code = code2;
44609
+ this.reason = reason;
44610
+ this.name = "NativeSafetyError";
44611
+ }
44612
+ }
44613
+ var executionSchema = "hasna.hooks.execution.v1";
44614
+ var hash = (value) => createHash7("sha256").update(value).digest("hex");
44615
+ var filename = "sumi-trash-guard.json";
44616
+ var owner2 = (uid2) => uid2 === 0 || uid2 === process.getuid?.();
44617
+ function safeDirectory(path) {
44618
+ for (let cursor = path;; cursor = dirname11(cursor)) {
44619
+ const stat2 = lstatSync7(cursor);
44620
+ if (!stat2.isDirectory() || !owner2(stat2.uid) || stat2.mode & 18)
44621
+ throw new NativeSafetyError("binding_invalid");
44622
+ if (dirname11(cursor) === cursor)
44623
+ return;
44624
+ }
44625
+ }
44626
+ function canonicalHome(home) {
44627
+ if (!isAbsolute7(home) || home.includes("\x00") || realpathSync7(home) !== resolve15(home))
44628
+ throw new NativeSafetyError("binding_invalid");
44629
+ safeDirectory(home);
44630
+ return resolve15(home);
44631
+ }
44632
+ function readOwned(path) {
44633
+ safeDirectory(dirname11(path));
44634
+ const fd = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
44635
+ try {
44636
+ const stat2 = fstatSync3(fd);
44637
+ if (!stat2.isFile() || !owner2(stat2.uid) || stat2.mode & 63 || stat2.size > 32768)
44638
+ throw new NativeSafetyError("binding_invalid");
44639
+ const value = readFileSync9(fd, "utf8");
44640
+ if (Buffer.byteLength(value) > 32768)
44641
+ throw new NativeSafetyError("binding_invalid");
44642
+ return value;
44643
+ } finally {
44644
+ closeSync4(fd);
44645
+ }
44646
+ }
44647
+ function parseRecord(text3, home) {
44648
+ const record3 = JSON.parse(text3);
44649
+ if (!record3 || Array.isArray(record3) || Object.keys(record3).sort().join() !== "command,harness,schema" || record3.schema !== "hasna.hooks.native-safety.v1" || record3.harness !== "sumi" || typeof record3.command !== "string")
44650
+ throw new NativeSafetyError("binding_invalid");
44651
+ const binding = nativeSafetyCommandBinding(record3.command);
44652
+ if (binding?.name !== "trash-guard" || binding.home !== home)
44653
+ throw new NativeSafetyError("binding_invalid");
44654
+ return record3;
44655
+ }
44656
+ function syncDirectory(path) {
44657
+ const fd = openSync4(path, constants3.O_RDONLY | constants3.O_DIRECTORY | constants3.O_NOFOLLOW);
44658
+ try {
44659
+ fsyncSync(fd);
44660
+ } finally {
44661
+ closeSync4(fd);
44662
+ }
44663
+ }
44664
+ function readNativeSafetyRegistration(home = homedir7()) {
44665
+ try {
44666
+ home = canonicalHome(home);
44667
+ const path = join13(home, ".hasna/hooks/native", filename), text3 = readOwned(path);
44668
+ return { record: parseRecord(text3, home), sha256: hash(text3), path };
44669
+ } catch (error61) {
44670
+ if (error61 instanceof NativeSafetyError)
44671
+ throw error61;
44672
+ throw new NativeSafetyError(error61?.code === "ENOENT" ? "binding_missing" : "binding_invalid");
44673
+ }
44674
+ }
44675
+ function registerNativeSafety(options = {}) {
44676
+ const home = canonicalHome(options.home ?? homedir7());
44677
+ const record3 = { schema: "hasna.hooks.native-safety.v1", harness: "sumi", command: options.command ?? installedNativeSafetyCommand("trash-guard") };
44678
+ const text3 = JSON.stringify(record3) + `
44679
+ `;
44680
+ parseRecord(text3, home);
44681
+ for (const dir of [join13(home, ".hasna"), join13(home, ".hasna/hooks"), join13(home, ".hasna/hooks/native")]) {
44682
+ try {
44683
+ mkdirSync9(dir, { mode: 448 });
44684
+ syncDirectory(dirname11(dir));
44685
+ } catch (error61) {
44686
+ if (error61.code !== "EEXIST")
44687
+ throw error61;
44688
+ }
44689
+ safeDirectory(dir);
44690
+ }
44691
+ const directory2 = join13(home, ".hasna/hooks/native"), path = join13(directory2, filename), lock = path + ".lock";
44692
+ let lockFD;
44693
+ try {
44694
+ lockFD = openSync4(lock, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
44695
+ } catch {
44696
+ throw new NativeSafetyError("binding_busy");
44697
+ }
44698
+ const lockIdentity = fstatSync3(lockFD);
44699
+ let temporaryIdentity;
44700
+ const temporary = join13(directory2, `.${filename}.${randomUUID5()}`);
44701
+ try {
44702
+ writeFileSync8(lockFD, JSON.stringify({ pid: process.pid, expectedSHA256: options.expectedSHA256 ?? "absent" }));
44703
+ fsyncSync(lockFD);
44704
+ syncDirectory(directory2);
44705
+ let before;
44706
+ try {
44707
+ before = readNativeSafetyRegistration(home);
44708
+ } catch (error61) {
44709
+ if (!(error61 instanceof NativeSafetyError) || error61.code !== "binding_missing")
44710
+ throw error61;
44711
+ }
44712
+ if (before?.sha256 === hash(text3))
44713
+ return { ok: true, changed: false, path, sha256: before.sha256 };
44714
+ if (before ? options.expectedSHA256 !== before.sha256 : options.expectedSHA256 !== undefined && options.expectedSHA256 !== "absent")
44715
+ throw new NativeSafetyError("binding_changed");
44716
+ if (before) {
44717
+ const backup = path + ".before-" + before.sha256;
44718
+ if (existsSync14(backup)) {
44719
+ if (hash(readOwned(backup)) !== before.sha256)
44720
+ throw new NativeSafetyError("binding_changed");
44721
+ } else {
44722
+ const original = readOwned(path);
44723
+ if (hash(original) !== before.sha256)
44724
+ throw new NativeSafetyError("binding_changed");
44725
+ const fd2 = openSync4(backup, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
44726
+ try {
44727
+ writeFileSync8(fd2, original);
44728
+ fsyncSync(fd2);
44729
+ } finally {
44730
+ closeSync4(fd2);
44731
+ }
44732
+ syncDirectory(directory2);
44733
+ }
44734
+ }
44735
+ const fd = openSync4(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
44736
+ try {
44737
+ temporaryIdentity = fstatSync3(fd);
44738
+ writeFileSync8(fd, text3);
44739
+ fsyncSync(fd);
44740
+ } finally {
44741
+ closeSync4(fd);
44742
+ }
44743
+ if (before) {
44744
+ if (hash(readOwned(path)) !== before.sha256)
44745
+ throw new NativeSafetyError("binding_changed");
44746
+ renameSync3(temporary, path);
44747
+ } else {
44748
+ linkSync3(temporary, path);
44749
+ unlinkSync5(temporary);
44750
+ }
44751
+ syncDirectory(directory2);
44752
+ const after = readNativeSafetyRegistration(home);
44753
+ if (after.sha256 !== hash(text3))
44754
+ throw new NativeSafetyError("binding_changed");
44755
+ return { ok: true, changed: true, path, sha256: after.sha256 };
44756
+ } finally {
44757
+ closeSync4(lockFD);
44758
+ const currentTemporary = lstatSync7(temporary, { throwIfNoEntry: false });
44759
+ if (temporaryIdentity && currentTemporary?.dev === temporaryIdentity.dev && currentTemporary.ino === temporaryIdentity.ino)
44760
+ unlinkSync5(temporary);
44761
+ const currentLock = lstatSync7(lock, { throwIfNoEntry: false });
44762
+ if (currentLock?.dev !== lockIdentity.dev || currentLock.ino !== lockIdentity.ino)
44763
+ throw new NativeSafetyError("binding_changed");
44764
+ unlinkSync5(lock);
44765
+ syncDirectory(directory2);
44766
+ }
44767
+ }
44768
+ async function evaluateNativeSafetyForExecution(event, options) {
44769
+ if (!options || typeof options.agent !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:@-]{0,127}$/.test(options.agent))
44770
+ throw new NativeSafetyError("input_invalid");
44771
+ let snapshot2;
44772
+ try {
44773
+ snapshot2 = JSON.parse(JSON.stringify(event));
44774
+ } catch {
44775
+ throw new NativeSafetyError("input_invalid");
44776
+ }
44777
+ if (snapshot2?.tool_input && Object.hasOwn(snapshot2.tool_input, "hasna_execution"))
44778
+ throw new NativeSafetyError("input_invalid");
44779
+ const before = readNativeSafetyRegistration(options.home);
44780
+ const binding = nativeSafetyCommandBinding(before.record.command);
44781
+ const decision = await evaluateRecord(snapshot2, before.record, options.signal, options.agent);
44782
+ if (readNativeSafetyRegistration(options.home).sha256 !== before.sha256)
44783
+ throw new NativeSafetyError("binding_changed");
44784
+ return decision.decision === "rewrite" ? {
44785
+ decision: "rewrite",
44786
+ execution: { command: decision.command, shell: "/bin/sh", cwd: snapshot2.cwd, env: { HOME: binding.home, PATH: binding.path } }
44787
+ } : decision;
44788
+ }
44789
+ async function evaluateRecord(event, record3, signal, executionAgent) {
44790
+ if (!event || event.hook_event_name !== "PreToolUse" || !["Bash", "apply_patch"].includes(event.tool_name) || typeof event.cwd !== "string" || !isAbsolute7(event.cwd) || !event.tool_input || typeof event.tool_input !== "object" || Array.isArray(event.tool_input))
44791
+ throw new NativeSafetyError("input_invalid");
44792
+ const input2 = JSON.stringify(executionAgent === undefined ? event : { ...event, hasna_execution: { schema: executionSchema, agent: executionAgent } });
44793
+ if (Buffer.byteLength(input2) > 1048576)
44794
+ throw new NativeSafetyError("input_invalid");
44795
+ const output2 = await new Promise((accept, reject) => {
44796
+ const child = execFile("/bin/sh", ["-c", record3.command], { cwd: "/", env: {}, encoding: "utf8", timeout: 8000, maxBuffer: 1048576, signal }, (error61, stdout, stderr) => {
44797
+ if (error61 || stderr) {
44798
+ reject(new NativeSafetyError("guard_failed"));
44799
+ return;
44800
+ }
44801
+ accept(stdout);
44802
+ });
44803
+ child.stdin?.on("error", () => {});
44804
+ child.stdin?.end(input2);
44805
+ });
44806
+ if (!output2)
44807
+ return { decision: "continue" };
44808
+ try {
44809
+ const value = JSON.parse(output2);
44810
+ if (Object.keys(value).join() !== "hookSpecificOutput")
44811
+ throw new Error;
44812
+ const verdict = value.hookSpecificOutput;
44813
+ if (verdict.hookEventName !== "PreToolUse")
44814
+ throw new Error;
44815
+ if (verdict.permissionDecision === "deny" && Object.keys(verdict).sort().join() === "hookEventName,permissionDecision,permissionDecisionReason" && typeof verdict.permissionDecisionReason === "string" && verdict.permissionDecisionReason.trim())
44816
+ return { decision: "deny", reason: verdict.permissionDecisionReason };
44817
+ if (event.tool_name === "Bash" && verdict.permissionDecision === "allow" && Object.keys(verdict).sort().join() === "hookEventName,permissionDecision,updatedInput" && typeof verdict.updatedInput?.command === "string" && verdict.updatedInput.command.trim() && (executionAgent === undefined || verdict.updatedInput.hasna_execution === executionSchema))
44818
+ return { decision: "rewrite", command: verdict.updatedInput.command };
44819
+ } catch {}
44820
+ throw new NativeSafetyError("guard_failed");
44821
+ }
44822
+ async function verifyNativeSafetyCommand(command, home = homedir7()) {
44823
+ home = canonicalHome(home);
44824
+ const record3 = parseRecord(JSON.stringify({ schema: "hasna.hooks.native-safety.v1", harness: "sumi", command }), home);
44825
+ const verdict = await evaluateRecord({ hook_event_name: "PreToolUse", tool_name: "Bash", cwd: home, tool_input: { command: "rm -- .hasna-trash-verification-only" } }, record3);
44826
+ if (verdict.decision !== "rewrite")
44827
+ throw new NativeSafetyError("guard_failed", verdict.decision === "deny" ? verdict.reason.slice(0, 512) : undefined);
44828
+ }
44829
+ async function verifyNativeSafetyRegistration(home = homedir7()) {
44830
+ const before = readNativeSafetyRegistration(home);
44831
+ await verifyNativeSafetyCommand(before.record.command, home);
44832
+ const after = readNativeSafetyRegistration(home);
44833
+ if (after.sha256 !== before.sha256)
44834
+ throw new NativeSafetyError("binding_changed");
44835
+ return { path: before.path, sha256: before.sha256 };
44836
+ }
44837
+ async function verifyNativeSafetyExecutionRegistration(home = homedir7()) {
44838
+ const before = readNativeSafetyRegistration(home);
44839
+ const verdict = await evaluateNativeSafetyForExecution({
44840
+ hook_event_name: "PreToolUse",
44841
+ tool_name: "Bash",
44842
+ cwd: home,
44843
+ tool_input: { command: "rm -- .hasna-trash-verification-only" }
44844
+ }, { home, agent: "native-safety-verification" });
44845
+ if (verdict.decision !== "rewrite")
44846
+ throw new NativeSafetyError("guard_failed", verdict.decision === "deny" ? verdict.reason.slice(0, 512) : undefined);
44847
+ if (readNativeSafetyRegistration(home).sha256 !== before.sha256)
44848
+ throw new NativeSafetyError("binding_changed");
44849
+ return { path: before.path, sha256: before.sha256 };
44850
+ }
44851
+
44852
+ // hooks/hook-trash-guard/src/binary.ts
44853
+ import { dirname as dirname17, isAbsolute as isAbsolute15, join as join17, resolve as resolve17 } from "path";
44854
+ import { accessSync, closeSync as closeSync5, constants as constants4, fstatSync as fstatSync4, lstatSync as lstatSync9, openSync as openSync5, readFileSync as readFileSync12, readlinkSync as readlinkSync3, readSync as readSync2, realpathSync as realpathSync8, statSync } from "fs";
44855
+ import { spawnSync } from "child_process";
44856
+ function trustedOwner(uid2) {
44857
+ return uid2 === 0 || uid2 === process.getuid?.();
44858
+ }
44859
+ function safeExecutableFile(path, systemBinAlias = false) {
44860
+ let current2 = path;
44861
+ for (let hop = 0;hop < 32; hop++) {
44862
+ if (!isAbsolute15(current2) || current2.length > 4096 || !safeBinaryDirectory("/"))
44863
+ return false;
44864
+ const parts = current2.split("/"), name = parts.pop();
44865
+ if (!name || name === "." || name === "..")
44866
+ return false;
44867
+ let parent = "/";
44868
+ for (const part of parts) {
44869
+ if (!part || part === ".")
44870
+ continue;
44871
+ if (part === "..") {
44872
+ parent = dirname17(parent);
44873
+ continue;
44874
+ }
44875
+ const next = join17(parent, part), info2 = lstatSync9(next);
44876
+ if (systemBinAlias && next === "/bin" && info2.isSymbolicLink() && info2.uid === 0) {
44877
+ const target = realpathSync8(next);
44878
+ if (!safeBinaryDirectory(target))
44879
+ return false;
44880
+ parent = target;
44881
+ continue;
44882
+ }
44883
+ if (!info2.isDirectory() || !trustedOwner(info2.uid) || info2.mode & 18)
44884
+ return false;
44885
+ parent = next;
44886
+ }
44887
+ const candidate = join17(parent, name), info = lstatSync9(candidate);
44888
+ if (!trustedOwner(info.uid))
44889
+ return false;
44890
+ if (info.isSymbolicLink()) {
44891
+ const target = readlinkSync3(candidate);
44892
+ current2 = isAbsolute15(target) ? target : parent + "/" + target;
44893
+ continue;
44894
+ }
44895
+ return info.isFile() && Boolean(info.mode & 73) && !(info.mode & 18);
44896
+ }
44897
+ return false;
44898
+ }
44899
+ function safeInterpreterSearchDirectory(path) {
44900
+ if (!isAbsolute15(path) || resolve17(path) !== (path.replace(/\/+$/, "") || "/"))
44901
+ return false;
44902
+ for (let cursor = resolve17(path);; cursor = dirname17(cursor)) {
44903
+ try {
44904
+ lstatSync9(cursor);
44905
+ return safeBinaryDirectory(cursor);
44906
+ } catch (error61) {
44907
+ if (error61.code !== "ENOENT")
44908
+ return false;
44909
+ }
44910
+ if (dirname17(cursor) === cursor)
44911
+ return false;
44912
+ }
44913
+ }
44914
+ function safeScriptInterpreter(executable, pathValue) {
44915
+ const fd = openSync5(executable, constants4.O_RDONLY | constants4.O_NONBLOCK | constants4.O_NOFOLLOW);
44916
+ let header;
44917
+ try {
44918
+ if (!fstatSync4(fd).isFile())
44919
+ return false;
44920
+ const bytes = Buffer.alloc(256), length = readSync2(fd, bytes, 0, bytes.length, 0);
44921
+ header = bytes.subarray(0, length).toString("utf8").split(`
44922
+ `)[0];
44923
+ } finally {
44924
+ closeSync5(fd);
44925
+ }
44926
+ const direct = /^#!(\/[A-Za-z0-9_./-]+)$/.exec(header);
44927
+ if (direct && direct[1] !== "/usr/bin/env")
44928
+ return safeExecutableFile(direct[1], true);
44929
+ if (header !== "#!/usr/bin/env bun" || !safeExecutableFile("/usr/bin/env", true))
44930
+ return false;
44931
+ for (const dir of pathValue.split(":").slice(0, 64)) {
44932
+ if (!safeInterpreterSearchDirectory(dir))
44933
+ return false;
44934
+ const interpreter = resolve17(dir, "bun");
44935
+ try {
44936
+ if (!statSync(interpreter).isFile())
44937
+ continue;
44938
+ accessSync(interpreter, constants4.X_OK);
44939
+ } catch {
44940
+ continue;
44941
+ }
44942
+ return safeExecutableFile(interpreter, true);
44943
+ }
44944
+ return false;
44945
+ }
44946
+ function safeBinaryDirectory(path) {
44947
+ let current2 = path;
44948
+ for (;; ) {
44949
+ const info = lstatSync9(current2);
44950
+ if (!info.isDirectory() || !trustedOwner(info.uid) || (info.mode & 18) !== 0)
44951
+ return false;
44952
+ const parent = dirname17(current2);
44953
+ if (parent === current2)
44954
+ return true;
44955
+ current2 = parent;
44956
+ }
44957
+ }
44958
+ function inspectTrashBinary(env = process.env) {
44959
+ const pathValue = env.PATH ?? "";
44960
+ const deadline = Date.now() + 2000;
44961
+ let failure = "missing";
44962
+ const reject = (code2) => {
44963
+ if (failure === "missing" || failure === "unrecognized_package" || code2.startsWith("unsafe_"))
44964
+ failure = code2;
44965
+ };
44966
+ for (const dir of pathValue.split(":").slice(0, 64)) {
44967
+ if (!isAbsolute15(dir) || Date.now() >= deadline)
44968
+ continue;
44969
+ const candidate = resolve17(dir, "trash");
44970
+ try {
44971
+ const stat2 = statSync(candidate);
44972
+ if (!stat2.isFile() || !trustedOwner(stat2.uid) || (stat2.mode & 73) === 0 || (stat2.mode & 18) !== 0) {
44973
+ reject("unsafe_executable");
44974
+ continue;
44975
+ }
44976
+ const executable = realpathSync8(candidate);
44977
+ if (!safeBinaryDirectory(resolve17(dir)) || !safeBinaryDirectory(dirname17(executable))) {
44978
+ reject("unsafe_directory");
44979
+ continue;
44980
+ }
44981
+ if (!safeExecutableFile(candidate)) {
44982
+ reject("unsafe_directory");
44983
+ continue;
44984
+ }
44985
+ const packageRoot = resolve17(dirname17(executable), "../..");
44986
+ const manifestPath = join17(packageRoot, "package.json");
44987
+ const manifestStat = statSync(manifestPath);
44988
+ if (!manifestStat.isFile() || !trustedOwner(manifestStat.uid) || manifestStat.size > 16384 || (manifestStat.mode & 18) !== 0 || lstatSync9(manifestPath).isSymbolicLink()) {
44989
+ reject("unsafe_manifest");
44990
+ continue;
44991
+ }
44992
+ const manifest = JSON.parse(readFileSync12(manifestPath, "utf8"));
44993
+ if (manifest.name !== "@hasna/trash" || typeof manifest.version !== "string" || typeof manifest.bin?.trash !== "string" || realpathSync8(resolve17(packageRoot, manifest.bin.trash)) !== executable) {
44994
+ reject("unrecognized_package");
44995
+ continue;
44996
+ }
44997
+ try {
44998
+ if (!safeScriptInterpreter(executable, pathValue)) {
44999
+ reject("unsafe_interpreter");
45000
+ continue;
45001
+ }
45002
+ } catch {
45003
+ reject("unsafe_interpreter");
45004
+ continue;
45005
+ }
45006
+ const probe = spawnSync(candidate, ["--identity"], {
45007
+ encoding: "utf8",
45008
+ timeout: Math.max(1, Math.min(500, deadline - Date.now())),
45009
+ maxBuffer: 2048,
45010
+ env: { PATH: pathValue },
45011
+ stdio: ["ignore", "pipe", "pipe"]
45012
+ });
45013
+ if (probe.error || probe.status !== 0) {
45014
+ reject("identity_failed");
45015
+ continue;
45016
+ }
45017
+ const identity3 = JSON.parse(probe.stdout);
45018
+ if (identity3.name === "@hasna/trash" && identity3.version === manifest.version && identity3.guardProtocol === "hasna.trash.guard.v1")
45019
+ return { ok: true, path: candidate, version: manifest.version };
45020
+ reject("unsupported_protocol");
45021
+ } catch {}
45022
+ }
45023
+ return { ok: false, code: failure };
45024
+ }
45025
+
45026
+ // src/cli/index.tsx
44591
45027
  init_registry();
44592
45028
  init_installer();
44593
45029
  init_registration();
@@ -44603,9 +45039,9 @@ init_local_opt_in();
44603
45039
  init_transport2();
44604
45040
  init_db();
44605
45041
  import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
44606
- var __dirname4 = dirname22(fileURLToPath6(import.meta.url));
44607
- var pkgPath = existsSync18(join29(__dirname4, "..", "package.json")) ? join29(__dirname4, "..", "package.json") : join29(__dirname4, "..", "..", "package.json");
44608
- var pkg2 = JSON.parse(readFileSync20(pkgPath, "utf-8"));
45042
+ var __dirname4 = dirname24(fileURLToPath6(import.meta.url));
45043
+ var pkgPath = existsSync19(join31(__dirname4, "..", "package.json")) ? join31(__dirname4, "..", "package.json") : join31(__dirname4, "..", "..", "package.json");
45044
+ var pkg2 = JSON.parse(readFileSync23(pkgPath, "utf-8"));
44609
45045
  var program3 = new Command;
44610
45046
  function resolveScope(options) {
44611
45047
  if (options.project)
@@ -44685,7 +45121,7 @@ async function openLocalStoreOrExit(json3) {
44685
45121
  function readToken(tokenFile) {
44686
45122
  if (tokenFile) {
44687
45123
  try {
44688
- return readFileSync20(tokenFile, "utf-8").trim();
45124
+ return readFileSync23(tokenFile, "utf-8").trim();
44689
45125
  } catch {
44690
45126
  return;
44691
45127
  }
@@ -44823,7 +45259,7 @@ program3.command("init").description("Register a new agent profile with a unique
44823
45259
  if (profile.name) {
44824
45260
  console.log(` ${chalk2.dim("Name:")} ${profile.name}`);
44825
45261
  }
44826
- console.log(` ${chalk2.dim("Profile:")} ${join29(getProfilesDir(), `${profile.agent_id}.json`)}`);
45262
+ console.log(` ${chalk2.dim("Profile:")} ${join31(getProfilesDir(), `${profile.agent_id}.json`)}`);
44827
45263
  console.log();
44828
45264
  console.log(chalk2.dim(" Install hooks with this profile:"));
44829
45265
  console.log(` hooks install gitguard --profile ${profile.agent_id}`);
@@ -44847,11 +45283,11 @@ program3.command("run").argument("<hook>", "Hook to run").option("--profile <id>
44847
45283
  process.exit(1);
44848
45284
  }
44849
45285
  const hookScript = resolveScriptPath2(hook);
44850
- if (!hookScript || !existsSync18(hookScript)) {
45286
+ if (!hookScript || !existsSync19(hookScript)) {
44851
45287
  console.error(JSON.stringify({ error: `Hook script not found: ${hookScript ?? hook}` }));
44852
45288
  process.exit(1);
44853
45289
  }
44854
- const content = readFileSync20(hookScript);
45290
+ const content = readFileSync23(hookScript);
44855
45291
  const sha4 = sha256Of2(content);
44856
45292
  const check2 = checkScriptHash2(hook, sha4);
44857
45293
  if (!check2.ok) {
@@ -45445,7 +45881,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
45445
45881
  });
45446
45882
  }
45447
45883
  const staleNames = new Set(stale.map((finding) => finding.hook));
45448
- const settingsExist = existsSync18(settingsPath);
45884
+ const settingsExist = existsSync19(settingsPath);
45449
45885
  if (!settingsExist) {
45450
45886
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
45451
45887
  }
@@ -45453,7 +45889,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
45453
45889
  let wiringCount = 0;
45454
45890
  if (settingsExist) {
45455
45891
  try {
45456
- const settings = JSON.parse(readFileSync20(settingsPath, "utf-8"));
45892
+ const settings = JSON.parse(readFileSync23(settingsPath, "utf-8"));
45457
45893
  wiringCount = countSettingsWiring(settings);
45458
45894
  } catch {}
45459
45895
  }
@@ -45464,7 +45900,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
45464
45900
  const meta3 = custom2 ? resolveHookMeta(name) : getHook(name);
45465
45901
  let hookHealthy = true;
45466
45902
  if (custom2) {
45467
- if (!existsSync18(custom2.scriptPath)) {
45903
+ if (!existsSync19(custom2.scriptPath)) {
45468
45904
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom2.scriptPath}`, severity: "error" });
45469
45905
  hookHealthy = false;
45470
45906
  }
@@ -45475,15 +45911,15 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
45475
45911
  continue;
45476
45912
  }
45477
45913
  const hookDir = getHookPath(name);
45478
- const hookScript = join29(hookDir, "src", "hook.ts");
45479
- if (!existsSync18(hookScript)) {
45914
+ const hookScript = join31(hookDir, "src", "hook.ts");
45915
+ if (!existsSync19(hookScript)) {
45480
45916
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
45481
45917
  hookHealthy = false;
45482
45918
  }
45483
45919
  }
45484
45920
  if (meta3 && settingsExist) {
45485
45921
  try {
45486
- const settings = JSON.parse(readFileSync20(settingsPath, "utf-8"));
45922
+ const settings = JSON.parse(readFileSync23(settingsPath, "utf-8"));
45487
45923
  if (!hookRegisteredInSettings(settings, name, meta3.event, meta3.matcher)) {
45488
45924
  const eventName = meta3.event.split(":")[0];
45489
45925
  issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
@@ -45633,18 +46069,18 @@ program3.command("update").argument("[hooks...]", "Hooks to update (defaults to
45633
46069
  const result = installHook(arg, { scope, overwrite: true });
45634
46070
  const resolved = resolveHook2(arg);
45635
46071
  if (result.success && resolved) {
45636
- const hash = await sha256File2(resolved.scriptPath);
45637
- setPinnedHook2(resolved.name, { version: resolved.version, sha256: hash, source: resolved.source });
46072
+ const hash4 = await sha256File2(resolved.scriptPath);
46073
+ setPinnedHook2(resolved.name, { version: resolved.version, sha256: hash4, source: resolved.source });
45638
46074
  if (!isLocalStoreRefused2()) {
45639
46075
  upsertHookRecord2(getDb2(), {
45640
46076
  name: resolved.name,
45641
46077
  version: resolved.version,
45642
- sha256: hash,
46078
+ sha256: hash4,
45643
46079
  source_type: resolved.source,
45644
46080
  last_verified_at: new Date().toISOString()
45645
46081
  });
45646
46082
  }
45647
- results2.push({ ...result, pinned: { version: resolved.version, sha256: hash } });
46083
+ results2.push({ ...result, pinned: { version: resolved.version, sha256: hash4 } });
45648
46084
  continue;
45649
46085
  }
45650
46086
  results2.push(result);
@@ -45686,10 +46122,10 @@ program3.command("docs").argument("[hook]", "Hook name (shows general docs if om
45686
46122
  return;
45687
46123
  }
45688
46124
  const hookPath = getHookPath(hook);
45689
- const readmePath = join29(hookPath, "README.md");
46125
+ const readmePath = join31(hookPath, "README.md");
45690
46126
  let readme = "";
45691
- if (existsSync18(readmePath)) {
45692
- readme = readFileSync20(readmePath, "utf-8");
46127
+ if (existsSync19(readmePath)) {
46128
+ readme = readFileSync23(readmePath, "utf-8");
45693
46129
  }
45694
46130
  if (options.json) {
45695
46131
  console.log(JSON.stringify({ ...meta3, readme }));
@@ -46317,7 +46753,38 @@ program3.command("mcp").option("-s, --stdio", "Use stdio transport (one process
46317
46753
  }
46318
46754
  });
46319
46755
  registerEventsCommands(program3, { source: "hooks" });
46320
- program3.command("safety").description("Register bundled native deletion guards without a registry credential").command("install <guard>").option("--target <target>", "codex or claude", "codex").option("--scope <scope>", "global or project", "global").option("--overwrite", "Replace this guard in place; native trust is still required").action((guard, options) => {
46756
+ var safety = program3.command("safety").description("Inspect and register bundled native deletion guards without a registry credential");
46757
+ safety.command("inspect").description("Check the installed Trash guard identity and filesystem safety without app credentials").action(() => {
46758
+ const result = inspectTrashBinary();
46759
+ console.log(JSON.stringify({ guard: "trash-guard", ...result }));
46760
+ if (!result.ok)
46761
+ process.exitCode = 1;
46762
+ });
46763
+ safety.command("install <guard>").option("--target <target>", "codex, claude or sumi", "codex").option("--scope <scope>", "global or project", "global").option("--overwrite", "Replace this guard in place; native trust is still required").option("--expected-binding-sha256 <sha256>", "Exact existing Sumi binding digest required for an upgrade").action(async (guard, options) => {
46764
+ if (options.target === "sumi") {
46765
+ try {
46766
+ if (guard !== "trash-guard" || options.scope !== "global" || options.overwrite)
46767
+ throw new NativeSafetyError("input_invalid");
46768
+ const command = installedNativeSafetyCommand("trash-guard");
46769
+ const binding = nativeSafetyCommandBinding(command);
46770
+ const installed = inspectTrashBinary({ PATH: binding.path });
46771
+ if (!installed.ok) {
46772
+ console.log(JSON.stringify({ ok: false, target: "sumi", code: installed.code }));
46773
+ process.exitCode = 1;
46774
+ return;
46775
+ }
46776
+ await verifyNativeSafetyCommand(command);
46777
+ const registered = registerNativeSafety({ command, expectedSHA256: options.expectedBindingSha256 });
46778
+ const verified = await verifyNativeSafetyRegistration();
46779
+ if (verified.sha256 !== registered.sha256)
46780
+ throw new NativeSafetyError("binding_changed");
46781
+ console.log(JSON.stringify({ target: "sumi", ...registered }));
46782
+ } catch (error61) {
46783
+ console.log(JSON.stringify({ ok: false, target: "sumi", code: error61 instanceof NativeSafetyError ? error61.code : "binding_invalid", ...error61 instanceof NativeSafetyError && error61.reason ? { reason: error61.reason } : {} }));
46784
+ process.exitCode = 1;
46785
+ }
46786
+ return;
46787
+ }
46321
46788
  if (!isNativeSafetyName(guard) || !["codex", "claude"].includes(options.target) || !["global", "project"].includes(options.scope)) {
46322
46789
  console.error("Unsupported bundled safety registration");
46323
46790
  process.exitCode = 1;
@@ -46328,6 +46795,17 @@ program3.command("safety").description("Register bundled native deletion guards
46328
46795
  if (!result.success)
46329
46796
  process.exitCode = 1;
46330
46797
  });
46798
+ safety.command("verify").requiredOption("--target <target>", "sumi").option("--execution", "Also require the pinned native execution capability; no file operation is run").description("Verify a required native binding and its supervisor without running a file operation").action(async (options) => {
46799
+ try {
46800
+ if (options.target !== "sumi")
46801
+ throw new NativeSafetyError("input_invalid");
46802
+ const current2 = await (options.execution ? verifyNativeSafetyExecutionRegistration() : verifyNativeSafetyRegistration());
46803
+ console.log(JSON.stringify({ ok: true, target: "sumi", sha256: current2.sha256, guardVerified: true, ...options.execution ? { executionCapabilityVerified: true } : {}, nativeAdoptionVerified: false }));
46804
+ } catch (error61) {
46805
+ console.log(JSON.stringify({ ok: false, target: "sumi", code: error61 instanceof NativeSafetyError ? error61.code : "guard_failed", ...error61 instanceof NativeSafetyError && error61.reason ? { reason: error61.reason } : {} }));
46806
+ process.exitCode = 1;
46807
+ }
46808
+ });
46331
46809
  var API_INDEPENDENT_COMMANDS = new Set([
46332
46810
  "safety",
46333
46811
  "serve",