@hasna/hooks 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/bin/hooks-mcp.js +103 -53
- package/bin/index.js +407 -128
- package/bin/serve.js +230 -38
- package/dist/index.d.ts +1 -1
- package/dist/index.js +344 -65
- package/dist/lib/resolver-types.d.ts +1 -1
- package/dist/lib/sync.d.ts +3 -2
- package/dist/lib/transport.d.ts +5 -1
- package/dist/sdk/index.d.ts +2 -2
- package/dist/sdk/index.js +275 -46
- package/package.json +3 -3
- package/scripts/validate-package.ts +70 -2
package/bin/index.js
CHANGED
|
@@ -4528,7 +4528,7 @@ function mementosCommandSuffix(value) {
|
|
|
4528
4528
|
return value.enabled ? ` --enable-mementos${value.secretRef ? ` --openrouter-secret-ref ${value.secretRef}` : ""}` : " --disable-mementos";
|
|
4529
4529
|
}
|
|
4530
4530
|
function retainedMementosRegistration(commands) {
|
|
4531
|
-
const values = commands.filter((command) => /^hooks run mementos-context(?:\s|$)/.test(command)).map((command) => {
|
|
4531
|
+
const values = commands.map((command) => command.trim()).filter((command) => /^hooks run mementos-context(?:\s|$)/.test(command)).map((command) => {
|
|
4532
4532
|
const args = command.trim().split(/\s+/).slice(3);
|
|
4533
4533
|
const flags = {};
|
|
4534
4534
|
const seen = new Set;
|
|
@@ -5790,8 +5790,36 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
|
|
|
5790
5790
|
if (!settings.hooks)
|
|
5791
5791
|
settings.hooks = {};
|
|
5792
5792
|
const retained = name === "mementos-context" && mementos === undefined ? retainedMementosRegistration(Object.values(settings.hooks).flatMap((groups) => Array.isArray(groups) ? groups.flatMap((group) => Array.isArray(group.hooks) ? group.hooks.map((hook) => hook.command).filter((command) => typeof command === "string") : []) : [])) : undefined;
|
|
5793
|
-
removeHookFromAllEvents(settings, name);
|
|
5794
5793
|
const hookCommand = (profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`) + mementosCommandSuffix(mementos ?? retained);
|
|
5794
|
+
if (target === "codex") {
|
|
5795
|
+
const positions = [];
|
|
5796
|
+
for (const [event, groups] of Object.entries(settings.hooks)) {
|
|
5797
|
+
for (const entry of groups) {
|
|
5798
|
+
for (let index = 0;index < entry.hooks.length; index++) {
|
|
5799
|
+
const command = entry.hooks[index]?.command;
|
|
5800
|
+
const match = typeof command === "string" && command.trim().match(/^(?:hooks run ([\w-]+)(?:\s|$)|hook-([\w-]+)$)/);
|
|
5801
|
+
if (match && (match[1] ?? match[2]) === name) {
|
|
5802
|
+
if (registrationName(command) !== name)
|
|
5803
|
+
throw new Error("Unrecognized Codex registration; reconcile it before updating this hook.");
|
|
5804
|
+
positions.push({ event, entry, index });
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
}
|
|
5809
|
+
if (positions.length) {
|
|
5810
|
+
if (positions.length !== uniqueEventKeys.length || uniqueEventKeys.some((event) => positions.filter((position) => position.event === event).length !== 1) || positions.some((position) => (position.entry.matcher ?? "") !== (meta.matcher ?? ""))) {
|
|
5811
|
+
throw new Error("Codex hook events, matchers or duplicate registrations require explicit reconciliation; no settings were changed.");
|
|
5812
|
+
}
|
|
5813
|
+
for (const { entry, index } of positions) {
|
|
5814
|
+
entry.hooks[index] = { ...entry.hooks[index], type: "command", command: hookCommand };
|
|
5815
|
+
if (typeof meta.timeoutSeconds === "number" && meta.timeoutSeconds > 0)
|
|
5816
|
+
entry.hooks[index].timeout = meta.timeoutSeconds;
|
|
5817
|
+
}
|
|
5818
|
+
writeSettings(settings, scope, target);
|
|
5819
|
+
return;
|
|
5820
|
+
}
|
|
5821
|
+
}
|
|
5822
|
+
removeHookFromAllEvents(settings, name);
|
|
5795
5823
|
for (const eventKey of uniqueEventKeys) {
|
|
5796
5824
|
if (!settings.hooks[eventKey])
|
|
5797
5825
|
settings.hooks[eventKey] = [];
|
|
@@ -6324,14 +6352,19 @@ var init_profiles = __esm(() => {
|
|
|
6324
6352
|
init_app_home();
|
|
6325
6353
|
});
|
|
6326
6354
|
|
|
6327
|
-
//
|
|
6355
|
+
// ../contracts/dist/client/transport.js
|
|
6328
6356
|
import { isIP as isIP2 } from "net";
|
|
6329
6357
|
import { spawnSync } from "child_process";
|
|
6330
|
-
import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as
|
|
6331
|
-
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
6332
|
-
import { createRequire as createRequire2 } from "module";
|
|
6358
|
+
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync22 } from "fs";
|
|
6359
|
+
import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
|
|
6333
6360
|
import { hostname as osHostname } from "os";
|
|
6334
|
-
import { isAbsolute as isAbsolute2, join as
|
|
6361
|
+
import { isAbsolute as isAbsolute2, join as join22 } from "path";
|
|
6362
|
+
import { createRequire as createRequire2 } from "module";
|
|
6363
|
+
import { fileURLToPath as fileURLToPath22 } from "url";
|
|
6364
|
+
import { closeSync as closeSync3, fstatSync, lstatSync as lstatSync2, openSync as openSync3, readFileSync as readFileSync7, realpathSync as realpathSync2, statSync } from "fs";
|
|
6365
|
+
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
6366
|
+
import { basename as basename2, dirname as dirname5, join as join12, relative, sep as sep2 } from "path";
|
|
6367
|
+
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
6335
6368
|
function envToken(name) {
|
|
6336
6369
|
return name.toUpperCase().replace(/-/g, "_");
|
|
6337
6370
|
}
|
|
@@ -6348,6 +6381,112 @@ function credentialOverrideEnvKey(name) {
|
|
|
6348
6381
|
function credentialPointerEnvKey(name) {
|
|
6349
6382
|
return `HASNA_${envToken(name)}_API_KEY_REF`;
|
|
6350
6383
|
}
|
|
6384
|
+
function e(e2, n, r) {
|
|
6385
|
+
throw new Error(r ? `No known conditions for "${n}" specifier in "${e2}" package` : `Missing "${n}" specifier in "${e2}" package`);
|
|
6386
|
+
}
|
|
6387
|
+
function n(n2, i, o, f) {
|
|
6388
|
+
let s, u, l = r(n2, o), c = function(e2) {
|
|
6389
|
+
let n3 = new Set(["default", ...e2.conditions || []]);
|
|
6390
|
+
return e2.unsafe || n3.add(e2.require ? "require" : "import"), e2.unsafe || n3.add(e2.browser ? "browser" : "node"), n3;
|
|
6391
|
+
}(f || {}), a = i[l];
|
|
6392
|
+
if (a === undefined) {
|
|
6393
|
+
let e2, n3, r, t;
|
|
6394
|
+
for (t in i)
|
|
6395
|
+
n3 && t.length < n3.length || (t[t.length - 1] === "/" && l.startsWith(t) ? (u = l.substring(t.length), n3 = t) : t.length > 1 && (r = t.indexOf("*", 1), ~r && (e2 = RegExp("^" + t.substring(0, r) + "(.*)" + t.substring(1 + r) + "$").exec(l), e2 && e2[1] && (u = e2[1], n3 = t))));
|
|
6396
|
+
a = i[n3];
|
|
6397
|
+
}
|
|
6398
|
+
return a || e(n2, l), s = t(a, c), s || e(n2, l, 1), u && function(e2, n3) {
|
|
6399
|
+
let r, t = 0, i2 = e2.length, o2 = /[*]/g, f2 = /[/]$/;
|
|
6400
|
+
for (;t < i2; t++)
|
|
6401
|
+
e2[t] = o2.test(r = e2[t]) ? r.replace(o2, n3) : f2.test(r) ? r + n3 : r;
|
|
6402
|
+
}(s, u), s;
|
|
6403
|
+
}
|
|
6404
|
+
function r(e2, n2, r2) {
|
|
6405
|
+
if (e2 === n2 || n2 === ".")
|
|
6406
|
+
return ".";
|
|
6407
|
+
let t = e2 + "/", i = t.length, o = n2.slice(0, i) === t, f = o ? n2.slice(i) : n2;
|
|
6408
|
+
return f[0] === "#" ? f : o || !r2 ? f.slice(0, 2) === "./" ? f : "./" + f : f;
|
|
6409
|
+
}
|
|
6410
|
+
function t(e2, n2, r2) {
|
|
6411
|
+
if (e2) {
|
|
6412
|
+
if (typeof e2 == "string")
|
|
6413
|
+
return r2 && r2.add(e2), [e2];
|
|
6414
|
+
let i, o;
|
|
6415
|
+
if (Array.isArray(e2)) {
|
|
6416
|
+
for (o = r2 || new Set, i = 0;i < e2.length; i++)
|
|
6417
|
+
t(e2[i], n2, o);
|
|
6418
|
+
if (!r2 && o.size)
|
|
6419
|
+
return [...o];
|
|
6420
|
+
} else
|
|
6421
|
+
for (i in e2)
|
|
6422
|
+
if (n2.has(i))
|
|
6423
|
+
return t(e2[i], n2, r2);
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
function o(e2, r2, t2) {
|
|
6427
|
+
let i, o2 = e2.exports;
|
|
6428
|
+
if (o2) {
|
|
6429
|
+
if (typeof o2 == "string")
|
|
6430
|
+
o2 = { ".": o2 };
|
|
6431
|
+
else
|
|
6432
|
+
for (i in o2) {
|
|
6433
|
+
i[0] !== "." && (o2 = { ".": o2 });
|
|
6434
|
+
break;
|
|
6435
|
+
}
|
|
6436
|
+
return n(e2.name, o2, r2 || ".", t2);
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6439
|
+
function entryPoint(directory) {
|
|
6440
|
+
const root = realpathSync2(directory);
|
|
6441
|
+
const fd = openSync3(join12(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
|
|
6442
|
+
let pkg;
|
|
6443
|
+
try {
|
|
6444
|
+
const stat = fstatSync(fd);
|
|
6445
|
+
if (!stat.isFile() || stat.size > MAX_PACKAGE_BYTES2)
|
|
6446
|
+
throw new Error("Invalid Secrets package metadata");
|
|
6447
|
+
pkg = JSON.parse(readFileSync7(fd, "utf8"));
|
|
6448
|
+
} finally {
|
|
6449
|
+
closeSync3(fd);
|
|
6450
|
+
}
|
|
6451
|
+
if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== PACKAGE) {
|
|
6452
|
+
throw new Error("Invalid Secrets package identity");
|
|
6453
|
+
}
|
|
6454
|
+
const target = o(pkg, ".")?.[0];
|
|
6455
|
+
if (typeof target !== "string" || !target.startsWith("./"))
|
|
6456
|
+
throw new Error("Missing Secrets import export");
|
|
6457
|
+
const parts = target.slice(2).split("/");
|
|
6458
|
+
if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
|
|
6459
|
+
throw new Error("Invalid Secrets import export");
|
|
6460
|
+
}
|
|
6461
|
+
const file = realpathSync2(join12(root, ...parts));
|
|
6462
|
+
const within = relative(root, file);
|
|
6463
|
+
if (!within || within === ".." || within.startsWith(`..${sep2}`) || !statSync(file).isFile()) {
|
|
6464
|
+
throw new Error("Secrets import export escapes its package or is not a file");
|
|
6465
|
+
}
|
|
6466
|
+
return pathToFileURL(file).href;
|
|
6467
|
+
}
|
|
6468
|
+
function resolveInstalledSecrets(parentUrl) {
|
|
6469
|
+
let directory = dirname5(fileURLToPath3(parentUrl));
|
|
6470
|
+
for (;; ) {
|
|
6471
|
+
if (basename2(directory) !== "node_modules") {
|
|
6472
|
+
const candidate = join12(directory, "node_modules", PACKAGE);
|
|
6473
|
+
let absent = false;
|
|
6474
|
+
try {
|
|
6475
|
+
lstatSync2(candidate);
|
|
6476
|
+
} catch (error) {
|
|
6477
|
+
if (error.code !== "ENOENT")
|
|
6478
|
+
throw error;
|
|
6479
|
+
absent = true;
|
|
6480
|
+
}
|
|
6481
|
+
if (!absent)
|
|
6482
|
+
return entryPoint(candidate);
|
|
6483
|
+
}
|
|
6484
|
+
const parent = dirname5(directory);
|
|
6485
|
+
if (parent === directory)
|
|
6486
|
+
throw new Error("Secrets SDK is not installed for this consumer");
|
|
6487
|
+
directory = parent;
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
6351
6490
|
function homeDir(env) {
|
|
6352
6491
|
const home = env.HOME?.trim();
|
|
6353
6492
|
return home ? home : null;
|
|
@@ -6361,14 +6500,14 @@ function hasnaHomeDir(env) {
|
|
|
6361
6500
|
if (override)
|
|
6362
6501
|
return override;
|
|
6363
6502
|
const home = homeDir(env);
|
|
6364
|
-
return home ?
|
|
6503
|
+
return home ? join22(home, HASNA_HOME_DIR) : null;
|
|
6365
6504
|
}
|
|
6366
6505
|
function appConfigDir(name, env) {
|
|
6367
6506
|
const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
|
|
6368
6507
|
if (configRoot)
|
|
6369
|
-
return
|
|
6508
|
+
return join22(configRoot, name);
|
|
6370
6509
|
const root = hasnaHomeDir(env);
|
|
6371
|
-
return root ?
|
|
6510
|
+
return root ? join22(root, name, CONFIG_SUBDIR) : null;
|
|
6372
6511
|
}
|
|
6373
6512
|
function credentialDiskSourceList(name, env, profile = null) {
|
|
6374
6513
|
if (!SAFE_APP_SLUG.test(name))
|
|
@@ -6377,7 +6516,7 @@ function credentialDiskSourceList(name, env, profile = null) {
|
|
|
6377
6516
|
if (!directory)
|
|
6378
6517
|
return [];
|
|
6379
6518
|
const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
|
|
6380
|
-
return [{ path:
|
|
6519
|
+
return [{ path: join22(directory, file), tier: "disk" }];
|
|
6381
6520
|
}
|
|
6382
6521
|
function credentialDiskSources(name, env) {
|
|
6383
6522
|
return credentialDiskSourceList(name, env, null).map((s) => s.path);
|
|
@@ -6431,7 +6570,7 @@ function readAppConfigFile(path) {
|
|
|
6431
6570
|
};
|
|
6432
6571
|
let fd = -1;
|
|
6433
6572
|
try {
|
|
6434
|
-
fd = openSync2(path,
|
|
6573
|
+
fd = openSync2(path, O_RDONLY2 | O_NOFOLLOW2 | O_NONBLOCK2);
|
|
6435
6574
|
} catch (error) {
|
|
6436
6575
|
const code = error.code;
|
|
6437
6576
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
@@ -6441,7 +6580,7 @@ function readAppConfigFile(path) {
|
|
|
6441
6580
|
unsafe(`the path could not be opened (${code ?? "unknown error"})`);
|
|
6442
6581
|
}
|
|
6443
6582
|
try {
|
|
6444
|
-
const before =
|
|
6583
|
+
const before = fstatSync2(fd);
|
|
6445
6584
|
if (!before.isFile())
|
|
6446
6585
|
unsafe("the path is not a regular file");
|
|
6447
6586
|
if (!configFileModeAllowed(before.mode)) {
|
|
@@ -6452,8 +6591,8 @@ function readAppConfigFile(path) {
|
|
|
6452
6591
|
unsafe("the file is not owned by the current user");
|
|
6453
6592
|
if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
|
|
6454
6593
|
unsafe("the file exceeds the size limit");
|
|
6455
|
-
const bytes =
|
|
6456
|
-
const after =
|
|
6594
|
+
const bytes = readFileSync22(fd);
|
|
6595
|
+
const after = fstatSync2(fd);
|
|
6457
6596
|
if (!configFileReadsCoherent(before, after)) {
|
|
6458
6597
|
unsafe("the file changed while being read");
|
|
6459
6598
|
}
|
|
@@ -6463,20 +6602,28 @@ function readAppConfigFile(path) {
|
|
|
6463
6602
|
closeSync2(fd);
|
|
6464
6603
|
}
|
|
6465
6604
|
}
|
|
6466
|
-
function readCredentialFile(path, apiKeyKeys) {
|
|
6605
|
+
function readCredentialFile(path, apiKeyKeys, pointerKey) {
|
|
6467
6606
|
const parsed = readAppConfigFile(path);
|
|
6468
6607
|
if (!parsed)
|
|
6469
6608
|
return null;
|
|
6470
|
-
for (const key of apiKeyKeys) {
|
|
6609
|
+
for (const key of [...apiKeyKeys, pointerKey]) {
|
|
6471
6610
|
if (parsed.unusable.has(key)) {
|
|
6472
6611
|
throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
|
|
6473
6612
|
}
|
|
6474
6613
|
}
|
|
6475
6614
|
const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
|
|
6615
|
+
const pointer = parsed.values.get(pointerKey)?.trim();
|
|
6616
|
+
if (pointer !== undefined) {
|
|
6617
|
+
if (!VAULT_POINTER_SHAPE.test(pointer))
|
|
6618
|
+
throw new CredentialFileUnsafeError(path, `${pointerKey} must name a vault item`);
|
|
6619
|
+
if (values.length)
|
|
6620
|
+
throw new CredentialFileUnsafeError(path, "a credential file cannot select both a literal key and a vault reference");
|
|
6621
|
+
return { apiKey: "", pointerVaultKey: pointer };
|
|
6622
|
+
}
|
|
6476
6623
|
if (new Set(values).size > 1) {
|
|
6477
6624
|
throw new CredentialFileUnsafeError(path, "credential aliases disagree");
|
|
6478
6625
|
}
|
|
6479
|
-
return values[0]
|
|
6626
|
+
return values[0] === undefined ? null : { apiKey: values[0] };
|
|
6480
6627
|
}
|
|
6481
6628
|
function appConfigDiskValue(name, env, keys) {
|
|
6482
6629
|
const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
|
|
@@ -6548,6 +6695,18 @@ function sealCredential(fields) {
|
|
|
6548
6695
|
});
|
|
6549
6696
|
return Object.freeze(sealed);
|
|
6550
6697
|
}
|
|
6698
|
+
function explicitCredential(appName, apiKey) {
|
|
6699
|
+
const source = "explicit apiKey option";
|
|
6700
|
+
assertUsableCredential(appName, source, apiKey);
|
|
6701
|
+
return sealCredential({
|
|
6702
|
+
apiKey,
|
|
6703
|
+
tier: "argument",
|
|
6704
|
+
source,
|
|
6705
|
+
deliberate: true,
|
|
6706
|
+
diskCandidates: [],
|
|
6707
|
+
warning: null
|
|
6708
|
+
});
|
|
6709
|
+
}
|
|
6551
6710
|
function firstEnvValue(env, keys) {
|
|
6552
6711
|
for (const key of keys) {
|
|
6553
6712
|
if (!Object.prototype.hasOwnProperty.call(env, key))
|
|
@@ -6733,12 +6892,13 @@ function resolveCredential(name, env, options = {}) {
|
|
|
6733
6892
|
}
|
|
6734
6893
|
const paths = profileDiskSources(name, env, profile);
|
|
6735
6894
|
for (const path of paths) {
|
|
6736
|
-
const value = readCredentialFile(path, apiKeyKeys);
|
|
6895
|
+
const value = readCredentialFile(path, apiKeyKeys, pointerKeyName);
|
|
6737
6896
|
if (value) {
|
|
6738
|
-
|
|
6897
|
+
if (!value.pointerVaultKey)
|
|
6898
|
+
assertUsableCredential(name, path, value.apiKey);
|
|
6739
6899
|
return sealCredential({
|
|
6740
|
-
|
|
6741
|
-
tier: "profile",
|
|
6900
|
+
...value,
|
|
6901
|
+
tier: value.pointerVaultKey ? "pointer" : "profile",
|
|
6742
6902
|
source: path,
|
|
6743
6903
|
deliberate: true,
|
|
6744
6904
|
diskCandidates: paths,
|
|
@@ -6746,7 +6906,7 @@ function resolveCredential(name, env, options = {}) {
|
|
|
6746
6906
|
});
|
|
6747
6907
|
}
|
|
6748
6908
|
}
|
|
6749
|
-
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);
|
|
6909
|
+
throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} or ${pointerKeyName} 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);
|
|
6750
6910
|
}
|
|
6751
6911
|
const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
|
|
6752
6912
|
const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
|
|
@@ -6771,17 +6931,27 @@ function resolveCredential(name, env, options = {}) {
|
|
|
6771
6931
|
});
|
|
6772
6932
|
}
|
|
6773
6933
|
const diskSourceList = credentialDiskSourceList(name, env, null);
|
|
6774
|
-
const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
|
|
6934
|
+
const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys, pointerKeyName) })).filter((hit) => hit.value !== null);
|
|
6775
6935
|
if (diskHits.length > 0) {
|
|
6776
6936
|
const winner = diskHits[0];
|
|
6777
|
-
|
|
6937
|
+
if (winner.value.pointerVaultKey) {
|
|
6938
|
+
return sealCredential({
|
|
6939
|
+
...winner.value,
|
|
6940
|
+
tier: "pointer",
|
|
6941
|
+
source: winner.src.path,
|
|
6942
|
+
deliberate: false,
|
|
6943
|
+
diskCandidates: diskPaths,
|
|
6944
|
+
warning: null
|
|
6945
|
+
});
|
|
6946
|
+
}
|
|
6947
|
+
assertUsableCredential(name, winner.src.path, winner.value.apiKey);
|
|
6778
6948
|
const divergentSources = [
|
|
6779
|
-
...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
|
|
6780
|
-
...envHit && envHit.value !== winner.value ? [envHit.key] : []
|
|
6949
|
+
...diskHits.slice(1).filter((hit) => hit.value.apiKey !== winner.value.apiKey || hit.value.pointerVaultKey !== winner.value.pointerVaultKey).map((hit) => hit.src.path),
|
|
6950
|
+
...envHit && envHit.value !== winner.value.apiKey ? [envHit.key] : []
|
|
6781
6951
|
];
|
|
6782
6952
|
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;
|
|
6783
6953
|
return sealCredential({
|
|
6784
|
-
apiKey: winner.value,
|
|
6954
|
+
apiKey: winner.value.apiKey,
|
|
6785
6955
|
tier: winner.src.tier,
|
|
6786
6956
|
source: winner.src.path,
|
|
6787
6957
|
deliberate: false,
|
|
@@ -6802,6 +6972,53 @@ function resolveCredential(name, env, options = {}) {
|
|
|
6802
6972
|
}
|
|
6803
6973
|
return null;
|
|
6804
6974
|
}
|
|
6975
|
+
async function completePointerCredential(name, pointerResolution, env = process.env) {
|
|
6976
|
+
const secretsEnv = snapshotClientEnvironment("secrets", env);
|
|
6977
|
+
const vaultKey = pointerResolution.pointerVaultKey;
|
|
6978
|
+
const pointerEnvKey = pointerResolution.source;
|
|
6979
|
+
if (!vaultKey) {
|
|
6980
|
+
throw new CredentialResolutionError(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
|
|
6981
|
+
}
|
|
6982
|
+
if (name === "secrets") {
|
|
6983
|
+
throw new CredentialResolutionError(name, "The Secrets bootstrap credential cannot reference the same hosted vault; configure an independent bootstrap provider.", [pointerEnvKey]);
|
|
6984
|
+
}
|
|
6985
|
+
let secretsSdk;
|
|
6986
|
+
try {
|
|
6987
|
+
const sdkUrl = resolveInstalledSecrets(import.meta.url);
|
|
6988
|
+
secretsSdk = requireSecretsSdk2(fileURLToPath22(sdkUrl));
|
|
6989
|
+
} catch {
|
|
6990
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
|
|
6991
|
+
}
|
|
6992
|
+
let client;
|
|
6993
|
+
try {
|
|
6994
|
+
const bootstrap = resolveCredential("secrets", secretsEnv);
|
|
6995
|
+
if (!bootstrap || bootstrap.tier === "pointer") {
|
|
6996
|
+
throw new CredentialResolutionError("secrets", "The Secrets vault requires an independent, non-reference bootstrap credential.", bootstrap ? [bootstrap.source] : []);
|
|
6997
|
+
}
|
|
6998
|
+
client = secretsSdk.createSecretsClientFromEnv(secretsEnv);
|
|
6999
|
+
} catch {
|
|
7000
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets client could not be configured from this ` + `environment (the secrets service URL and key env are missing or invalid). A vault pointer is TERMINAL and ` + `never falls through to a literal or disk credential.`, [pointerEnvKey]);
|
|
7001
|
+
}
|
|
7002
|
+
let secret;
|
|
7003
|
+
try {
|
|
7004
|
+
secret = await client.getSecret({ key: vaultKey });
|
|
7005
|
+
} catch {
|
|
7006
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the vault could not be reached or the item is ` + `unavailable. A vault pointer is TERMINAL and never falls through to a literal or disk credential.`, [pointerEnvKey]);
|
|
7007
|
+
}
|
|
7008
|
+
const value = secret.value;
|
|
7009
|
+
if (!value) {
|
|
7010
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
|
|
7011
|
+
}
|
|
7012
|
+
assertUsableCredential(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
|
|
7013
|
+
return sealCredential({
|
|
7014
|
+
apiKey: value,
|
|
7015
|
+
tier: "pointer",
|
|
7016
|
+
source: `${pointerEnvKey} -> vault:${vaultKey}`,
|
|
7017
|
+
deliberate: pointerResolution.deliberate,
|
|
7018
|
+
diskCandidates: pointerResolution.diskCandidates,
|
|
7019
|
+
warning: null
|
|
7020
|
+
});
|
|
7021
|
+
}
|
|
6805
7022
|
function defaultFleetGatewayBaseUrl(name) {
|
|
6806
7023
|
return `${DEFAULT_FLEET_GATEWAY_ORIGIN}/${validateAppSlug(name)}`;
|
|
6807
7024
|
}
|
|
@@ -7004,8 +7221,9 @@ function credentialDiskSourcesForMessage(name, env) {
|
|
|
7004
7221
|
const paths = credentialDiskSources(name, env);
|
|
7005
7222
|
return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
|
|
7006
7223
|
}
|
|
7007
|
-
var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES2, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, AMBIENT_ENVIRONMENT2,
|
|
7224
|
+
var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", PACKAGE = "@hasna/secrets", MAX_PACKAGE_BYTES2, CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES2, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, AMBIENT_ENVIRONMENT2, requireSecretsSdk2, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2;
|
|
7008
7225
|
var init_transport = __esm(() => {
|
|
7226
|
+
MAX_PACKAGE_BYTES2 = 1024 * 1024;
|
|
7009
7227
|
CredentialResolutionError = class CredentialResolutionError extends Error {
|
|
7010
7228
|
appName;
|
|
7011
7229
|
attempted;
|
|
@@ -7033,7 +7251,6 @@ var init_transport = __esm(() => {
|
|
|
7033
7251
|
INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
|
|
7034
7252
|
CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
|
|
7035
7253
|
AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
7036
|
-
SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
|
|
7037
7254
|
requireSecretsSdk2 = createRequire2(import.meta.url);
|
|
7038
7255
|
ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
|
|
7039
7256
|
DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
@@ -7122,7 +7339,9 @@ var exports_transport = {};
|
|
|
7122
7339
|
__export(exports_transport, {
|
|
7123
7340
|
rethrowHooksAuthorityFailure: () => rethrowHooksAuthorityFailure,
|
|
7124
7341
|
resolveHooksTransport: () => resolveHooksTransport,
|
|
7342
|
+
resolveHooksServePublishKeyAsync: () => resolveHooksServePublishKeyAsync,
|
|
7125
7343
|
resolveHooksServePublishKey: () => resolveHooksServePublishKey,
|
|
7344
|
+
resolveHooksRequestAuthority: () => resolveHooksRequestAuthority,
|
|
7126
7345
|
hooksRegistryOrigin: () => hooksRegistryOrigin,
|
|
7127
7346
|
announceHooksLocalMode: () => announceHooksLocalMode,
|
|
7128
7347
|
__resetHooksLocalNotice: () => __resetHooksLocalNotice
|
|
@@ -7161,6 +7380,40 @@ function rethrowHooksAuthorityFailure(error) {
|
|
|
7161
7380
|
}
|
|
7162
7381
|
throw new Error(`REMOTE_API_URL_INVALID: ${message} local mode fallback is disabled`, { cause: error });
|
|
7163
7382
|
}
|
|
7383
|
+
function sameCredential(left, right) {
|
|
7384
|
+
return left === null || right === null ? left === right : left.apiKey === right.apiKey && left.pointerVaultKey === right.pointerVaultKey && left.source === right.source && left.tier === right.tier;
|
|
7385
|
+
}
|
|
7386
|
+
function changedBinding() {
|
|
7387
|
+
throw new Error("REMOTE_API_CREDENTIAL_INVALID: The configured Hooks authority or credential changed while preparing a request; no request was sent.");
|
|
7388
|
+
}
|
|
7389
|
+
async function resolveHooksRequestAuthority(authority) {
|
|
7390
|
+
if (authority.origin !== hooksRegistryOrigin(authority.v1BaseUrl))
|
|
7391
|
+
changedBinding();
|
|
7392
|
+
const binding = authorityBindings.get(authority);
|
|
7393
|
+
if (!binding)
|
|
7394
|
+
return { ...authority, apiKey: explicitCredential("hooks", authority.apiKey).apiKey };
|
|
7395
|
+
const snapshot = () => {
|
|
7396
|
+
const next = resolveHooksTransport(binding.env, binding.options);
|
|
7397
|
+
if (next.mode !== "remote" || !next.authority || next.authority.v1BaseUrl !== authority.v1BaseUrl)
|
|
7398
|
+
changedBinding();
|
|
7399
|
+
return { authority: next.authority, binding: authorityBindings.get(next.authority) };
|
|
7400
|
+
};
|
|
7401
|
+
const first = snapshot();
|
|
7402
|
+
const reviewed = snapshot();
|
|
7403
|
+
if (!sameCredential(first.binding.credential, reviewed.binding.credential))
|
|
7404
|
+
changedBinding();
|
|
7405
|
+
let credential = reviewed.binding.credential;
|
|
7406
|
+
try {
|
|
7407
|
+
if (credential.tier === "pointer")
|
|
7408
|
+
credential = await completePointerCredential("hooks", credential, binding.env);
|
|
7409
|
+
} catch (error) {
|
|
7410
|
+
rethrowHooksAuthorityFailure(error);
|
|
7411
|
+
}
|
|
7412
|
+
const final = snapshot();
|
|
7413
|
+
if (!sameCredential(reviewed.binding.credential, final.binding.credential))
|
|
7414
|
+
changedBinding();
|
|
7415
|
+
return { ...reviewed.authority, apiKey: credential.apiKey };
|
|
7416
|
+
}
|
|
7164
7417
|
function resolveHooksTransport(env = process.env, options = {}) {
|
|
7165
7418
|
if (selectsHooksLocalStore(env)) {
|
|
7166
7419
|
announceLocal(options.notice, "HASNA_HOOKS_LOCAL is set and nothing configures a registry authority");
|
|
@@ -7174,7 +7427,7 @@ function resolveHooksTransport(env = process.env, options = {}) {
|
|
|
7174
7427
|
try {
|
|
7175
7428
|
credential = resolveCredential("hooks", resolverEnv, credentialOptions);
|
|
7176
7429
|
resolution = resolveClientTransport("hooks", resolverEnv, {
|
|
7177
|
-
credentials: credential ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
|
|
7430
|
+
credentials: credential && credential.tier !== "pointer" ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
|
|
7178
7431
|
});
|
|
7179
7432
|
} catch (error) {
|
|
7180
7433
|
rethrowHooksAuthorityFailure(error);
|
|
@@ -7182,31 +7435,50 @@ function resolveHooksTransport(env = process.env, options = {}) {
|
|
|
7182
7435
|
if (!credential) {
|
|
7183
7436
|
throw new Error("REMOTE_API_CONFIG_MISSING: no hooks registry credential resolved; " + "local mode fallback is disabled");
|
|
7184
7437
|
}
|
|
7438
|
+
const authority = {
|
|
7439
|
+
origin: hooksRegistryOrigin(resolution.baseUrl),
|
|
7440
|
+
apiKey: credential.apiKey,
|
|
7441
|
+
apiUrlSource: resolution.apiUrlSource,
|
|
7442
|
+
apiKeySource: credential.source,
|
|
7443
|
+
apiKeyTier: credential.tier,
|
|
7444
|
+
v1BaseUrl: resolution.baseUrl,
|
|
7445
|
+
warning: resolution.warning
|
|
7446
|
+
};
|
|
7447
|
+
authorityBindings.set(authority, { env, options, credential });
|
|
7185
7448
|
return {
|
|
7186
7449
|
mode: "remote",
|
|
7187
7450
|
source: `${credential.source}+${resolution.apiUrlSource ?? "default"}`,
|
|
7188
|
-
authority
|
|
7189
|
-
origin: hooksRegistryOrigin(resolution.baseUrl),
|
|
7190
|
-
apiKey: credential.apiKey,
|
|
7191
|
-
apiUrlSource: resolution.apiUrlSource,
|
|
7192
|
-
apiKeySource: credential.source,
|
|
7193
|
-
apiKeyTier: credential.tier,
|
|
7194
|
-
v1BaseUrl: resolution.baseUrl,
|
|
7195
|
-
warning: resolution.warning
|
|
7196
|
-
}
|
|
7451
|
+
authority
|
|
7197
7452
|
};
|
|
7198
7453
|
}
|
|
7199
7454
|
function resolveHooksServePublishKey(env = process.env, options = {}) {
|
|
7200
7455
|
const { env: resolverEnv, credentials } = hooksResolverInputs(env, options.credentials);
|
|
7201
7456
|
const tier1 = options.credentials?.apiKey;
|
|
7202
7457
|
const credentialOptions = tier1 !== undefined ? { ...credentials, apiKey: tier1 } : credentials;
|
|
7203
|
-
|
|
7458
|
+
const credential = resolveCredential("hooks", resolverEnv, credentialOptions);
|
|
7459
|
+
if (credential?.tier === "pointer")
|
|
7460
|
+
throw new Error("Vault references require resolveHooksServePublishKeyAsync().");
|
|
7461
|
+
return credential?.apiKey;
|
|
7204
7462
|
}
|
|
7205
|
-
|
|
7463
|
+
async function resolveHooksServePublishKeyAsync(env = process.env, options = {}) {
|
|
7464
|
+
const { env: resolverEnv, credentials } = hooksResolverInputs(env, options.credentials);
|
|
7465
|
+
const read = () => resolveCredential("hooks", resolverEnv, credentials);
|
|
7466
|
+
const selected = read();
|
|
7467
|
+
if (!selected || selected.tier !== "pointer")
|
|
7468
|
+
return selected?.apiKey;
|
|
7469
|
+
if (!sameCredential(selected, read()))
|
|
7470
|
+
changedBinding();
|
|
7471
|
+
const completed = await completePointerCredential("hooks", selected, env);
|
|
7472
|
+
if (!sameCredential(selected, read()))
|
|
7473
|
+
changedBinding();
|
|
7474
|
+
return completed.apiKey;
|
|
7475
|
+
}
|
|
7476
|
+
var localNoticePrinted = false, authorityBindings;
|
|
7206
7477
|
var init_transport2 = __esm(() => {
|
|
7207
7478
|
init_transport();
|
|
7208
7479
|
init_config();
|
|
7209
7480
|
init_local_opt_in();
|
|
7481
|
+
authorityBindings = new WeakMap;
|
|
7210
7482
|
});
|
|
7211
7483
|
|
|
7212
7484
|
// src/lib/db-writer.ts
|
|
@@ -7328,8 +7600,8 @@ var init_db_writer = __esm(() => {
|
|
|
7328
7600
|
});
|
|
7329
7601
|
|
|
7330
7602
|
// src/lib/hook-env.ts
|
|
7331
|
-
import { dirname as
|
|
7332
|
-
import { existsSync as existsSync13, realpathSync as
|
|
7603
|
+
import { dirname as dirname6, isAbsolute as isAbsolute3 } from "path";
|
|
7604
|
+
import { existsSync as existsSync13, realpathSync as realpathSync3, statSync as statSync2 } from "fs";
|
|
7333
7605
|
import { tmpdir } from "os";
|
|
7334
7606
|
function isDeniedEnvName(name) {
|
|
7335
7607
|
const upper = name.toUpperCase();
|
|
@@ -7358,7 +7630,7 @@ function isUnsafePathEntry(entry, home) {
|
|
|
7358
7630
|
return true;
|
|
7359
7631
|
const canonical = (path) => {
|
|
7360
7632
|
try {
|
|
7361
|
-
return
|
|
7633
|
+
return realpathSync3(path);
|
|
7362
7634
|
} catch {
|
|
7363
7635
|
return path;
|
|
7364
7636
|
}
|
|
@@ -7370,7 +7642,7 @@ function isUnsafePathEntry(entry, home) {
|
|
|
7370
7642
|
return true;
|
|
7371
7643
|
}
|
|
7372
7644
|
try {
|
|
7373
|
-
if ((
|
|
7645
|
+
if ((statSync2(entry).mode & 2) !== 0)
|
|
7374
7646
|
return true;
|
|
7375
7647
|
} catch {}
|
|
7376
7648
|
return false;
|
|
@@ -7379,7 +7651,7 @@ function sanitizeHookPath(pathValue, home) {
|
|
|
7379
7651
|
const baseline = PATH_BASELINE.slice();
|
|
7380
7652
|
if (process.platform === "darwin" && existsSync13("/opt/homebrew/bin"))
|
|
7381
7653
|
baseline.push("/opt/homebrew/bin");
|
|
7382
|
-
const bunDir =
|
|
7654
|
+
const bunDir = dirname6(process.execPath);
|
|
7383
7655
|
const seen = new Set;
|
|
7384
7656
|
const out = [];
|
|
7385
7657
|
for (const dir of baseline) {
|
|
@@ -7504,9 +7776,9 @@ __export(exports_run, {
|
|
|
7504
7776
|
});
|
|
7505
7777
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
7506
7778
|
import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
7507
|
-
import { basename as
|
|
7779
|
+
import { basename as basename3, dirname as dirname7, isAbsolute as isAbsolute4, join as join13 } from "path";
|
|
7508
7780
|
function extensionOf(scriptPath) {
|
|
7509
|
-
const base =
|
|
7781
|
+
const base = basename3(scriptPath);
|
|
7510
7782
|
const dot = base.lastIndexOf(".");
|
|
7511
7783
|
return dot > 0 ? base.slice(dot + 1) : "ts";
|
|
7512
7784
|
}
|
|
@@ -7518,9 +7790,9 @@ function shebangInterpreter(firstLine) {
|
|
|
7518
7790
|
return null;
|
|
7519
7791
|
const parts = rest.split(/\s+/);
|
|
7520
7792
|
if (parts[0] === "env" || parts[0] === "/usr/bin/env") {
|
|
7521
|
-
return parts[1] && parts[1] !== "-S" ?
|
|
7793
|
+
return parts[1] && parts[1] !== "-S" ? basename3(parts[1]) : parts[2] ? basename3(parts[2]) : null;
|
|
7522
7794
|
}
|
|
7523
|
-
return
|
|
7795
|
+
return basename3(parts[0]);
|
|
7524
7796
|
}
|
|
7525
7797
|
function interpreterFor(name, scriptPath, content) {
|
|
7526
7798
|
const ext = extensionOf(scriptPath);
|
|
@@ -7554,8 +7826,8 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
7554
7826
|
result = await Promise.race([
|
|
7555
7827
|
reader.read(),
|
|
7556
7828
|
new Promise((_, reject) => {
|
|
7557
|
-
const
|
|
7558
|
-
|
|
7829
|
+
const t2 = setTimeout(() => reject(new Error("pipe-drain-deadline")), remaining);
|
|
7830
|
+
t2.unref?.();
|
|
7559
7831
|
})
|
|
7560
7832
|
]);
|
|
7561
7833
|
} catch {
|
|
@@ -7573,7 +7845,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
7573
7845
|
return chunks.join("");
|
|
7574
7846
|
}
|
|
7575
7847
|
async function executeVerifiedScript(options) {
|
|
7576
|
-
const scriptDir =
|
|
7848
|
+
const scriptDir = dirname7(options.scriptPath);
|
|
7577
7849
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
7578
7850
|
const tempPath = join13(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
7579
7851
|
let proc = null;
|
|
@@ -7808,7 +8080,14 @@ function collectBundledCatalog() {
|
|
|
7808
8080
|
}
|
|
7809
8081
|
async function fetchJson(base, path, apiKey, opts) {
|
|
7810
8082
|
const headers = { accept: "application/json" };
|
|
7811
|
-
if (apiKey)
|
|
8083
|
+
if (typeof apiKey === "object") {
|
|
8084
|
+
if (apiKey.origin !== base)
|
|
8085
|
+
throw new Error("Hooks registry authority does not match the request target");
|
|
8086
|
+
const resolved = await resolveHooksRequestAuthority(apiKey);
|
|
8087
|
+
if (resolved.origin !== base)
|
|
8088
|
+
throw new Error("Hooks registry authority changed before dispatch");
|
|
8089
|
+
headers["x-api-key"] = resolved.apiKey;
|
|
8090
|
+
} else if (apiKey)
|
|
7812
8091
|
headers["x-api-key"] = apiKey;
|
|
7813
8092
|
const res = await fetch(`${base}${path}`, {
|
|
7814
8093
|
headers,
|
|
@@ -7876,8 +8155,8 @@ async function planSync(options = {}) {
|
|
|
7876
8155
|
credentials: options.credentials
|
|
7877
8156
|
});
|
|
7878
8157
|
if (transport.mode === "remote" && transport.authority) {
|
|
7879
|
-
const { origin
|
|
7880
|
-
const { catalog, remoteLock } = await fetchRemoteState(origin,
|
|
8158
|
+
const { origin } = transport.authority;
|
|
8159
|
+
const { catalog, remoteLock } = await fetchRemoteState(origin, transport.authority);
|
|
7881
8160
|
return { apiUrl: origin, dryRun: options.dryRun ?? false, diff: computeDiff(catalog, remoteLock) };
|
|
7882
8161
|
}
|
|
7883
8162
|
const diff = computeDiff(collectBundledCatalog(), null);
|
|
@@ -7905,10 +8184,10 @@ async function syncHooks(options = {}) {
|
|
|
7905
8184
|
}
|
|
7906
8185
|
return plan;
|
|
7907
8186
|
}
|
|
7908
|
-
const { origin
|
|
7909
|
-
const { catalog, remoteLock } = await fetchRemoteState(origin,
|
|
8187
|
+
const { origin } = transport.authority;
|
|
8188
|
+
const { catalog, remoteLock } = await fetchRemoteState(origin, transport.authority);
|
|
7910
8189
|
const diff = computeDiff(catalog, remoteLock);
|
|
7911
|
-
const staged = await stageSyncArtifacts(origin,
|
|
8190
|
+
const staged = await stageSyncArtifacts(origin, transport.authority, [...diff.added, ...diff.updated], remoteLock);
|
|
7912
8191
|
await commitSyncArtifacts(staged, origin, remoteLock);
|
|
7913
8192
|
return { apiUrl: origin, dryRun: false, diff };
|
|
7914
8193
|
}
|
|
@@ -8569,8 +8848,8 @@ function secureEqual(a, b) {
|
|
|
8569
8848
|
const ab = enc.encode(a);
|
|
8570
8849
|
const bb = enc.encode(b);
|
|
8571
8850
|
let diff = ab.length ^ bb.length;
|
|
8572
|
-
const
|
|
8573
|
-
for (let i = 0;i <
|
|
8851
|
+
const n2 = Math.max(ab.length, bb.length);
|
|
8852
|
+
for (let i = 0;i < n2; i++) {
|
|
8574
8853
|
diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
|
|
8575
8854
|
}
|
|
8576
8855
|
return diff === 0;
|
|
@@ -8741,11 +9020,11 @@ __export(exports_serve, {
|
|
|
8741
9020
|
DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
|
|
8742
9021
|
});
|
|
8743
9022
|
import { readFileSync as readFileSync10 } from "fs";
|
|
8744
|
-
import { dirname as
|
|
8745
|
-
import { fileURLToPath as
|
|
9023
|
+
import { dirname as dirname8, join as join15 } from "path";
|
|
9024
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8746
9025
|
function packageVersion() {
|
|
8747
9026
|
try {
|
|
8748
|
-
const pkgPath = join15(
|
|
9027
|
+
const pkgPath = join15(dirname8(fileURLToPath4(import.meta.url)), "..", "package.json");
|
|
8749
9028
|
return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
|
|
8750
9029
|
} catch {
|
|
8751
9030
|
return "0.0.0";
|
|
@@ -8907,7 +9186,7 @@ function startServeServer(options) {
|
|
|
8907
9186
|
hostname: host,
|
|
8908
9187
|
port,
|
|
8909
9188
|
async fetch(req) {
|
|
8910
|
-
return handleServeRequest(req,
|
|
9189
|
+
return handleServeRequest(req, await resolveHooksServePublishKeyAsync());
|
|
8911
9190
|
}
|
|
8912
9191
|
});
|
|
8913
9192
|
console.error(`hooks registry listening on http://${host}:${server.port} (publish requires an API key)`);
|
|
@@ -9048,8 +9327,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
9048
9327
|
import { z as z2 } from "zod";
|
|
9049
9328
|
import { createServer } from "http";
|
|
9050
9329
|
import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
|
|
9051
|
-
import { join as join16, dirname as
|
|
9052
|
-
import { fileURLToPath as
|
|
9330
|
+
import { join as join16, dirname as dirname9 } from "path";
|
|
9331
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
9053
9332
|
async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
9054
9333
|
const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
|
|
9055
9334
|
const resolved = resolveHook2(name);
|
|
@@ -9123,8 +9402,8 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
|
9123
9402
|
return ran;
|
|
9124
9403
|
}
|
|
9125
9404
|
function formatInstallResults(results, extra) {
|
|
9126
|
-
const installed = results.filter((
|
|
9127
|
-
const failed = results.filter((
|
|
9405
|
+
const installed = results.filter((r2) => r2.success).map((r2) => r2.hook);
|
|
9406
|
+
const failed = results.filter((r2) => !r2.success).map((r2) => ({ hook: r2.hook, error: r2.error }));
|
|
9128
9407
|
return {
|
|
9129
9408
|
content: [{
|
|
9130
9409
|
type: "text",
|
|
@@ -9487,8 +9766,8 @@ function createHooksServer() {
|
|
|
9487
9766
|
}
|
|
9488
9767
|
return installHook(name, { scope, overwrite: true });
|
|
9489
9768
|
});
|
|
9490
|
-
const updated = results.filter((
|
|
9491
|
-
const failed = results.filter((
|
|
9769
|
+
const updated = results.filter((r2) => r2.success).map((r2) => r2.hook);
|
|
9770
|
+
const failed = results.filter((r2) => !r2.success).map((r2) => ({ hook: r2.hook, error: r2.error }));
|
|
9492
9771
|
return { content: [{ type: "text", text: JSON.stringify({ updated, failed, total: results.length }) }] };
|
|
9493
9772
|
});
|
|
9494
9773
|
defineTool("hooks_context", "Get compact agent context in one call: installed hooks, active profile summary, settings path, and doctor status.", {
|
|
@@ -9575,7 +9854,7 @@ function createHooksServer() {
|
|
|
9575
9854
|
return { name, decision: "block", error: err instanceof Error ? err.message : String(err) };
|
|
9576
9855
|
}
|
|
9577
9856
|
}));
|
|
9578
|
-
const blocked = results.find((
|
|
9857
|
+
const blocked = results.find((r2) => r2.decision === "block");
|
|
9579
9858
|
return {
|
|
9580
9859
|
content: [{
|
|
9581
9860
|
type: "text",
|
|
@@ -9609,8 +9888,8 @@ function createHooksServer() {
|
|
|
9609
9888
|
const target = agent_type === "gemini" ? "gemini" : "claude";
|
|
9610
9889
|
const toInstall = hooks && hooks.length > 0 ? hooks : ["gitguard", "checkpoint", "checktests", "protectfiles"];
|
|
9611
9890
|
const results = toInstall.map((h) => installHook(h, { scope, overwrite: false, profile: profile.agent_id, target }));
|
|
9612
|
-
const installed = results.filter((
|
|
9613
|
-
const failed = results.filter((
|
|
9891
|
+
const installed = results.filter((r2) => r2.success).map((r2) => r2.hook);
|
|
9892
|
+
const failed = results.filter((r2) => !r2.success).map((r2) => ({ hook: r2.hook, error: r2.error }));
|
|
9614
9893
|
return {
|
|
9615
9894
|
content: [{
|
|
9616
9895
|
type: "text",
|
|
@@ -9668,8 +9947,8 @@ function createHooksServer() {
|
|
|
9668
9947
|
disabled.push(name);
|
|
9669
9948
|
settings.hooks.__disabled = disabled;
|
|
9670
9949
|
const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync8 } = await import("fs");
|
|
9671
|
-
const { dirname:
|
|
9672
|
-
mkdirSync8(
|
|
9950
|
+
const { dirname: dirname10 } = await import("path");
|
|
9951
|
+
mkdirSync8(dirname10(settingsPath), { recursive: true });
|
|
9673
9952
|
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
9674
9953
|
`);
|
|
9675
9954
|
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
|
|
@@ -9685,7 +9964,7 @@ function createHooksServer() {
|
|
|
9685
9964
|
settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
|
|
9686
9965
|
} catch {}
|
|
9687
9966
|
if (settings.hooks?.__disabled) {
|
|
9688
|
-
settings.hooks.__disabled = settings.hooks.__disabled.filter((
|
|
9967
|
+
settings.hooks.__disabled = settings.hooks.__disabled.filter((n2) => n2 !== name);
|
|
9689
9968
|
if (settings.hooks.__disabled.length === 0)
|
|
9690
9969
|
delete settings.hooks.__disabled;
|
|
9691
9970
|
const { writeFileSync: writeFileSync7 } = await import("fs");
|
|
@@ -9742,9 +10021,9 @@ function createHooksServer() {
|
|
|
9742
10021
|
const m = s.match(/^(\d+)(s|m|h|d)$/);
|
|
9743
10022
|
if (!m)
|
|
9744
10023
|
return null;
|
|
9745
|
-
const
|
|
10024
|
+
const n2 = parseInt(m[1]);
|
|
9746
10025
|
const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
|
|
9747
|
-
return new Date(Date.now() -
|
|
10026
|
+
return new Date(Date.now() - n2 * ms).toISOString();
|
|
9748
10027
|
}
|
|
9749
10028
|
let sql = "SELECT * FROM hook_events WHERE 1=1";
|
|
9750
10029
|
const params = [];
|
|
@@ -9782,10 +10061,10 @@ function createHooksServer() {
|
|
|
9782
10061
|
defineTool("hooks_log_tail", "Show recent hook events from SQLite. Compact summaries by default.", {
|
|
9783
10062
|
n: z2.number().default(20).describe("Number of most recent events to return"),
|
|
9784
10063
|
compact: z2.boolean().default(true).describe("Return compact event summaries by default. Set false for full rows.")
|
|
9785
|
-
}, async ({ n, compact }) => {
|
|
10064
|
+
}, async ({ n: n2, compact }) => {
|
|
9786
10065
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
|
|
9787
10066
|
const db = getDb2();
|
|
9788
|
-
const maxRows = boundedLimit(
|
|
10067
|
+
const maxRows = boundedLimit(n2, 20, compact ? 100 : 500);
|
|
9789
10068
|
const rows = db.query("SELECT * FROM hook_events ORDER BY timestamp DESC LIMIT ?").all(maxRows);
|
|
9790
10069
|
const projected = projectLogRows(rows);
|
|
9791
10070
|
return {
|
|
@@ -9812,9 +10091,9 @@ function createHooksServer() {
|
|
|
9812
10091
|
const m = s.match(/^(\d+)(s|m|h|d)$/);
|
|
9813
10092
|
if (!m)
|
|
9814
10093
|
return s;
|
|
9815
|
-
const
|
|
10094
|
+
const n2 = parseInt(m[1]);
|
|
9816
10095
|
const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
|
|
9817
|
-
return new Date(Date.now() -
|
|
10096
|
+
return new Date(Date.now() - n2 * ms).toISOString();
|
|
9818
10097
|
}
|
|
9819
10098
|
const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
|
|
9820
10099
|
const rows = db.query("SELECT * FROM hook_events WHERE error IS NOT NULL AND timestamp >= ? ORDER BY timestamp DESC LIMIT ?").all(ts, maxRows);
|
|
@@ -9840,20 +10119,20 @@ function createHooksServer() {
|
|
|
9840
10119
|
const m = s.match(/^(\d+)(s|m|h|d)$/);
|
|
9841
10120
|
if (!m)
|
|
9842
10121
|
return s;
|
|
9843
|
-
const
|
|
10122
|
+
const n2 = parseInt(m[1]);
|
|
9844
10123
|
const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
|
|
9845
|
-
return new Date(Date.now() -
|
|
10124
|
+
return new Date(Date.now() - n2 * ms).toISOString();
|
|
9846
10125
|
}
|
|
9847
10126
|
const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
|
|
9848
10127
|
const totals = db.query("SELECT hook_name, COUNT(*) as total, SUM(CASE WHEN error IS NOT NULL THEN 1 ELSE 0 END) as errors FROM hook_events WHERE timestamp >= ? GROUP BY hook_name ORDER BY total DESC").all(ts);
|
|
9849
|
-
const summary = totals.map((
|
|
9850
|
-
hook_name:
|
|
9851
|
-
total:
|
|
9852
|
-
errors:
|
|
9853
|
-
error_rate:
|
|
10128
|
+
const summary = totals.map((r2) => ({
|
|
10129
|
+
hook_name: r2.hook_name,
|
|
10130
|
+
total: r2.total,
|
|
10131
|
+
errors: r2.errors,
|
|
10132
|
+
error_rate: r2.total > 0 ? (r2.errors / r2.total * 100).toFixed(1) + "%" : "0%"
|
|
9854
10133
|
}));
|
|
9855
|
-
const grandTotal = totals.reduce((s,
|
|
9856
|
-
const grandErrors = totals.reduce((s,
|
|
10134
|
+
const grandTotal = totals.reduce((s, r2) => s + r2.total, 0);
|
|
10135
|
+
const grandErrors = totals.reduce((s, r2) => s + r2.errors, 0);
|
|
9857
10136
|
return {
|
|
9858
10137
|
content: [{
|
|
9859
10138
|
type: "text",
|
|
@@ -9884,8 +10163,8 @@ function createHooksServer() {
|
|
|
9884
10163
|
pkg.version
|
|
9885
10164
|
]);
|
|
9886
10165
|
return { content: [{ type: "text", text: "Feedback saved. Thank you!" }] };
|
|
9887
|
-
} catch (
|
|
9888
|
-
return { content: [{ type: "text", text: String(
|
|
10166
|
+
} catch (e2) {
|
|
10167
|
+
return { content: [{ type: "text", text: String(e2) }], isError: true };
|
|
9889
10168
|
}
|
|
9890
10169
|
});
|
|
9891
10170
|
defineTool("register_agent", "Register an agent session. Returns agent_id. Auto-triggers a heartbeat.", {
|
|
@@ -10052,7 +10331,7 @@ var init_server = __esm(() => {
|
|
|
10052
10331
|
init_redact();
|
|
10053
10332
|
init_authority();
|
|
10054
10333
|
init_storage();
|
|
10055
|
-
__dirname3 =
|
|
10334
|
+
__dirname3 = dirname9(fileURLToPath5(import.meta.url));
|
|
10056
10335
|
pkg = { name: "@hasna/hooks", version: "0.0.0" };
|
|
10057
10336
|
try {
|
|
10058
10337
|
for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
|
|
@@ -11801,8 +12080,8 @@ var {
|
|
|
11801
12080
|
// src/cli/index.tsx
|
|
11802
12081
|
import chalk2 from "chalk";
|
|
11803
12082
|
import { existsSync as existsSync16, readFileSync as readFileSync12, rmSync as rmSync4 } from "fs";
|
|
11804
|
-
import { join as join17, dirname as
|
|
11805
|
-
import { fileURLToPath as
|
|
12083
|
+
import { join as join17, dirname as dirname10 } from "path";
|
|
12084
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
11806
12085
|
|
|
11807
12086
|
// src/cli/components/App.tsx
|
|
11808
12087
|
import { useState as useState7 } from "react";
|
|
@@ -12989,7 +13268,7 @@ init_local_opt_in();
|
|
|
12989
13268
|
init_transport2();
|
|
12990
13269
|
init_db();
|
|
12991
13270
|
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
12992
|
-
var __dirname4 =
|
|
13271
|
+
var __dirname4 = dirname10(fileURLToPath6(import.meta.url));
|
|
12993
13272
|
var pkgPath = existsSync16(join17(__dirname4, "..", "package.json")) ? join17(__dirname4, "..", "package.json") : join17(__dirname4, "..", "..", "package.json");
|
|
12994
13273
|
var pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
12995
13274
|
var program2 = new Command;
|
|
@@ -13116,19 +13395,19 @@ function printDisclosureHint(hidden, detailCommand, options = {}) {
|
|
|
13116
13395
|
}
|
|
13117
13396
|
}
|
|
13118
13397
|
function editDistance(a, b) {
|
|
13119
|
-
const m = a.length,
|
|
13120
|
-
const dp = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(
|
|
13121
|
-
for (let j = 0;j <=
|
|
13398
|
+
const m = a.length, n2 = b.length;
|
|
13399
|
+
const dp = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n2).fill(0)]);
|
|
13400
|
+
for (let j = 0;j <= n2; j++)
|
|
13122
13401
|
dp[0][j] = j;
|
|
13123
13402
|
for (let i = 1;i <= m; i++) {
|
|
13124
|
-
for (let j = 1;j <=
|
|
13403
|
+
for (let j = 1;j <= n2; j++) {
|
|
13125
13404
|
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
13126
13405
|
}
|
|
13127
13406
|
}
|
|
13128
|
-
return dp[m][
|
|
13407
|
+
return dp[m][n2];
|
|
13129
13408
|
}
|
|
13130
13409
|
function suggestHooks(name, max = 3) {
|
|
13131
|
-
return HOOKS.map((h) => ({ name: h.name, dist: editDistance(name.toLowerCase(), h.name.toLowerCase()) })).filter(({ dist }) => dist <= 4).sort((a, b) => a.dist - b.dist).slice(0, max).map(({ name:
|
|
13410
|
+
return HOOKS.map((h) => ({ name: h.name, dist: editDistance(name.toLowerCase(), h.name.toLowerCase()) })).filter(({ dist }) => dist <= 4).sort((a, b) => a.dist - b.dist).slice(0, max).map(({ name: n2 }) => n2);
|
|
13132
13411
|
}
|
|
13133
13412
|
program2.name("hooks").description("Install hooks for AI coding agents").version(pkg2.version);
|
|
13134
13413
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive hook browser").action(() => {
|
|
@@ -13409,10 +13688,10 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
|
|
|
13409
13688
|
return { name, version };
|
|
13410
13689
|
}
|
|
13411
13690
|
const pinnedRequests = toInstall.map((arg) => ({ arg, pinned: parseNameVersion(arg) })).filter((x) => x.pinned !== null);
|
|
13412
|
-
const customSources = toInstall.filter((
|
|
13691
|
+
const customSources = toInstall.filter((n2) => !getHook(n2) && isCustomSource2(n2));
|
|
13413
13692
|
if (options.dryRun) {
|
|
13414
|
-
const known = toInstall.filter((
|
|
13415
|
-
const unknown = toInstall.filter((
|
|
13693
|
+
const known = toInstall.filter((n2) => getHook(n2));
|
|
13694
|
+
const unknown = toInstall.filter((n2) => !getHook(n2) && !isCustomSource2(n2) && !parseNameVersion(n2) && !readCustomManifest2(n2));
|
|
13416
13695
|
if (options.json) {
|
|
13417
13696
|
console.log(JSON.stringify({
|
|
13418
13697
|
dryRun: true,
|
|
@@ -13486,7 +13765,7 @@ Dry run \u2014 would install (${scope}, ${target}):
|
|
|
13486
13765
|
for (const { arg, pinned } of pinnedRequests) {
|
|
13487
13766
|
try {
|
|
13488
13767
|
const transport = resolveHooksTransport2();
|
|
13489
|
-
const pinnedInstall = transport.mode === "remote" && transport.authority ? await fetchPinnedHook2(pinned.name, pinned.version, transport.authority.origin, transport.authority
|
|
13768
|
+
const pinnedInstall = transport.mode === "remote" && transport.authority ? await fetchPinnedHook2(pinned.name, pinned.version, transport.authority.origin, transport.authority) : await installPinnedFromBundled2(pinned.name, pinned.version);
|
|
13490
13769
|
installedPinned.push(pinnedInstall.name);
|
|
13491
13770
|
if (options.json) {
|
|
13492
13771
|
console.log(JSON.stringify({ pinned: { request: arg, name: pinnedInstall.name, version: pinnedInstall.version, sha256: pinnedInstall.sha256, source: pinnedInstall.source } }));
|
|
@@ -13505,7 +13784,7 @@ Dry run \u2014 would install (${scope}, ${target}):
|
|
|
13505
13784
|
}
|
|
13506
13785
|
}
|
|
13507
13786
|
const namesToRegister = [...new Set([
|
|
13508
|
-
...toInstall.filter((
|
|
13787
|
+
...toInstall.filter((n2) => !isCustomSource2(n2) && !parseNameVersion(n2)),
|
|
13509
13788
|
...installedCustom,
|
|
13510
13789
|
...installedPinned
|
|
13511
13790
|
])];
|
|
@@ -13529,22 +13808,22 @@ Dry run \u2014 would install (${scope}, ${target}):
|
|
|
13529
13808
|
}
|
|
13530
13809
|
if (options.json) {
|
|
13531
13810
|
console.log(JSON.stringify({
|
|
13532
|
-
installed: results.filter((
|
|
13533
|
-
failed: results.filter((
|
|
13534
|
-
fragments: results.filter((
|
|
13811
|
+
installed: results.filter((r2) => r2.success).map((r2) => r2.hook),
|
|
13812
|
+
failed: results.filter((r2) => !r2.success).map((r2) => ({ hook: r2.hook, error: r2.error })),
|
|
13813
|
+
fragments: results.filter((r2) => r2.success && r2.fragment).map((r2) => ({ hook: r2.hook, fragment: r2.fragment, applied: r2.applied, configPath: r2.configPath, note: r2.note })),
|
|
13535
13814
|
total: results.length,
|
|
13536
|
-
success: results.filter((
|
|
13815
|
+
success: results.filter((r2) => r2.success).length,
|
|
13537
13816
|
scope,
|
|
13538
13817
|
target,
|
|
13539
|
-
applied: results.some((
|
|
13540
|
-
...target === "codex" ? { notes: results.filter((
|
|
13818
|
+
applied: results.some((r2) => r2.applied),
|
|
13819
|
+
...target === "codex" ? { notes: results.filter((r2) => r2.success && r2.note).map((r2) => r2.note) } : {}
|
|
13541
13820
|
}));
|
|
13542
|
-
if (results.some((
|
|
13821
|
+
if (results.some((r2) => !r2.success)) {
|
|
13543
13822
|
process.exitCode = 1;
|
|
13544
13823
|
}
|
|
13545
13824
|
return;
|
|
13546
13825
|
}
|
|
13547
|
-
const successCount = results.filter((
|
|
13826
|
+
const successCount = results.filter((r2) => r2.success).length;
|
|
13548
13827
|
const settingsFile = target === "codewith" ? options.applyCodewith ? options.codewithConfig : "TOML fragment only (configs should apply)" : formatSettingsPath(scope, target);
|
|
13549
13828
|
console.log(chalk2.bold(`
|
|
13550
13829
|
Installing hooks (${scope}, ${target})...
|
|
@@ -13996,7 +14275,7 @@ program2.command("update").argument("[hooks...]", "Hooks to update (defaults to
|
|
|
13996
14275
|
if (pinned) {
|
|
13997
14276
|
try {
|
|
13998
14277
|
const transport = resolveHooksTransport2();
|
|
13999
|
-
const pinnedInstall = transport.mode === "remote" && transport.authority ? await fetchPinnedHook2(pinned.name, pinned.version, transport.authority.origin, transport.authority
|
|
14278
|
+
const pinnedInstall = transport.mode === "remote" && transport.authority ? await fetchPinnedHook2(pinned.name, pinned.version, transport.authority.origin, transport.authority) : await installPinnedFromBundled2(pinned.name, pinned.version);
|
|
14000
14279
|
const registered = installHook(pinnedInstall.name, { scope, overwrite: true });
|
|
14001
14280
|
if (!registered.success) {
|
|
14002
14281
|
results.push({ hook: arg, success: false, error: registered.error ?? "registration failed" });
|
|
@@ -14037,10 +14316,10 @@ program2.command("update").argument("[hooks...]", "Hooks to update (defaults to
|
|
|
14037
14316
|
}
|
|
14038
14317
|
if (options.json) {
|
|
14039
14318
|
console.log(JSON.stringify({
|
|
14040
|
-
updated: results.filter((
|
|
14041
|
-
failed: results.filter((
|
|
14319
|
+
updated: results.filter((r2) => r2.success).map((r2) => r2.hook),
|
|
14320
|
+
failed: results.filter((r2) => !r2.success).map((r2) => ({ hook: r2.hook, error: r2.error }))
|
|
14042
14321
|
}));
|
|
14043
|
-
if (results.some((
|
|
14322
|
+
if (results.some((r2) => !r2.success)) {
|
|
14044
14323
|
process.exitCode = 1;
|
|
14045
14324
|
}
|
|
14046
14325
|
return;
|
|
@@ -14055,7 +14334,7 @@ Updating hooks...
|
|
|
14055
14334
|
console.log(chalk2.red(`\u2717 ${result.hook}: ${result.error}`));
|
|
14056
14335
|
}
|
|
14057
14336
|
}
|
|
14058
|
-
if (results.some((
|
|
14337
|
+
if (results.some((r2) => !r2.success)) {
|
|
14059
14338
|
process.exitCode = 1;
|
|
14060
14339
|
}
|
|
14061
14340
|
});
|
|
@@ -14400,16 +14679,16 @@ logCmd.command("errors").description("Show hook events that contain errors").opt
|
|
|
14400
14679
|
const m = s.match(/^(\d+)(s|m|h|d)$/);
|
|
14401
14680
|
if (!m)
|
|
14402
14681
|
return 86400000;
|
|
14403
|
-
const
|
|
14682
|
+
const n2 = parseInt(m[1]);
|
|
14404
14683
|
switch (m[2]) {
|
|
14405
14684
|
case "s":
|
|
14406
|
-
return
|
|
14685
|
+
return n2 * 1000;
|
|
14407
14686
|
case "m":
|
|
14408
|
-
return
|
|
14687
|
+
return n2 * 60 * 1000;
|
|
14409
14688
|
case "h":
|
|
14410
|
-
return
|
|
14689
|
+
return n2 * 60 * 60 * 1000;
|
|
14411
14690
|
case "d":
|
|
14412
|
-
return
|
|
14691
|
+
return n2 * 24 * 60 * 60 * 1000;
|
|
14413
14692
|
default:
|
|
14414
14693
|
return 86400000;
|
|
14415
14694
|
}
|