@hasna/hooks 0.7.8 → 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/bin/index.js CHANGED
@@ -1120,8 +1120,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1120
1120
  args = args.slice();
1121
1121
  let launchWithNode = false;
1122
1122
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1123
- function findFile(baseDir, baseName) {
1124
- const localBin = path.resolve(baseDir, baseName);
1123
+ function findFile(baseDir2, baseName) {
1124
+ const localBin = path.resolve(baseDir2, baseName);
1125
1125
  if (fs.existsSync(localBin))
1126
1126
  return localBin;
1127
1127
  if (sourceExt.includes(path.extname(baseName)))
@@ -1886,6 +1886,132 @@ var require_commander = __commonJS((exports) => {
1886
1886
  exports.InvalidOptionArgumentError = InvalidArgumentError;
1887
1887
  });
1888
1888
 
1889
+ // ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
1890
+ import { homedir as homedir3 } from "os";
1891
+ import { join as join4 } from "path";
1892
+ function assertApp2(app) {
1893
+ if (typeof app !== "string" || app.length === 0) {
1894
+ throw new TypeError("paths: app must be a non-empty string");
1895
+ }
1896
+ if (!APP_SLUG_RE2.test(app)) {
1897
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
1898
+ }
1899
+ }
1900
+ function envOf2(options) {
1901
+ return options.env ?? process.env;
1902
+ }
1903
+ function envValue2(options, kind) {
1904
+ const value = envOf2(options)[KIND_ENV2[kind]];
1905
+ return typeof value === "string" && value.length > 0 ? value : undefined;
1906
+ }
1907
+ function isMacOS2(platform) {
1908
+ return platform === "darwin";
1909
+ }
1910
+ function baseDir2(kind, options) {
1911
+ const override = envValue2(options, kind);
1912
+ if (override)
1913
+ return override;
1914
+ const home = options.home ?? homedir3();
1915
+ const platform = options.platform ?? process.platform;
1916
+ if (isMacOS2(platform)) {
1917
+ switch (kind) {
1918
+ case "config":
1919
+ case "data":
1920
+ return join4(home, "Library", "Application Support", "Hasna");
1921
+ case "cache":
1922
+ return join4(home, "Library", "Caches", "Hasna");
1923
+ case "state":
1924
+ return join4(home, "Library", "Logs", "Hasna");
1925
+ }
1926
+ }
1927
+ switch (kind) {
1928
+ case "config":
1929
+ return join4(home, ".config", "hasna");
1930
+ case "data":
1931
+ return join4(home, ".local", "share", "hasna");
1932
+ case "state":
1933
+ return join4(home, ".local", "state", "hasna");
1934
+ case "cache":
1935
+ return join4(home, ".cache", "hasna");
1936
+ }
1937
+ }
1938
+ function resolvePath2(kind, options) {
1939
+ assertApp2(options.app);
1940
+ const appSegment = options.internal === true ? join4("internal", options.app) : options.app;
1941
+ return join4(baseDir2(kind, options), appSegment);
1942
+ }
1943
+ function dataDir2(options) {
1944
+ return resolvePath2("data", options);
1945
+ }
1946
+ var KIND_ENV2, APP_SLUG_RE2;
1947
+ var init_dist = __esm(() => {
1948
+ KIND_ENV2 = {
1949
+ config: "HASNA_CONFIG_HOME",
1950
+ data: "HASNA_DATA_HOME",
1951
+ state: "HASNA_STATE_HOME",
1952
+ cache: "HASNA_CACHE_HOME"
1953
+ };
1954
+ APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1955
+ });
1956
+
1957
+ // src/lib/app-home.ts
1958
+ import { existsSync as existsSync3 } from "fs";
1959
+ import { homedir as homedir5 } from "os";
1960
+ import { join as join6, resolve as resolve2 } from "path";
1961
+ function getHomeDir(env = process.env) {
1962
+ return env.HOME || env.USERPROFILE || homedir5();
1963
+ }
1964
+ function getResolverDataRoot(env = process.env) {
1965
+ return dataDir2({ app: "hooks", home: getHomeDir(env), env });
1966
+ }
1967
+ function getLegacyDataRoot(env = process.env) {
1968
+ return join6(getHomeDir(env), ".hasna", "hooks");
1969
+ }
1970
+ function adoptResolverDataRoot(resolved, env = process.env) {
1971
+ const dataOverride = env.HASNA_DATA_HOME;
1972
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
1973
+ return true;
1974
+ return existsSync3(join6(resolved, STORE_MARKER));
1975
+ }
1976
+ function getExplicitDataDir(env = process.env) {
1977
+ const dir = env.HASNA_HOOKS_DATA_DIR ?? env.HOOKS_DATA_DIR;
1978
+ if (typeof dir === "string" && dir.trim().length > 0)
1979
+ return dir.trim();
1980
+ return;
1981
+ }
1982
+ function getExactDataRoot(env = process.env) {
1983
+ const dir = env.HASNA_HOOKS_HOME?.trim() || env.HOOKS_HOME?.trim();
1984
+ if (dir)
1985
+ return resolve2(dir);
1986
+ return;
1987
+ }
1988
+ function getEffectiveDataRoot(env = process.env) {
1989
+ const explicit = getExplicitDataDir(env);
1990
+ if (explicit)
1991
+ return explicit;
1992
+ const exact = getExactDataRoot(env);
1993
+ if (exact)
1994
+ return exact;
1995
+ const resolved = getResolverDataRoot(env);
1996
+ return adoptResolverDataRoot(resolved, env) ? resolve2(resolved) : getLegacyDataRoot(env);
1997
+ }
1998
+ function getReportedDbPath(env = process.env) {
1999
+ const explicit = getExplicitDbPath(env);
2000
+ if (explicit)
2001
+ return explicit;
2002
+ return join6(getEffectiveDataRoot(env), "hooks.db");
2003
+ }
2004
+ function getExplicitDbPath(env = process.env) {
2005
+ const db = env.HASNA_HOOKS_DB_PATH ?? env.HOOKS_DB_PATH;
2006
+ if (typeof db === "string" && db.trim().length > 0)
2007
+ return db.trim();
2008
+ return;
2009
+ }
2010
+ var STORE_MARKER = "hooks.db";
2011
+ var init_app_home = __esm(() => {
2012
+ init_dist();
2013
+ });
2014
+
1889
2015
  // src/lib/registry.ts
1890
2016
  function getHooksByCategory(category) {
1891
2017
  return HOOKS.filter((h) => h.category === category);
@@ -1902,6 +2028,7 @@ function getHook(name) {
1902
2028
  }
1903
2029
  var CATEGORIES, HOOKS;
1904
2030
  var init_registry = __esm(() => {
2031
+ init_app_home();
1905
2032
  CATEGORIES = [
1906
2033
  "Git Safety",
1907
2034
  "Code Quality",
@@ -2229,7 +2356,7 @@ var init_registry = __esm(() => {
2229
2356
  {
2230
2357
  name: "sessionlog",
2231
2358
  displayName: "Session Log",
2232
- description: "Logs every tool call to SQLite (~/.hasna/hooks/hooks.db)",
2359
+ description: `Logs every tool call to SQLite (${getReportedDbPath()})`,
2233
2360
  version: "0.1.0",
2234
2361
  category: "Observability",
2235
2362
  event: "PostToolUse",
@@ -2239,7 +2366,7 @@ var init_registry = __esm(() => {
2239
2366
  {
2240
2367
  name: "commandlog",
2241
2368
  displayName: "Command Log",
2242
- description: "Logs every Bash command to SQLite (~/.hasna/hooks/hooks.db)",
2369
+ description: `Logs every Bash command to SQLite (${getReportedDbPath()})`,
2243
2370
  version: "0.1.0",
2244
2371
  category: "Observability",
2245
2372
  event: "PostToolUse",
@@ -2259,7 +2386,7 @@ var init_registry = __esm(() => {
2259
2386
  {
2260
2387
  name: "errornotify",
2261
2388
  displayName: "Error Notify",
2262
- description: "Detects tool failures and logs errors to SQLite (~/.hasna/hooks/hooks.db)",
2389
+ description: `Detects tool failures and logs errors to SQLite (${getReportedDbPath()})`,
2263
2390
  version: "0.1.0",
2264
2391
  category: "Observability",
2265
2392
  event: "PostToolUse",
@@ -4071,7 +4198,8 @@ var require_cli_spinners = __commonJS((exports, module) => {
4071
4198
 
4072
4199
  // ../contracts/dist/client/transport.js
4073
4200
  import { readFileSync, statSync } from "fs";
4074
- import { join as join2 } from "path";
4201
+ import { createRequire } from "module";
4202
+ import { join as join7 } from "path";
4075
4203
  function envToken(name) {
4076
4204
  return name.toUpperCase().replace(/-/g, "_");
4077
4205
  }
@@ -4085,24 +4213,48 @@ function clientTransportEnvKeys(name) {
4085
4213
  function credentialOverrideEnvKey(name) {
4086
4214
  return `HASNA_${envToken(name)}_API_KEY_OVERRIDE`;
4087
4215
  }
4216
+ function credentialPointerEnvKey(name) {
4217
+ return `HASNA_${envToken(name)}_API_KEY_REF`;
4218
+ }
4088
4219
  function homeDir(env) {
4089
4220
  const home = env.HOME?.trim();
4090
4221
  return home ? home : null;
4091
4222
  }
4092
- function credentialDiskSources(name, env) {
4093
- return profileDiskSources(name, env, null);
4094
- }
4095
- function profileDiskSources(name, env, profile) {
4223
+ function credentialDiskSourceList(name, env, profile = null) {
4096
4224
  const home = homeDir(env);
4097
4225
  if (!home || !SAFE_APP_SLUG.test(name))
4098
4226
  return [];
4099
4227
  const stem = profile ? `${name}.${profile}` : name;
4100
4228
  const configStem = profile ? `${name}-${profile}` : name;
4101
4229
  return [
4102
- join2(home, HASNA_STATE_DIR, FLEET_CREDENTIAL_DIR, `${stem}.env`),
4103
- join2(home, CONFIG_DIR, CONFIG_NAMESPACE, `${configStem}-cloud.env`)
4230
+ {
4231
+ path: join7(home, HASNA_STATE_DIR, FLEET_CREDENTIAL_DIR, `${stem}.env`),
4232
+ tier: "fleet-env",
4233
+ deprecated: false
4234
+ },
4235
+ {
4236
+ path: join7(home, HASNA_STATE_DIR, LEGACY_CLOUD_DIR, `${stem}.env`),
4237
+ tier: "legacy-cloud",
4238
+ deprecated: true
4239
+ },
4240
+ {
4241
+ path: join7(home, CONFIG_DIR, CONFIG_NAMESPACE, `${configStem}.env`),
4242
+ tier: "config",
4243
+ deprecated: false
4244
+ },
4245
+ {
4246
+ path: join7(home, CONFIG_DIR, CONFIG_NAMESPACE, `${configStem}-cloud.env`),
4247
+ tier: "config-legacy",
4248
+ deprecated: true
4249
+ }
4104
4250
  ];
4105
4251
  }
4252
+ function credentialDiskSources(name, env) {
4253
+ return credentialDiskSourceList(name, env, null).map((s) => s.path);
4254
+ }
4255
+ function profileDiskSources(name, env, profile) {
4256
+ return credentialDiskSourceList(name, env, profile).map((s) => s.path);
4257
+ }
4106
4258
  function parseEnvFile(text) {
4107
4259
  const values = new Map;
4108
4260
  for (const rawLine of text.split(/\r?\n/)) {
@@ -4153,6 +4305,9 @@ function readCredentialFile(path, apiKeyKeys) {
4153
4305
  return null;
4154
4306
  }
4155
4307
  function assertUsableCredential(appName, source, value) {
4308
+ if (VAULT_POINTER_SHAPE.test(value)) {
4309
+ 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]);
4310
+ }
4156
4311
  if (!ILLEGAL_IN_HEADER_VALUE.test(value))
4157
4312
  return;
4158
4313
  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]);
@@ -4174,6 +4329,14 @@ function sealCredential(fields) {
4174
4329
  writable: false,
4175
4330
  configurable: false
4176
4331
  });
4332
+ if (fields.pointerVaultKey !== undefined) {
4333
+ Object.defineProperty(sealed, "pointerVaultKey", {
4334
+ value: fields.pointerVaultKey,
4335
+ enumerable: false,
4336
+ writable: false,
4337
+ configurable: false
4338
+ });
4339
+ }
4177
4340
  Object.defineProperty(sealed, INSPECT_CUSTOM, {
4178
4341
  value: () => ({ ...visible, apiKey: "[redacted]" }),
4179
4342
  enumerable: false,
@@ -4245,6 +4408,27 @@ function resolveCredential(name, env, options = {}) {
4245
4408
  warning: null
4246
4409
  });
4247
4410
  }
4411
+ const pointerKeyName = credentialPointerEnvKey(name);
4412
+ const pointerRaw = env[pointerKeyName];
4413
+ if (pointerRaw !== undefined) {
4414
+ const pointer = pointerRaw.trim();
4415
+ if (!pointer) {
4416
+ 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]);
4417
+ }
4418
+ if (!VAULT_POINTER_SHAPE.test(pointer)) {
4419
+ 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]);
4420
+ }
4421
+ return sealCredential({
4422
+ apiKey: "",
4423
+ pointerVaultKey: pointer,
4424
+ tier: "pointer",
4425
+ source: pointerKeyName,
4426
+ deliberate: true,
4427
+ deprecated: false,
4428
+ diskCandidates: diskPaths,
4429
+ warning: null
4430
+ });
4431
+ }
4248
4432
  const profile = options.profile?.trim() || env[CREDENTIAL_PROFILE_ENV_KEY]?.trim();
4249
4433
  if (profile) {
4250
4434
  const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY;
@@ -4269,26 +4453,42 @@ function resolveCredential(name, env, options = {}) {
4269
4453
  }
4270
4454
  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);
4271
4455
  }
4272
- const diskHits = diskPaths.map((path) => ({ path, value: readCredentialFile(path, apiKeyKeys) })).filter((hit) => hit.value !== null);
4456
+ const diskSourceList = credentialDiskSourceList(name, env, null);
4457
+ const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
4273
4458
  if (diskHits.length > 0) {
4274
4459
  const winner = diskHits[0];
4275
- assertUsableCredential(name, winner.path, winner.value);
4460
+ assertUsableCredential(name, winner.src.path, winner.value);
4276
4461
  const divergentSources = [
4277
- ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.path),
4462
+ ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
4278
4463
  ...(() => {
4279
4464
  const legacyHit = firstEnvValue(env, apiKeyKeys);
4280
4465
  return legacyHit && legacyHit.value !== winner.value ? [legacyHit.key] : [];
4281
4466
  })()
4282
4467
  ];
4283
- 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;
4468
+ 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;
4469
+ let deprecated = winner.src.deprecated;
4470
+ let finalWarning = warning;
4471
+ if (winner.src.deprecated) {
4472
+ deprecated = true;
4473
+ const sink = options.onDeprecation ?? defaultDeprecationSink;
4474
+ const notified = deprecationNotified();
4475
+ const noticeKey = `${name}:${winner.src.path}`;
4476
+ if (!notified.has(noticeKey)) {
4477
+ notified.add(noticeKey);
4478
+ const target = diskSourceList[0]?.path ?? "<none>";
4479
+ 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.`;
4480
+ sink(message);
4481
+ }
4482
+ finalWarning = [warning, `Legacy credential source: ${winner.src.path}. Removed after ${LEGACY_CLOUD_REMOVAL_DEADLINE}.`].filter(Boolean).join(" ") || null;
4483
+ }
4284
4484
  return sealCredential({
4285
4485
  apiKey: winner.value,
4286
- tier: "disk",
4287
- source: winner.path,
4486
+ tier: winner.src.tier,
4487
+ source: winner.src.path,
4288
4488
  deliberate: false,
4289
- deprecated: false,
4489
+ deprecated,
4290
4490
  diskCandidates: diskPaths,
4291
- warning
4491
+ warning: finalWarning
4292
4492
  });
4293
4493
  }
4294
4494
  const legacy = firstEnvValue(env, apiKeyKeys);
@@ -4314,7 +4514,7 @@ function resolveCredential(name, env, options = {}) {
4314
4514
  }
4315
4515
  return null;
4316
4516
  }
4317
- 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;
4517
+ 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;
4318
4518
  var init_transport = __esm(() => {
4319
4519
  CredentialResolutionError = class CredentialResolutionError extends Error {
4320
4520
  appName;
@@ -4330,9 +4530,12 @@ var init_transport = __esm(() => {
4330
4530
  SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
4331
4531
  SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
4332
4532
  ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
4533
+ VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
4333
4534
  INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
4334
4535
  CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
4335
4536
  DEPRECATION_REGISTRY = Symbol.for("hasna:contracts:credentialDeprecationNotices");
4537
+ SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
4538
+ requireSecretsSdk = createRequire(import.meta.url);
4336
4539
  IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
4337
4540
  AUTHORITY_OVERRIDE_HEADERS = new Set([
4338
4541
  "host",
@@ -4355,34 +4558,30 @@ __export(exports_config, {
4355
4558
  getCustomHooksDir: () => getCustomHooksDir,
4356
4559
  getConfigPath: () => getConfigPath
4357
4560
  });
4358
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
4359
- import { join as join3 } from "path";
4360
- import { homedir as homedir2 } from "os";
4561
+ import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
4562
+ import { join as join8 } from "path";
4361
4563
  function getHooksDataDir() {
4362
- const explicit = process.env.HASNA_HOOKS_DATA_DIR ?? process.env.HOOKS_DATA_DIR;
4363
- if (explicit)
4364
- return explicit;
4365
- return join3(homedir2(), ".hasna", "hooks");
4564
+ return getEffectiveDataRoot();
4366
4565
  }
4367
4566
  function getCustomHooksDir() {
4368
- return join3(getHooksDataDir(), "hooks");
4567
+ return join8(getHooksDataDir(), "hooks");
4369
4568
  }
4370
4569
  function getLockPath() {
4371
4570
  const explicit = process.env.HASNA_HOOKS_LOCK_PATH ?? process.env.HOOKS_LOCK_PATH;
4372
4571
  if (explicit)
4373
4572
  return explicit;
4374
- return join3(getHooksDataDir(), "hooks.lock");
4573
+ return join8(getHooksDataDir(), "hooks.lock");
4375
4574
  }
4376
4575
  function getConfigPath() {
4377
4576
  const explicit = process.env.HASNA_HOOKS_CONFIG_PATH ?? process.env.HOOKS_CONFIG_PATH;
4378
4577
  if (explicit)
4379
4578
  return explicit;
4380
- return join3(getHooksDataDir(), "config.json");
4579
+ return join8(getHooksDataDir(), "config.json");
4381
4580
  }
4382
4581
  function readConfig() {
4383
4582
  try {
4384
4583
  const path = getConfigPath();
4385
- if (!existsSync2(path))
4584
+ if (!existsSync5(path))
4386
4585
  return {};
4387
4586
  return JSON.parse(readFileSync2(path, "utf-8"));
4388
4587
  } catch {
@@ -4391,7 +4590,7 @@ function readConfig() {
4391
4590
  }
4392
4591
  function writeConfig(config) {
4393
4592
  const path = getConfigPath();
4394
- mkdirSync(join3(path, ".."), { recursive: true });
4593
+ mkdirSync(join8(path, ".."), { recursive: true });
4395
4594
  writeFileSync(path, JSON.stringify(config, null, 2) + `
4396
4595
  `, "utf-8");
4397
4596
  return path;
@@ -4408,6 +4607,7 @@ function resolveApiKey() {
4408
4607
  }
4409
4608
  var init_config = __esm(() => {
4410
4609
  init_transport();
4610
+ init_app_home();
4411
4611
  });
4412
4612
 
4413
4613
  // src/lib/semver.ts
@@ -4434,8 +4634,8 @@ __export(exports_manifest, {
4434
4634
  ScriptContainmentError: () => ScriptContainmentError,
4435
4635
  HOOK_NAME_RE: () => HOOK_NAME_RE
4436
4636
  });
4437
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync, realpathSync, writeFileSync as writeFileSync2 } from "fs";
4438
- import { basename, dirname, isAbsolute, join as join4, normalize, resolve, sep } from "path";
4637
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync, realpathSync, writeFileSync as writeFileSync2 } from "fs";
4638
+ import { basename, dirname, isAbsolute, join as join9, normalize, resolve as resolve3, sep } from "path";
4439
4639
  import { z } from "zod";
4440
4640
  function parseManifest(json) {
4441
4641
  const parsed = JSON.parse(json);
@@ -4451,8 +4651,8 @@ function isWithin(child, parent) {
4451
4651
  return child === parent || child.startsWith(parent + sep);
4452
4652
  }
4453
4653
  function assertContainedLexical(target, root, raw) {
4454
- const lexical = resolve(target);
4455
- const rootLexical = resolve(root);
4654
+ const lexical = resolve3(target);
4655
+ const rootLexical = resolve3(root);
4456
4656
  if (!isWithin(lexical, rootLexical)) {
4457
4657
  throw new ScriptContainmentError(target, root, raw);
4458
4658
  }
@@ -4461,14 +4661,14 @@ function assertContainedLexical(target, root, raw) {
4461
4661
  function realpathNearest(path) {
4462
4662
  const tail = [];
4463
4663
  let cur = path;
4464
- while (!existsSync3(cur)) {
4664
+ while (!existsSync6(cur)) {
4465
4665
  const parent = dirname(cur);
4466
4666
  if (parent === cur)
4467
4667
  break;
4468
4668
  tail.unshift(basename(cur));
4469
4669
  cur = parent;
4470
4670
  }
4471
- return join4(realpathSync(cur), ...tail);
4671
+ return join9(realpathSync(cur), ...tail);
4472
4672
  }
4473
4673
  function assertContainedReal(root, target, raw) {
4474
4674
  const rootReal = realpathSync(root);
@@ -4501,25 +4701,25 @@ function resolveScript(manifest, manifestDir) {
4501
4701
  if (isInlineScript(manifest))
4502
4702
  return { path: scriptRelFor(manifest), content: manifest.script };
4503
4703
  const rel = scriptRelFor(manifest);
4504
- const resolved = assertContained(join4(manifestDir, rel), manifestDir, manifest.script);
4505
- if (!existsSync3(resolved)) {
4704
+ const resolved = assertContained(join9(manifestDir, rel), manifestDir, manifest.script);
4705
+ if (!existsSync6(resolved)) {
4506
4706
  throw new Error(`manifest script file not found: ${resolved}`);
4507
4707
  }
4508
4708
  return { path: rel, content: readFileSync3(resolved, "utf-8") };
4509
4709
  }
4510
4710
  function customHookDir(name) {
4511
- return join4(getCustomHooksDir(), shortManifestName(name));
4711
+ return join9(getCustomHooksDir(), shortManifestName(name));
4512
4712
  }
4513
4713
  function readCustomManifest(name) {
4514
4714
  const dir = customHookDir(name);
4515
- const manifestPath = join4(dir, "manifest.json");
4516
- if (!existsSync3(manifestPath))
4715
+ const manifestPath = join9(dir, "manifest.json");
4716
+ if (!existsSync6(manifestPath))
4517
4717
  return;
4518
4718
  try {
4519
4719
  const manifest = parseManifest(readFileSync3(manifestPath, "utf-8"));
4520
4720
  const script = resolveScript(manifest, dir);
4521
4721
  const scriptIsInline = isInlineScript(manifest);
4522
- return { manifest, scriptPath: join4(dir, script.path), scriptContent: script.content, scriptIsInline };
4722
+ return { manifest, scriptPath: join9(dir, script.path), scriptContent: script.content, scriptIsInline };
4523
4723
  } catch (err) {
4524
4724
  if (err instanceof ScriptContainmentError)
4525
4725
  throw err;
@@ -4528,7 +4728,7 @@ function readCustomManifest(name) {
4528
4728
  }
4529
4729
  function listCustomHooks() {
4530
4730
  const dir = getCustomHooksDir();
4531
- if (!existsSync3(dir))
4731
+ if (!existsSync6(dir))
4532
4732
  return [];
4533
4733
  const results = [];
4534
4734
  for (const entry of readdirSafe(dir)) {
@@ -4543,7 +4743,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
4543
4743
  if (isAbsolute(scriptRelPath)) {
4544
4744
  throw new ScriptContainmentError(scriptRelPath, dir);
4545
4745
  }
4546
- const scriptPath = scriptRelPath.includes("/") ? join4(dir, scriptRelPath) : join4(dir, scriptRelPath);
4746
+ const scriptPath = scriptRelPath.includes("/") ? join9(dir, scriptRelPath) : join9(dir, scriptRelPath);
4547
4747
  assertContainedLexical(scriptPath, dir, scriptRelPath);
4548
4748
  mkdirSync2(dir, { recursive: true });
4549
4749
  assertContainedReal(dir, scriptPath, scriptRelPath);
@@ -4551,7 +4751,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
4551
4751
  if (scriptDir !== dir)
4552
4752
  mkdirSync2(scriptDir, { recursive: true });
4553
4753
  writeFileSync2(scriptPath, scriptContent, "utf-8");
4554
- writeFileSync2(join4(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + `
4754
+ writeFileSync2(join9(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + `
4555
4755
  `, "utf-8");
4556
4756
  return { dir, scriptPath };
4557
4757
  }
@@ -4595,14 +4795,14 @@ __export(exports_resolve, {
4595
4795
  resolveHook: () => resolveHook,
4596
4796
  resolveBundledHooksDir: () => resolveBundledHooksDir
4597
4797
  });
4598
- import { dirname as dirname2, join as join5 } from "path";
4599
- import { existsSync as existsSync4 } from "fs";
4798
+ import { dirname as dirname2, join as join10 } from "path";
4799
+ import { existsSync as existsSync7 } from "fs";
4600
4800
  import { fileURLToPath } from "url";
4601
4801
  function resolveBundledHooksDir(moduleDir = dirname2(fileURLToPath(import.meta.url))) {
4602
- const sourceLayout = join5(moduleDir, "..", "..", "hooks");
4603
- if (existsSync4(join5(sourceLayout, "hook-gitguard")))
4802
+ const sourceLayout = join10(moduleDir, "..", "..", "hooks");
4803
+ if (existsSync7(join10(sourceLayout, "hook-gitguard")))
4604
4804
  return sourceLayout;
4605
- return join5(moduleDir, "..", "hooks");
4805
+ return join10(moduleDir, "..", "hooks");
4606
4806
  }
4607
4807
  function resolveHookDir(name) {
4608
4808
  const custom = readCustomManifest(name);
@@ -4613,11 +4813,11 @@ function resolveHookDir(name) {
4613
4813
  return;
4614
4814
  const short = shortManifestName(name);
4615
4815
  const bundledBase = resolveBundledHooksDir();
4616
- const direct = join5(bundledBase, short);
4617
- if (existsSync4(direct))
4816
+ const direct = join10(bundledBase, short);
4817
+ if (existsSync7(direct))
4618
4818
  return direct;
4619
- const prefixed = join5(bundledBase, `hook-${short}`);
4620
- return existsSync4(prefixed) ? prefixed : undefined;
4819
+ const prefixed = join10(bundledBase, `hook-${short}`);
4820
+ return existsSync7(prefixed) ? prefixed : undefined;
4621
4821
  }
4622
4822
  function resolveScriptPath(name) {
4623
4823
  const custom = readCustomManifest(name);
@@ -4626,8 +4826,8 @@ function resolveScriptPath(name) {
4626
4826
  const dir = resolveHookDir(name);
4627
4827
  if (!dir)
4628
4828
  return;
4629
- const script = join5(dir, "src", "hook.ts");
4630
- return existsSync4(script) ? script : undefined;
4829
+ const script = join10(dir, "src", "hook.ts");
4830
+ return existsSync7(script) ? script : undefined;
4631
4831
  }
4632
4832
  function resolveHookMeta(name) {
4633
4833
  const custom = readCustomManifest(name);
@@ -4876,9 +5076,9 @@ var init_migrations = __esm(() => {
4876
5076
  });
4877
5077
 
4878
5078
  // src/db/legacy-import.ts
4879
- import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
4880
- import { join as join6 } from "path";
4881
- import { homedir as homedir3 } from "os";
5079
+ import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
5080
+ import { join as join11 } from "path";
5081
+ import { homedir as homedir6 } from "os";
4882
5082
  function ensureMetaTable(db) {
4883
5083
  db.exec(`
4884
5084
  CREATE TABLE IF NOT EXISTS _meta (
@@ -4952,25 +5152,25 @@ function importErrorsLog(db, filePath) {
4952
5152
  } catch {}
4953
5153
  return count;
4954
5154
  }
4955
- function runLegacyImport(db, homeDir2 = homedir3()) {
5155
+ function runLegacyImport(db, homeDir2 = homedir6()) {
4956
5156
  try {
4957
5157
  if (isAlreadyDone(db))
4958
5158
  return;
4959
5159
  let total = 0;
4960
- const claudeProjectsDir = join6(homeDir2, ".claude", "projects");
4961
- if (existsSync5(claudeProjectsDir)) {
5160
+ const claudeProjectsDir = join11(homeDir2, ".claude", "projects");
5161
+ if (existsSync8(claudeProjectsDir)) {
4962
5162
  try {
4963
5163
  const projectDirs = readdirSync2(claudeProjectsDir);
4964
5164
  for (const dir of projectDirs) {
4965
- const projectDir = join6(claudeProjectsDir, dir);
5165
+ const projectDir = join11(claudeProjectsDir, dir);
4966
5166
  try {
4967
5167
  const files = readdirSync2(projectDir);
4968
5168
  for (const file of files) {
4969
5169
  if (file.match(/^session-log-\d{4}-\d{2}-\d{2}\.jsonl$/)) {
4970
- total += importJsonlFile(db, join6(projectDir, file));
5170
+ total += importJsonlFile(db, join11(projectDir, file));
4971
5171
  }
4972
5172
  if (file === "errors.log") {
4973
- total += importErrorsLog(db, join6(projectDir, file));
5173
+ total += importErrorsLog(db, join11(projectDir, file));
4974
5174
  }
4975
5175
  }
4976
5176
  } catch {}
@@ -5013,31 +5213,27 @@ __export(exports_db, {
5013
5213
  closeDb: () => closeDb
5014
5214
  });
5015
5215
  import { Database } from "bun:sqlite";
5016
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, cpSync } from "fs";
5017
- import { join as join7 } from "path";
5018
- import { homedir as homedir4 } from "os";
5216
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, cpSync } from "fs";
5217
+ import { join as join12 } from "path";
5019
5218
  function resolveDataDir() {
5020
- const explicit = process.env.HASNA_HOOKS_DATA_DIR ?? process.env.HOOKS_DATA_DIR;
5021
- if (explicit)
5022
- return explicit;
5023
- const newDir = join7(homedir4(), ".hasna", "hooks");
5024
- const oldDir = join7(homedir4(), ".hooks");
5025
- if (!existsSync6(newDir) && existsSync6(oldDir)) {
5026
- mkdirSync3(join7(homedir4(), ".hasna"), { recursive: true });
5027
- cpSync(oldDir, newDir, { recursive: true });
5219
+ const effective = getEffectiveDataRoot();
5220
+ const oldDir = join12(getHomeDir(), ".hooks");
5221
+ if (!existsSync9(effective) && existsSync9(oldDir)) {
5222
+ mkdirSync3(effective, { recursive: true });
5223
+ cpSync(oldDir, effective, { recursive: true });
5028
5224
  }
5029
- return newDir;
5225
+ return effective;
5030
5226
  }
5031
5227
  function getDbPath() {
5032
5228
  const explicitDb = process.env.HASNA_HOOKS_DB_PATH ?? process.env.HOOKS_DB_PATH;
5033
5229
  if (explicitDb)
5034
5230
  return explicitDb;
5035
- const dataDir = resolveDataDir();
5036
- return join7(dataDir, "hooks.db");
5231
+ const dataDir3 = resolveDataDir();
5232
+ return join12(dataDir3, "hooks.db");
5037
5233
  }
5038
5234
  function ensureDir(dbPath) {
5039
5235
  const dir = dbPath.substring(0, dbPath.lastIndexOf("/"));
5040
- if (dir && !existsSync6(dir)) {
5236
+ if (dir && !existsSync9(dir)) {
5041
5237
  mkdirSync3(dir, { recursive: true });
5042
5238
  }
5043
5239
  }
@@ -5045,7 +5241,7 @@ function getDb() {
5045
5241
  if (instance)
5046
5242
  return instance;
5047
5243
  const dbPath = getDbPath();
5048
- const isNew = dbPath === ":memory:" || !existsSync6(dbPath);
5244
+ const isNew = dbPath === ":memory:" || !existsSync9(dbPath);
5049
5245
  ensureDir(dbPath);
5050
5246
  instance = new Database(dbPath);
5051
5247
  instance.exec("PRAGMA busy_timeout=5000");
@@ -5081,6 +5277,7 @@ function createTestDb() {
5081
5277
  }
5082
5278
  var instance = null;
5083
5279
  var init_db = __esm(() => {
5280
+ init_app_home();
5084
5281
  init_migrations();
5085
5282
  init_legacy_import();
5086
5283
  });
@@ -5107,7 +5304,7 @@ __export(exports_store, {
5107
5304
  LockFileError: () => LockFileError
5108
5305
  });
5109
5306
  import { createHash } from "crypto";
5110
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync } from "fs";
5307
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync } from "fs";
5111
5308
  import { readFile as readFile2 } from "fs/promises";
5112
5309
  import { randomBytes } from "crypto";
5113
5310
  function sha256Of(input) {
@@ -5169,7 +5366,7 @@ function assertLockShape(parsed, path) {
5169
5366
  }
5170
5367
  function readLock() {
5171
5368
  const path = getLockPath();
5172
- if (!existsSync7(path))
5369
+ if (!existsSync10(path))
5173
5370
  return { hooks: {} };
5174
5371
  let raw;
5175
5372
  try {
@@ -5311,9 +5508,9 @@ __export(exports_installer, {
5311
5508
  getHookPath: () => getHookPath,
5312
5509
  buildCodewithTomlFragment: () => buildCodewithTomlFragment
5313
5510
  });
5314
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync4 } from "fs";
5315
- import { join as join8, dirname as dirname3 } from "path";
5316
- import { homedir as homedir5 } from "os";
5511
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync4 } from "fs";
5512
+ import { join as join13, dirname as dirname3 } from "path";
5513
+ import { homedir as homedir7 } from "os";
5317
5514
  import { fileURLToPath as fileURLToPath2 } from "url";
5318
5515
  function normalizeHookName(name) {
5319
5516
  return name.startsWith("hook-") ? name : `hook-${name}`;
@@ -5352,24 +5549,24 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
5352
5549
  return globalOverride;
5353
5550
  const dir = getTargetSettingsDir(target);
5354
5551
  if (scope === "project") {
5355
- return target === "codewith" ? join8(process.cwd(), dir, "config.toml") : join8(process.cwd(), dir, "settings.json");
5552
+ return target === "codewith" ? join13(process.cwd(), dir, "config.toml") : join13(process.cwd(), dir, "settings.json");
5356
5553
  }
5357
- return target === "codewith" ? join8(homedir5(), dir, "config.toml") : join8(homedir5(), dir, "settings.json");
5554
+ return target === "codewith" ? join13(homedir7(), dir, "config.toml") : join13(homedir7(), dir, "settings.json");
5358
5555
  }
5359
5556
  function getHookPath(name) {
5360
5557
  const shortName = shortHookName(name);
5361
5558
  const resolved = resolveHookDir(shortName);
5362
5559
  if (resolved)
5363
5560
  return resolved;
5364
- return join8(HOOKS_DIR, normalizeHookName(shortName));
5561
+ return join13(HOOKS_DIR, normalizeHookName(shortName));
5365
5562
  }
5366
5563
  function hookExists(name) {
5367
- return existsSync8(getHookPath(name));
5564
+ return existsSync11(getHookPath(name));
5368
5565
  }
5369
5566
  function readSettings(scope = "global", target = "claude") {
5370
5567
  const path = getSettingsPath(scope, target);
5371
5568
  try {
5372
- if (existsSync8(path)) {
5569
+ if (existsSync11(path)) {
5373
5570
  return JSON.parse(readFileSync6(path, "utf-8"));
5374
5571
  }
5375
5572
  } catch (error) {
@@ -5380,7 +5577,7 @@ function readSettings(scope = "global", target = "claude") {
5380
5577
  function writeSettings(settings, scope = "global", target = "claude") {
5381
5578
  const path = getSettingsPath(scope, target);
5382
5579
  const dir = dirname3(path);
5383
- if (!existsSync8(dir)) {
5580
+ if (!existsSync11(dir)) {
5384
5581
  mkdirSync5(dir, { recursive: true });
5385
5582
  }
5386
5583
  writeFileSync4(path, JSON.stringify(settings, null, 2) + `
@@ -5469,7 +5666,7 @@ function buildCodewithTomlFragment(name, profile) {
5469
5666
  function readCodewithConfig(scope, configPath) {
5470
5667
  const path = getSettingsPath(scope, "codewith", configPath);
5471
5668
  try {
5472
- return existsSync8(path) ? readFileSync6(path, "utf-8") : "";
5669
+ return existsSync11(path) ? readFileSync6(path, "utf-8") : "";
5473
5670
  } catch {
5474
5671
  return "";
5475
5672
  }
@@ -5477,9 +5674,9 @@ function readCodewithConfig(scope, configPath) {
5477
5674
  function appendCodewithFragment(fragment, scope, configPath) {
5478
5675
  const path = getSettingsPath(scope, "codewith", configPath);
5479
5676
  const dir = dirname3(path);
5480
- if (!existsSync8(dir))
5677
+ if (!existsSync11(dir))
5481
5678
  mkdirSync5(dir, { recursive: true });
5482
- const existing = existsSync8(path) ? readFileSync6(path, "utf-8") : "";
5679
+ const existing = existsSync11(path) ? readFileSync6(path, "utf-8") : "";
5483
5680
  const sep2 = existing.trim() ? `
5484
5681
 
5485
5682
  ` : "";
@@ -5779,7 +5976,7 @@ function codewithHasHookEntryInText(configText, name) {
5779
5976
  }
5780
5977
  function codewithHasHookEntry(name, scope = "global") {
5781
5978
  const path = getSettingsPath(scope, "codewith");
5782
- if (!existsSync8(path))
5979
+ if (!existsSync11(path))
5783
5980
  return false;
5784
5981
  try {
5785
5982
  return codewithHasHookEntryInText(readFileSync6(path, "utf-8"), name);
@@ -5813,7 +6010,7 @@ function uninstallHook(name, scope = "global", target = "claude") {
5813
6010
  const registrationsRemaining = [];
5814
6011
  if (target === "codewith" || target === "all") {
5815
6012
  const codewithPath = getSettingsPath(scope, "codewith");
5816
- if (existsSync8(codewithPath)) {
6013
+ if (existsSync11(codewithPath)) {
5817
6014
  const before = readFileSync6(codewithPath, "utf-8");
5818
6015
  const after = removeCodewithHookEntry(before, shortName);
5819
6016
  if (after.removed) {
@@ -5860,7 +6057,7 @@ var init_installer = __esm(() => {
5860
6057
  init_manifest();
5861
6058
  init_store();
5862
6059
  __dirname2 = dirname3(fileURLToPath2(import.meta.url));
5863
- HOOKS_DIR = existsSync8(join8(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join8(__dirname2, "..", "..", "hooks") : join8(__dirname2, "..", "hooks");
6060
+ HOOKS_DIR = existsSync11(join13(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join13(__dirname2, "..", "..", "hooks") : join13(__dirname2, "..", "hooks");
5864
6061
  EVENT_MAP = {
5865
6062
  claude: {
5866
6063
  PreToolUse: "PreToolUse",
@@ -6007,29 +6204,31 @@ var init_redact = __esm(() => {
6007
6204
  });
6008
6205
 
6009
6206
  // src/lib/profiles.ts
6010
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync3, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
6011
- import { join as join9 } from "path";
6012
- import { homedir as homedir6 } from "os";
6207
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync3, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
6208
+ import { join as join14 } from "path";
6013
6209
  function resolveProfilesDir() {
6014
- const newDir = join9(homedir6(), ".hasna", "hooks", "profiles");
6015
- const oldDir = join9(homedir6(), ".hooks", "profiles");
6016
- if (!existsSync9(newDir) && existsSync9(oldDir)) {
6017
- mkdirSync6(join9(homedir6(), ".hasna", "hooks"), { recursive: true });
6210
+ const newDir = join14(getEffectiveDataRoot(), "profiles");
6211
+ const oldDir = join14(getHomeDir(), ".hooks", "profiles");
6212
+ if (!existsSync12(newDir) && existsSync12(oldDir)) {
6213
+ mkdirSync6(newDir, { recursive: true });
6018
6214
  cpSync2(oldDir, newDir, { recursive: true });
6019
6215
  }
6020
6216
  return newDir;
6021
6217
  }
6022
6218
  function ensureProfilesDir() {
6023
- if (!existsSync9(PROFILES_DIR)) {
6219
+ if (!existsSync12(PROFILES_DIR)) {
6024
6220
  mkdirSync6(PROFILES_DIR, { recursive: true });
6025
6221
  }
6026
6222
  }
6027
6223
  function profilePath(id) {
6028
- return join9(PROFILES_DIR, `${id}.json`);
6224
+ return join14(PROFILES_DIR, `${id}.json`);
6029
6225
  }
6030
6226
  function shortUuid() {
6031
6227
  return crypto.randomUUID().slice(0, 8);
6032
6228
  }
6229
+ function getProfilesDir() {
6230
+ return PROFILES_DIR;
6231
+ }
6033
6232
  function createProfile(input) {
6034
6233
  ensureProfilesDir();
6035
6234
  const id = shortUuid();
@@ -6051,7 +6250,7 @@ function createProfile(input) {
6051
6250
  function getProfile(id) {
6052
6251
  const path = profilePath(id);
6053
6252
  try {
6054
- if (!existsSync9(path))
6253
+ if (!existsSync12(path))
6055
6254
  return null;
6056
6255
  return JSON.parse(readFileSync7(path, "utf-8"));
6057
6256
  } catch {
@@ -6059,14 +6258,14 @@ function getProfile(id) {
6059
6258
  }
6060
6259
  }
6061
6260
  function listProfiles() {
6062
- if (!existsSync9(PROFILES_DIR))
6261
+ if (!existsSync12(PROFILES_DIR))
6063
6262
  return [];
6064
6263
  try {
6065
6264
  const files = readdirSync3(PROFILES_DIR).filter((f) => f.endsWith(".json"));
6066
6265
  const profiles = [];
6067
6266
  for (const file of files) {
6068
6267
  try {
6069
- const content = readFileSync7(join9(PROFILES_DIR, file), "utf-8");
6268
+ const content = readFileSync7(join14(PROFILES_DIR, file), "utf-8");
6070
6269
  profiles.push(JSON.parse(content));
6071
6270
  } catch {}
6072
6271
  }
@@ -6096,7 +6295,7 @@ function importProfiles(profiles) {
6096
6295
  continue;
6097
6296
  }
6098
6297
  const path = profilePath(profile.agent_id);
6099
- if (existsSync9(path)) {
6298
+ if (existsSync12(path)) {
6100
6299
  skipped++;
6101
6300
  continue;
6102
6301
  }
@@ -6108,6 +6307,7 @@ function importProfiles(profiles) {
6108
6307
  }
6109
6308
  var PROFILES_DIR;
6110
6309
  var init_profiles = __esm(() => {
6310
+ init_app_home();
6111
6311
  PROFILES_DIR = resolveProfilesDir();
6112
6312
  });
6113
6313
 
@@ -6209,7 +6409,7 @@ var init_db_writer = __esm(() => {
6209
6409
 
6210
6410
  // src/lib/hook-env.ts
6211
6411
  import { dirname as dirname4, isAbsolute as isAbsolute2 } from "path";
6212
- import { existsSync as existsSync10, statSync as statSync2 } from "fs";
6412
+ import { existsSync as existsSync13, statSync as statSync2 } from "fs";
6213
6413
  function isDeniedEnvName(name) {
6214
6414
  const upper = name.toUpperCase();
6215
6415
  if (DENY_PREFIXES.some((prefix) => upper.startsWith(prefix)))
@@ -6243,7 +6443,7 @@ function isUnsafePathEntry(entry, home) {
6243
6443
  }
6244
6444
  function sanitizeHookPath(pathValue, home) {
6245
6445
  const baseline = PATH_BASELINE.slice();
6246
- if (process.platform === "darwin" && existsSync10("/opt/homebrew/bin"))
6446
+ if (process.platform === "darwin" && existsSync13("/opt/homebrew/bin"))
6247
6447
  baseline.push("/opt/homebrew/bin");
6248
6448
  const bunDir = dirname4(process.execPath);
6249
6449
  const seen = new Set;
@@ -6369,7 +6569,7 @@ __export(exports_run, {
6369
6569
  });
6370
6570
  import { randomBytes as randomBytes2 } from "crypto";
6371
6571
  import { rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
6372
- import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute3, join as join10 } from "path";
6572
+ import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute3, join as join15 } from "path";
6373
6573
  function extensionOf(scriptPath) {
6374
6574
  const base = basename2(scriptPath);
6375
6575
  const dot = base.lastIndexOf(".");
@@ -6440,7 +6640,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
6440
6640
  async function executeVerifiedScript(options) {
6441
6641
  const scriptDir = dirname5(options.scriptPath);
6442
6642
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
6443
- const tempPath = join10(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
6643
+ const tempPath = join15(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
6444
6644
  let proc = null;
6445
6645
  let timedOut = false;
6446
6646
  const timeoutTimer = options.timeout ? setTimeout(() => {
@@ -6531,12 +6731,12 @@ __export(exports_custom_install, {
6531
6731
  installCustomSource: () => installCustomSource,
6532
6732
  ensureCustomDir: () => ensureCustomDir
6533
6733
  });
6534
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
6535
- import { join as join11 } from "path";
6734
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
6735
+ import { join as join16 } from "path";
6536
6736
  import { tmpdir } from "os";
6537
6737
  function manifestAt(dir) {
6538
- const path = join11(dir, "manifest.json");
6539
- if (!existsSync11(path))
6738
+ const path = join16(dir, "manifest.json");
6739
+ if (!existsSync14(path))
6540
6740
  return;
6541
6741
  try {
6542
6742
  return parseManifest(readFileSync8(path, "utf-8"));
@@ -6558,8 +6758,8 @@ function writeFromManifest(manifest, srcDir, source, kind) {
6558
6758
  return { name, version: manifest.version, kind, source, dir, scriptPath };
6559
6759
  }
6560
6760
  async function cloneToTemp(gitUrl) {
6561
- const tmp = mkdtempSync(join11(tmpdir(), "hooks-install-"));
6562
- const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join11(tmp, "repo")], {
6761
+ const tmp = mkdtempSync(join16(tmpdir(), "hooks-install-"));
6762
+ const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join16(tmp, "repo")], {
6563
6763
  stdout: "pipe",
6564
6764
  stderr: "pipe"
6565
6765
  });
@@ -6572,7 +6772,7 @@ async function cloneToTemp(gitUrl) {
6572
6772
  rmSync4(tmp, { recursive: true, force: true });
6573
6773
  throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
6574
6774
  }
6575
- return join11(tmp, "repo");
6775
+ return join16(tmp, "repo");
6576
6776
  }
6577
6777
  function isGitUrl(value) {
6578
6778
  return value.startsWith("git@") || value.startsWith("ssh://") || value.startsWith("file://") || value.endsWith(".git");
@@ -6582,7 +6782,7 @@ function isCustomSource(value) {
6582
6782
  return true;
6583
6783
  if (value.startsWith("http://") || value.startsWith("https://"))
6584
6784
  return true;
6585
- return existsSync11(value);
6785
+ return existsSync14(value);
6586
6786
  }
6587
6787
  async function installCustomSource(source) {
6588
6788
  if (isGitUrl(source)) {
@@ -6596,7 +6796,7 @@ async function installCustomSource(source) {
6596
6796
  if (source.startsWith("http://") || source.startsWith("https://")) {
6597
6797
  return installManifestUrl(source);
6598
6798
  }
6599
- if (!existsSync11(source)) {
6799
+ if (!existsSync14(source)) {
6600
6800
  throw new Error(`No such local path: ${source}`);
6601
6801
  }
6602
6802
  if (source.endsWith("manifest.json")) {
@@ -7558,17 +7758,18 @@ var init_openapi = __esm(() => {
7558
7758
  var exports_serve = {};
7559
7759
  __export(exports_serve, {
7560
7760
  startServeServer: () => startServeServer,
7761
+ resolveServeOptions: () => resolveServeOptions,
7561
7762
  handleServeRequest: () => handleServeRequest,
7562
7763
  SERVE_SERVICE_NAME: () => SERVE_SERVICE_NAME,
7563
7764
  SERVE_HOST: () => SERVE_HOST,
7564
7765
  DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
7565
7766
  });
7566
7767
  import { readFileSync as readFileSync10 } from "fs";
7567
- import { dirname as dirname6, join as join12 } from "path";
7768
+ import { dirname as dirname6, join as join17 } from "path";
7568
7769
  import { fileURLToPath as fileURLToPath3 } from "url";
7569
7770
  function packageVersion() {
7570
7771
  try {
7571
- const pkgPath = join12(dirname6(fileURLToPath3(import.meta.url)), "..", "package.json");
7772
+ const pkgPath = join17(dirname6(fileURLToPath3(import.meta.url)), "..", "package.json");
7572
7773
  return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
7573
7774
  } catch {
7574
7775
  return "0.0.0";
@@ -7717,9 +7918,15 @@ function handleServeRequest(req, apiKey) {
7717
7918
  }
7718
7919
  return Promise.resolve(json({ error: "Not Found" }, 404));
7719
7920
  }
7921
+ function resolveServeOptions(options) {
7922
+ const envPort = process.env.PORT?.trim();
7923
+ const envHost = process.env.HOST?.trim();
7924
+ const port = options.port ?? (envPort && /^[0-9]+$/.test(envPort) ? parseInt(envPort, 10) : DEFAULT_SERVE_PORT);
7925
+ const host = options.host ?? (envHost && envHost.length > 0 ? envHost : SERVE_HOST);
7926
+ return { port, host };
7927
+ }
7720
7928
  function startServeServer(options) {
7721
- const port = options.port ?? DEFAULT_SERVE_PORT;
7722
- const host = options.host ?? SERVE_HOST;
7929
+ const { port, host } = resolveServeOptions(options);
7723
7930
  const apiKey = resolveApiKey();
7724
7931
  const server = Bun.serve({
7725
7932
  hostname: host,
@@ -7839,14 +8046,14 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
7839
8046
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7840
8047
  import { z as z2 } from "zod";
7841
8048
  import { createServer } from "http";
7842
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
7843
- import { join as join13, dirname as dirname7 } from "path";
8049
+ import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
8050
+ import { join as join18, dirname as dirname7 } from "path";
7844
8051
  import { fileURLToPath as fileURLToPath4 } from "url";
7845
8052
  async function runVerifiedHook(name, stdin, timeoutMs) {
7846
8053
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
7847
8054
  const resolved = resolveHook2(name);
7848
8055
  const hookScript = resolveScriptPath2(name);
7849
- if (!resolved || !hookScript || !existsSync12(hookScript)) {
8056
+ if (!resolved || !hookScript || !existsSync15(hookScript)) {
7850
8057
  throw new Error(`Hook '${name}' not found`);
7851
8058
  }
7852
8059
  const content = readFileSync11(hookScript);
@@ -8054,7 +8261,7 @@ function createHooksServer() {
8054
8261
  const settingsPath = getSettingsPath(scope);
8055
8262
  const issues = [];
8056
8263
  const healthy = [];
8057
- const settingsExist = existsSync12(settingsPath);
8264
+ const settingsExist = existsSync15(settingsPath);
8058
8265
  if (!settingsExist) {
8059
8266
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
8060
8267
  }
@@ -8064,7 +8271,7 @@ function createHooksServer() {
8064
8271
  const meta = custom ? resolveHookMeta(name) : getHook(name);
8065
8272
  let hookHealthy = true;
8066
8273
  if (custom) {
8067
- if (!existsSync12(custom.scriptPath)) {
8274
+ if (!existsSync15(custom.scriptPath)) {
8068
8275
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
8069
8276
  hookHealthy = false;
8070
8277
  }
@@ -8074,7 +8281,7 @@ function createHooksServer() {
8074
8281
  continue;
8075
8282
  }
8076
8283
  const hookDir = getHookPath(name);
8077
- if (!existsSync12(join13(hookDir, "src", "hook.ts"))) {
8284
+ if (!existsSync15(join18(hookDir, "src", "hook.ts"))) {
8078
8285
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
8079
8286
  hookHealthy = false;
8080
8287
  }
@@ -8111,9 +8318,9 @@ function createHooksServer() {
8111
8318
  return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
8112
8319
  }
8113
8320
  const hookPath = getHookPath(name);
8114
- const readmePath = join13(hookPath, "README.md");
8321
+ const readmePath = join18(hookPath, "README.md");
8115
8322
  let readme = "";
8116
- if (existsSync12(readmePath)) {
8323
+ if (existsSync15(readmePath)) {
8117
8324
  readme = readFileSync11(readmePath, "utf-8");
8118
8325
  }
8119
8326
  if (verbose) {
@@ -8297,7 +8504,7 @@ function createHooksServer() {
8297
8504
  const ctx = {
8298
8505
  scope,
8299
8506
  settings_path: settingsPath,
8300
- settings_exists: existsSync12(settingsPath),
8507
+ settings_exists: existsSync15(settingsPath),
8301
8508
  registered_hooks: hooks,
8302
8509
  hook_count: hooks.length,
8303
8510
  healthy,
@@ -8443,7 +8650,7 @@ function createHooksServer() {
8443
8650
  const settingsPath = getSettingsPath(scope);
8444
8651
  let settings = {};
8445
8652
  try {
8446
- if (existsSync12(settingsPath))
8653
+ if (existsSync15(settingsPath))
8447
8654
  settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
8448
8655
  } catch {}
8449
8656
  if (!settings.hooks)
@@ -8466,7 +8673,7 @@ function createHooksServer() {
8466
8673
  const settingsPath = getSettingsPath(scope);
8467
8674
  let settings = {};
8468
8675
  try {
8469
- if (existsSync12(settingsPath))
8676
+ if (existsSync15(settingsPath))
8470
8677
  settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
8471
8678
  } catch {}
8472
8679
  if (settings.hooks?.__disabled) {
@@ -8831,8 +9038,8 @@ var init_server = __esm(() => {
8831
9038
  pkg = { name: "@hasna/hooks", version: "0.0.0" };
8832
9039
  try {
8833
9040
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
8834
- const p = join13(__dirname3, rel);
8835
- if (existsSync12(p)) {
9041
+ const p = join18(__dirname3, rel);
9042
+ if (existsSync15(p)) {
8836
9043
  pkg = JSON.parse(readFileSync11(p, "utf-8"));
8837
9044
  break;
8838
9045
  }
@@ -8904,7 +9111,11 @@ var init_http = () => {};
8904
9111
  // ../events/dist/commander.js
8905
9112
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
8906
9113
  import { Buffer as Buffer2 } from "buffer";
9114
+ import { existsSync as existsSync2 } from "fs";
9115
+ import { join as join3 } from "path";
8907
9116
  import { existsSync } from "fs";
9117
+ import { homedir as homedir2 } from "os";
9118
+ import { join as join2, resolve } from "path";
8908
9119
  import { homedir } from "os";
8909
9120
  import { join } from "path";
8910
9121
  import { createHmac, timingSafeEqual } from "crypto";
@@ -9010,13 +9221,106 @@ function channelMatchesEvent(channel, event) {
9010
9221
  return true;
9011
9222
  return channel.filters.some((filter) => eventMatchesFilter(event, filter));
9012
9223
  }
9224
+ var KIND_ENV = {
9225
+ config: "HASNA_CONFIG_HOME",
9226
+ data: "HASNA_DATA_HOME",
9227
+ state: "HASNA_STATE_HOME",
9228
+ cache: "HASNA_CACHE_HOME"
9229
+ };
9230
+ var APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
9231
+ function assertApp(app) {
9232
+ if (typeof app !== "string" || app.length === 0) {
9233
+ throw new TypeError("paths: app must be a non-empty string");
9234
+ }
9235
+ if (!APP_SLUG_RE.test(app)) {
9236
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
9237
+ }
9238
+ }
9239
+ function envOf(options) {
9240
+ return options.env ?? process.env;
9241
+ }
9242
+ function envValue(options, kind) {
9243
+ const value = envOf(options)[KIND_ENV[kind]];
9244
+ return typeof value === "string" && value.length > 0 ? value : undefined;
9245
+ }
9246
+ function isMacOS(platform) {
9247
+ return platform === "darwin";
9248
+ }
9249
+ function baseDir(kind, options) {
9250
+ const override = envValue(options, kind);
9251
+ if (override)
9252
+ return override;
9253
+ const home = options.home ?? homedir();
9254
+ const platform = options.platform ?? process.platform;
9255
+ if (isMacOS(platform)) {
9256
+ switch (kind) {
9257
+ case "config":
9258
+ case "data":
9259
+ return join(home, "Library", "Application Support", "Hasna");
9260
+ case "cache":
9261
+ return join(home, "Library", "Caches", "Hasna");
9262
+ case "state":
9263
+ return join(home, "Library", "Logs", "Hasna");
9264
+ }
9265
+ }
9266
+ switch (kind) {
9267
+ case "config":
9268
+ return join(home, ".config", "hasna");
9269
+ case "data":
9270
+ return join(home, ".local", "share", "hasna");
9271
+ case "state":
9272
+ return join(home, ".local", "state", "hasna");
9273
+ case "cache":
9274
+ return join(home, ".cache", "hasna");
9275
+ }
9276
+ }
9277
+ function resolvePath(kind, options) {
9278
+ assertApp(options.app);
9279
+ const appSegment = options.internal === true ? join("internal", options.app) : options.app;
9280
+ return join(baseDir(kind, options), appSegment);
9281
+ }
9282
+ function dataDir(options) {
9283
+ return resolvePath("data", options);
9284
+ }
9013
9285
  var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
9014
9286
  var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
9287
+ var EVENTS_STORE_SENTINEL_FILE = "events.json";
9288
+ function effectiveHome() {
9289
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
9290
+ }
9291
+ function legacyHomeDir() {
9292
+ return join2(effectiveHome(), ".hasna", "events");
9293
+ }
9294
+ function resolverHome() {
9295
+ return dataDir({ app: "events", home: effectiveHome() || undefined });
9296
+ }
9297
+ function adoptResolverHome(resolved, env = process.env) {
9298
+ const dataOverride = env.HASNA_DATA_HOME;
9299
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
9300
+ return true;
9301
+ return existsSync(join2(resolved, EVENTS_STORE_SENTINEL_FILE));
9302
+ }
9303
+ function exactEventsHome() {
9304
+ const dir = process.env[HASNA_EVENTS_DIR_ENV];
9305
+ if (dir && dir.trim())
9306
+ return dir.trim();
9307
+ const home = process.env[HASNA_EVENTS_HOME_ENV];
9308
+ if (home && home.trim())
9309
+ return home.trim();
9310
+ return;
9311
+ }
9312
+ function getEventsHome() {
9313
+ const exact = exactEventsHome();
9314
+ if (exact)
9315
+ return resolve(exact);
9316
+ const resolved = resolverHome();
9317
+ return adoptResolverHome(resolved) ? resolve(resolved) : resolve(legacyHomeDir());
9318
+ }
9015
9319
  var LOCAL_JSON_EVENT_CURSOR_PREFIX = "local-json-v1:";
9016
9320
  var DEFAULT_EVENT_PAGE_LIMIT = 100;
9017
9321
  var MAX_EVENT_PAGE_LIMIT = 1000;
9018
9322
  function getEventsDataDir(override) {
9019
- return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
9323
+ return override || getEventsHome();
9020
9324
  }
9021
9325
  function getActiveEventsDirEnv() {
9022
9326
  if (process.env[HASNA_EVENTS_DIR_ENV])
@@ -9032,12 +9336,12 @@ class JsonEventsStore {
9032
9336
  channelsPath;
9033
9337
  eventsPath;
9034
9338
  deliveriesPath;
9035
- constructor(dataDir = getEventsDataDir()) {
9036
- this.dataDir = dataDir;
9037
- this.runtime = localJsonRuntime(dataDir);
9038
- this.channelsPath = join(dataDir, "channels.json");
9039
- this.eventsPath = join(dataDir, "events.json");
9040
- this.deliveriesPath = join(dataDir, "deliveries.json");
9339
+ constructor(dataDir2 = getEventsDataDir()) {
9340
+ this.dataDir = dataDir2;
9341
+ this.runtime = localJsonRuntime(dataDir2);
9342
+ this.channelsPath = join3(dataDir2, "channels.json");
9343
+ this.eventsPath = join3(dataDir2, "events.json");
9344
+ this.deliveriesPath = join3(dataDir2, "deliveries.json");
9041
9345
  }
9042
9346
  async init() {
9043
9347
  await mkdir(this.dataDir, { recursive: true, mode: 448 });
@@ -9154,7 +9458,7 @@ class JsonEventsStore {
9154
9458
  };
9155
9459
  }
9156
9460
  async ensureArrayFile(path) {
9157
- if (!existsSync(path)) {
9461
+ if (!existsSync2(path)) {
9158
9462
  await writeFile(path, `[]
9159
9463
  `, { encoding: "utf-8", mode: 384 });
9160
9464
  }
@@ -9184,7 +9488,7 @@ class JsonEventsStore {
9184
9488
  });
9185
9489
  }
9186
9490
  }
9187
- function localJsonRuntime(dataDir = getEventsDataDir()) {
9491
+ function localJsonRuntime(dataDir2 = getEventsDataDir()) {
9188
9492
  return {
9189
9493
  mode: "local-files",
9190
9494
  name: "json-events-store",
@@ -9197,7 +9501,7 @@ function localJsonRuntime(dataDir = getEventsDataDir()) {
9197
9501
  durable: true,
9198
9502
  idempotency: "best-effort-local",
9199
9503
  replayCursors: true,
9200
- description: `Local JSON files in ${dataDir}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
9504
+ description: `Local JSON files in ${dataDir2}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
9201
9505
  };
9202
9506
  }
9203
9507
  function encodeLocalJsonEventCursor(offset, options = {}) {
@@ -9261,8 +9565,8 @@ function assertCursorFilter(name, cursorValue, optionValue) {
9261
9565
  function findEventByIdentity(events, identity) {
9262
9566
  return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
9263
9567
  }
9264
- async function getEventsStatus(dataDir) {
9265
- const store = new JsonEventsStore(dataDir);
9568
+ async function getEventsStatus(dataDir2) {
9569
+ const store = new JsonEventsStore(dataDir2);
9266
9570
  await store.init();
9267
9571
  const [channels, events, deliveries] = await Promise.all([
9268
9572
  store.listChannels(),
@@ -9304,9 +9608,9 @@ async function getEventsStatus(dataDir) {
9304
9608
  }
9305
9609
  };
9306
9610
  }
9307
- function statusFile(dataDir, fileName, records) {
9308
- const path = join(dataDir, fileName);
9309
- return { path, exists: existsSync(path), records };
9611
+ function statusFile(dataDir2, fileName, records) {
9612
+ const path = join3(dataDir2, fileName);
9613
+ return { path, exists: existsSync2(path), records };
9310
9614
  }
9311
9615
  var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
9312
9616
  function buildSignatureBase(timestamp, body) {
@@ -9645,7 +9949,7 @@ async function pinnedNativeRequest(target, addresses, method, headers, body, sig
9645
9949
  callback(null, entries);
9646
9950
  }
9647
9951
  };
9648
- return new Promise((resolve, reject) => {
9952
+ return new Promise((resolve2, reject) => {
9649
9953
  const request = isHttps ? nodeHttpsRequest(requestOptions, onResponse) : nodeHttpRequest(requestOptions, onResponse);
9650
9954
  const onAbort = () => {
9651
9955
  const error = new Error("The operation was aborted.");
@@ -9672,7 +9976,7 @@ async function pinnedNativeRequest(target, addresses, method, headers, body, sig
9672
9976
  else if (Array.isArray(value))
9673
9977
  headersRecord[name] = value.join(", ");
9674
9978
  }
9675
- resolve(new Response(Buffer.concat(chunks), { status: response.statusCode ?? 200, headers: headersRecord }));
9979
+ resolve2(new Response(Buffer.concat(chunks), { status: response.statusCode ?? 200, headers: headersRecord }));
9676
9980
  });
9677
9981
  }
9678
9982
  });
@@ -9769,7 +10073,7 @@ async function dispatchCommand(event, channel) {
9769
10073
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
9770
10074
  HASNA_EVENT_JSON: eventJson
9771
10075
  };
9772
- return new Promise((resolve) => {
10076
+ return new Promise((resolve2) => {
9773
10077
  const child = spawn(channel.command.command, channel.command.args ?? [], {
9774
10078
  cwd: channel.command.cwd,
9775
10079
  env,
@@ -9787,7 +10091,7 @@ async function dispatchCommand(event, channel) {
9787
10091
  });
9788
10092
  child.on("error", (error) => {
9789
10093
  clearTimeout(timeout);
9790
- resolve({
10094
+ resolve2({
9791
10095
  attempt: 1,
9792
10096
  status: "failed",
9793
10097
  startedAt,
@@ -9800,7 +10104,7 @@ async function dispatchCommand(event, channel) {
9800
10104
  child.on("close", (code, signal) => {
9801
10105
  clearTimeout(timeout);
9802
10106
  const success = code === 0;
9803
- resolve({
10107
+ resolve2({
9804
10108
  attempt: 1,
9805
10109
  status: success ? "success" : "failed",
9806
10110
  startedAt,
@@ -10455,8 +10759,8 @@ var {
10455
10759
 
10456
10760
  // src/cli/index.tsx
10457
10761
  import chalk2 from "chalk";
10458
- import { existsSync as existsSync13, readFileSync as readFileSync12, rmSync as rmSync5 } from "fs";
10459
- import { join as join14, dirname as dirname8 } from "path";
10762
+ import { existsSync as existsSync16, readFileSync as readFileSync12, rmSync as rmSync5 } from "fs";
10763
+ import { join as join19, dirname as dirname8 } from "path";
10460
10764
  import { fileURLToPath as fileURLToPath5 } from "url";
10461
10765
 
10462
10766
  // src/cli/components/App.tsx
@@ -11637,9 +11941,11 @@ init_manifest();
11637
11941
  init_resolve();
11638
11942
  init_store();
11639
11943
  init_semver();
11944
+ init_app_home();
11945
+ init_config();
11640
11946
  import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
11641
11947
  var __dirname4 = dirname8(fileURLToPath5(import.meta.url));
11642
- var pkgPath = existsSync13(join14(__dirname4, "..", "package.json")) ? join14(__dirname4, "..", "package.json") : join14(__dirname4, "..", "..", "package.json");
11948
+ var pkgPath = existsSync16(join19(__dirname4, "..", "package.json")) ? join19(__dirname4, "..", "package.json") : join19(__dirname4, "..", "..", "package.json");
11643
11949
  var pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
11644
11950
  var program2 = new Command;
11645
11951
  function resolveScope(options) {
@@ -11791,7 +12097,7 @@ program2.command("init").description("Register a new agent profile with a unique
11791
12097
  if (profile.name) {
11792
12098
  console.log(` ${chalk2.dim("Name:")} ${profile.name}`);
11793
12099
  }
11794
- console.log(` ${chalk2.dim("Profile:")} ~/.hasna/hooks/profiles/${profile.agent_id}.json`);
12100
+ console.log(` ${chalk2.dim("Profile:")} ${join19(getProfilesDir(), `${profile.agent_id}.json`)}`);
11795
12101
  console.log();
11796
12102
  console.log(chalk2.dim(" Install hooks with this profile:"));
11797
12103
  console.log(` hooks install gitguard --profile ${profile.agent_id}`);
@@ -11807,7 +12113,7 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
11807
12113
  process.exit(1);
11808
12114
  }
11809
12115
  const hookScript = resolveScriptPath2(hook);
11810
- if (!hookScript || !existsSync13(hookScript)) {
12116
+ if (!hookScript || !existsSync16(hookScript)) {
11811
12117
  console.error(JSON.stringify({ error: `Hook script not found: ${hookScript ?? hook}` }));
11812
12118
  process.exit(1);
11813
12119
  }
@@ -12345,7 +12651,7 @@ program2.command("info").argument("<hook>", "Hook name").option("-j, --json", "O
12345
12651
  const { readCustomManifest: readCustomManifest2 } = await Promise.resolve().then(() => (init_manifest(), exports_manifest));
12346
12652
  const custom = readCustomManifest2(meta.name);
12347
12653
  const source = custom ? "custom" : "bundled";
12348
- const sourceNote = custom ? "custom (~/.hasna/hooks/hooks/) overrides the bundled registry" : "bundled registry";
12654
+ const sourceNote = custom ? `custom (${getCustomHooksDir()}/) overrides the bundled registry` : "bundled registry";
12349
12655
  if (options.json) {
12350
12656
  console.log(JSON.stringify({ ...meta, source, source_note: sourceNote, global: globalInstalled, project: projectInstalled }));
12351
12657
  return;
@@ -12379,7 +12685,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
12379
12685
  const settingsPath = getSettingsPath(scope);
12380
12686
  const issues = [];
12381
12687
  const healthy = [];
12382
- const settingsExist = existsSync13(settingsPath);
12688
+ const settingsExist = existsSync16(settingsPath);
12383
12689
  if (!settingsExist) {
12384
12690
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
12385
12691
  }
@@ -12396,7 +12702,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
12396
12702
  const meta = custom ? resolveHookMeta(name) : getHook(name);
12397
12703
  let hookHealthy = true;
12398
12704
  if (custom) {
12399
- if (!existsSync13(custom.scriptPath)) {
12705
+ if (!existsSync16(custom.scriptPath)) {
12400
12706
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
12401
12707
  hookHealthy = false;
12402
12708
  }
@@ -12407,8 +12713,8 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
12407
12713
  continue;
12408
12714
  }
12409
12715
  const hookDir = getHookPath(name);
12410
- const hookScript = join14(hookDir, "src", "hook.ts");
12411
- if (!existsSync13(hookScript)) {
12716
+ const hookScript = join19(hookDir, "src", "hook.ts");
12717
+ if (!existsSync16(hookScript)) {
12412
12718
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
12413
12719
  hookHealthy = false;
12414
12720
  }
@@ -12582,9 +12888,9 @@ program2.command("docs").argument("[hook]", "Hook name (shows general docs if om
12582
12888
  return;
12583
12889
  }
12584
12890
  const hookPath = getHookPath(hook);
12585
- const readmePath = join14(hookPath, "README.md");
12891
+ const readmePath = join19(hookPath, "README.md");
12586
12892
  let readme = "";
12587
- if (existsSync13(readmePath)) {
12893
+ if (existsSync16(readmePath)) {
12588
12894
  readme = readFileSync12(readmePath, "utf-8");
12589
12895
  }
12590
12896
  if (options.json) {
@@ -12815,7 +13121,7 @@ program2.command("profile-import").argument("<file>", "JSON file to import profi
12815
13121
  console.log(chalk2.dim(` Skipped ${result.skipped} (already exist or invalid)`));
12816
13122
  }
12817
13123
  });
12818
- var logCmd = program2.command("log").description("Query hook event logs from SQLite (~/.hasna/hooks/hooks.db)");
13124
+ var logCmd = program2.command("log").description(`Query hook event logs from SQLite (${getReportedDbPath()})`);
12819
13125
  logCmd.command("list").description("List hook events").option("--hook <name>", "Filter by hook name").option("--session <id>", "Filter by session ID").option("-n, --limit <n>", "Number of rows to show", "50").option("-j, --json", "Output as JSON", false).action(async (options) => {
12820
13126
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
12821
13127
  const db = getDb2();
@@ -13164,6 +13470,29 @@ Cloudflare provisioning complete:
13164
13470
  console.log(chalk2.dim(" wrangler.toml, fill in the D1 database id, then run the commands above."));
13165
13471
  console.log();
13166
13472
  });
13473
+ program2.command("migrate").description("Apply PostgreSQL migrations to the storage database (HASNA_HOOKS_DATABASE_URL)").option("-j, --json", "Output as JSON", false).action(async (options) => {
13474
+ try {
13475
+ const { getStoragePg: getStoragePg2, runStorageMigrations: runStorageMigrations2 } = await Promise.resolve().then(() => (init_storage(), exports_storage));
13476
+ const remote = await getStoragePg2();
13477
+ try {
13478
+ await runStorageMigrations2(remote);
13479
+ } finally {
13480
+ await remote.close();
13481
+ }
13482
+ if (options.json) {
13483
+ console.log(JSON.stringify({ ok: true }));
13484
+ return;
13485
+ }
13486
+ console.log(chalk2.green("\u2713 PostgreSQL migrations applied"));
13487
+ } catch (error) {
13488
+ const message = error instanceof Error ? error.message : String(error);
13489
+ if (options.json)
13490
+ console.log(JSON.stringify({ error: message }));
13491
+ else
13492
+ console.error(chalk2.red(`\u2717 ${message}`));
13493
+ process.exitCode = 1;
13494
+ }
13495
+ });
13167
13496
  program2.command("mcp").option("-s, --stdio", "Use stdio transport (one process per agent)", false).option("--sse", "Use legacy SSE transport (port 39427)", false).option("--http", "Use Streamable HTTP transport (explicit; this is also the default)", false).option("-p, --port <port>", "Port for HTTP/SSE transport (defaults to 8847 for HTTP, 39427 for SSE)").description("Start MCP server for AI agent integration (default: shared Streamable HTTP)").action(async (options) => {
13168
13497
  if (options.stdio) {
13169
13498
  const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_server(), exports_server));