@hasna/hooks 0.7.9 → 0.7.10

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
@@ -15,6 +15,132 @@ var __export = (target, all) => {
15
15
  };
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
+ // ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
19
+ import { homedir } from "os";
20
+ import { join } from "path";
21
+ function assertApp(app) {
22
+ if (typeof app !== "string" || app.length === 0) {
23
+ throw new TypeError("paths: app must be a non-empty string");
24
+ }
25
+ if (!APP_SLUG_RE.test(app)) {
26
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
27
+ }
28
+ }
29
+ function envOf(options) {
30
+ return options.env ?? process.env;
31
+ }
32
+ function envValue(options, kind) {
33
+ const value = envOf(options)[KIND_ENV[kind]];
34
+ return typeof value === "string" && value.length > 0 ? value : undefined;
35
+ }
36
+ function isMacOS(platform) {
37
+ return platform === "darwin";
38
+ }
39
+ function baseDir(kind, options) {
40
+ const override = envValue(options, kind);
41
+ if (override)
42
+ return override;
43
+ const home = options.home ?? homedir();
44
+ const platform = options.platform ?? process.platform;
45
+ if (isMacOS(platform)) {
46
+ switch (kind) {
47
+ case "config":
48
+ case "data":
49
+ return join(home, "Library", "Application Support", "Hasna");
50
+ case "cache":
51
+ return join(home, "Library", "Caches", "Hasna");
52
+ case "state":
53
+ return join(home, "Library", "Logs", "Hasna");
54
+ }
55
+ }
56
+ switch (kind) {
57
+ case "config":
58
+ return join(home, ".config", "hasna");
59
+ case "data":
60
+ return join(home, ".local", "share", "hasna");
61
+ case "state":
62
+ return join(home, ".local", "state", "hasna");
63
+ case "cache":
64
+ return join(home, ".cache", "hasna");
65
+ }
66
+ }
67
+ function resolvePath(kind, options) {
68
+ assertApp(options.app);
69
+ const appSegment = options.internal === true ? join("internal", options.app) : options.app;
70
+ return join(baseDir(kind, options), appSegment);
71
+ }
72
+ function dataDir(options) {
73
+ return resolvePath("data", options);
74
+ }
75
+ var KIND_ENV, APP_SLUG_RE;
76
+ var init_dist = __esm(() => {
77
+ KIND_ENV = {
78
+ config: "HASNA_CONFIG_HOME",
79
+ data: "HASNA_DATA_HOME",
80
+ state: "HASNA_STATE_HOME",
81
+ cache: "HASNA_CACHE_HOME"
82
+ };
83
+ APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
84
+ });
85
+
86
+ // src/lib/app-home.ts
87
+ import { existsSync } from "fs";
88
+ import { homedir as homedir2 } from "os";
89
+ import { join as join2, resolve } from "path";
90
+ function getHomeDir(env = process.env) {
91
+ return env.HOME || env.USERPROFILE || homedir2();
92
+ }
93
+ function getResolverDataRoot(env = process.env) {
94
+ return dataDir({ app: "hooks", home: getHomeDir(env), env });
95
+ }
96
+ function getLegacyDataRoot(env = process.env) {
97
+ return join2(getHomeDir(env), ".hasna", "hooks");
98
+ }
99
+ function adoptResolverDataRoot(resolved, env = process.env) {
100
+ const dataOverride = env.HASNA_DATA_HOME;
101
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
102
+ return true;
103
+ return existsSync(join2(resolved, STORE_MARKER));
104
+ }
105
+ function getExplicitDataDir(env = process.env) {
106
+ const dir = env.HASNA_HOOKS_DATA_DIR ?? env.HOOKS_DATA_DIR;
107
+ if (typeof dir === "string" && dir.trim().length > 0)
108
+ return dir.trim();
109
+ return;
110
+ }
111
+ function getExactDataRoot(env = process.env) {
112
+ const dir = env.HASNA_HOOKS_HOME?.trim() || env.HOOKS_HOME?.trim();
113
+ if (dir)
114
+ return resolve(dir);
115
+ return;
116
+ }
117
+ function getEffectiveDataRoot(env = process.env) {
118
+ const explicit = getExplicitDataDir(env);
119
+ if (explicit)
120
+ return explicit;
121
+ const exact = getExactDataRoot(env);
122
+ if (exact)
123
+ return exact;
124
+ const resolved = getResolverDataRoot(env);
125
+ return adoptResolverDataRoot(resolved, env) ? resolve(resolved) : getLegacyDataRoot(env);
126
+ }
127
+ function getReportedDbPath(env = process.env) {
128
+ const explicit = getExplicitDbPath(env);
129
+ if (explicit)
130
+ return explicit;
131
+ return join2(getEffectiveDataRoot(env), "hooks.db");
132
+ }
133
+ function getExplicitDbPath(env = process.env) {
134
+ const db = env.HASNA_HOOKS_DB_PATH ?? env.HOOKS_DB_PATH;
135
+ if (typeof db === "string" && db.trim().length > 0)
136
+ return db.trim();
137
+ return;
138
+ }
139
+ var STORE_MARKER = "hooks.db";
140
+ var init_app_home = __esm(() => {
141
+ init_dist();
142
+ });
143
+
18
144
  // src/db/schema.ts
19
145
  var CREATE_HOOK_EVENTS_TABLE = `
20
146
  CREATE TABLE IF NOT EXISTS hook_events (
@@ -209,9 +335,9 @@ var init_migrations = __esm(() => {
209
335
  });
210
336
 
211
337
  // src/db/legacy-import.ts
212
- import { existsSync, readdirSync, readFileSync } from "fs";
213
- import { join } from "path";
214
- import { homedir } from "os";
338
+ import { existsSync as existsSync2, readdirSync, readFileSync } from "fs";
339
+ import { join as join3 } from "path";
340
+ import { homedir as homedir3 } from "os";
215
341
  function ensureMetaTable(db) {
216
342
  db.exec(`
217
343
  CREATE TABLE IF NOT EXISTS _meta (
@@ -285,25 +411,25 @@ function importErrorsLog(db, filePath) {
285
411
  } catch {}
286
412
  return count;
287
413
  }
288
- function runLegacyImport(db, homeDir = homedir()) {
414
+ function runLegacyImport(db, homeDir = homedir3()) {
289
415
  try {
290
416
  if (isAlreadyDone(db))
291
417
  return;
292
418
  let total = 0;
293
- const claudeProjectsDir = join(homeDir, ".claude", "projects");
294
- if (existsSync(claudeProjectsDir)) {
419
+ const claudeProjectsDir = join3(homeDir, ".claude", "projects");
420
+ if (existsSync2(claudeProjectsDir)) {
295
421
  try {
296
422
  const projectDirs = readdirSync(claudeProjectsDir);
297
423
  for (const dir of projectDirs) {
298
- const projectDir = join(claudeProjectsDir, dir);
424
+ const projectDir = join3(claudeProjectsDir, dir);
299
425
  try {
300
426
  const files = readdirSync(projectDir);
301
427
  for (const file of files) {
302
428
  if (file.match(/^session-log-\d{4}-\d{2}-\d{2}\.jsonl$/)) {
303
- total += importJsonlFile(db, join(projectDir, file));
429
+ total += importJsonlFile(db, join3(projectDir, file));
304
430
  }
305
431
  if (file === "errors.log") {
306
- total += importErrorsLog(db, join(projectDir, file));
432
+ total += importErrorsLog(db, join3(projectDir, file));
307
433
  }
308
434
  }
309
435
  } catch {}
@@ -339,31 +465,27 @@ function runRetention(db, days) {
339
465
 
340
466
  // src/db/index.ts
341
467
  import { Database } from "bun:sqlite";
342
- import { existsSync as existsSync2, mkdirSync, cpSync } from "fs";
343
- import { join as join2 } from "path";
344
- import { homedir as homedir2 } from "os";
468
+ import { existsSync as existsSync3, mkdirSync, cpSync } from "fs";
469
+ import { join as join4 } from "path";
345
470
  function resolveDataDir() {
346
- const explicit = process.env.HASNA_HOOKS_DATA_DIR ?? process.env.HOOKS_DATA_DIR;
347
- if (explicit)
348
- return explicit;
349
- const newDir = join2(homedir2(), ".hasna", "hooks");
350
- const oldDir = join2(homedir2(), ".hooks");
351
- if (!existsSync2(newDir) && existsSync2(oldDir)) {
352
- mkdirSync(join2(homedir2(), ".hasna"), { recursive: true });
353
- cpSync(oldDir, newDir, { recursive: true });
471
+ const effective = getEffectiveDataRoot();
472
+ const oldDir = join4(getHomeDir(), ".hooks");
473
+ if (!existsSync3(effective) && existsSync3(oldDir)) {
474
+ mkdirSync(effective, { recursive: true });
475
+ cpSync(oldDir, effective, { recursive: true });
354
476
  }
355
- return newDir;
477
+ return effective;
356
478
  }
357
479
  function getDbPath() {
358
480
  const explicitDb = process.env.HASNA_HOOKS_DB_PATH ?? process.env.HOOKS_DB_PATH;
359
481
  if (explicitDb)
360
482
  return explicitDb;
361
- const dataDir = resolveDataDir();
362
- return join2(dataDir, "hooks.db");
483
+ const dataDir2 = resolveDataDir();
484
+ return join4(dataDir2, "hooks.db");
363
485
  }
364
486
  function ensureDir(dbPath) {
365
487
  const dir = dbPath.substring(0, dbPath.lastIndexOf("/"));
366
- if (dir && !existsSync2(dir)) {
488
+ if (dir && !existsSync3(dir)) {
367
489
  mkdirSync(dir, { recursive: true });
368
490
  }
369
491
  }
@@ -371,7 +493,7 @@ function getDb() {
371
493
  if (instance)
372
494
  return instance;
373
495
  const dbPath = getDbPath();
374
- const isNew = dbPath === ":memory:" || !existsSync2(dbPath);
496
+ const isNew = dbPath === ":memory:" || !existsSync3(dbPath);
375
497
  ensureDir(dbPath);
376
498
  instance = new Database(dbPath);
377
499
  instance.exec("PRAGMA busy_timeout=5000");
@@ -395,6 +517,7 @@ function getDb() {
395
517
  }
396
518
  var instance = null;
397
519
  var init_db = __esm(() => {
520
+ init_app_home();
398
521
  init_migrations();
399
522
  init_legacy_import();
400
523
  });
@@ -4421,7 +4544,8 @@ var init_zod = __esm(() => {
4421
4544
 
4422
4545
  // ../contracts/dist/client/transport.js
4423
4546
  import { readFileSync as readFileSync2, statSync } from "fs";
4424
- import { join as join3 } from "path";
4547
+ import { createRequire } from "module";
4548
+ import { join as join5 } from "path";
4425
4549
  function envToken(name) {
4426
4550
  return name.toUpperCase().replace(/-/g, "_");
4427
4551
  }
@@ -4435,24 +4559,48 @@ function clientTransportEnvKeys(name) {
4435
4559
  function credentialOverrideEnvKey(name) {
4436
4560
  return `HASNA_${envToken(name)}_API_KEY_OVERRIDE`;
4437
4561
  }
4562
+ function credentialPointerEnvKey(name) {
4563
+ return `HASNA_${envToken(name)}_API_KEY_REF`;
4564
+ }
4438
4565
  function homeDir(env) {
4439
4566
  const home = env.HOME?.trim();
4440
4567
  return home ? home : null;
4441
4568
  }
4442
- function credentialDiskSources(name, env) {
4443
- return profileDiskSources(name, env, null);
4444
- }
4445
- function profileDiskSources(name, env, profile) {
4569
+ function credentialDiskSourceList(name, env, profile = null) {
4446
4570
  const home = homeDir(env);
4447
4571
  if (!home || !SAFE_APP_SLUG.test(name))
4448
4572
  return [];
4449
4573
  const stem = profile ? `${name}.${profile}` : name;
4450
4574
  const configStem = profile ? `${name}-${profile}` : name;
4451
4575
  return [
4452
- join3(home, HASNA_STATE_DIR, FLEET_CREDENTIAL_DIR, `${stem}.env`),
4453
- join3(home, CONFIG_DIR, CONFIG_NAMESPACE, `${configStem}-cloud.env`)
4576
+ {
4577
+ path: join5(home, HASNA_STATE_DIR, FLEET_CREDENTIAL_DIR, `${stem}.env`),
4578
+ tier: "fleet-env",
4579
+ deprecated: false
4580
+ },
4581
+ {
4582
+ path: join5(home, HASNA_STATE_DIR, LEGACY_CLOUD_DIR, `${stem}.env`),
4583
+ tier: "legacy-cloud",
4584
+ deprecated: true
4585
+ },
4586
+ {
4587
+ path: join5(home, CONFIG_DIR, CONFIG_NAMESPACE, `${configStem}.env`),
4588
+ tier: "config",
4589
+ deprecated: false
4590
+ },
4591
+ {
4592
+ path: join5(home, CONFIG_DIR, CONFIG_NAMESPACE, `${configStem}-cloud.env`),
4593
+ tier: "config-legacy",
4594
+ deprecated: true
4595
+ }
4454
4596
  ];
4455
4597
  }
4598
+ function credentialDiskSources(name, env) {
4599
+ return credentialDiskSourceList(name, env, null).map((s) => s.path);
4600
+ }
4601
+ function profileDiskSources(name, env, profile) {
4602
+ return credentialDiskSourceList(name, env, profile).map((s) => s.path);
4603
+ }
4456
4604
  function parseEnvFile(text) {
4457
4605
  const values = new Map;
4458
4606
  for (const rawLine of text.split(/\r?\n/)) {
@@ -4503,6 +4651,9 @@ function readCredentialFile(path, apiKeyKeys) {
4503
4651
  return null;
4504
4652
  }
4505
4653
  function assertUsableCredential(appName, source, value) {
4654
+ if (VAULT_POINTER_SHAPE.test(value)) {
4655
+ throw new CredentialResolutionError(appName, `The credential from ${source} looks like a secrets-vault pointer (a path-shaped reference like ` + `'namespace/app/live/api_key'). A vault path is NEVER accepted as a literal API key. ` + `Use ${credentialPointerEnvKey(appName)} to resolve the key through the vault, or provide the actual key value.`, [source]);
4656
+ }
4506
4657
  if (!ILLEGAL_IN_HEADER_VALUE.test(value))
4507
4658
  return;
4508
4659
  throw new CredentialResolutionError(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
@@ -4524,6 +4675,14 @@ function sealCredential(fields) {
4524
4675
  writable: false,
4525
4676
  configurable: false
4526
4677
  });
4678
+ if (fields.pointerVaultKey !== undefined) {
4679
+ Object.defineProperty(sealed, "pointerVaultKey", {
4680
+ value: fields.pointerVaultKey,
4681
+ enumerable: false,
4682
+ writable: false,
4683
+ configurable: false
4684
+ });
4685
+ }
4527
4686
  Object.defineProperty(sealed, INSPECT_CUSTOM, {
4528
4687
  value: () => ({ ...visible, apiKey: "[redacted]" }),
4529
4688
  enumerable: false,
@@ -4595,6 +4754,27 @@ function resolveCredential(name, env, options = {}) {
4595
4754
  warning: null
4596
4755
  });
4597
4756
  }
4757
+ const pointerKeyName = credentialPointerEnvKey(name);
4758
+ const pointerRaw = env[pointerKeyName];
4759
+ if (pointerRaw !== undefined) {
4760
+ const pointer = pointerRaw.trim();
4761
+ if (!pointer) {
4762
+ throw new CredentialResolutionError(name, `${pointerKeyName} is set but empty. It is a deliberate vault pointer, so it is not resolved around: ` + `either give it a vault item key or unset it to fall back to the credential on disk.`, [pointerKeyName]);
4763
+ }
4764
+ if (!VAULT_POINTER_SHAPE.test(pointer)) {
4765
+ throw new CredentialResolutionError(name, `${pointerKeyName} must name a vault ITEM KEY (a path-shaped reference like ` + `'namespace/app/live/api_key'), not a credential value. A pointer that carries a literal is refused.`, [pointerKeyName]);
4766
+ }
4767
+ return sealCredential({
4768
+ apiKey: "",
4769
+ pointerVaultKey: pointer,
4770
+ tier: "pointer",
4771
+ source: pointerKeyName,
4772
+ deliberate: true,
4773
+ deprecated: false,
4774
+ diskCandidates: diskPaths,
4775
+ warning: null
4776
+ });
4777
+ }
4598
4778
  const profile = options.profile?.trim() || env[CREDENTIAL_PROFILE_ENV_KEY]?.trim();
4599
4779
  if (profile) {
4600
4780
  const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY;
@@ -4619,26 +4799,42 @@ function resolveCredential(name, env, options = {}) {
4619
4799
  }
4620
4800
  throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY}.`, paths);
4621
4801
  }
4622
- const diskHits = diskPaths.map((path) => ({ path, value: readCredentialFile(path, apiKeyKeys) })).filter((hit) => hit.value !== null);
4802
+ const diskSourceList = credentialDiskSourceList(name, env, null);
4803
+ const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
4623
4804
  if (diskHits.length > 0) {
4624
4805
  const winner = diskHits[0];
4625
- assertUsableCredential(name, winner.path, winner.value);
4806
+ assertUsableCredential(name, winner.src.path, winner.value);
4626
4807
  const divergentSources = [
4627
- ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.path),
4808
+ ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
4628
4809
  ...(() => {
4629
4810
  const legacyHit = firstEnvValue(env, apiKeyKeys);
4630
4811
  return legacyHit && legacyHit.value !== winner.value ? [legacyHit.key] : [];
4631
4812
  })()
4632
4813
  ];
4633
- const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
4814
+ const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.src.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.src.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
4815
+ let deprecated = winner.src.deprecated;
4816
+ let finalWarning = warning;
4817
+ if (winner.src.deprecated) {
4818
+ deprecated = true;
4819
+ const sink = options.onDeprecation ?? defaultDeprecationSink;
4820
+ const notified = deprecationNotified();
4821
+ const noticeKey = `${name}:${winner.src.path}`;
4822
+ if (!notified.has(noticeKey)) {
4823
+ notified.add(noticeKey);
4824
+ const target = diskSourceList[0]?.path ?? "<none>";
4825
+ const message = `[${name}] DEPRECATED: the API key came from ${winner.src.path} \u2014 a legacy credential location. ` + `The primary location is ${target} (~/.hasna/fleet-env/<app>.env). The legacy 'cloud' tiers are ` + `removed after ${LEGACY_CLOUD_REMOVAL_DEADLINE}. Migrate the key to the primary location.`;
4826
+ sink(message);
4827
+ }
4828
+ finalWarning = [warning, `Legacy credential source: ${winner.src.path}. Removed after ${LEGACY_CLOUD_REMOVAL_DEADLINE}.`].filter(Boolean).join(" ") || null;
4829
+ }
4634
4830
  return sealCredential({
4635
4831
  apiKey: winner.value,
4636
- tier: "disk",
4637
- source: winner.path,
4832
+ tier: winner.src.tier,
4833
+ source: winner.src.path,
4638
4834
  deliberate: false,
4639
- deprecated: false,
4835
+ deprecated,
4640
4836
  diskCandidates: diskPaths,
4641
- warning
4837
+ warning: finalWarning
4642
4838
  });
4643
4839
  }
4644
4840
  const legacy = firstEnvValue(env, apiKeyKeys);
@@ -4664,7 +4860,7 @@ function resolveCredential(name, env, options = {}) {
4664
4860
  }
4665
4861
  return null;
4666
4862
  }
4667
- var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, HASNA_STATE_DIR = ".hasna", FLEET_CREDENTIAL_DIR = "cloud", CONFIG_DIR = ".config", CONFIG_NAMESPACE = "hasna", MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, INSPECT_CUSTOM, CREDENTIAL_SEAL, DEPRECATION_REGISTRY, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
4863
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, HASNA_STATE_DIR = ".hasna", FLEET_CREDENTIAL_DIR = "fleet-env", LEGACY_CLOUD_DIR = "cloud", CONFIG_DIR = ".config", CONFIG_NAMESPACE = "hasna", LEGACY_CLOUD_REMOVAL_DEADLINE = "2026-10-01", MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, INSPECT_CUSTOM, CREDENTIAL_SEAL, DEPRECATION_REGISTRY, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
4668
4864
  var init_transport = __esm(() => {
4669
4865
  CredentialResolutionError = class CredentialResolutionError extends Error {
4670
4866
  appName;
@@ -4680,9 +4876,12 @@ var init_transport = __esm(() => {
4680
4876
  SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
4681
4877
  SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
4682
4878
  ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
4879
+ VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
4683
4880
  INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
4684
4881
  CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
4685
4882
  DEPRECATION_REGISTRY = Symbol.for("hasna:contracts:credentialDeprecationNotices");
4883
+ SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
4884
+ requireSecretsSdk = createRequire(import.meta.url);
4686
4885
  IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
4687
4886
  AUTHORITY_OVERRIDE_HEADERS = new Set([
4688
4887
  "host",
@@ -4694,34 +4893,30 @@ var init_transport = __esm(() => {
4694
4893
  });
4695
4894
 
4696
4895
  // src/config.ts
4697
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
4698
- import { join as join4 } from "path";
4699
- import { homedir as homedir3 } from "os";
4896
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
4897
+ import { join as join6 } from "path";
4700
4898
  function getHooksDataDir() {
4701
- const explicit = process.env.HASNA_HOOKS_DATA_DIR ?? process.env.HOOKS_DATA_DIR;
4702
- if (explicit)
4703
- return explicit;
4704
- return join4(homedir3(), ".hasna", "hooks");
4899
+ return getEffectiveDataRoot();
4705
4900
  }
4706
4901
  function getCustomHooksDir() {
4707
- return join4(getHooksDataDir(), "hooks");
4902
+ return join6(getHooksDataDir(), "hooks");
4708
4903
  }
4709
4904
  function getLockPath() {
4710
4905
  const explicit = process.env.HASNA_HOOKS_LOCK_PATH ?? process.env.HOOKS_LOCK_PATH;
4711
4906
  if (explicit)
4712
4907
  return explicit;
4713
- return join4(getHooksDataDir(), "hooks.lock");
4908
+ return join6(getHooksDataDir(), "hooks.lock");
4714
4909
  }
4715
4910
  function getConfigPath() {
4716
4911
  const explicit = process.env.HASNA_HOOKS_CONFIG_PATH ?? process.env.HOOKS_CONFIG_PATH;
4717
4912
  if (explicit)
4718
4913
  return explicit;
4719
- return join4(getHooksDataDir(), "config.json");
4914
+ return join6(getHooksDataDir(), "config.json");
4720
4915
  }
4721
4916
  function readConfig() {
4722
4917
  try {
4723
4918
  const path = getConfigPath();
4724
- if (!existsSync3(path))
4919
+ if (!existsSync4(path))
4725
4920
  return {};
4726
4921
  return JSON.parse(readFileSync3(path, "utf-8"));
4727
4922
  } catch {
@@ -4730,7 +4925,7 @@ function readConfig() {
4730
4925
  }
4731
4926
  function writeConfig(config) {
4732
4927
  const path = getConfigPath();
4733
- mkdirSync2(join4(path, ".."), { recursive: true });
4928
+ mkdirSync2(join6(path, ".."), { recursive: true });
4734
4929
  writeFileSync(path, JSON.stringify(config, null, 2) + `
4735
4930
  `, "utf-8");
4736
4931
  return path;
@@ -4747,6 +4942,7 @@ function resolveApiKey() {
4747
4942
  }
4748
4943
  var init_config = __esm(() => {
4749
4944
  init_transport();
4945
+ init_app_home();
4750
4946
  });
4751
4947
 
4752
4948
  // src/lib/semver.ts
@@ -4773,8 +4969,8 @@ __export(exports_manifest, {
4773
4969
  ScriptContainmentError: () => ScriptContainmentError,
4774
4970
  HOOK_NAME_RE: () => HOOK_NAME_RE
4775
4971
  });
4776
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync, writeFileSync as writeFileSync2 } from "fs";
4777
- import { basename, dirname, isAbsolute, join as join5, normalize, resolve, sep } from "path";
4972
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync, writeFileSync as writeFileSync2 } from "fs";
4973
+ import { basename, dirname, isAbsolute, join as join7, normalize, resolve as resolve2, sep } from "path";
4778
4974
  function parseManifest(json) {
4779
4975
  const parsed = JSON.parse(json);
4780
4976
  return manifestSchema.parse(parsed);
@@ -4789,8 +4985,8 @@ function isWithin(child, parent) {
4789
4985
  return child === parent || child.startsWith(parent + sep);
4790
4986
  }
4791
4987
  function assertContainedLexical(target, root, raw) {
4792
- const lexical = resolve(target);
4793
- const rootLexical = resolve(root);
4988
+ const lexical = resolve2(target);
4989
+ const rootLexical = resolve2(root);
4794
4990
  if (!isWithin(lexical, rootLexical)) {
4795
4991
  throw new ScriptContainmentError(target, root, raw);
4796
4992
  }
@@ -4799,14 +4995,14 @@ function assertContainedLexical(target, root, raw) {
4799
4995
  function realpathNearest(path) {
4800
4996
  const tail = [];
4801
4997
  let cur = path;
4802
- while (!existsSync4(cur)) {
4998
+ while (!existsSync5(cur)) {
4803
4999
  const parent = dirname(cur);
4804
5000
  if (parent === cur)
4805
5001
  break;
4806
5002
  tail.unshift(basename(cur));
4807
5003
  cur = parent;
4808
5004
  }
4809
- return join5(realpathSync(cur), ...tail);
5005
+ return join7(realpathSync(cur), ...tail);
4810
5006
  }
4811
5007
  function assertContainedReal(root, target, raw) {
4812
5008
  const rootReal = realpathSync(root);
@@ -4839,25 +5035,25 @@ function resolveScript(manifest, manifestDir) {
4839
5035
  if (isInlineScript(manifest))
4840
5036
  return { path: scriptRelFor(manifest), content: manifest.script };
4841
5037
  const rel = scriptRelFor(manifest);
4842
- const resolved = assertContained(join5(manifestDir, rel), manifestDir, manifest.script);
4843
- if (!existsSync4(resolved)) {
5038
+ const resolved = assertContained(join7(manifestDir, rel), manifestDir, manifest.script);
5039
+ if (!existsSync5(resolved)) {
4844
5040
  throw new Error(`manifest script file not found: ${resolved}`);
4845
5041
  }
4846
5042
  return { path: rel, content: readFileSync4(resolved, "utf-8") };
4847
5043
  }
4848
5044
  function customHookDir(name) {
4849
- return join5(getCustomHooksDir(), shortManifestName(name));
5045
+ return join7(getCustomHooksDir(), shortManifestName(name));
4850
5046
  }
4851
5047
  function readCustomManifest(name) {
4852
5048
  const dir = customHookDir(name);
4853
- const manifestPath = join5(dir, "manifest.json");
4854
- if (!existsSync4(manifestPath))
5049
+ const manifestPath = join7(dir, "manifest.json");
5050
+ if (!existsSync5(manifestPath))
4855
5051
  return;
4856
5052
  try {
4857
5053
  const manifest = parseManifest(readFileSync4(manifestPath, "utf-8"));
4858
5054
  const script = resolveScript(manifest, dir);
4859
5055
  const scriptIsInline = isInlineScript(manifest);
4860
- return { manifest, scriptPath: join5(dir, script.path), scriptContent: script.content, scriptIsInline };
5056
+ return { manifest, scriptPath: join7(dir, script.path), scriptContent: script.content, scriptIsInline };
4861
5057
  } catch (err) {
4862
5058
  if (err instanceof ScriptContainmentError)
4863
5059
  throw err;
@@ -4866,7 +5062,7 @@ function readCustomManifest(name) {
4866
5062
  }
4867
5063
  function listCustomHooks() {
4868
5064
  const dir = getCustomHooksDir();
4869
- if (!existsSync4(dir))
5065
+ if (!existsSync5(dir))
4870
5066
  return [];
4871
5067
  const results = [];
4872
5068
  for (const entry of readdirSafe(dir)) {
@@ -4881,7 +5077,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
4881
5077
  if (isAbsolute(scriptRelPath)) {
4882
5078
  throw new ScriptContainmentError(scriptRelPath, dir);
4883
5079
  }
4884
- const scriptPath = scriptRelPath.includes("/") ? join5(dir, scriptRelPath) : join5(dir, scriptRelPath);
5080
+ const scriptPath = scriptRelPath.includes("/") ? join7(dir, scriptRelPath) : join7(dir, scriptRelPath);
4885
5081
  assertContainedLexical(scriptPath, dir, scriptRelPath);
4886
5082
  mkdirSync3(dir, { recursive: true });
4887
5083
  assertContainedReal(dir, scriptPath, scriptRelPath);
@@ -4889,7 +5085,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
4889
5085
  if (scriptDir !== dir)
4890
5086
  mkdirSync3(scriptDir, { recursive: true });
4891
5087
  writeFileSync2(scriptPath, scriptContent, "utf-8");
4892
- writeFileSync2(join5(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + `
5088
+ writeFileSync2(join7(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + `
4893
5089
  `, "utf-8");
4894
5090
  return { dir, scriptPath };
4895
5091
  }
@@ -4947,7 +5143,7 @@ __export(exports_store, {
4947
5143
  LockFileError: () => LockFileError
4948
5144
  });
4949
5145
  import { createHash } from "crypto";
4950
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync } from "fs";
5146
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync } from "fs";
4951
5147
  import { readFile } from "fs/promises";
4952
5148
  import { randomBytes } from "crypto";
4953
5149
  function sha256Of(input) {
@@ -5009,7 +5205,7 @@ function assertLockShape(parsed, path) {
5009
5205
  }
5010
5206
  function readLock() {
5011
5207
  const path = getLockPath();
5012
- if (!existsSync6(path))
5208
+ if (!existsSync7(path))
5013
5209
  return { hooks: {} };
5014
5210
  let raw;
5015
5211
  try {
@@ -5147,25 +5343,24 @@ __export(exports_profiles, {
5147
5343
  deleteProfile: () => deleteProfile,
5148
5344
  createProfile: () => createProfile
5149
5345
  });
5150
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync3, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
5151
- import { join as join8 } from "path";
5152
- import { homedir as homedir5 } from "os";
5346
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync3, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
5347
+ import { join as join10 } from "path";
5153
5348
  function resolveProfilesDir() {
5154
- const newDir = join8(homedir5(), ".hasna", "hooks", "profiles");
5155
- const oldDir = join8(homedir5(), ".hooks", "profiles");
5156
- if (!existsSync8(newDir) && existsSync8(oldDir)) {
5157
- mkdirSync6(join8(homedir5(), ".hasna", "hooks"), { recursive: true });
5349
+ const newDir = join10(getEffectiveDataRoot(), "profiles");
5350
+ const oldDir = join10(getHomeDir(), ".hooks", "profiles");
5351
+ if (!existsSync9(newDir) && existsSync9(oldDir)) {
5352
+ mkdirSync6(newDir, { recursive: true });
5158
5353
  cpSync2(oldDir, newDir, { recursive: true });
5159
5354
  }
5160
5355
  return newDir;
5161
5356
  }
5162
5357
  function ensureProfilesDir() {
5163
- if (!existsSync8(PROFILES_DIR)) {
5358
+ if (!existsSync9(PROFILES_DIR)) {
5164
5359
  mkdirSync6(PROFILES_DIR, { recursive: true });
5165
5360
  }
5166
5361
  }
5167
5362
  function profilePath(id) {
5168
- return join8(PROFILES_DIR, `${id}.json`);
5363
+ return join10(PROFILES_DIR, `${id}.json`);
5169
5364
  }
5170
5365
  function shortUuid() {
5171
5366
  return crypto.randomUUID().slice(0, 8);
@@ -5194,7 +5389,7 @@ function createProfile(input) {
5194
5389
  function getProfile(id) {
5195
5390
  const path = profilePath(id);
5196
5391
  try {
5197
- if (!existsSync8(path))
5392
+ if (!existsSync9(path))
5198
5393
  return null;
5199
5394
  return JSON.parse(readFileSync7(path, "utf-8"));
5200
5395
  } catch {
@@ -5202,14 +5397,14 @@ function getProfile(id) {
5202
5397
  }
5203
5398
  }
5204
5399
  function listProfiles() {
5205
- if (!existsSync8(PROFILES_DIR))
5400
+ if (!existsSync9(PROFILES_DIR))
5206
5401
  return [];
5207
5402
  try {
5208
5403
  const files = readdirSync3(PROFILES_DIR).filter((f) => f.endsWith(".json"));
5209
5404
  const profiles = [];
5210
5405
  for (const file of files) {
5211
5406
  try {
5212
- const content = readFileSync7(join8(PROFILES_DIR, file), "utf-8");
5407
+ const content = readFileSync7(join10(PROFILES_DIR, file), "utf-8");
5213
5408
  profiles.push(JSON.parse(content));
5214
5409
  } catch {}
5215
5410
  }
@@ -5232,7 +5427,7 @@ function updateProfile(id, data) {
5232
5427
  }
5233
5428
  function deleteProfile(id) {
5234
5429
  const path = profilePath(id);
5235
- if (!existsSync8(path))
5430
+ if (!existsSync9(path))
5236
5431
  return false;
5237
5432
  try {
5238
5433
  rmSync2(path);
@@ -5262,7 +5457,7 @@ function importProfiles(profiles) {
5262
5457
  continue;
5263
5458
  }
5264
5459
  const path = profilePath(profile.agent_id);
5265
- if (existsSync8(path)) {
5460
+ if (existsSync9(path)) {
5266
5461
  skipped++;
5267
5462
  continue;
5268
5463
  }
@@ -5274,12 +5469,13 @@ function importProfiles(profiles) {
5274
5469
  }
5275
5470
  var PROFILES_DIR;
5276
5471
  var init_profiles = __esm(() => {
5472
+ init_app_home();
5277
5473
  PROFILES_DIR = resolveProfilesDir();
5278
5474
  });
5279
5475
 
5280
5476
  // src/lib/hook-env.ts
5281
5477
  import { dirname as dirname5, isAbsolute as isAbsolute2 } from "path";
5282
- import { existsSync as existsSync10, statSync as statSync2 } from "fs";
5478
+ import { existsSync as existsSync11, statSync as statSync2 } from "fs";
5283
5479
  function isDeniedEnvName(name) {
5284
5480
  const upper = name.toUpperCase();
5285
5481
  if (DENY_PREFIXES.some((prefix) => upper.startsWith(prefix)))
@@ -5313,7 +5509,7 @@ function isUnsafePathEntry(entry, home) {
5313
5509
  }
5314
5510
  function sanitizeHookPath(pathValue, home) {
5315
5511
  const baseline = PATH_BASELINE.slice();
5316
- if (process.platform === "darwin" && existsSync10("/opt/homebrew/bin"))
5512
+ if (process.platform === "darwin" && existsSync11("/opt/homebrew/bin"))
5317
5513
  baseline.push("/opt/homebrew/bin");
5318
5514
  const bunDir = dirname5(process.execPath);
5319
5515
  const seen = new Set;
@@ -5439,7 +5635,7 @@ __export(exports_run, {
5439
5635
  });
5440
5636
  import { randomBytes as randomBytes2 } from "crypto";
5441
5637
  import { rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
5442
- import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join11 } from "path";
5638
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join13 } from "path";
5443
5639
  function extensionOf(scriptPath) {
5444
5640
  const base = basename2(scriptPath);
5445
5641
  const dot = base.lastIndexOf(".");
@@ -5510,7 +5706,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
5510
5706
  async function executeVerifiedScript(options) {
5511
5707
  const scriptDir = dirname6(options.scriptPath);
5512
5708
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
5513
- const tempPath = join11(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
5709
+ const tempPath = join13(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
5514
5710
  let proc = null;
5515
5711
  let timedOut = false;
5516
5712
  const timeoutTimer = options.timeout ? setTimeout(() => {
@@ -6125,6 +6321,7 @@ function coerceForSqlite(value) {
6125
6321
  return String(value);
6126
6322
  }
6127
6323
  // src/lib/registry.ts
6324
+ init_app_home();
6128
6325
  var HOOK_EVENTS = [
6129
6326
  "PreToolUse",
6130
6327
  "PostToolUse",
@@ -6462,7 +6659,7 @@ var HOOKS = [
6462
6659
  {
6463
6660
  name: "sessionlog",
6464
6661
  displayName: "Session Log",
6465
- description: "Logs every tool call to SQLite (~/.hasna/hooks/hooks.db)",
6662
+ description: `Logs every tool call to SQLite (${getReportedDbPath()})`,
6466
6663
  version: "0.1.0",
6467
6664
  category: "Observability",
6468
6665
  event: "PostToolUse",
@@ -6472,7 +6669,7 @@ var HOOKS = [
6472
6669
  {
6473
6670
  name: "commandlog",
6474
6671
  displayName: "Command Log",
6475
- description: "Logs every Bash command to SQLite (~/.hasna/hooks/hooks.db)",
6672
+ description: `Logs every Bash command to SQLite (${getReportedDbPath()})`,
6476
6673
  version: "0.1.0",
6477
6674
  category: "Observability",
6478
6675
  event: "PostToolUse",
@@ -6492,7 +6689,7 @@ var HOOKS = [
6492
6689
  {
6493
6690
  name: "errornotify",
6494
6691
  displayName: "Error Notify",
6495
- description: "Detects tool failures and logs errors to SQLite (~/.hasna/hooks/hooks.db)",
6692
+ description: `Detects tool failures and logs errors to SQLite (${getReportedDbPath()})`,
6496
6693
  version: "0.1.0",
6497
6694
  category: "Observability",
6498
6695
  event: "PostToolUse",
@@ -6674,21 +6871,21 @@ function getHook(name) {
6674
6871
  return HOOKS.find((h) => h.name === name);
6675
6872
  }
6676
6873
  // src/lib/installer.ts
6677
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync4 } from "fs";
6678
- import { join as join7, dirname as dirname3 } from "path";
6874
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync4 } from "fs";
6875
+ import { join as join9, dirname as dirname3 } from "path";
6679
6876
  import { homedir as homedir4 } from "os";
6680
6877
  import { fileURLToPath as fileURLToPath2 } from "url";
6681
6878
 
6682
6879
  // src/lib/resolve.ts
6683
- import { dirname as dirname2, join as join6 } from "path";
6684
- import { existsSync as existsSync5 } from "fs";
6880
+ import { dirname as dirname2, join as join8 } from "path";
6881
+ import { existsSync as existsSync6 } from "fs";
6685
6882
  import { fileURLToPath } from "url";
6686
6883
  init_manifest();
6687
6884
  function resolveBundledHooksDir(moduleDir = dirname2(fileURLToPath(import.meta.url))) {
6688
- const sourceLayout = join6(moduleDir, "..", "..", "hooks");
6689
- if (existsSync5(join6(sourceLayout, "hook-gitguard")))
6885
+ const sourceLayout = join8(moduleDir, "..", "..", "hooks");
6886
+ if (existsSync6(join8(sourceLayout, "hook-gitguard")))
6690
6887
  return sourceLayout;
6691
- return join6(moduleDir, "..", "hooks");
6888
+ return join8(moduleDir, "..", "hooks");
6692
6889
  }
6693
6890
  function resolveHookDir(name) {
6694
6891
  const custom2 = readCustomManifest(name);
@@ -6699,11 +6896,11 @@ function resolveHookDir(name) {
6699
6896
  return;
6700
6897
  const short = shortManifestName(name);
6701
6898
  const bundledBase = resolveBundledHooksDir();
6702
- const direct = join6(bundledBase, short);
6703
- if (existsSync5(direct))
6899
+ const direct = join8(bundledBase, short);
6900
+ if (existsSync6(direct))
6704
6901
  return direct;
6705
- const prefixed = join6(bundledBase, `hook-${short}`);
6706
- return existsSync5(prefixed) ? prefixed : undefined;
6902
+ const prefixed = join8(bundledBase, `hook-${short}`);
6903
+ return existsSync6(prefixed) ? prefixed : undefined;
6707
6904
  }
6708
6905
  function resolveScriptPath(name) {
6709
6906
  const custom2 = readCustomManifest(name);
@@ -6712,8 +6909,8 @@ function resolveScriptPath(name) {
6712
6909
  const dir = resolveHookDir(name);
6713
6910
  if (!dir)
6714
6911
  return;
6715
- const script = join6(dir, "src", "hook.ts");
6716
- return existsSync5(script) ? script : undefined;
6912
+ const script = join8(dir, "src", "hook.ts");
6913
+ return existsSync6(script) ? script : undefined;
6717
6914
  }
6718
6915
  function resolveHookMeta(name) {
6719
6916
  const custom2 = readCustomManifest(name);
@@ -6768,7 +6965,7 @@ function resolveHook(name) {
6768
6965
  init_manifest();
6769
6966
  init_store();
6770
6967
  var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
6771
- var HOOKS_DIR = existsSync7(join7(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join7(__dirname2, "..", "..", "hooks") : join7(__dirname2, "..", "hooks");
6968
+ var HOOKS_DIR = existsSync8(join9(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join9(__dirname2, "..", "..", "hooks") : join9(__dirname2, "..", "hooks");
6772
6969
  function normalizeHookName(name) {
6773
6970
  return name.startsWith("hook-") ? name : `hook-${name}`;
6774
6971
  }
@@ -6838,24 +7035,24 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
6838
7035
  return globalOverride;
6839
7036
  const dir = getTargetSettingsDir(target);
6840
7037
  if (scope === "project") {
6841
- return target === "codewith" ? join7(process.cwd(), dir, "config.toml") : join7(process.cwd(), dir, "settings.json");
7038
+ return target === "codewith" ? join9(process.cwd(), dir, "config.toml") : join9(process.cwd(), dir, "settings.json");
6842
7039
  }
6843
- return target === "codewith" ? join7(homedir4(), dir, "config.toml") : join7(homedir4(), dir, "settings.json");
7040
+ return target === "codewith" ? join9(homedir4(), dir, "config.toml") : join9(homedir4(), dir, "settings.json");
6844
7041
  }
6845
7042
  function getHookPath(name) {
6846
7043
  const shortName = shortHookName(name);
6847
7044
  const resolved = resolveHookDir(shortName);
6848
7045
  if (resolved)
6849
7046
  return resolved;
6850
- return join7(HOOKS_DIR, normalizeHookName(shortName));
7047
+ return join9(HOOKS_DIR, normalizeHookName(shortName));
6851
7048
  }
6852
7049
  function hookExists(name) {
6853
- return existsSync7(getHookPath(name));
7050
+ return existsSync8(getHookPath(name));
6854
7051
  }
6855
7052
  function readSettings(scope = "global", target = "claude") {
6856
7053
  const path = getSettingsPath(scope, target);
6857
7054
  try {
6858
- if (existsSync7(path)) {
7055
+ if (existsSync8(path)) {
6859
7056
  return JSON.parse(readFileSync6(path, "utf-8"));
6860
7057
  }
6861
7058
  } catch (error) {
@@ -6866,7 +7063,7 @@ function readSettings(scope = "global", target = "claude") {
6866
7063
  function writeSettings(settings, scope = "global", target = "claude") {
6867
7064
  const path = getSettingsPath(scope, target);
6868
7065
  const dir = dirname3(path);
6869
- if (!existsSync7(dir)) {
7066
+ if (!existsSync8(dir)) {
6870
7067
  mkdirSync5(dir, { recursive: true });
6871
7068
  }
6872
7069
  writeFileSync4(path, JSON.stringify(settings, null, 2) + `
@@ -6955,7 +7152,7 @@ function buildCodewithTomlFragment(name, profile) {
6955
7152
  function readCodewithConfig(scope, configPath) {
6956
7153
  const path = getSettingsPath(scope, "codewith", configPath);
6957
7154
  try {
6958
- return existsSync7(path) ? readFileSync6(path, "utf-8") : "";
7155
+ return existsSync8(path) ? readFileSync6(path, "utf-8") : "";
6959
7156
  } catch {
6960
7157
  return "";
6961
7158
  }
@@ -6963,9 +7160,9 @@ function readCodewithConfig(scope, configPath) {
6963
7160
  function appendCodewithFragment(fragment, scope, configPath) {
6964
7161
  const path = getSettingsPath(scope, "codewith", configPath);
6965
7162
  const dir = dirname3(path);
6966
- if (!existsSync7(dir))
7163
+ if (!existsSync8(dir))
6967
7164
  mkdirSync5(dir, { recursive: true });
6968
- const existing = existsSync7(path) ? readFileSync6(path, "utf-8") : "";
7165
+ const existing = existsSync8(path) ? readFileSync6(path, "utf-8") : "";
6969
7166
  const sep2 = existing.trim() ? `
6970
7167
 
6971
7168
  ` : "";
@@ -7266,7 +7463,7 @@ function codewithHasHookEntryInText(configText, name) {
7266
7463
  }
7267
7464
  function codewithHasHookEntry(name, scope = "global") {
7268
7465
  const path = getSettingsPath(scope, "codewith");
7269
- if (!existsSync7(path))
7466
+ if (!existsSync8(path))
7270
7467
  return false;
7271
7468
  try {
7272
7469
  return codewithHasHookEntryInText(readFileSync6(path, "utf-8"), name);
@@ -7300,7 +7497,7 @@ function uninstallHook(name, scope = "global", target = "claude") {
7300
7497
  const registrationsRemaining = [];
7301
7498
  if (target === "codewith" || target === "all") {
7302
7499
  const codewithPath = getSettingsPath(scope, "codewith");
7303
- if (existsSync7(codewithPath)) {
7500
+ if (existsSync8(codewithPath)) {
7304
7501
  const before = readFileSync6(codewithPath, "utf-8");
7305
7502
  const after = removeCodewithHookEntry(before, shortName);
7306
7503
  if (after.removed) {
@@ -7345,17 +7542,17 @@ init_profiles();
7345
7542
  init_config();
7346
7543
  init_store();
7347
7544
  init_manifest();
7348
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
7545
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
7349
7546
 
7350
7547
  // src/lib/custom-install.ts
7351
7548
  init_config();
7352
7549
  init_manifest();
7353
- import { existsSync as existsSync9, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
7354
- import { join as join9 } from "path";
7550
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
7551
+ import { join as join11 } from "path";
7355
7552
  import { tmpdir } from "os";
7356
7553
  function manifestAt(dir) {
7357
- const path = join9(dir, "manifest.json");
7358
- if (!existsSync9(path))
7554
+ const path = join11(dir, "manifest.json");
7555
+ if (!existsSync10(path))
7359
7556
  return;
7360
7557
  try {
7361
7558
  return parseManifest(readFileSync8(path, "utf-8"));
@@ -7377,8 +7574,8 @@ function writeFromManifest(manifest, srcDir, source, kind) {
7377
7574
  return { name, version: manifest.version, kind, source, dir, scriptPath };
7378
7575
  }
7379
7576
  async function cloneToTemp(gitUrl) {
7380
- const tmp = mkdtempSync(join9(tmpdir(), "hooks-install-"));
7381
- const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join9(tmp, "repo")], {
7577
+ const tmp = mkdtempSync(join11(tmpdir(), "hooks-install-"));
7578
+ const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join11(tmp, "repo")], {
7382
7579
  stdout: "pipe",
7383
7580
  stderr: "pipe"
7384
7581
  });
@@ -7391,7 +7588,7 @@ async function cloneToTemp(gitUrl) {
7391
7588
  rmSync3(tmp, { recursive: true, force: true });
7392
7589
  throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
7393
7590
  }
7394
- return join9(tmp, "repo");
7591
+ return join11(tmp, "repo");
7395
7592
  }
7396
7593
  function isGitUrl(value) {
7397
7594
  return value.startsWith("git@") || value.startsWith("ssh://") || value.startsWith("file://") || value.endsWith(".git");
@@ -7401,7 +7598,7 @@ function isCustomSource(value) {
7401
7598
  return true;
7402
7599
  if (value.startsWith("http://") || value.startsWith("https://"))
7403
7600
  return true;
7404
- return existsSync9(value);
7601
+ return existsSync10(value);
7405
7602
  }
7406
7603
  async function installCustomSource(source) {
7407
7604
  if (isGitUrl(source)) {
@@ -7415,7 +7612,7 @@ async function installCustomSource(source) {
7415
7612
  if (source.startsWith("http://") || source.startsWith("https://")) {
7416
7613
  return installManifestUrl(source);
7417
7614
  }
7418
- if (!existsSync9(source)) {
7615
+ if (!existsSync10(source)) {
7419
7616
  throw new Error(`No such local path: ${source}`);
7420
7617
  }
7421
7618
  if (source.endsWith("manifest.json")) {
@@ -7715,7 +7912,7 @@ init_manifest();
7715
7912
  init_store();
7716
7913
  init_config();
7717
7914
  import { readFileSync as readFileSync10 } from "fs";
7718
- import { dirname as dirname4, join as join10 } from "path";
7915
+ import { dirname as dirname4, join as join12 } from "path";
7719
7916
  import { fileURLToPath as fileURLToPath3 } from "url";
7720
7917
 
7721
7918
  // src/lib/secure-compare.ts
@@ -7889,7 +8086,7 @@ var SERVE_HOST = "127.0.0.1";
7889
8086
  var SERVE_SERVICE_NAME = "hooks-registry";
7890
8087
  function packageVersion() {
7891
8088
  try {
7892
- const pkgPath = join10(dirname4(fileURLToPath3(import.meta.url)), "..", "package.json");
8089
+ const pkgPath = join12(dirname4(fileURLToPath3(import.meta.url)), "..", "package.json");
7893
8090
  return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
7894
8091
  } catch {
7895
8092
  return "0.0.0";
@@ -8038,9 +8235,15 @@ function handleServeRequest(req, apiKey) {
8038
8235
  }
8039
8236
  return Promise.resolve(json({ error: "Not Found" }, 404));
8040
8237
  }
8238
+ function resolveServeOptions(options) {
8239
+ const envPort = process.env.PORT?.trim();
8240
+ const envHost = process.env.HOST?.trim();
8241
+ const port = options.port ?? (envPort && /^[0-9]+$/.test(envPort) ? parseInt(envPort, 10) : DEFAULT_SERVE_PORT);
8242
+ const host = options.host ?? (envHost && envHost.length > 0 ? envHost : SERVE_HOST);
8243
+ return { port, host };
8244
+ }
8041
8245
  function startServeServer(options) {
8042
- const port = options.port ?? DEFAULT_SERVE_PORT;
8043
- const host = options.host ?? SERVE_HOST;
8246
+ const { port, host } = resolveServeOptions(options);
8044
8247
  const apiKey = resolveApiKey();
8045
8248
  const server = Bun.serve({
8046
8249
  hostname: host,
@@ -8151,7 +8354,7 @@ async function runHook(name, input, options = {}) {
8151
8354
  throw new Error(`Hook '${name}' not found`);
8152
8355
  const hookScript = getHookPath(name) + "/src/hook.ts";
8153
8356
  const resolvedScript = custom2?.scriptPath;
8154
- const script = resolvedScript ?? (existsSync11(hookScript) ? hookScript : undefined);
8357
+ const script = resolvedScript ?? (existsSync12(hookScript) ? hookScript : undefined);
8155
8358
  if (!script)
8156
8359
  throw new Error(`Hook script not found: ${name}`);
8157
8360
  const { sha256Of: sha256Of2, checkScriptHash: checkScriptHash2 } = await Promise.resolve().then(() => (init_store(), exports_store));