@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/bin/index.js +483 -177
- package/bin/serve.js +281 -82
- package/dist/db/index.d.ts +3 -1
- package/dist/index.js +334 -131
- package/dist/lib/app-home.d.ts +65 -0
- package/dist/lib/profiles.d.ts +4 -1
- package/dist/serve.d.ts +7 -0
- package/dist/storage.js +148 -25
- package/package.json +6 -4
- package/scripts/artifact-scan.ts +49 -0
- package/scripts/ensure-profiles-dir.mjs +48 -0
- package/scripts/validate-package.ts +280 -0
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(
|
|
1124
|
-
const localBin = path.resolve(
|
|
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:
|
|
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:
|
|
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:
|
|
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 {
|
|
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
|
|
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
|
-
|
|
4103
|
-
|
|
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
|
|
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:
|
|
4287
|
-
source: winner.path,
|
|
4486
|
+
tier: winner.src.tier,
|
|
4487
|
+
source: winner.src.path,
|
|
4288
4488
|
deliberate: false,
|
|
4289
|
-
deprecated
|
|
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
|
|
4359
|
-
import { join as
|
|
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
|
-
|
|
4363
|
-
if (explicit)
|
|
4364
|
-
return explicit;
|
|
4365
|
-
return join3(homedir2(), ".hasna", "hooks");
|
|
4564
|
+
return getEffectiveDataRoot();
|
|
4366
4565
|
}
|
|
4367
4566
|
function getCustomHooksDir() {
|
|
4368
|
-
return
|
|
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
|
|
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
|
|
4579
|
+
return join8(getHooksDataDir(), "config.json");
|
|
4381
4580
|
}
|
|
4382
4581
|
function readConfig() {
|
|
4383
4582
|
try {
|
|
4384
4583
|
const path = getConfigPath();
|
|
4385
|
-
if (!
|
|
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(
|
|
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
|
|
4438
|
-
import { basename, dirname, isAbsolute, join as
|
|
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 =
|
|
4455
|
-
const rootLexical =
|
|
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 (!
|
|
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
|
|
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(
|
|
4505
|
-
if (!
|
|
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
|
|
4711
|
+
return join9(getCustomHooksDir(), shortManifestName(name));
|
|
4512
4712
|
}
|
|
4513
4713
|
function readCustomManifest(name) {
|
|
4514
4714
|
const dir = customHookDir(name);
|
|
4515
|
-
const manifestPath =
|
|
4516
|
-
if (!
|
|
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:
|
|
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 (!
|
|
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("/") ?
|
|
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(
|
|
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
|
|
4599
|
-
import { existsSync as
|
|
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 =
|
|
4603
|
-
if (
|
|
4802
|
+
const sourceLayout = join10(moduleDir, "..", "..", "hooks");
|
|
4803
|
+
if (existsSync7(join10(sourceLayout, "hook-gitguard")))
|
|
4604
4804
|
return sourceLayout;
|
|
4605
|
-
return
|
|
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 =
|
|
4617
|
-
if (
|
|
4816
|
+
const direct = join10(bundledBase, short);
|
|
4817
|
+
if (existsSync7(direct))
|
|
4618
4818
|
return direct;
|
|
4619
|
-
const prefixed =
|
|
4620
|
-
return
|
|
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 =
|
|
4630
|
-
return
|
|
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
|
|
4880
|
-
import { join as
|
|
4881
|
-
import { homedir as
|
|
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 =
|
|
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 =
|
|
4961
|
-
if (
|
|
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 =
|
|
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,
|
|
5170
|
+
total += importJsonlFile(db, join11(projectDir, file));
|
|
4971
5171
|
}
|
|
4972
5172
|
if (file === "errors.log") {
|
|
4973
|
-
total += importErrorsLog(db,
|
|
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
|
|
5017
|
-
import { join as
|
|
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
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
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
|
|
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
|
|
5036
|
-
return
|
|
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 && !
|
|
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:" || !
|
|
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
|
|
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 (!
|
|
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
|
|
5315
|
-
import { join as
|
|
5316
|
-
import { homedir as
|
|
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" ?
|
|
5552
|
+
return target === "codewith" ? join13(process.cwd(), dir, "config.toml") : join13(process.cwd(), dir, "settings.json");
|
|
5356
5553
|
}
|
|
5357
|
-
return target === "codewith" ?
|
|
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
|
|
5561
|
+
return join13(HOOKS_DIR, normalizeHookName(shortName));
|
|
5365
5562
|
}
|
|
5366
5563
|
function hookExists(name) {
|
|
5367
|
-
return
|
|
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 (
|
|
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 (!
|
|
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
|
|
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 (!
|
|
5677
|
+
if (!existsSync11(dir))
|
|
5481
5678
|
mkdirSync5(dir, { recursive: true });
|
|
5482
|
-
const existing =
|
|
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 (!
|
|
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 (
|
|
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 =
|
|
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
|
|
6011
|
-
import { join as
|
|
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 =
|
|
6015
|
-
const oldDir =
|
|
6016
|
-
if (!
|
|
6017
|
-
mkdirSync6(
|
|
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 (!
|
|
6219
|
+
if (!existsSync12(PROFILES_DIR)) {
|
|
6024
6220
|
mkdirSync6(PROFILES_DIR, { recursive: true });
|
|
6025
6221
|
}
|
|
6026
6222
|
}
|
|
6027
6223
|
function profilePath(id) {
|
|
6028
|
-
return
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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 (
|
|
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
|
|
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" &&
|
|
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
|
|
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 =
|
|
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
|
|
6535
|
-
import { join as
|
|
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 =
|
|
6539
|
-
if (!
|
|
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(
|
|
6562
|
-
const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl,
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
7843
|
-
import { join as
|
|
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 || !
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
8321
|
+
const readmePath = join18(hookPath, "README.md");
|
|
8115
8322
|
let readme = "";
|
|
8116
|
-
if (
|
|
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:
|
|
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 (
|
|
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 (
|
|
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 =
|
|
8835
|
-
if (
|
|
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 ||
|
|
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(
|
|
9036
|
-
this.dataDir =
|
|
9037
|
-
this.runtime = localJsonRuntime(
|
|
9038
|
-
this.channelsPath =
|
|
9039
|
-
this.eventsPath =
|
|
9040
|
-
this.deliveriesPath =
|
|
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 (!
|
|
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(
|
|
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 ${
|
|
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(
|
|
9265
|
-
const store = new JsonEventsStore(
|
|
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(
|
|
9308
|
-
const path =
|
|
9309
|
-
return { path, exists:
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
10459
|
-
import { join as
|
|
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 =
|
|
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:")}
|
|
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 || !
|
|
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 ?
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
12411
|
-
if (!
|
|
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 =
|
|
12891
|
+
const readmePath = join19(hookPath, "README.md");
|
|
12586
12892
|
let readme = "";
|
|
12587
|
-
if (
|
|
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(
|
|
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();
|