@hasna/hooks 0.9.4 → 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 +73 -51
- package/bin/index.js +377 -126
- package/bin/serve.js +230 -38
- package/dist/index.d.ts +1 -1
- package/dist/index.js +314 -63
- 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
|
@@ -6352,14 +6352,19 @@ var init_profiles = __esm(() => {
|
|
|
6352
6352
|
init_app_home();
|
|
6353
6353
|
});
|
|
6354
6354
|
|
|
6355
|
-
//
|
|
6355
|
+
// ../contracts/dist/client/transport.js
|
|
6356
6356
|
import { isIP as isIP2 } from "net";
|
|
6357
6357
|
import { spawnSync } from "child_process";
|
|
6358
|
-
import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as
|
|
6359
|
-
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
6360
|
-
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";
|
|
6361
6360
|
import { hostname as osHostname } from "os";
|
|
6362
|
-
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";
|
|
6363
6368
|
function envToken(name) {
|
|
6364
6369
|
return name.toUpperCase().replace(/-/g, "_");
|
|
6365
6370
|
}
|
|
@@ -6376,6 +6381,112 @@ function credentialOverrideEnvKey(name) {
|
|
|
6376
6381
|
function credentialPointerEnvKey(name) {
|
|
6377
6382
|
return `HASNA_${envToken(name)}_API_KEY_REF`;
|
|
6378
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
|
+
}
|
|
6379
6490
|
function homeDir(env) {
|
|
6380
6491
|
const home = env.HOME?.trim();
|
|
6381
6492
|
return home ? home : null;
|
|
@@ -6389,14 +6500,14 @@ function hasnaHomeDir(env) {
|
|
|
6389
6500
|
if (override)
|
|
6390
6501
|
return override;
|
|
6391
6502
|
const home = homeDir(env);
|
|
6392
|
-
return home ?
|
|
6503
|
+
return home ? join22(home, HASNA_HOME_DIR) : null;
|
|
6393
6504
|
}
|
|
6394
6505
|
function appConfigDir(name, env) {
|
|
6395
6506
|
const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
|
|
6396
6507
|
if (configRoot)
|
|
6397
|
-
return
|
|
6508
|
+
return join22(configRoot, name);
|
|
6398
6509
|
const root = hasnaHomeDir(env);
|
|
6399
|
-
return root ?
|
|
6510
|
+
return root ? join22(root, name, CONFIG_SUBDIR) : null;
|
|
6400
6511
|
}
|
|
6401
6512
|
function credentialDiskSourceList(name, env, profile = null) {
|
|
6402
6513
|
if (!SAFE_APP_SLUG.test(name))
|
|
@@ -6405,7 +6516,7 @@ function credentialDiskSourceList(name, env, profile = null) {
|
|
|
6405
6516
|
if (!directory)
|
|
6406
6517
|
return [];
|
|
6407
6518
|
const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
|
|
6408
|
-
return [{ path:
|
|
6519
|
+
return [{ path: join22(directory, file), tier: "disk" }];
|
|
6409
6520
|
}
|
|
6410
6521
|
function credentialDiskSources(name, env) {
|
|
6411
6522
|
return credentialDiskSourceList(name, env, null).map((s) => s.path);
|
|
@@ -6459,7 +6570,7 @@ function readAppConfigFile(path) {
|
|
|
6459
6570
|
};
|
|
6460
6571
|
let fd = -1;
|
|
6461
6572
|
try {
|
|
6462
|
-
fd = openSync2(path,
|
|
6573
|
+
fd = openSync2(path, O_RDONLY2 | O_NOFOLLOW2 | O_NONBLOCK2);
|
|
6463
6574
|
} catch (error) {
|
|
6464
6575
|
const code = error.code;
|
|
6465
6576
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
@@ -6469,7 +6580,7 @@ function readAppConfigFile(path) {
|
|
|
6469
6580
|
unsafe(`the path could not be opened (${code ?? "unknown error"})`);
|
|
6470
6581
|
}
|
|
6471
6582
|
try {
|
|
6472
|
-
const before =
|
|
6583
|
+
const before = fstatSync2(fd);
|
|
6473
6584
|
if (!before.isFile())
|
|
6474
6585
|
unsafe("the path is not a regular file");
|
|
6475
6586
|
if (!configFileModeAllowed(before.mode)) {
|
|
@@ -6480,8 +6591,8 @@ function readAppConfigFile(path) {
|
|
|
6480
6591
|
unsafe("the file is not owned by the current user");
|
|
6481
6592
|
if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
|
|
6482
6593
|
unsafe("the file exceeds the size limit");
|
|
6483
|
-
const bytes =
|
|
6484
|
-
const after =
|
|
6594
|
+
const bytes = readFileSync22(fd);
|
|
6595
|
+
const after = fstatSync2(fd);
|
|
6485
6596
|
if (!configFileReadsCoherent(before, after)) {
|
|
6486
6597
|
unsafe("the file changed while being read");
|
|
6487
6598
|
}
|
|
@@ -6491,20 +6602,28 @@ function readAppConfigFile(path) {
|
|
|
6491
6602
|
closeSync2(fd);
|
|
6492
6603
|
}
|
|
6493
6604
|
}
|
|
6494
|
-
function readCredentialFile(path, apiKeyKeys) {
|
|
6605
|
+
function readCredentialFile(path, apiKeyKeys, pointerKey) {
|
|
6495
6606
|
const parsed = readAppConfigFile(path);
|
|
6496
6607
|
if (!parsed)
|
|
6497
6608
|
return null;
|
|
6498
|
-
for (const key of apiKeyKeys) {
|
|
6609
|
+
for (const key of [...apiKeyKeys, pointerKey]) {
|
|
6499
6610
|
if (parsed.unusable.has(key)) {
|
|
6500
6611
|
throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
|
|
6501
6612
|
}
|
|
6502
6613
|
}
|
|
6503
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
|
+
}
|
|
6504
6623
|
if (new Set(values).size > 1) {
|
|
6505
6624
|
throw new CredentialFileUnsafeError(path, "credential aliases disagree");
|
|
6506
6625
|
}
|
|
6507
|
-
return values[0]
|
|
6626
|
+
return values[0] === undefined ? null : { apiKey: values[0] };
|
|
6508
6627
|
}
|
|
6509
6628
|
function appConfigDiskValue(name, env, keys) {
|
|
6510
6629
|
const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
|
|
@@ -6576,6 +6695,18 @@ function sealCredential(fields) {
|
|
|
6576
6695
|
});
|
|
6577
6696
|
return Object.freeze(sealed);
|
|
6578
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
|
+
}
|
|
6579
6710
|
function firstEnvValue(env, keys) {
|
|
6580
6711
|
for (const key of keys) {
|
|
6581
6712
|
if (!Object.prototype.hasOwnProperty.call(env, key))
|
|
@@ -6761,12 +6892,13 @@ function resolveCredential(name, env, options = {}) {
|
|
|
6761
6892
|
}
|
|
6762
6893
|
const paths = profileDiskSources(name, env, profile);
|
|
6763
6894
|
for (const path of paths) {
|
|
6764
|
-
const value = readCredentialFile(path, apiKeyKeys);
|
|
6895
|
+
const value = readCredentialFile(path, apiKeyKeys, pointerKeyName);
|
|
6765
6896
|
if (value) {
|
|
6766
|
-
|
|
6897
|
+
if (!value.pointerVaultKey)
|
|
6898
|
+
assertUsableCredential(name, path, value.apiKey);
|
|
6767
6899
|
return sealCredential({
|
|
6768
|
-
|
|
6769
|
-
tier: "profile",
|
|
6900
|
+
...value,
|
|
6901
|
+
tier: value.pointerVaultKey ? "pointer" : "profile",
|
|
6770
6902
|
source: path,
|
|
6771
6903
|
deliberate: true,
|
|
6772
6904
|
diskCandidates: paths,
|
|
@@ -6774,7 +6906,7 @@ function resolveCredential(name, env, options = {}) {
|
|
|
6774
6906
|
});
|
|
6775
6907
|
}
|
|
6776
6908
|
}
|
|
6777
|
-
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);
|
|
6778
6910
|
}
|
|
6779
6911
|
const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
|
|
6780
6912
|
const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
|
|
@@ -6799,17 +6931,27 @@ function resolveCredential(name, env, options = {}) {
|
|
|
6799
6931
|
});
|
|
6800
6932
|
}
|
|
6801
6933
|
const diskSourceList = credentialDiskSourceList(name, env, null);
|
|
6802
|
-
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);
|
|
6803
6935
|
if (diskHits.length > 0) {
|
|
6804
6936
|
const winner = diskHits[0];
|
|
6805
|
-
|
|
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);
|
|
6806
6948
|
const divergentSources = [
|
|
6807
|
-
...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
|
|
6808
|
-
...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] : []
|
|
6809
6951
|
];
|
|
6810
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;
|
|
6811
6953
|
return sealCredential({
|
|
6812
|
-
apiKey: winner.value,
|
|
6954
|
+
apiKey: winner.value.apiKey,
|
|
6813
6955
|
tier: winner.src.tier,
|
|
6814
6956
|
source: winner.src.path,
|
|
6815
6957
|
deliberate: false,
|
|
@@ -6830,6 +6972,53 @@ function resolveCredential(name, env, options = {}) {
|
|
|
6830
6972
|
}
|
|
6831
6973
|
return null;
|
|
6832
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
|
+
}
|
|
6833
7022
|
function defaultFleetGatewayBaseUrl(name) {
|
|
6834
7023
|
return `${DEFAULT_FLEET_GATEWAY_ORIGIN}/${validateAppSlug(name)}`;
|
|
6835
7024
|
}
|
|
@@ -7032,8 +7221,9 @@ function credentialDiskSourcesForMessage(name, env) {
|
|
|
7032
7221
|
const paths = credentialDiskSources(name, env);
|
|
7033
7222
|
return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
|
|
7034
7223
|
}
|
|
7035
|
-
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;
|
|
7036
7225
|
var init_transport = __esm(() => {
|
|
7226
|
+
MAX_PACKAGE_BYTES2 = 1024 * 1024;
|
|
7037
7227
|
CredentialResolutionError = class CredentialResolutionError extends Error {
|
|
7038
7228
|
appName;
|
|
7039
7229
|
attempted;
|
|
@@ -7061,7 +7251,6 @@ var init_transport = __esm(() => {
|
|
|
7061
7251
|
INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
|
|
7062
7252
|
CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
|
|
7063
7253
|
AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
7064
|
-
SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
|
|
7065
7254
|
requireSecretsSdk2 = createRequire2(import.meta.url);
|
|
7066
7255
|
ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
|
|
7067
7256
|
DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
@@ -7150,7 +7339,9 @@ var exports_transport = {};
|
|
|
7150
7339
|
__export(exports_transport, {
|
|
7151
7340
|
rethrowHooksAuthorityFailure: () => rethrowHooksAuthorityFailure,
|
|
7152
7341
|
resolveHooksTransport: () => resolveHooksTransport,
|
|
7342
|
+
resolveHooksServePublishKeyAsync: () => resolveHooksServePublishKeyAsync,
|
|
7153
7343
|
resolveHooksServePublishKey: () => resolveHooksServePublishKey,
|
|
7344
|
+
resolveHooksRequestAuthority: () => resolveHooksRequestAuthority,
|
|
7154
7345
|
hooksRegistryOrigin: () => hooksRegistryOrigin,
|
|
7155
7346
|
announceHooksLocalMode: () => announceHooksLocalMode,
|
|
7156
7347
|
__resetHooksLocalNotice: () => __resetHooksLocalNotice
|
|
@@ -7189,6 +7380,40 @@ function rethrowHooksAuthorityFailure(error) {
|
|
|
7189
7380
|
}
|
|
7190
7381
|
throw new Error(`REMOTE_API_URL_INVALID: ${message} local mode fallback is disabled`, { cause: error });
|
|
7191
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
|
+
}
|
|
7192
7417
|
function resolveHooksTransport(env = process.env, options = {}) {
|
|
7193
7418
|
if (selectsHooksLocalStore(env)) {
|
|
7194
7419
|
announceLocal(options.notice, "HASNA_HOOKS_LOCAL is set and nothing configures a registry authority");
|
|
@@ -7202,7 +7427,7 @@ function resolveHooksTransport(env = process.env, options = {}) {
|
|
|
7202
7427
|
try {
|
|
7203
7428
|
credential = resolveCredential("hooks", resolverEnv, credentialOptions);
|
|
7204
7429
|
resolution = resolveClientTransport("hooks", resolverEnv, {
|
|
7205
|
-
credentials: credential ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
|
|
7430
|
+
credentials: credential && credential.tier !== "pointer" ? { ...credentialOptions, apiKey: credential.apiKey } : credentialOptions
|
|
7206
7431
|
});
|
|
7207
7432
|
} catch (error) {
|
|
7208
7433
|
rethrowHooksAuthorityFailure(error);
|
|
@@ -7210,31 +7435,50 @@ function resolveHooksTransport(env = process.env, options = {}) {
|
|
|
7210
7435
|
if (!credential) {
|
|
7211
7436
|
throw new Error("REMOTE_API_CONFIG_MISSING: no hooks registry credential resolved; " + "local mode fallback is disabled");
|
|
7212
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 });
|
|
7213
7448
|
return {
|
|
7214
7449
|
mode: "remote",
|
|
7215
7450
|
source: `${credential.source}+${resolution.apiUrlSource ?? "default"}`,
|
|
7216
|
-
authority
|
|
7217
|
-
origin: hooksRegistryOrigin(resolution.baseUrl),
|
|
7218
|
-
apiKey: credential.apiKey,
|
|
7219
|
-
apiUrlSource: resolution.apiUrlSource,
|
|
7220
|
-
apiKeySource: credential.source,
|
|
7221
|
-
apiKeyTier: credential.tier,
|
|
7222
|
-
v1BaseUrl: resolution.baseUrl,
|
|
7223
|
-
warning: resolution.warning
|
|
7224
|
-
}
|
|
7451
|
+
authority
|
|
7225
7452
|
};
|
|
7226
7453
|
}
|
|
7227
7454
|
function resolveHooksServePublishKey(env = process.env, options = {}) {
|
|
7228
7455
|
const { env: resolverEnv, credentials } = hooksResolverInputs(env, options.credentials);
|
|
7229
7456
|
const tier1 = options.credentials?.apiKey;
|
|
7230
7457
|
const credentialOptions = tier1 !== undefined ? { ...credentials, apiKey: tier1 } : credentials;
|
|
7231
|
-
|
|
7458
|
+
const credential = resolveCredential("hooks", resolverEnv, credentialOptions);
|
|
7459
|
+
if (credential?.tier === "pointer")
|
|
7460
|
+
throw new Error("Vault references require resolveHooksServePublishKeyAsync().");
|
|
7461
|
+
return credential?.apiKey;
|
|
7232
7462
|
}
|
|
7233
|
-
|
|
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;
|
|
7234
7477
|
var init_transport2 = __esm(() => {
|
|
7235
7478
|
init_transport();
|
|
7236
7479
|
init_config();
|
|
7237
7480
|
init_local_opt_in();
|
|
7481
|
+
authorityBindings = new WeakMap;
|
|
7238
7482
|
});
|
|
7239
7483
|
|
|
7240
7484
|
// src/lib/db-writer.ts
|
|
@@ -7356,8 +7600,8 @@ var init_db_writer = __esm(() => {
|
|
|
7356
7600
|
});
|
|
7357
7601
|
|
|
7358
7602
|
// src/lib/hook-env.ts
|
|
7359
|
-
import { dirname as
|
|
7360
|
-
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";
|
|
7361
7605
|
import { tmpdir } from "os";
|
|
7362
7606
|
function isDeniedEnvName(name) {
|
|
7363
7607
|
const upper = name.toUpperCase();
|
|
@@ -7386,7 +7630,7 @@ function isUnsafePathEntry(entry, home) {
|
|
|
7386
7630
|
return true;
|
|
7387
7631
|
const canonical = (path) => {
|
|
7388
7632
|
try {
|
|
7389
|
-
return
|
|
7633
|
+
return realpathSync3(path);
|
|
7390
7634
|
} catch {
|
|
7391
7635
|
return path;
|
|
7392
7636
|
}
|
|
@@ -7398,7 +7642,7 @@ function isUnsafePathEntry(entry, home) {
|
|
|
7398
7642
|
return true;
|
|
7399
7643
|
}
|
|
7400
7644
|
try {
|
|
7401
|
-
if ((
|
|
7645
|
+
if ((statSync2(entry).mode & 2) !== 0)
|
|
7402
7646
|
return true;
|
|
7403
7647
|
} catch {}
|
|
7404
7648
|
return false;
|
|
@@ -7407,7 +7651,7 @@ function sanitizeHookPath(pathValue, home) {
|
|
|
7407
7651
|
const baseline = PATH_BASELINE.slice();
|
|
7408
7652
|
if (process.platform === "darwin" && existsSync13("/opt/homebrew/bin"))
|
|
7409
7653
|
baseline.push("/opt/homebrew/bin");
|
|
7410
|
-
const bunDir =
|
|
7654
|
+
const bunDir = dirname6(process.execPath);
|
|
7411
7655
|
const seen = new Set;
|
|
7412
7656
|
const out = [];
|
|
7413
7657
|
for (const dir of baseline) {
|
|
@@ -7532,9 +7776,9 @@ __export(exports_run, {
|
|
|
7532
7776
|
});
|
|
7533
7777
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
7534
7778
|
import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
7535
|
-
import { basename as
|
|
7779
|
+
import { basename as basename3, dirname as dirname7, isAbsolute as isAbsolute4, join as join13 } from "path";
|
|
7536
7780
|
function extensionOf(scriptPath) {
|
|
7537
|
-
const base =
|
|
7781
|
+
const base = basename3(scriptPath);
|
|
7538
7782
|
const dot = base.lastIndexOf(".");
|
|
7539
7783
|
return dot > 0 ? base.slice(dot + 1) : "ts";
|
|
7540
7784
|
}
|
|
@@ -7546,9 +7790,9 @@ function shebangInterpreter(firstLine) {
|
|
|
7546
7790
|
return null;
|
|
7547
7791
|
const parts = rest.split(/\s+/);
|
|
7548
7792
|
if (parts[0] === "env" || parts[0] === "/usr/bin/env") {
|
|
7549
|
-
return parts[1] && parts[1] !== "-S" ?
|
|
7793
|
+
return parts[1] && parts[1] !== "-S" ? basename3(parts[1]) : parts[2] ? basename3(parts[2]) : null;
|
|
7550
7794
|
}
|
|
7551
|
-
return
|
|
7795
|
+
return basename3(parts[0]);
|
|
7552
7796
|
}
|
|
7553
7797
|
function interpreterFor(name, scriptPath, content) {
|
|
7554
7798
|
const ext = extensionOf(scriptPath);
|
|
@@ -7582,8 +7826,8 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
7582
7826
|
result = await Promise.race([
|
|
7583
7827
|
reader.read(),
|
|
7584
7828
|
new Promise((_, reject) => {
|
|
7585
|
-
const
|
|
7586
|
-
|
|
7829
|
+
const t2 = setTimeout(() => reject(new Error("pipe-drain-deadline")), remaining);
|
|
7830
|
+
t2.unref?.();
|
|
7587
7831
|
})
|
|
7588
7832
|
]);
|
|
7589
7833
|
} catch {
|
|
@@ -7601,7 +7845,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
7601
7845
|
return chunks.join("");
|
|
7602
7846
|
}
|
|
7603
7847
|
async function executeVerifiedScript(options) {
|
|
7604
|
-
const scriptDir =
|
|
7848
|
+
const scriptDir = dirname7(options.scriptPath);
|
|
7605
7849
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
7606
7850
|
const tempPath = join13(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
7607
7851
|
let proc = null;
|
|
@@ -7836,7 +8080,14 @@ function collectBundledCatalog() {
|
|
|
7836
8080
|
}
|
|
7837
8081
|
async function fetchJson(base, path, apiKey, opts) {
|
|
7838
8082
|
const headers = { accept: "application/json" };
|
|
7839
|
-
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)
|
|
7840
8091
|
headers["x-api-key"] = apiKey;
|
|
7841
8092
|
const res = await fetch(`${base}${path}`, {
|
|
7842
8093
|
headers,
|
|
@@ -7904,8 +8155,8 @@ async function planSync(options = {}) {
|
|
|
7904
8155
|
credentials: options.credentials
|
|
7905
8156
|
});
|
|
7906
8157
|
if (transport.mode === "remote" && transport.authority) {
|
|
7907
|
-
const { origin
|
|
7908
|
-
const { catalog, remoteLock } = await fetchRemoteState(origin,
|
|
8158
|
+
const { origin } = transport.authority;
|
|
8159
|
+
const { catalog, remoteLock } = await fetchRemoteState(origin, transport.authority);
|
|
7909
8160
|
return { apiUrl: origin, dryRun: options.dryRun ?? false, diff: computeDiff(catalog, remoteLock) };
|
|
7910
8161
|
}
|
|
7911
8162
|
const diff = computeDiff(collectBundledCatalog(), null);
|
|
@@ -7933,10 +8184,10 @@ async function syncHooks(options = {}) {
|
|
|
7933
8184
|
}
|
|
7934
8185
|
return plan;
|
|
7935
8186
|
}
|
|
7936
|
-
const { origin
|
|
7937
|
-
const { catalog, remoteLock } = await fetchRemoteState(origin,
|
|
8187
|
+
const { origin } = transport.authority;
|
|
8188
|
+
const { catalog, remoteLock } = await fetchRemoteState(origin, transport.authority);
|
|
7938
8189
|
const diff = computeDiff(catalog, remoteLock);
|
|
7939
|
-
const staged = await stageSyncArtifacts(origin,
|
|
8190
|
+
const staged = await stageSyncArtifacts(origin, transport.authority, [...diff.added, ...diff.updated], remoteLock);
|
|
7940
8191
|
await commitSyncArtifacts(staged, origin, remoteLock);
|
|
7941
8192
|
return { apiUrl: origin, dryRun: false, diff };
|
|
7942
8193
|
}
|
|
@@ -8597,8 +8848,8 @@ function secureEqual(a, b) {
|
|
|
8597
8848
|
const ab = enc.encode(a);
|
|
8598
8849
|
const bb = enc.encode(b);
|
|
8599
8850
|
let diff = ab.length ^ bb.length;
|
|
8600
|
-
const
|
|
8601
|
-
for (let i = 0;i <
|
|
8851
|
+
const n2 = Math.max(ab.length, bb.length);
|
|
8852
|
+
for (let i = 0;i < n2; i++) {
|
|
8602
8853
|
diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
|
|
8603
8854
|
}
|
|
8604
8855
|
return diff === 0;
|
|
@@ -8769,11 +9020,11 @@ __export(exports_serve, {
|
|
|
8769
9020
|
DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
|
|
8770
9021
|
});
|
|
8771
9022
|
import { readFileSync as readFileSync10 } from "fs";
|
|
8772
|
-
import { dirname as
|
|
8773
|
-
import { fileURLToPath as
|
|
9023
|
+
import { dirname as dirname8, join as join15 } from "path";
|
|
9024
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8774
9025
|
function packageVersion() {
|
|
8775
9026
|
try {
|
|
8776
|
-
const pkgPath = join15(
|
|
9027
|
+
const pkgPath = join15(dirname8(fileURLToPath4(import.meta.url)), "..", "package.json");
|
|
8777
9028
|
return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
|
|
8778
9029
|
} catch {
|
|
8779
9030
|
return "0.0.0";
|
|
@@ -8935,7 +9186,7 @@ function startServeServer(options) {
|
|
|
8935
9186
|
hostname: host,
|
|
8936
9187
|
port,
|
|
8937
9188
|
async fetch(req) {
|
|
8938
|
-
return handleServeRequest(req,
|
|
9189
|
+
return handleServeRequest(req, await resolveHooksServePublishKeyAsync());
|
|
8939
9190
|
}
|
|
8940
9191
|
});
|
|
8941
9192
|
console.error(`hooks registry listening on http://${host}:${server.port} (publish requires an API key)`);
|
|
@@ -9076,8 +9327,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
9076
9327
|
import { z as z2 } from "zod";
|
|
9077
9328
|
import { createServer } from "http";
|
|
9078
9329
|
import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
|
|
9079
|
-
import { join as join16, dirname as
|
|
9080
|
-
import { fileURLToPath as
|
|
9330
|
+
import { join as join16, dirname as dirname9 } from "path";
|
|
9331
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
9081
9332
|
async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
9082
9333
|
const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
|
|
9083
9334
|
const resolved = resolveHook2(name);
|
|
@@ -9151,8 +9402,8 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
|
9151
9402
|
return ran;
|
|
9152
9403
|
}
|
|
9153
9404
|
function formatInstallResults(results, extra) {
|
|
9154
|
-
const installed = results.filter((
|
|
9155
|
-
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 }));
|
|
9156
9407
|
return {
|
|
9157
9408
|
content: [{
|
|
9158
9409
|
type: "text",
|
|
@@ -9515,8 +9766,8 @@ function createHooksServer() {
|
|
|
9515
9766
|
}
|
|
9516
9767
|
return installHook(name, { scope, overwrite: true });
|
|
9517
9768
|
});
|
|
9518
|
-
const updated = results.filter((
|
|
9519
|
-
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 }));
|
|
9520
9771
|
return { content: [{ type: "text", text: JSON.stringify({ updated, failed, total: results.length }) }] };
|
|
9521
9772
|
});
|
|
9522
9773
|
defineTool("hooks_context", "Get compact agent context in one call: installed hooks, active profile summary, settings path, and doctor status.", {
|
|
@@ -9603,7 +9854,7 @@ function createHooksServer() {
|
|
|
9603
9854
|
return { name, decision: "block", error: err instanceof Error ? err.message : String(err) };
|
|
9604
9855
|
}
|
|
9605
9856
|
}));
|
|
9606
|
-
const blocked = results.find((
|
|
9857
|
+
const blocked = results.find((r2) => r2.decision === "block");
|
|
9607
9858
|
return {
|
|
9608
9859
|
content: [{
|
|
9609
9860
|
type: "text",
|
|
@@ -9637,8 +9888,8 @@ function createHooksServer() {
|
|
|
9637
9888
|
const target = agent_type === "gemini" ? "gemini" : "claude";
|
|
9638
9889
|
const toInstall = hooks && hooks.length > 0 ? hooks : ["gitguard", "checkpoint", "checktests", "protectfiles"];
|
|
9639
9890
|
const results = toInstall.map((h) => installHook(h, { scope, overwrite: false, profile: profile.agent_id, target }));
|
|
9640
|
-
const installed = results.filter((
|
|
9641
|
-
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 }));
|
|
9642
9893
|
return {
|
|
9643
9894
|
content: [{
|
|
9644
9895
|
type: "text",
|
|
@@ -9696,8 +9947,8 @@ function createHooksServer() {
|
|
|
9696
9947
|
disabled.push(name);
|
|
9697
9948
|
settings.hooks.__disabled = disabled;
|
|
9698
9949
|
const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync8 } = await import("fs");
|
|
9699
|
-
const { dirname:
|
|
9700
|
-
mkdirSync8(
|
|
9950
|
+
const { dirname: dirname10 } = await import("path");
|
|
9951
|
+
mkdirSync8(dirname10(settingsPath), { recursive: true });
|
|
9701
9952
|
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
9702
9953
|
`);
|
|
9703
9954
|
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
|
|
@@ -9713,7 +9964,7 @@ function createHooksServer() {
|
|
|
9713
9964
|
settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
|
|
9714
9965
|
} catch {}
|
|
9715
9966
|
if (settings.hooks?.__disabled) {
|
|
9716
|
-
settings.hooks.__disabled = settings.hooks.__disabled.filter((
|
|
9967
|
+
settings.hooks.__disabled = settings.hooks.__disabled.filter((n2) => n2 !== name);
|
|
9717
9968
|
if (settings.hooks.__disabled.length === 0)
|
|
9718
9969
|
delete settings.hooks.__disabled;
|
|
9719
9970
|
const { writeFileSync: writeFileSync7 } = await import("fs");
|
|
@@ -9770,9 +10021,9 @@ function createHooksServer() {
|
|
|
9770
10021
|
const m = s.match(/^(\d+)(s|m|h|d)$/);
|
|
9771
10022
|
if (!m)
|
|
9772
10023
|
return null;
|
|
9773
|
-
const
|
|
10024
|
+
const n2 = parseInt(m[1]);
|
|
9774
10025
|
const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
|
|
9775
|
-
return new Date(Date.now() -
|
|
10026
|
+
return new Date(Date.now() - n2 * ms).toISOString();
|
|
9776
10027
|
}
|
|
9777
10028
|
let sql = "SELECT * FROM hook_events WHERE 1=1";
|
|
9778
10029
|
const params = [];
|
|
@@ -9810,10 +10061,10 @@ function createHooksServer() {
|
|
|
9810
10061
|
defineTool("hooks_log_tail", "Show recent hook events from SQLite. Compact summaries by default.", {
|
|
9811
10062
|
n: z2.number().default(20).describe("Number of most recent events to return"),
|
|
9812
10063
|
compact: z2.boolean().default(true).describe("Return compact event summaries by default. Set false for full rows.")
|
|
9813
|
-
}, async ({ n, compact }) => {
|
|
10064
|
+
}, async ({ n: n2, compact }) => {
|
|
9814
10065
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
|
|
9815
10066
|
const db = getDb2();
|
|
9816
|
-
const maxRows = boundedLimit(
|
|
10067
|
+
const maxRows = boundedLimit(n2, 20, compact ? 100 : 500);
|
|
9817
10068
|
const rows = db.query("SELECT * FROM hook_events ORDER BY timestamp DESC LIMIT ?").all(maxRows);
|
|
9818
10069
|
const projected = projectLogRows(rows);
|
|
9819
10070
|
return {
|
|
@@ -9840,9 +10091,9 @@ function createHooksServer() {
|
|
|
9840
10091
|
const m = s.match(/^(\d+)(s|m|h|d)$/);
|
|
9841
10092
|
if (!m)
|
|
9842
10093
|
return s;
|
|
9843
|
-
const
|
|
10094
|
+
const n2 = parseInt(m[1]);
|
|
9844
10095
|
const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
|
|
9845
|
-
return new Date(Date.now() -
|
|
10096
|
+
return new Date(Date.now() - n2 * ms).toISOString();
|
|
9846
10097
|
}
|
|
9847
10098
|
const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
|
|
9848
10099
|
const rows = db.query("SELECT * FROM hook_events WHERE error IS NOT NULL AND timestamp >= ? ORDER BY timestamp DESC LIMIT ?").all(ts, maxRows);
|
|
@@ -9868,20 +10119,20 @@ function createHooksServer() {
|
|
|
9868
10119
|
const m = s.match(/^(\d+)(s|m|h|d)$/);
|
|
9869
10120
|
if (!m)
|
|
9870
10121
|
return s;
|
|
9871
|
-
const
|
|
10122
|
+
const n2 = parseInt(m[1]);
|
|
9872
10123
|
const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
|
|
9873
|
-
return new Date(Date.now() -
|
|
10124
|
+
return new Date(Date.now() - n2 * ms).toISOString();
|
|
9874
10125
|
}
|
|
9875
10126
|
const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
|
|
9876
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);
|
|
9877
|
-
const summary = totals.map((
|
|
9878
|
-
hook_name:
|
|
9879
|
-
total:
|
|
9880
|
-
errors:
|
|
9881
|
-
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%"
|
|
9882
10133
|
}));
|
|
9883
|
-
const grandTotal = totals.reduce((s,
|
|
9884
|
-
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);
|
|
9885
10136
|
return {
|
|
9886
10137
|
content: [{
|
|
9887
10138
|
type: "text",
|
|
@@ -9912,8 +10163,8 @@ function createHooksServer() {
|
|
|
9912
10163
|
pkg.version
|
|
9913
10164
|
]);
|
|
9914
10165
|
return { content: [{ type: "text", text: "Feedback saved. Thank you!" }] };
|
|
9915
|
-
} catch (
|
|
9916
|
-
return { content: [{ type: "text", text: String(
|
|
10166
|
+
} catch (e2) {
|
|
10167
|
+
return { content: [{ type: "text", text: String(e2) }], isError: true };
|
|
9917
10168
|
}
|
|
9918
10169
|
});
|
|
9919
10170
|
defineTool("register_agent", "Register an agent session. Returns agent_id. Auto-triggers a heartbeat.", {
|
|
@@ -10080,7 +10331,7 @@ var init_server = __esm(() => {
|
|
|
10080
10331
|
init_redact();
|
|
10081
10332
|
init_authority();
|
|
10082
10333
|
init_storage();
|
|
10083
|
-
__dirname3 =
|
|
10334
|
+
__dirname3 = dirname9(fileURLToPath5(import.meta.url));
|
|
10084
10335
|
pkg = { name: "@hasna/hooks", version: "0.0.0" };
|
|
10085
10336
|
try {
|
|
10086
10337
|
for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
|
|
@@ -11829,8 +12080,8 @@ var {
|
|
|
11829
12080
|
// src/cli/index.tsx
|
|
11830
12081
|
import chalk2 from "chalk";
|
|
11831
12082
|
import { existsSync as existsSync16, readFileSync as readFileSync12, rmSync as rmSync4 } from "fs";
|
|
11832
|
-
import { join as join17, dirname as
|
|
11833
|
-
import { fileURLToPath as
|
|
12083
|
+
import { join as join17, dirname as dirname10 } from "path";
|
|
12084
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
11834
12085
|
|
|
11835
12086
|
// src/cli/components/App.tsx
|
|
11836
12087
|
import { useState as useState7 } from "react";
|
|
@@ -13017,7 +13268,7 @@ init_local_opt_in();
|
|
|
13017
13268
|
init_transport2();
|
|
13018
13269
|
init_db();
|
|
13019
13270
|
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
13020
|
-
var __dirname4 =
|
|
13271
|
+
var __dirname4 = dirname10(fileURLToPath6(import.meta.url));
|
|
13021
13272
|
var pkgPath = existsSync16(join17(__dirname4, "..", "package.json")) ? join17(__dirname4, "..", "package.json") : join17(__dirname4, "..", "..", "package.json");
|
|
13022
13273
|
var pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
13023
13274
|
var program2 = new Command;
|
|
@@ -13144,19 +13395,19 @@ function printDisclosureHint(hidden, detailCommand, options = {}) {
|
|
|
13144
13395
|
}
|
|
13145
13396
|
}
|
|
13146
13397
|
function editDistance(a, b) {
|
|
13147
|
-
const m = a.length,
|
|
13148
|
-
const dp = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(
|
|
13149
|
-
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++)
|
|
13150
13401
|
dp[0][j] = j;
|
|
13151
13402
|
for (let i = 1;i <= m; i++) {
|
|
13152
|
-
for (let j = 1;j <=
|
|
13403
|
+
for (let j = 1;j <= n2; j++) {
|
|
13153
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]);
|
|
13154
13405
|
}
|
|
13155
13406
|
}
|
|
13156
|
-
return dp[m][
|
|
13407
|
+
return dp[m][n2];
|
|
13157
13408
|
}
|
|
13158
13409
|
function suggestHooks(name, max = 3) {
|
|
13159
|
-
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);
|
|
13160
13411
|
}
|
|
13161
13412
|
program2.name("hooks").description("Install hooks for AI coding agents").version(pkg2.version);
|
|
13162
13413
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive hook browser").action(() => {
|
|
@@ -13437,10 +13688,10 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
|
|
|
13437
13688
|
return { name, version };
|
|
13438
13689
|
}
|
|
13439
13690
|
const pinnedRequests = toInstall.map((arg) => ({ arg, pinned: parseNameVersion(arg) })).filter((x) => x.pinned !== null);
|
|
13440
|
-
const customSources = toInstall.filter((
|
|
13691
|
+
const customSources = toInstall.filter((n2) => !getHook(n2) && isCustomSource2(n2));
|
|
13441
13692
|
if (options.dryRun) {
|
|
13442
|
-
const known = toInstall.filter((
|
|
13443
|
-
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));
|
|
13444
13695
|
if (options.json) {
|
|
13445
13696
|
console.log(JSON.stringify({
|
|
13446
13697
|
dryRun: true,
|
|
@@ -13514,7 +13765,7 @@ Dry run \u2014 would install (${scope}, ${target}):
|
|
|
13514
13765
|
for (const { arg, pinned } of pinnedRequests) {
|
|
13515
13766
|
try {
|
|
13516
13767
|
const transport = resolveHooksTransport2();
|
|
13517
|
-
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);
|
|
13518
13769
|
installedPinned.push(pinnedInstall.name);
|
|
13519
13770
|
if (options.json) {
|
|
13520
13771
|
console.log(JSON.stringify({ pinned: { request: arg, name: pinnedInstall.name, version: pinnedInstall.version, sha256: pinnedInstall.sha256, source: pinnedInstall.source } }));
|
|
@@ -13533,7 +13784,7 @@ Dry run \u2014 would install (${scope}, ${target}):
|
|
|
13533
13784
|
}
|
|
13534
13785
|
}
|
|
13535
13786
|
const namesToRegister = [...new Set([
|
|
13536
|
-
...toInstall.filter((
|
|
13787
|
+
...toInstall.filter((n2) => !isCustomSource2(n2) && !parseNameVersion(n2)),
|
|
13537
13788
|
...installedCustom,
|
|
13538
13789
|
...installedPinned
|
|
13539
13790
|
])];
|
|
@@ -13557,22 +13808,22 @@ Dry run \u2014 would install (${scope}, ${target}):
|
|
|
13557
13808
|
}
|
|
13558
13809
|
if (options.json) {
|
|
13559
13810
|
console.log(JSON.stringify({
|
|
13560
|
-
installed: results.filter((
|
|
13561
|
-
failed: results.filter((
|
|
13562
|
-
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 })),
|
|
13563
13814
|
total: results.length,
|
|
13564
|
-
success: results.filter((
|
|
13815
|
+
success: results.filter((r2) => r2.success).length,
|
|
13565
13816
|
scope,
|
|
13566
13817
|
target,
|
|
13567
|
-
applied: results.some((
|
|
13568
|
-
...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) } : {}
|
|
13569
13820
|
}));
|
|
13570
|
-
if (results.some((
|
|
13821
|
+
if (results.some((r2) => !r2.success)) {
|
|
13571
13822
|
process.exitCode = 1;
|
|
13572
13823
|
}
|
|
13573
13824
|
return;
|
|
13574
13825
|
}
|
|
13575
|
-
const successCount = results.filter((
|
|
13826
|
+
const successCount = results.filter((r2) => r2.success).length;
|
|
13576
13827
|
const settingsFile = target === "codewith" ? options.applyCodewith ? options.codewithConfig : "TOML fragment only (configs should apply)" : formatSettingsPath(scope, target);
|
|
13577
13828
|
console.log(chalk2.bold(`
|
|
13578
13829
|
Installing hooks (${scope}, ${target})...
|
|
@@ -14024,7 +14275,7 @@ program2.command("update").argument("[hooks...]", "Hooks to update (defaults to
|
|
|
14024
14275
|
if (pinned) {
|
|
14025
14276
|
try {
|
|
14026
14277
|
const transport = resolveHooksTransport2();
|
|
14027
|
-
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);
|
|
14028
14279
|
const registered = installHook(pinnedInstall.name, { scope, overwrite: true });
|
|
14029
14280
|
if (!registered.success) {
|
|
14030
14281
|
results.push({ hook: arg, success: false, error: registered.error ?? "registration failed" });
|
|
@@ -14065,10 +14316,10 @@ program2.command("update").argument("[hooks...]", "Hooks to update (defaults to
|
|
|
14065
14316
|
}
|
|
14066
14317
|
if (options.json) {
|
|
14067
14318
|
console.log(JSON.stringify({
|
|
14068
|
-
updated: results.filter((
|
|
14069
|
-
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 }))
|
|
14070
14321
|
}));
|
|
14071
|
-
if (results.some((
|
|
14322
|
+
if (results.some((r2) => !r2.success)) {
|
|
14072
14323
|
process.exitCode = 1;
|
|
14073
14324
|
}
|
|
14074
14325
|
return;
|
|
@@ -14083,7 +14334,7 @@ Updating hooks...
|
|
|
14083
14334
|
console.log(chalk2.red(`\u2717 ${result.hook}: ${result.error}`));
|
|
14084
14335
|
}
|
|
14085
14336
|
}
|
|
14086
|
-
if (results.some((
|
|
14337
|
+
if (results.some((r2) => !r2.success)) {
|
|
14087
14338
|
process.exitCode = 1;
|
|
14088
14339
|
}
|
|
14089
14340
|
});
|
|
@@ -14428,16 +14679,16 @@ logCmd.command("errors").description("Show hook events that contain errors").opt
|
|
|
14428
14679
|
const m = s.match(/^(\d+)(s|m|h|d)$/);
|
|
14429
14680
|
if (!m)
|
|
14430
14681
|
return 86400000;
|
|
14431
|
-
const
|
|
14682
|
+
const n2 = parseInt(m[1]);
|
|
14432
14683
|
switch (m[2]) {
|
|
14433
14684
|
case "s":
|
|
14434
|
-
return
|
|
14685
|
+
return n2 * 1000;
|
|
14435
14686
|
case "m":
|
|
14436
|
-
return
|
|
14687
|
+
return n2 * 60 * 1000;
|
|
14437
14688
|
case "h":
|
|
14438
|
-
return
|
|
14689
|
+
return n2 * 60 * 60 * 1000;
|
|
14439
14690
|
case "d":
|
|
14440
|
-
return
|
|
14691
|
+
return n2 * 24 * 60 * 60 * 1000;
|
|
14441
14692
|
default:
|
|
14442
14693
|
return 86400000;
|
|
14443
14694
|
}
|