@hasna/hooks 0.6.3 → 0.6.4

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/dist/index.js CHANGED
@@ -22,7 +22,7 @@ var CREATE_HOOK_EVENTS_TABLE = `
22
22
  timestamp TEXT NOT NULL,
23
23
  session_id TEXT NOT NULL,
24
24
  hook_name TEXT NOT NULL,
25
- event_type TEXT NOT NULL CHECK (event_type IN ('PreToolUse', 'PostToolUse', 'Stop', 'Notification', 'SessionStart', 'SessionEnd', 'UserPromptSubmit')),
25
+ event_type TEXT NOT NULL CHECK (event_type IN ('PreToolUse', 'PostToolUse', 'Stop', 'Notification', 'SessionStart', 'SessionEnd', 'UserPromptSubmit', 'SubagentStart')),
26
26
  tool_name TEXT,
27
27
  tool_input TEXT,
28
28
  result TEXT CHECK (result IN ('continue', 'block', NULL)),
@@ -137,6 +137,35 @@ var init_004_hooks_table = __esm(() => {
137
137
  ];
138
138
  });
139
139
 
140
+ // src/db/migrations/005_subagent_start_event.ts
141
+ function up5(db) {
142
+ const row = db.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?").get("hook_events");
143
+ if (!row?.sql)
144
+ return;
145
+ if (row.sql.includes("SubagentStart"))
146
+ return;
147
+ db.exec("BEGIN");
148
+ try {
149
+ db.exec("ALTER TABLE hook_events RENAME TO hook_events_old");
150
+ db.exec(CREATE_HOOK_EVENTS_TABLE);
151
+ db.exec(`INSERT INTO hook_events
152
+ (id, timestamp, session_id, hook_name, event_type, tool_name, tool_input, result, error, duration_ms, project_dir, metadata)
153
+ SELECT id, timestamp, session_id, hook_name, event_type, tool_name, tool_input, result, error, duration_ms, project_dir, metadata
154
+ FROM hook_events_old`);
155
+ db.exec("DROP TABLE hook_events_old");
156
+ for (const idx of CREATE_INDEXES) {
157
+ db.exec(idx);
158
+ }
159
+ db.exec("COMMIT");
160
+ } catch (err) {
161
+ db.exec("ROLLBACK");
162
+ throw err;
163
+ }
164
+ }
165
+ var init_005_subagent_start_event = __esm(() => {
166
+ init_schema();
167
+ });
168
+
140
169
  // src/db/migrations/index.ts
141
170
  function ensureMigrationsTable(db) {
142
171
  db.exec(`
@@ -169,11 +198,13 @@ var init_migrations = __esm(() => {
169
198
  init_002_session_events();
170
199
  init_003_user_prompt_submit_event();
171
200
  init_004_hooks_table();
201
+ init_005_subagent_start_event();
172
202
  MIGRATIONS = [
173
203
  { version: "001_initial", up },
174
204
  { version: "002_session_events", up: up2 },
175
205
  { version: "003_user_prompt_submit_event", up: up3 },
176
- { version: "004_hooks_table", up: up4 }
206
+ { version: "004_hooks_table", up: up4 },
207
+ { version: "005_subagent_start_event", up: up5 }
177
208
  ];
178
209
  });
179
210
 
@@ -343,6 +374,7 @@ function getDb() {
343
374
  const isNew = dbPath === ":memory:" || !existsSync2(dbPath);
344
375
  ensureDir(dbPath);
345
376
  instance = new Database(dbPath);
377
+ instance.exec("PRAGMA busy_timeout=5000");
346
378
  instance.exec("PRAGMA journal_mode=WAL");
347
379
  instance.exec("PRAGMA foreign_keys=ON");
348
380
  runMigrations(instance);
@@ -4545,149 +4577,6 @@ var init_manifest = __esm(() => {
4545
4577
  };
4546
4578
  });
4547
4579
 
4548
- // src/lib/profiles.ts
4549
- var exports_profiles = {};
4550
- __export(exports_profiles, {
4551
- updateProfile: () => updateProfile,
4552
- touchProfile: () => touchProfile,
4553
- listProfiles: () => listProfiles,
4554
- importProfiles: () => importProfiles,
4555
- getProfilesDir: () => getProfilesDir,
4556
- getProfile: () => getProfile,
4557
- exportProfiles: () => exportProfiles,
4558
- deleteProfile: () => deleteProfile,
4559
- createProfile: () => createProfile
4560
- });
4561
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4, readdirSync as readdirSync3, rmSync, cpSync as cpSync2 } from "fs";
4562
- import { join as join7 } from "path";
4563
- import { homedir as homedir5 } from "os";
4564
- function resolveProfilesDir() {
4565
- const newDir = join7(homedir5(), ".hasna", "hooks", "profiles");
4566
- const oldDir = join7(homedir5(), ".hooks", "profiles");
4567
- if (!existsSync7(newDir) && existsSync7(oldDir)) {
4568
- mkdirSync5(join7(homedir5(), ".hasna", "hooks"), { recursive: true });
4569
- cpSync2(oldDir, newDir, { recursive: true });
4570
- }
4571
- return newDir;
4572
- }
4573
- function ensureProfilesDir() {
4574
- if (!existsSync7(PROFILES_DIR)) {
4575
- mkdirSync5(PROFILES_DIR, { recursive: true });
4576
- }
4577
- }
4578
- function profilePath(id) {
4579
- return join7(PROFILES_DIR, `${id}.json`);
4580
- }
4581
- function shortUuid() {
4582
- return crypto.randomUUID().slice(0, 8);
4583
- }
4584
- function getProfilesDir() {
4585
- return PROFILES_DIR;
4586
- }
4587
- function createProfile(input) {
4588
- ensureProfilesDir();
4589
- const id = shortUuid();
4590
- const now = new Date().toISOString();
4591
- const profile = {
4592
- agent_id: id,
4593
- agent_type: input.agent_type,
4594
- created_at: now,
4595
- last_seen_at: now,
4596
- preferences: {}
4597
- };
4598
- if (input.name) {
4599
- profile.name = input.name;
4600
- }
4601
- writeFileSync4(profilePath(id), JSON.stringify(profile, null, 2) + `
4602
- `);
4603
- return profile;
4604
- }
4605
- function getProfile(id) {
4606
- const path = profilePath(id);
4607
- try {
4608
- if (!existsSync7(path))
4609
- return null;
4610
- return JSON.parse(readFileSync5(path, "utf-8"));
4611
- } catch {
4612
- return null;
4613
- }
4614
- }
4615
- function listProfiles() {
4616
- if (!existsSync7(PROFILES_DIR))
4617
- return [];
4618
- try {
4619
- const files = readdirSync3(PROFILES_DIR).filter((f) => f.endsWith(".json"));
4620
- const profiles = [];
4621
- for (const file of files) {
4622
- try {
4623
- const content = readFileSync5(join7(PROFILES_DIR, file), "utf-8");
4624
- profiles.push(JSON.parse(content));
4625
- } catch {}
4626
- }
4627
- return profiles.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
4628
- } catch {
4629
- return [];
4630
- }
4631
- }
4632
- function updateProfile(id, data) {
4633
- const profile = getProfile(id);
4634
- if (!profile)
4635
- return null;
4636
- if (data.name !== undefined)
4637
- profile.name = data.name;
4638
- if (data.preferences !== undefined)
4639
- profile.preferences = data.preferences;
4640
- writeFileSync4(profilePath(id), JSON.stringify(profile, null, 2) + `
4641
- `);
4642
- return profile;
4643
- }
4644
- function deleteProfile(id) {
4645
- const path = profilePath(id);
4646
- if (!existsSync7(path))
4647
- return false;
4648
- try {
4649
- rmSync(path);
4650
- return true;
4651
- } catch {
4652
- return false;
4653
- }
4654
- }
4655
- function touchProfile(id) {
4656
- const profile = getProfile(id);
4657
- if (!profile)
4658
- return;
4659
- profile.last_seen_at = new Date().toISOString();
4660
- writeFileSync4(profilePath(id), JSON.stringify(profile, null, 2) + `
4661
- `);
4662
- }
4663
- function exportProfiles() {
4664
- return listProfiles();
4665
- }
4666
- function importProfiles(profiles) {
4667
- ensureProfilesDir();
4668
- let imported = 0;
4669
- let skipped = 0;
4670
- for (const profile of profiles) {
4671
- if (!profile.agent_id || !profile.agent_type) {
4672
- skipped++;
4673
- continue;
4674
- }
4675
- const path = profilePath(profile.agent_id);
4676
- if (existsSync7(path)) {
4677
- skipped++;
4678
- continue;
4679
- }
4680
- writeFileSync4(path, JSON.stringify(profile, null, 2) + `
4681
- `);
4682
- imported++;
4683
- }
4684
- return { imported, skipped };
4685
- }
4686
- var PROFILES_DIR;
4687
- var init_profiles = __esm(() => {
4688
- PROFILES_DIR = resolveProfilesDir();
4689
- });
4690
-
4691
4580
  // src/lib/store.ts
4692
4581
  var exports_store = {};
4693
4582
  __export(exports_store, {
@@ -4700,14 +4589,16 @@ __export(exports_store, {
4700
4589
  retrustHook: () => retrustHook,
4701
4590
  removePinnedHook: () => removePinnedHook,
4702
4591
  removeHookRecord: () => removeHookRecord,
4592
+ removeHookFromStore: () => removeHookFromStore,
4703
4593
  readLock: () => readLock,
4594
+ pinInstalledHook: () => pinInstalledHook,
4704
4595
  listHookRecords: () => listHookRecords,
4705
4596
  getPinnedHook: () => getPinnedHook,
4706
4597
  getHookRecord: () => getHookRecord,
4707
4598
  checkScriptHash: () => checkScriptHash
4708
4599
  });
4709
4600
  import { createHash } from "crypto";
4710
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4601
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
4711
4602
  import { readFile } from "fs/promises";
4712
4603
  function sha256Of(input) {
4713
4604
  return createHash("sha256").update(input).digest("hex");
@@ -4754,9 +4645,9 @@ function removeHookRecord(db, name) {
4754
4645
  function readLock() {
4755
4646
  try {
4756
4647
  const path = getLockPath();
4757
- if (!existsSync8(path))
4648
+ if (!existsSync6(path))
4758
4649
  return { hooks: {} };
4759
- const parsed = JSON.parse(readFileSync6(path, "utf-8"));
4650
+ const parsed = JSON.parse(readFileSync4(path, "utf-8"));
4760
4651
  return { hooks: parsed.hooks ?? {} };
4761
4652
  } catch {
4762
4653
  return { hooks: {} };
@@ -4764,12 +4655,12 @@ function readLock() {
4764
4655
  }
4765
4656
  function writeLock(lock) {
4766
4657
  const path = getLockPath();
4767
- mkdirSync6(path.substring(0, path.lastIndexOf("/")) || ".", { recursive: true });
4658
+ mkdirSync4(path.substring(0, path.lastIndexOf("/")) || ".", { recursive: true });
4768
4659
  const sorted = {};
4769
4660
  for (const name of Object.keys(lock.hooks).sort()) {
4770
4661
  sorted[name] = lock.hooks[name];
4771
4662
  }
4772
- writeFileSync5(path, JSON.stringify({ hooks: sorted }, null, 2) + `
4663
+ writeFileSync3(path, JSON.stringify({ hooks: sorted }, null, 2) + `
4773
4664
  `, "utf-8");
4774
4665
  return path;
4775
4666
  }
@@ -4789,6 +4680,25 @@ function removePinnedHook(name) {
4789
4680
  writeLock(lock);
4790
4681
  return true;
4791
4682
  }
4683
+ function pinInstalledHook(name, version, sha256, source, sourceRef) {
4684
+ const now = new Date().toISOString();
4685
+ const db = getDb();
4686
+ upsertHookRecord(db, {
4687
+ name,
4688
+ version,
4689
+ sha256,
4690
+ source_type: source,
4691
+ source_ref: sourceRef ?? null,
4692
+ last_verified_at: now
4693
+ });
4694
+ setPinnedHook(name, { version, sha256, source });
4695
+ }
4696
+ function removeHookFromStore(name) {
4697
+ const removedPin = removePinnedHook(name);
4698
+ const db = getDb();
4699
+ const removedRecord = removeHookRecord(db, name);
4700
+ return { removedPin, removedRecord };
4701
+ }
4792
4702
  function checkScriptHash(name, actual) {
4793
4703
  const db = getDb();
4794
4704
  const record = getHookRecord(db, name);
@@ -4825,7 +4735,7 @@ async function verifyScriptHash(name, scriptPath) {
4825
4735
  return checkScriptHash(name, await sha256File(scriptPath));
4826
4736
  }
4827
4737
  function retrustHook(name, scriptPath, version, source) {
4828
- const actual = sha256Of(readFileSync6(scriptPath));
4738
+ const actual = sha256Of(readFileSync4(scriptPath));
4829
4739
  const db = getDb();
4830
4740
  upsertHookRecord(db, {
4831
4741
  name,
@@ -4842,13 +4752,157 @@ var init_store = __esm(() => {
4842
4752
  init_config();
4843
4753
  });
4844
4754
 
4755
+ // src/lib/profiles.ts
4756
+ var exports_profiles = {};
4757
+ __export(exports_profiles, {
4758
+ updateProfile: () => updateProfile,
4759
+ touchProfile: () => touchProfile,
4760
+ listProfiles: () => listProfiles,
4761
+ importProfiles: () => importProfiles,
4762
+ getProfilesDir: () => getProfilesDir,
4763
+ getProfile: () => getProfile,
4764
+ exportProfiles: () => exportProfiles,
4765
+ deleteProfile: () => deleteProfile,
4766
+ createProfile: () => createProfile
4767
+ });
4768
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync3, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
4769
+ import { join as join7 } from "path";
4770
+ import { homedir as homedir5 } from "os";
4771
+ function resolveProfilesDir() {
4772
+ const newDir = join7(homedir5(), ".hasna", "hooks", "profiles");
4773
+ const oldDir = join7(homedir5(), ".hooks", "profiles");
4774
+ if (!existsSync8(newDir) && existsSync8(oldDir)) {
4775
+ mkdirSync6(join7(homedir5(), ".hasna", "hooks"), { recursive: true });
4776
+ cpSync2(oldDir, newDir, { recursive: true });
4777
+ }
4778
+ return newDir;
4779
+ }
4780
+ function ensureProfilesDir() {
4781
+ if (!existsSync8(PROFILES_DIR)) {
4782
+ mkdirSync6(PROFILES_DIR, { recursive: true });
4783
+ }
4784
+ }
4785
+ function profilePath(id) {
4786
+ return join7(PROFILES_DIR, `${id}.json`);
4787
+ }
4788
+ function shortUuid() {
4789
+ return crypto.randomUUID().slice(0, 8);
4790
+ }
4791
+ function getProfilesDir() {
4792
+ return PROFILES_DIR;
4793
+ }
4794
+ function createProfile(input) {
4795
+ ensureProfilesDir();
4796
+ const id = shortUuid();
4797
+ const now = new Date().toISOString();
4798
+ const profile = {
4799
+ agent_id: id,
4800
+ agent_type: input.agent_type,
4801
+ created_at: now,
4802
+ last_seen_at: now,
4803
+ preferences: {}
4804
+ };
4805
+ if (input.name) {
4806
+ profile.name = input.name;
4807
+ }
4808
+ writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
4809
+ `);
4810
+ return profile;
4811
+ }
4812
+ function getProfile(id) {
4813
+ const path = profilePath(id);
4814
+ try {
4815
+ if (!existsSync8(path))
4816
+ return null;
4817
+ return JSON.parse(readFileSync6(path, "utf-8"));
4818
+ } catch {
4819
+ return null;
4820
+ }
4821
+ }
4822
+ function listProfiles() {
4823
+ if (!existsSync8(PROFILES_DIR))
4824
+ return [];
4825
+ try {
4826
+ const files = readdirSync3(PROFILES_DIR).filter((f) => f.endsWith(".json"));
4827
+ const profiles = [];
4828
+ for (const file of files) {
4829
+ try {
4830
+ const content = readFileSync6(join7(PROFILES_DIR, file), "utf-8");
4831
+ profiles.push(JSON.parse(content));
4832
+ } catch {}
4833
+ }
4834
+ return profiles.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
4835
+ } catch {
4836
+ return [];
4837
+ }
4838
+ }
4839
+ function updateProfile(id, data) {
4840
+ const profile = getProfile(id);
4841
+ if (!profile)
4842
+ return null;
4843
+ if (data.name !== undefined)
4844
+ profile.name = data.name;
4845
+ if (data.preferences !== undefined)
4846
+ profile.preferences = data.preferences;
4847
+ writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
4848
+ `);
4849
+ return profile;
4850
+ }
4851
+ function deleteProfile(id) {
4852
+ const path = profilePath(id);
4853
+ if (!existsSync8(path))
4854
+ return false;
4855
+ try {
4856
+ rmSync2(path);
4857
+ return true;
4858
+ } catch {
4859
+ return false;
4860
+ }
4861
+ }
4862
+ function touchProfile(id) {
4863
+ const profile = getProfile(id);
4864
+ if (!profile)
4865
+ return;
4866
+ profile.last_seen_at = new Date().toISOString();
4867
+ writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
4868
+ `);
4869
+ }
4870
+ function exportProfiles() {
4871
+ return listProfiles();
4872
+ }
4873
+ function importProfiles(profiles) {
4874
+ ensureProfilesDir();
4875
+ let imported = 0;
4876
+ let skipped = 0;
4877
+ for (const profile of profiles) {
4878
+ if (!profile.agent_id || !profile.agent_type) {
4879
+ skipped++;
4880
+ continue;
4881
+ }
4882
+ const path = profilePath(profile.agent_id);
4883
+ if (existsSync8(path)) {
4884
+ skipped++;
4885
+ continue;
4886
+ }
4887
+ writeFileSync5(path, JSON.stringify(profile, null, 2) + `
4888
+ `);
4889
+ imported++;
4890
+ }
4891
+ return { imported, skipped };
4892
+ }
4893
+ var PROFILES_DIR;
4894
+ var init_profiles = __esm(() => {
4895
+ PROFILES_DIR = resolveProfilesDir();
4896
+ });
4897
+
4845
4898
  // src/lib/run.ts
4846
4899
  var exports_run = {};
4847
4900
  __export(exports_run, {
4848
- executeVerifiedScript: () => executeVerifiedScript
4901
+ executeVerifiedScript: () => executeVerifiedScript,
4902
+ HookTimeoutError: () => HookTimeoutError
4849
4903
  });
4850
4904
  import { randomBytes } from "crypto";
4851
- import { rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
4905
+ import { rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
4852
4906
  import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute2, join as join9 } from "path";
4853
4907
  function extensionOf(scriptPath) {
4854
4908
  const base = basename2(scriptPath);
@@ -4885,37 +4939,192 @@ function interpreterFor(name, scriptPath, content) {
4885
4939
  return { command: ["bun", "run"], tempExt: ext };
4886
4940
  throw new Error(`Refusing to run hook '${name}': unsupported script extension '.${ext}' (supported: .sh .bash .ts .tsx .js .jsx .mjs .cjs .mts .cts, or a recognized shebang)`);
4887
4941
  }
4942
+ async function readPipeWithDeadline(stream, deadlineMs) {
4943
+ const reader = stream.getReader();
4944
+ const chunks = [];
4945
+ const deadline = Date.now() + deadlineMs;
4946
+ try {
4947
+ while (true) {
4948
+ const remaining = deadline - Date.now();
4949
+ if (remaining <= 0)
4950
+ break;
4951
+ let result;
4952
+ try {
4953
+ result = await Promise.race([
4954
+ reader.read(),
4955
+ new Promise((_, reject) => {
4956
+ const t = setTimeout(() => reject(new Error("pipe-drain-deadline")), remaining);
4957
+ t.unref?.();
4958
+ })
4959
+ ]);
4960
+ } catch {
4961
+ break;
4962
+ }
4963
+ if (result.done)
4964
+ break;
4965
+ chunks.push(new TextDecoder().decode(result.value));
4966
+ }
4967
+ } finally {
4968
+ try {
4969
+ await reader.cancel();
4970
+ } catch {}
4971
+ }
4972
+ return chunks.join("");
4973
+ }
4888
4974
  async function executeVerifiedScript(options) {
4889
4975
  const scriptDir = dirname4(options.scriptPath);
4890
4976
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
4891
4977
  const tempPath = join9(scriptDir, `.hooks-verified-${randomBytes(12).toString("hex")}.${interpreter.tempExt}`);
4978
+ let proc = null;
4979
+ let timedOut = false;
4980
+ const timeoutTimer = options.timeout ? setTimeout(() => {
4981
+ timedOut = true;
4982
+ killGroup(proc);
4983
+ }, options.timeout) : null;
4892
4984
  try {
4893
4985
  writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
4894
- const proc = Bun.spawn([...interpreter.command, isAbsolute2(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
4986
+ proc = Bun.spawn([...interpreter.command, isAbsolute2(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
4895
4987
  stdin: new Response(options.stdin),
4896
4988
  stdout: "pipe",
4897
4989
  stderr: "pipe",
4898
4990
  env: options.env ?? process.env,
4899
- ...options.timeout ? { timeout: options.timeout } : {}
4991
+ detached: true
4900
4992
  });
4901
- const [stdoutText, stderrText, exitCode] = await Promise.all([
4902
- new Response(proc.stdout).text(),
4903
- new Response(proc.stderr).text(),
4904
- proc.exited
4993
+ const exitCode = await proc.exited;
4994
+ if (timedOut) {
4995
+ throw new HookTimeoutError(options.timeout);
4996
+ }
4997
+ const [stdoutText, stderrText] = await Promise.all([
4998
+ readPipeWithDeadline(proc.stdout, 750),
4999
+ readPipeWithDeadline(proc.stderr, 750)
4905
5000
  ]);
5001
+ killGroup(proc);
4906
5002
  return { stdout: stdoutText, stderr: stderrText, exitCode };
4907
5003
  } finally {
5004
+ if (timeoutTimer)
5005
+ clearTimeout(timeoutTimer);
5006
+ try {
5007
+ rmSync4(tempPath, { force: true });
5008
+ } catch {}
5009
+ }
5010
+ }
5011
+ function killGroup(proc) {
5012
+ if (!proc)
5013
+ return;
5014
+ try {
5015
+ process.kill(-proc.pid, "SIGKILL");
5016
+ } catch {
4908
5017
  try {
4909
- rmSync3(tempPath, { force: true });
5018
+ process.kill(proc.pid, "SIGKILL");
4910
5019
  } catch {}
4911
5020
  }
4912
5021
  }
4913
- var BASH_EXTENSIONS, BUN_EXTENSIONS;
5022
+ var HookTimeoutError, BASH_EXTENSIONS, BUN_EXTENSIONS;
4914
5023
  var init_run = __esm(() => {
5024
+ HookTimeoutError = class HookTimeoutError extends Error {
5025
+ timeoutMs;
5026
+ constructor(timeoutMs) {
5027
+ super(`Hook timed out after ${timeoutMs}ms (process group killed)`);
5028
+ this.name = "HookTimeoutError";
5029
+ this.timeoutMs = timeoutMs;
5030
+ }
5031
+ };
4915
5032
  BASH_EXTENSIONS = new Set(["sh", "bash"]);
4916
5033
  BUN_EXTENSIONS = new Set(["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"]);
4917
5034
  });
4918
5035
 
5036
+ // src/lib/db-writer.ts
5037
+ var exports_db_writer = {};
5038
+ __export(exports_db_writer, {
5039
+ writeHookEvent: () => writeHookEvent,
5040
+ resolveEventType: () => resolveEventType,
5041
+ recordHookRun: () => recordHookRun,
5042
+ normalizeEventType: () => normalizeEventType
5043
+ });
5044
+ function nanoid2() {
5045
+ return crypto.randomUUID().replace(/-/g, "").slice(0, 21);
5046
+ }
5047
+ function writeHookEvent(event) {
5048
+ try {
5049
+ const db = getDb();
5050
+ const id = nanoid2();
5051
+ const timestamp = event.timestamp ?? new Date().toISOString();
5052
+ db.run(`INSERT INTO hook_events
5053
+ (id, timestamp, session_id, hook_name, event_type, tool_name, tool_input, result, error, duration_ms, project_dir, metadata)
5054
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
5055
+ id,
5056
+ timestamp,
5057
+ event.session_id,
5058
+ event.hook_name,
5059
+ event.event_type,
5060
+ event.tool_name ?? null,
5061
+ event.tool_input ? event.tool_input.slice(0, 500) : null,
5062
+ event.result ?? null,
5063
+ event.error ?? null,
5064
+ event.duration_ms ?? null,
5065
+ event.project_dir ?? null,
5066
+ event.metadata ?? null
5067
+ ]);
5068
+ } catch (err) {
5069
+ process.stderr.write(`[hooks db-writer] failed to write event: ${err}
5070
+ `);
5071
+ }
5072
+ }
5073
+ function normalizeEventType(value) {
5074
+ if (typeof value !== "string")
5075
+ return null;
5076
+ if (ALLOWED_EVENT_TYPES.has(value))
5077
+ return value;
5078
+ const bare = value.split(":")[0] ?? "";
5079
+ return ALLOWED_EVENT_TYPES.has(bare) ? bare : null;
5080
+ }
5081
+ function resolveEventType(inputEvent, fallbackEvent) {
5082
+ const fromInput = normalizeEventType(inputEvent);
5083
+ if (fromInput)
5084
+ return fromInput;
5085
+ return normalizeEventType(fallbackEvent ?? null);
5086
+ }
5087
+ function recordHookRun(record) {
5088
+ const eventType = record.eventType ? normalizeEventType(record.eventType) : null;
5089
+ if (!eventType) {
5090
+ process.stderr.write(`[hooks db-writer] skipped event for '${record.hookName}': unsupported event type '${String(record.eventType)}'
5091
+ `);
5092
+ return;
5093
+ }
5094
+ const metadata = {
5095
+ ...record.metadata ?? {},
5096
+ version: record.version ?? null,
5097
+ sha256: record.sha256 ?? null,
5098
+ exit_code: record.exitCode
5099
+ };
5100
+ writeHookEvent({
5101
+ session_id: record.sessionId ?? "cli",
5102
+ hook_name: record.hookName,
5103
+ event_type: eventType,
5104
+ tool_name: record.toolName ?? null,
5105
+ tool_input: record.toolInput !== undefined ? JSON.stringify(record.toolInput) : null,
5106
+ result: record.result ?? null,
5107
+ error: record.error ?? null,
5108
+ duration_ms: record.durationMs,
5109
+ project_dir: record.projectDir ?? null,
5110
+ metadata: JSON.stringify(metadata)
5111
+ });
5112
+ }
5113
+ var ALLOWED_EVENT_TYPES;
5114
+ var init_db_writer = __esm(() => {
5115
+ init_db();
5116
+ ALLOWED_EVENT_TYPES = new Set([
5117
+ "PreToolUse",
5118
+ "PostToolUse",
5119
+ "Stop",
5120
+ "Notification",
5121
+ "SessionStart",
5122
+ "SessionEnd",
5123
+ "UserPromptSubmit",
5124
+ "SubagentStart"
5125
+ ]);
5126
+ });
5127
+
4919
5128
  // src/db/storage-sync.ts
4920
5129
  init_db();
4921
5130
 
@@ -5847,7 +6056,7 @@ function getHook(name) {
5847
6056
  return HOOKS.find((h) => h.name === name);
5848
6057
  }
5849
6058
  // src/lib/installer.ts
5850
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
6059
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync4 } from "fs";
5851
6060
  import { join as join6, dirname as dirname3 } from "path";
5852
6061
  import { homedir as homedir4 } from "os";
5853
6062
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -5917,7 +6126,8 @@ function resolveHook(name) {
5917
6126
  events: custom2.manifest.events,
5918
6127
  description: custom2.manifest.description ?? "Custom hook",
5919
6128
  scriptPath: custom2.scriptPath,
5920
- meta: meta2
6129
+ meta: meta2,
6130
+ timeoutMs: custom2.manifest.timeout_ms ?? null
5921
6131
  };
5922
6132
  }
5923
6133
  const meta = getHook(shortManifestName(name));
@@ -5931,13 +6141,16 @@ function resolveHook(name) {
5931
6141
  events: meta.events && meta.events.length > 0 ? meta.events : [meta.event],
5932
6142
  description: meta.description,
5933
6143
  scriptPath,
5934
- meta
6144
+ meta,
6145
+ timeoutMs: null
5935
6146
  };
5936
6147
  }
5937
6148
 
5938
6149
  // src/lib/installer.ts
6150
+ init_manifest();
6151
+ init_store();
5939
6152
  var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
5940
- var HOOKS_DIR = existsSync6(join6(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join6(__dirname2, "..", "..", "hooks") : join6(__dirname2, "..", "hooks");
6153
+ var HOOKS_DIR = existsSync7(join6(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join6(__dirname2, "..", "..", "hooks") : join6(__dirname2, "..", "hooks");
5941
6154
  function normalizeHookName(name) {
5942
6155
  return name.startsWith("hook-") ? name : `hook-${name}`;
5943
6156
  }
@@ -6019,13 +6232,13 @@ function getHookPath(name) {
6019
6232
  return join6(HOOKS_DIR, normalizeHookName(shortName));
6020
6233
  }
6021
6234
  function hookExists(name) {
6022
- return existsSync6(getHookPath(name));
6235
+ return existsSync7(getHookPath(name));
6023
6236
  }
6024
6237
  function readSettings(scope = "global", target = "claude") {
6025
6238
  const path = getSettingsPath(scope, target);
6026
6239
  try {
6027
- if (existsSync6(path)) {
6028
- return JSON.parse(readFileSync4(path, "utf-8"));
6240
+ if (existsSync7(path)) {
6241
+ return JSON.parse(readFileSync5(path, "utf-8"));
6029
6242
  }
6030
6243
  } catch (error) {
6031
6244
  console.warn(`[hooks] Failed to read settings at ${path}: ${error instanceof Error ? error.message : String(error)}`);
@@ -6035,10 +6248,10 @@ function readSettings(scope = "global", target = "claude") {
6035
6248
  function writeSettings(settings, scope = "global", target = "claude") {
6036
6249
  const path = getSettingsPath(scope, target);
6037
6250
  const dir = dirname3(path);
6038
- if (!existsSync6(dir)) {
6039
- mkdirSync4(dir, { recursive: true });
6251
+ if (!existsSync7(dir)) {
6252
+ mkdirSync5(dir, { recursive: true });
6040
6253
  }
6041
- writeFileSync3(path, JSON.stringify(settings, null, 2) + `
6254
+ writeFileSync4(path, JSON.stringify(settings, null, 2) + `
6042
6255
  `);
6043
6256
  }
6044
6257
  function getTargetEventName(internalEvent, target) {
@@ -6124,7 +6337,7 @@ function buildCodewithTomlFragment(name, profile) {
6124
6337
  function readCodewithConfig(scope, configPath) {
6125
6338
  const path = getSettingsPath(scope, "codewith", configPath);
6126
6339
  try {
6127
- return existsSync6(path) ? readFileSync4(path, "utf-8") : "";
6340
+ return existsSync7(path) ? readFileSync5(path, "utf-8") : "";
6128
6341
  } catch {
6129
6342
  return "";
6130
6343
  }
@@ -6132,13 +6345,13 @@ function readCodewithConfig(scope, configPath) {
6132
6345
  function appendCodewithFragment(fragment, scope, configPath) {
6133
6346
  const path = getSettingsPath(scope, "codewith", configPath);
6134
6347
  const dir = dirname3(path);
6135
- if (!existsSync6(dir))
6136
- mkdirSync4(dir, { recursive: true });
6137
- const existing = existsSync6(path) ? readFileSync4(path, "utf-8") : "";
6348
+ if (!existsSync7(dir))
6349
+ mkdirSync5(dir, { recursive: true });
6350
+ const existing = existsSync7(path) ? readFileSync5(path, "utf-8") : "";
6138
6351
  const sep2 = existing.trim() ? `
6139
6352
 
6140
6353
  ` : "";
6141
- writeFileSync3(path, `${existing.replace(/\s*$/, "")}${sep2}${fragment}`);
6354
+ writeFileSync4(path, `${existing.replace(/\s*$/, "")}${sep2}${fragment}`);
6142
6355
  return path;
6143
6356
  }
6144
6357
  function detectConflict(name, scope, target) {
@@ -6314,9 +6527,6 @@ function removeHookFromAllEvents(settings, name) {
6314
6527
  }
6315
6528
  }
6316
6529
  function unregisterHook(name, scope = "global", target = "claude") {
6317
- const meta = resolveHookMeta(name);
6318
- if (!meta)
6319
- return;
6320
6530
  const settings = readSettings(scope, target);
6321
6531
  if (!settings.hooks)
6322
6532
  return;
@@ -6382,6 +6592,136 @@ function removeHookForTarget(name, scope, target) {
6382
6592
  unregisterHook(name, scope, target);
6383
6593
  return true;
6384
6594
  }
6595
+ function removeCodewithHookEntry(configText, name) {
6596
+ const sections = configText.split(/\n\s*\n/);
6597
+ const nameRe = new RegExp(`^command\\s*=\\s*"hooks run ${escapeRegExp(name)}(?:\\s+--profile\\s+[\\w-]+)?"$`);
6598
+ const entryHeaderRe = /^\[\[hooks\.([\w]+)\.hooks\]\]$/;
6599
+ const eventHeaderRe = /^\[\[hooks\.([\w]+)\]\]$/;
6600
+ const eventsWithEntries = new Set;
6601
+ const kept = [];
6602
+ let removed = false;
6603
+ for (const section of sections) {
6604
+ const lines = section.split(`
6605
+ `);
6606
+ const entryMatch = entryHeaderRe.exec(lines[0] ?? "");
6607
+ if (entryMatch) {
6608
+ const event = entryMatch[1];
6609
+ eventsWithEntries.add(event);
6610
+ if (lines.some((line) => nameRe.test(line.trim()))) {
6611
+ removed = true;
6612
+ continue;
6613
+ }
6614
+ kept.push({ text: section });
6615
+ continue;
6616
+ }
6617
+ const eventMatch = eventHeaderRe.exec(lines[0] ?? "");
6618
+ kept.push({ text: section, eventHeaderFor: eventMatch ? eventMatch[1] : undefined });
6619
+ }
6620
+ const keptEntriesByEvent = new Set;
6621
+ for (const item of kept) {
6622
+ const m = entryHeaderRe.exec(item.text.split(`
6623
+ `)[0] ?? "");
6624
+ if (m)
6625
+ keptEntriesByEvent.add(m[1]);
6626
+ }
6627
+ const finalSections = kept.filter((item) => {
6628
+ if (item.eventHeaderFor === undefined)
6629
+ return true;
6630
+ if (!eventsWithEntries.has(item.eventHeaderFor))
6631
+ return true;
6632
+ return keptEntriesByEvent.has(item.eventHeaderFor);
6633
+ }).map((item) => item.text);
6634
+ return {
6635
+ text: finalSections.join(`
6636
+
6637
+ `) + (configText.endsWith(`
6638
+ `) ? `
6639
+ ` : ""),
6640
+ removed
6641
+ };
6642
+ }
6643
+ function escapeRegExp(value) {
6644
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6645
+ }
6646
+ function codewithHasHookEntryInText(configText, name) {
6647
+ return new RegExp(`hooks run ${escapeRegExp(name)}(?:\\s+--profile\\s+[\\w-]+)?(?:$|\\s|")`).test(configText);
6648
+ }
6649
+ function codewithHasHookEntry(name) {
6650
+ const path = getSettingsPath("global", "codewith");
6651
+ if (!existsSync7(path))
6652
+ return false;
6653
+ try {
6654
+ return codewithHasHookEntryInText(readFileSync5(path, "utf-8"), name);
6655
+ } catch {
6656
+ return false;
6657
+ }
6658
+ }
6659
+ function uninstallHook(name, scope = "global", target = "claude") {
6660
+ const shortName = shortHookName(name);
6661
+ const custom2 = readCustomManifest(shortName);
6662
+ const bundledMeta = custom2 ? undefined : getHook(shortName);
6663
+ const existsInTarget = (t) => getRegisteredHooksForTarget("global", t).includes(shortName) || getRegisteredHooksForTarget("project", t).includes(shortName);
6664
+ const registeredClaudeOrGemini = target === "all" ? existsInTarget("claude") || existsInTarget("gemini") : false;
6665
+ const registeredGlobal = registeredClaudeOrGemini || getRegisteredHooksForTarget("global", target === "all" ? "claude" : target).includes(shortName);
6666
+ const registeredProject = registeredClaudeOrGemini || getRegisteredHooksForTarget("project", target === "all" ? "claude" : target).includes(shortName);
6667
+ const codewithOnly = (target === "codewith" || target === "all") && codewithHasHookEntry(shortName);
6668
+ if (!custom2 && !bundledMeta && !registeredGlobal && !registeredProject && !codewithOnly) {
6669
+ return { name: shortName, removed: false, source: null, settingsScopes: [], storeDirRemoved: false, pinRemoved: false, dbRecordRemoved: false, registrationsRemaining: [], error: `Hook '${shortName}' not found` };
6670
+ }
6671
+ const writableTargets = target === "all" ? ["claude", "gemini"] : target === "codewith" ? [] : [target];
6672
+ const scopes = [];
6673
+ for (const s of ["global", "project"]) {
6674
+ for (const t of writableTargets) {
6675
+ if (getRegisteredHooksForTarget(s, t).includes(shortName)) {
6676
+ unregisterHook(shortName, s, t);
6677
+ if (!scopes.includes(s))
6678
+ scopes.push(s);
6679
+ }
6680
+ }
6681
+ }
6682
+ const registrationsRemaining = [];
6683
+ if (target === "codewith" || target === "all") {
6684
+ const codewithPath = getSettingsPath("global", "codewith");
6685
+ if (existsSync7(codewithPath)) {
6686
+ const before = readFileSync5(codewithPath, "utf-8");
6687
+ const after = removeCodewithHookEntry(before, shortName);
6688
+ if (after.removed) {
6689
+ writeFileSync4(codewithPath, after.text, "utf-8");
6690
+ }
6691
+ if (codewithHasHookEntryInText(after.removed ? after.text : before, shortName)) {
6692
+ registrationsRemaining.push("codewith");
6693
+ }
6694
+ }
6695
+ }
6696
+ if (custom2) {
6697
+ try {
6698
+ rmSync(customHookDir(shortName), { recursive: true, force: true });
6699
+ } catch {
6700
+ return {
6701
+ name: shortName,
6702
+ removed: false,
6703
+ source: "custom",
6704
+ settingsScopes: scopes,
6705
+ storeDirRemoved: false,
6706
+ pinRemoved: false,
6707
+ dbRecordRemoved: false,
6708
+ registrationsRemaining,
6709
+ error: `Hook '${shortName}' store directory could not be removed; trust records preserved (no fail-open)`
6710
+ };
6711
+ }
6712
+ }
6713
+ const { removedPin, removedRecord } = removeHookFromStore(shortName);
6714
+ return {
6715
+ name: shortName,
6716
+ removed: true,
6717
+ source: custom2 ? "custom" : bundledMeta ? "bundled" : "registered-only",
6718
+ settingsScopes: scopes,
6719
+ storeDirRemoved: custom2 ? true : false,
6720
+ pinRemoved: removedPin,
6721
+ dbRecordRemoved: removedRecord,
6722
+ registrationsRemaining
6723
+ };
6724
+ }
6385
6725
  // src/index.ts
6386
6726
  init_profiles();
6387
6727
  init_config();
@@ -6392,7 +6732,7 @@ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
6392
6732
  // src/lib/custom-install.ts
6393
6733
  init_config();
6394
6734
  init_manifest();
6395
- import { existsSync as existsSync9, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync2 } from "fs";
6735
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
6396
6736
  import { join as join8 } from "path";
6397
6737
  import { tmpdir } from "os";
6398
6738
  function manifestAt(dir) {
@@ -6430,7 +6770,7 @@ async function cloneToTemp(gitUrl) {
6430
6770
  proc.exited
6431
6771
  ]);
6432
6772
  if (exitCode !== 0) {
6433
- rmSync2(tmp, { recursive: true, force: true });
6773
+ rmSync3(tmp, { recursive: true, force: true });
6434
6774
  throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
6435
6775
  }
6436
6776
  return join8(tmp, "repo");
@@ -6451,7 +6791,7 @@ async function installCustomSource(source) {
6451
6791
  try {
6452
6792
  return copyManifestDir(repoDir, source, "git");
6453
6793
  } finally {
6454
- rmSync2(repoDir, { recursive: true, force: true });
6794
+ rmSync3(repoDir, { recursive: true, force: true });
6455
6795
  }
6456
6796
  }
6457
6797
  if (source.startsWith("http://") || source.startsWith("https://")) {
@@ -6523,6 +6863,7 @@ async function fetchJson(base, path) {
6523
6863
  headers["x-api-key"] = apiKey;
6524
6864
  const res = await fetch(`${base}${path}`, {
6525
6865
  headers,
6866
+ redirect: "error",
6526
6867
  signal: AbortSignal.timeout(15000)
6527
6868
  });
6528
6869
  if (res.status === 401) {
@@ -6626,11 +6967,14 @@ async function syncHooks(options = {}) {
6626
6967
  writeCustomHook(name, manifest, artifact.script, scriptRel);
6627
6968
  const scriptPath = resolveScriptPath(name);
6628
6969
  const actual = await sha256File(scriptPath);
6629
- setPinnedHook(name, { version: entry.version, sha256: actual, source: entry.source ?? "remote" });
6970
+ if (actual !== entry.sha256) {
6971
+ throw new Error(`sha256 mismatch after write for '${name}@${entry.version}': lock says ${entry.sha256}, on disk ${actual}`);
6972
+ }
6973
+ setPinnedHook(name, { version: entry.version, sha256: entry.sha256, source: entry.source ?? "remote" });
6630
6974
  upsertHookRecord(db, {
6631
6975
  name,
6632
6976
  version: entry.version,
6633
- sha256: actual,
6977
+ sha256: entry.sha256,
6634
6978
  source_type: entry.source ?? "remote",
6635
6979
  source_ref: apiUrl,
6636
6980
  last_verified_at: new Date().toISOString()
@@ -6638,6 +6982,59 @@ async function syncHooks(options = {}) {
6638
6982
  }
6639
6983
  return { apiUrl, dryRun: false, diff };
6640
6984
  }
6985
+ async function fetchPinnedHook(name, version, apiUrl) {
6986
+ const remoteLock = await fetchJson(apiUrl, "/api/v1/lock");
6987
+ const entry = remoteLock.hooks?.[name];
6988
+ if (!entry) {
6989
+ throw new Error(`Hook '${name}' is not in the remote registry lock`);
6990
+ }
6991
+ if (entry.version !== version) {
6992
+ throw new Error(`Hook '${name}' version ${version} is not in the remote registry (available: ${entry.version})`);
6993
+ }
6994
+ const artifact = await fetchJson(apiUrl, `/api/v1/hooks/${name}/${version}`);
6995
+ if (!artifact.manifest || typeof artifact.script !== "string") {
6996
+ throw new Error(`artifact for '${name}@${version}' is malformed`);
6997
+ }
6998
+ const manifestName = shortManifestName(artifact.manifest.name ?? name);
6999
+ if (manifestName !== name) {
7000
+ throw new Error(`artifact for '${name}@${version}' declares a different hook name ('${manifestName}')`);
7001
+ }
7002
+ if (artifact.manifest.version !== version) {
7003
+ throw new Error(`artifact for '${name}@${version}' declares a different version ('${artifact.manifest.version}')`);
7004
+ }
7005
+ const actualSha = sha256OfText(artifact.script);
7006
+ if (actualSha !== entry.sha256) {
7007
+ throw new Error(`sha256 mismatch for '${name}@${version}': lock says ${entry.sha256}, artifact has ${actualSha}`);
7008
+ }
7009
+ const manifest = parseManifest(JSON.stringify({ ...artifact.manifest, name: shortManifestName(artifact.manifest.name || name) }));
7010
+ const scriptRel = artifact.manifest.script.includes(`
7011
+ `) ? "script.ts" : artifact.manifest.script;
7012
+ writeCustomHook(name, manifest, artifact.script, scriptRel);
7013
+ const scriptPath = resolveScriptPath(name);
7014
+ const actual = await sha256File(scriptPath);
7015
+ if (actual !== entry.sha256) {
7016
+ throw new Error(`sha256 mismatch after write for '${name}@${version}': lock says ${entry.sha256}, on disk ${actual}`);
7017
+ }
7018
+ const db = getDb();
7019
+ setPinnedHook(name, { version, sha256: entry.sha256, source: entry.source ?? "remote" });
7020
+ upsertHookRecord(db, {
7021
+ name,
7022
+ version,
7023
+ sha256: entry.sha256,
7024
+ source_type: entry.source ?? "remote",
7025
+ source_ref: apiUrl,
7026
+ last_verified_at: new Date().toISOString()
7027
+ });
7028
+ return {
7029
+ name,
7030
+ version,
7031
+ sha256: entry.sha256,
7032
+ source: entry.source ?? "remote",
7033
+ source_ref: apiUrl,
7034
+ artifact,
7035
+ scriptPath
7036
+ };
7037
+ }
6641
7038
  // src/serve.ts
6642
7039
  init_manifest();
6643
7040
  init_store();
@@ -6897,16 +7294,68 @@ async function runHook(name, input, options = {}) {
6897
7294
  };
6898
7295
  }
6899
7296
  }
6900
- const { executeVerifiedScript: executeVerifiedScript2 } = await Promise.resolve().then(() => (init_run(), exports_run));
6901
- const { stdout: stdoutText, stderr: stderrText, exitCode } = await executeVerifiedScript2({
6902
- name,
6903
- scriptPath: script,
6904
- content,
6905
- args: custom2?.manifest.args ?? [],
6906
- stdin: JSON.stringify(hookInput),
6907
- env: process.env,
6908
- timeout: custom2?.manifest.timeout_ms
6909
- });
7297
+ const { executeVerifiedScript: executeVerifiedScript2, HookTimeoutError: HookTimeoutError2 } = await Promise.resolve().then(() => (init_run(), exports_run));
7298
+ const manifestTimeout = custom2?.manifest.timeout_ms ?? null;
7299
+ const effectiveTimeout = options.timeout !== undefined && options.timeout > 0 ? options.timeout : manifestTimeout ?? null;
7300
+ const started = Date.now();
7301
+ let stdoutText = "";
7302
+ let stderrText = "";
7303
+ let exitCode = 0;
7304
+ try {
7305
+ ({ stdout: stdoutText, stderr: stderrText, exitCode } = await executeVerifiedScript2({
7306
+ name,
7307
+ scriptPath: script,
7308
+ content,
7309
+ args: custom2?.manifest.args ?? [],
7310
+ stdin: JSON.stringify(hookInput),
7311
+ env: process.env,
7312
+ timeout: effectiveTimeout ?? undefined
7313
+ }));
7314
+ } catch (err) {
7315
+ if (err instanceof HookTimeoutError2) {
7316
+ try {
7317
+ const { recordHookRun: recordHookRun2, resolveEventType: resolveEventType2 } = await Promise.resolve().then(() => (init_db_writer(), exports_db_writer));
7318
+ recordHookRun2({
7319
+ hookName: name,
7320
+ eventType: resolveEventType2(input.hook_event_name, custom2?.manifest.events[0] ?? "PostToolUse"),
7321
+ version: custom2?.manifest.version,
7322
+ sha256: sha256Of2(content),
7323
+ sessionId: typeof input.session_id === "string" ? input.session_id : null,
7324
+ toolName: typeof input.tool_name === "string" ? input.tool_name : null,
7325
+ toolInput: input.tool_input,
7326
+ error: err.message.slice(0, 500),
7327
+ exitCode: -1,
7328
+ durationMs: Date.now() - started,
7329
+ projectDir: process.cwd()
7330
+ });
7331
+ } catch {}
7332
+ throw err;
7333
+ }
7334
+ throw err;
7335
+ }
7336
+ const durationMs = Date.now() - started;
7337
+ try {
7338
+ const { recordHookRun: recordHookRun2, resolveEventType: resolveEventType2 } = await Promise.resolve().then(() => (init_db_writer(), exports_db_writer));
7339
+ let outputJson = {};
7340
+ try {
7341
+ outputJson = JSON.parse(stdoutText);
7342
+ } catch {}
7343
+ const blocked = outputJson.decision === "block" || outputJson.continue === false;
7344
+ recordHookRun2({
7345
+ hookName: name,
7346
+ eventType: resolveEventType2(input.hook_event_name, custom2?.manifest.events[0] ?? "PostToolUse"),
7347
+ version: custom2?.manifest.version,
7348
+ sha256: sha256Of2(content),
7349
+ sessionId: typeof input.session_id === "string" ? input.session_id : null,
7350
+ toolName: typeof input.tool_name === "string" ? input.tool_name : null,
7351
+ toolInput: input.tool_input,
7352
+ result: blocked ? "block" : "continue",
7353
+ error: exitCode !== 0 ? (stderrText || `hook exited with code ${exitCode}`).slice(0, 500) : null,
7354
+ exitCode,
7355
+ durationMs,
7356
+ projectDir: process.cwd()
7357
+ });
7358
+ } catch {}
6910
7359
  let output = {};
6911
7360
  try {
6912
7361
  output = JSON.parse(stdoutText);
@@ -6922,6 +7371,7 @@ export {
6922
7371
  verifyScriptHash,
6923
7372
  upsertHookRecord,
6924
7373
  updateProfile,
7374
+ uninstallHook,
6925
7375
  touchProfile,
6926
7376
  syncHooks,
6927
7377
  storageSync,
@@ -6946,12 +7396,14 @@ export {
6946
7396
  removeProjectHook,
6947
7397
  removePinnedHook,
6948
7398
  removeHookRecord,
7399
+ removeHookFromStore,
6949
7400
  removeHook,
6950
7401
  readLock,
6951
7402
  readCustomManifest,
6952
7403
  readConfig,
6953
7404
  provisionCloudflareResources,
6954
7405
  planSync,
7406
+ pinInstalledHook,
6955
7407
  parseStorageTables,
6956
7408
  parseManifest,
6957
7409
  listProjectHooks,
@@ -6991,6 +7443,7 @@ export {
6991
7443
  getHook,
6992
7444
  getCustomHooksDir,
6993
7445
  getConfigPath,
7446
+ fetchPinnedHook,
6994
7447
  exportProfiles,
6995
7448
  deleteProfile,
6996
7449
  createProfile,